Skip to content

Commit

Permalink
Implemented support for kubernetes.io/os (#932)
Browse files Browse the repository at this point in the history
  • Loading branch information
ellistarn authored Dec 7, 2021
1 parent e21b84c commit effd8d2
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 19 deletions.
1 change: 1 addition & 0 deletions pkg/apis/provisioning/v1alpha5/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ var (
v1.LabelTopologyZone,
v1.LabelInstanceTypeStable,
v1.LabelArchStable,
v1.LabelOSStable,
LabelCapacityType,
v1.LabelHostname, // Used internally for hostname topology spread
)
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/provisioning/v1alpha5/requirements.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ func (r Requirements) Architectures() sets.String {
return r.Requirement(v1.LabelArchStable)
}

func (r Requirements) OperatingSystems() sets.String {
return r.Requirement(v1.LabelOSStable)
}

func (r Requirements) CapacityTypes() sets.String {
return r.Requirement(LabelCapacityType)
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/cloudprovider/aws/instancetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/aws/karpenter/pkg/utils/resources"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/sets"
)

// EC2VMAvailableMemoryFactor assumes the EC2 VM will consume <7.25% of the memory of a given machine
Expand All @@ -42,6 +43,10 @@ func (i *InstanceType) Offerings() []cloudprovider.Offering {
return i.AvailableOfferings
}

func (i *InstanceType) OperatingSystems() sets.String {
return sets.NewString("linux")
}

func (i *InstanceType) Architecture() string {
for _, architecture := range i.ProcessorInfo.SupportedArchitectures {
if value, ok := v1alpha1.AWSToKubeArchitectures[aws.StringValue(architecture)]; ok {
Expand Down
46 changes: 28 additions & 18 deletions pkg/cloudprovider/fake/instancetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/sets"
)

func NewInstanceType(options InstanceTypeOptions) *InstanceType {
Expand All @@ -33,6 +34,9 @@ func NewInstanceType(options InstanceTypeOptions) *InstanceType {
if len(options.architecture) == 0 {
options.architecture = "amd64"
}
if len(options.operatingSystems) == 0 {
options.operatingSystems = sets.NewString("linux", "windows", "mac")
}
if options.cpu.IsZero() {
options.cpu = resource.MustParse("4")
}
Expand All @@ -44,29 +48,31 @@ func NewInstanceType(options InstanceTypeOptions) *InstanceType {
}
return &InstanceType{
InstanceTypeOptions: InstanceTypeOptions{
name: options.name,
offerings: options.offerings,
architecture: options.architecture,
cpu: options.cpu,
memory: options.memory,
pods: options.pods,
nvidiaGPUs: options.nvidiaGPUs,
amdGPUs: options.amdGPUs,
awsNeurons: options.awsNeurons,
name: options.name,
offerings: options.offerings,
architecture: options.architecture,
operatingSystems: options.operatingSystems,
cpu: options.cpu,
memory: options.memory,
pods: options.pods,
nvidiaGPUs: options.nvidiaGPUs,
amdGPUs: options.amdGPUs,
awsNeurons: options.awsNeurons,
},
}
}

type InstanceTypeOptions struct {
name string
offerings []cloudprovider.Offering
architecture string
cpu resource.Quantity
memory resource.Quantity
pods resource.Quantity
nvidiaGPUs resource.Quantity
amdGPUs resource.Quantity
awsNeurons resource.Quantity
name string
offerings []cloudprovider.Offering
architecture string
operatingSystems sets.String
cpu resource.Quantity
memory resource.Quantity
pods resource.Quantity
nvidiaGPUs resource.Quantity
amdGPUs resource.Quantity
awsNeurons resource.Quantity
}

type InstanceType struct {
Expand All @@ -85,6 +91,10 @@ func (i *InstanceType) Architecture() string {
return i.architecture
}

func (i *InstanceType) OperatingSystems() sets.String {
return i.operatingSystems
}

func (i *InstanceType) CPU() *resource.Quantity {
return &i.cpu
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/cloudprovider/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/aws/karpenter/pkg/apis/provisioning/v1alpha5"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/kubernetes"
"knative.dev/pkg/apis"
)
Expand Down Expand Up @@ -55,6 +56,7 @@ type InstanceType interface {
// Note that though this is an array it is expected that all the Offerings are unique from one another
Offerings() []Offering
Architecture() string
OperatingSystems() sets.String
CPU() *resource.Quantity
Memory() *resource.Quantity
Pods() *resource.Quantity
Expand Down
10 changes: 9 additions & 1 deletion pkg/controllers/provisioning/binpacking/packable.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func PackablesFor(ctx context.Context, instanceTypes []cloudprovider.InstanceTyp
packable.validateZones(constraints),
packable.validateInstanceType(constraints),
packable.validateArchitecture(constraints),
packable.validateOperatingSystems(constraints),
packable.validateCapacityTypes(constraints),
// Although this will remove instances that have GPUs when
// not required, removal of instance types that *lack*
Expand Down Expand Up @@ -153,7 +154,7 @@ func (p *Packable) reservePod(pod *v1.Pod) bool {

func (p *Packable) validateInstanceType(constraints *v1alpha5.Constraints) error {
if !constraints.Requirements.InstanceTypes().Has(p.Name()) {
return fmt.Errorf("instance type %s is not in %v", p.Name(), constraints.Requirements.InstanceTypes().List())
return fmt.Errorf("instance type %s not in %v", p.Name(), constraints.Requirements.InstanceTypes().List())
}
return nil
}
Expand All @@ -165,6 +166,13 @@ func (p *Packable) validateArchitecture(constraints *v1alpha5.Constraints) error
return nil
}

func (p *Packable) validateOperatingSystems(constraints *v1alpha5.Constraints) error {
if constraints.Requirements.OperatingSystems().Intersection(p.OperatingSystems()).Len() == 0 {
return fmt.Errorf("operating systems %s not in %v", p.OperatingSystems(), constraints.Requirements.OperatingSystems().List())
}
return nil
}

func (p *Packable) validateZones(constraints *v1alpha5.Constraints) error {
zones := sets.String{}
for _, offering := range p.Offerings() {
Expand Down
2 changes: 2 additions & 0 deletions pkg/controllers/provisioning/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ func requirements(instanceTypes []cloudprovider.InstanceType) (requirements v1al
v1.LabelInstanceTypeStable: sets.NewString(),
v1.LabelTopologyZone: sets.NewString(),
v1.LabelArchStable: sets.NewString(),
v1.LabelOSStable: sets.NewString(),
v1alpha5.LabelCapacityType: sets.NewString(),
}
for _, instanceType := range instanceTypes {
Expand All @@ -149,6 +150,7 @@ func requirements(instanceTypes []cloudprovider.InstanceType) (requirements v1al
}
supported[v1.LabelInstanceTypeStable].Insert(instanceType.Name())
supported[v1.LabelArchStable].Insert(instanceType.Architecture())
supported[v1.LabelOSStable].Insert(instanceType.OperatingSystems().List()...)
}
for key, values := range supported {
requirements = append(requirements, v1.NodeSelectorRequirement{Key: key, Operator: v1.NodeSelectorOpIn, Values: values.UnsortedList()})
Expand Down
4 changes: 4 additions & 0 deletions pkg/controllers/provisioning/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ var _ = Describe("Provisioning", func() {
test.UnschedulablePod(test.PodOptions{NodeSelector: map[string]string{v1.LabelInstanceTypeStable: "default-instance-type"}}),
// Constrained by architecture
test.UnschedulablePod(test.PodOptions{NodeSelector: map[string]string{v1.LabelArchStable: "arm64"}}),
// Constrained by operatingSystem
test.UnschedulablePod(test.PodOptions{NodeSelector: map[string]string{v1.LabelOSStable: "linux"}}),
}
unschedulable := []*v1.Pod{
// Ignored, matches another provisioner
Expand All @@ -114,6 +116,8 @@ var _ = Describe("Provisioning", func() {
test.UnschedulablePod(test.PodOptions{NodeSelector: map[string]string{v1.LabelInstanceTypeStable: "unknown"}}),
// Ignored, invalid architecture
test.UnschedulablePod(test.PodOptions{NodeSelector: map[string]string{v1.LabelArchStable: "unknown"}}),
// Ignored, invalid operating system
test.UnschedulablePod(test.PodOptions{NodeSelector: map[string]string{v1.LabelOSStable: "unknown"}}),
// Ignored, invalid capacity type
test.UnschedulablePod(test.PodOptions{NodeSelector: map[string]string{v1alpha5.LabelCapacityType: "unknown"}}),
// Ignored, label selector does not match
Expand Down

1 comment on commit effd8d2

@tuananh
Copy link
Contributor

@tuananh tuananh commented on effd8d2 Dec 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ellistarn i thought kubernetes.io/os values is from runtime.GOOS as defined by Go. the list is here https://github.com/golang/go/blob/a19e72cb89fd33e5bf1474887e267806f65b7a40/src/go/build/syslist.go#L10

const goosList = "aix android darwin dragonfly freebsd hurd illumos ios js linux nacl netbsd openbsd plan9 solaris windows zos "

in there, macOS is darwin instead of mac.

https://kubernetes.io/docs/reference/labels-annotations-taints/#kubernetes-io-os

Please sign in to comment.