Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create constraints on offerings #1262

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions kwok/cloudprovider/cloudprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@ func addInstanceLabels(labels map[string]string, instanceType *cloudprovider.Ins
// Kwok has some scalability limitations.
// Randomly add each new node to one of the pre-created kwokPartitions.
ret[kwokPartitionLabelKey] = lo.Sample(KwokPartitions)
ret[v1beta1.CapacityTypeLabelKey] = offering.CapacityType
ret[v1.LabelTopologyZone] = offering.Zone
ret[v1beta1.CapacityTypeLabelKey] = offering.Requirements.Get(v1beta1.CapacityTypeLabelKey).Any()
ret[v1.LabelTopologyZone] = offering.Requirements.Get(v1.LabelTopologyZone).Any()
ret[v1.LabelHostname] = nodeClaim.Name

ret[kwokLabelKey] = kwokLabelValue
Expand Down
8 changes: 6 additions & 2 deletions kwok/cloudprovider/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,12 @@ func newInstanceType(options InstanceTypeOptions) *cloudprovider.InstanceType {
scheduling.NewRequirement(v1.LabelInstanceTypeStable, v1.NodeSelectorOpIn, options.Name),
scheduling.NewRequirement(v1.LabelArchStable, v1.NodeSelectorOpIn, options.Architecture),
scheduling.NewRequirement(v1.LabelOSStable, v1.NodeSelectorOpIn, osNames...),
scheduling.NewRequirement(v1.LabelTopologyZone, v1.NodeSelectorOpIn, lo.Map(options.Offerings.Available(), func(o cloudprovider.Offering, _ int) string { return o.Zone })...),
scheduling.NewRequirement(v1beta1.CapacityTypeLabelKey, v1.NodeSelectorOpIn, lo.Map(options.Offerings.Available(), func(o cloudprovider.Offering, _ int) string { return o.CapacityType })...),
scheduling.NewRequirement(v1.LabelTopologyZone, v1.NodeSelectorOpIn, lo.Map(options.Offerings.Available(), func(o cloudprovider.Offering, _ int) string {
return o.Requirements.Get(v1.LabelTopologyZone).Any()
})...),
scheduling.NewRequirement(v1beta1.CapacityTypeLabelKey, v1.NodeSelectorOpIn, lo.Map(options.Offerings.Available(), func(o cloudprovider.Offering, _ int) string {
return o.Requirements.Get(v1beta1.CapacityTypeLabelKey).Any()
})...),
scheduling.NewRequirement(InstanceSizeLabelKey, v1.NodeSelectorOpIn, options.instanceTypeLabels[InstanceSizeLabelKey]),
scheduling.NewRequirement(InstanceFamilyLabelKey, v1.NodeSelectorOpIn, options.instanceTypeLabels[InstanceFamilyLabelKey]),
scheduling.NewRequirement(InstanceCPULabelKey, v1.NodeSelectorOpIn, options.instanceTypeLabels[InstanceCPULabelKey]),
Expand Down
11 changes: 7 additions & 4 deletions kwok/tools/gen_instance_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
kwok "sigs.k8s.io/karpenter/kwok/cloudprovider"
"sigs.k8s.io/karpenter/pkg/apis/v1beta1"
"sigs.k8s.io/karpenter/pkg/cloudprovider"
"sigs.k8s.io/karpenter/pkg/scheduling"
)

var (
Expand Down Expand Up @@ -93,10 +94,12 @@ func constructGenericInstanceTypes() []kwok.InstanceTypeOptions {
for _, zone := range KwokZones {
for _, ct := range []string{v1beta1.CapacityTypeSpot, v1beta1.CapacityTypeOnDemand} {
opts.Offerings = append(opts.Offerings, cloudprovider.Offering{
CapacityType: ct,
Zone: zone,
Price: lo.Ternary(ct == v1beta1.CapacityTypeSpot, price*.7, price),
Available: true,
Requirements: scheduling.NewLabelRequirements(map[string]string{
v1beta1.CapacityTypeLabelKey: ct,
v1.LabelTopologyZone: zone,
}),
Price: lo.Ternary(ct == v1beta1.CapacityTypeSpot, price*.7, price),
Available: true,
})
}
}
Expand Down
9 changes: 3 additions & 6 deletions pkg/cloudprovider/fake/cloudprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,9 @@ func (c *CloudProvider) Create(ctx context.Context, nodeClaim *v1beta1.NodeClaim
}
// Find Offering
for _, o := range instanceType.Offerings.Available() {
if reqs.Compatible(scheduling.NewRequirements(
scheduling.NewRequirement(v1.LabelTopologyZone, v1.NodeSelectorOpIn, o.Zone),
scheduling.NewRequirement(v1beta1.CapacityTypeLabelKey, v1.NodeSelectorOpIn, o.CapacityType),
), scheduling.AllowUndefinedWellKnownLabels) == nil {
labels[v1.LabelTopologyZone] = o.Zone
labels[v1beta1.CapacityTypeLabelKey] = o.CapacityType
if reqs.Compatible(o.Requirements, scheduling.AllowUndefinedWellKnownLabels) == nil {
labels[v1.LabelTopologyZone] = o.Requirements.Get(v1.LabelTopologyZone).Any()
labels[v1beta1.CapacityTypeLabelKey] = o.Requirements.Get(v1beta1.CapacityTypeLabelKey).Any()
break
}
}
Expand Down
43 changes: 32 additions & 11 deletions pkg/cloudprovider/fake/instancetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,26 @@ func NewInstanceTypeWithCustomRequirement(options InstanceTypeOptions, customReq
}
if len(options.Offerings) == 0 {
options.Offerings = []cloudprovider.Offering{
{CapacityType: "spot", Zone: "test-zone-1", Price: PriceFromResources(options.Resources), Available: true},
{CapacityType: "spot", Zone: "test-zone-2", Price: PriceFromResources(options.Resources), Available: true},
{CapacityType: "on-demand", Zone: "test-zone-1", Price: PriceFromResources(options.Resources), Available: true},
{CapacityType: "on-demand", Zone: "test-zone-2", Price: PriceFromResources(options.Resources), Available: true},
{CapacityType: "on-demand", Zone: "test-zone-3", Price: PriceFromResources(options.Resources), Available: true},
{Requirements: scheduling.NewLabelRequirements(map[string]string{
v1beta1.CapacityTypeLabelKey: "spot",
v1.LabelTopologyZone: "test-zone-1",
}), Price: PriceFromResources(options.Resources), Available: true},
{Requirements: scheduling.NewLabelRequirements(map[string]string{
v1beta1.CapacityTypeLabelKey: "spot",
v1.LabelTopologyZone: "test-zone-2",
}), Price: PriceFromResources(options.Resources), Available: true},
{Requirements: scheduling.NewLabelRequirements(map[string]string{
v1beta1.CapacityTypeLabelKey: "on-demand",
v1.LabelTopologyZone: "test-zone-1",
}), Price: PriceFromResources(options.Resources), Available: true},
{Requirements: scheduling.NewLabelRequirements(map[string]string{
v1beta1.CapacityTypeLabelKey: "on-demand",
v1.LabelTopologyZone: "test-zone-2",
}), Price: PriceFromResources(options.Resources), Available: true},
{Requirements: scheduling.NewLabelRequirements(map[string]string{
v1beta1.CapacityTypeLabelKey: "on-demand",
v1.LabelTopologyZone: "test-zone-3",
}), Price: PriceFromResources(options.Resources), Available: true},
}
}
if len(options.Architecture) == 0 {
Expand All @@ -83,8 +98,12 @@ func NewInstanceTypeWithCustomRequirement(options InstanceTypeOptions, customReq
scheduling.NewRequirement(v1.LabelInstanceTypeStable, v1.NodeSelectorOpIn, options.Name),
scheduling.NewRequirement(v1.LabelArchStable, v1.NodeSelectorOpIn, options.Architecture),
scheduling.NewRequirement(v1.LabelOSStable, v1.NodeSelectorOpIn, sets.List(options.OperatingSystems)...),
scheduling.NewRequirement(v1.LabelTopologyZone, v1.NodeSelectorOpIn, lo.Map(options.Offerings.Available(), func(o cloudprovider.Offering, _ int) string { return o.Zone })...),
scheduling.NewRequirement(v1beta1.CapacityTypeLabelKey, v1.NodeSelectorOpIn, lo.Map(options.Offerings.Available(), func(o cloudprovider.Offering, _ int) string { return o.CapacityType })...),
scheduling.NewRequirement(v1.LabelTopologyZone, v1.NodeSelectorOpIn, lo.Map(options.Offerings.Available(), func(o cloudprovider.Offering, _ int) string {
return o.Requirements.Get(v1.LabelTopologyZone).Any()
})...),
scheduling.NewRequirement(v1beta1.CapacityTypeLabelKey, v1.NodeSelectorOpIn, lo.Map(options.Offerings.Available(), func(o cloudprovider.Offering, _ int) string {
return o.Requirements.Get(v1beta1.CapacityTypeLabelKey).Any()
})...),
scheduling.NewRequirement(LabelInstanceSize, v1.NodeSelectorOpDoesNotExist),
scheduling.NewRequirement(ExoticInstanceLabelKey, v1.NodeSelectorOpDoesNotExist),
scheduling.NewRequirement(IntegerInstanceLabelKey, v1.NodeSelectorOpIn, fmt.Sprint(options.Resources.Cpu().Value())),
Expand Down Expand Up @@ -135,10 +154,12 @@ func InstanceTypesAssorted() []*cloudprovider.InstanceType {
price := PriceFromResources(opts.Resources)
opts.Offerings = []cloudprovider.Offering{
{
CapacityType: ct,
Zone: zone,
Price: price,
Available: true,
Requirements: scheduling.NewLabelRequirements(map[string]string{
v1beta1.CapacityTypeLabelKey: ct,
v1.LabelTopologyZone: zone,
}),
Price: price,
Available: true,
},
}
instanceTypes = append(instanceTypes, NewInstanceType(opts))
Expand Down
23 changes: 15 additions & 8 deletions pkg/cloudprovider/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,11 @@ func (i InstanceTypeOverhead) Total() v1.ResourceList {
}

// An Offering describes where an InstanceType is available to be used, with the expectation that its properties
// may be tightly coupled (e.g. the availability of an instance type in some zone is scoped to a capacity type)
// may be tightly coupled (e.g. the availability of an instance type in some zone is scoped to a capacity type) and
// these properties are captured with labels in Requirements.
// Requirements are required to contain the keys v1beta1.CapacityTypeLabelKey and v1.LabelTopologyZone
type Offering struct {
rschalo marked this conversation as resolved.
Show resolved Hide resolved
CapacityType string
Zone string
Requirements scheduling.Requirements
rschalo marked this conversation as resolved.
Show resolved Hide resolved
Price float64
// Available is added so that Offerings can return all offerings that have ever existed for an instance type,
// so we can get historical pricing data for calculating savings in consolidation
Expand All @@ -228,10 +229,10 @@ type Offering struct {
type Offerings []Offering

// Get gets the offering from an offering slice that matches the
// passed zone and capacity type
func (ofs Offerings) Get(ct, zone string) (Offering, bool) {
// passed zone, capacityType, and other constraints
func (ofs Offerings) Get(reqs scheduling.Requirements) (Offering, bool) {
rschalo marked this conversation as resolved.
Show resolved Hide resolved
return lo.Find(ofs, func(of Offering) bool {
return of.CapacityType == ct && of.Zone == zone
return reqs.Compatible(of.Requirements, scheduling.AllowUndefinedWellKnownLabels) == nil
})
}

Expand All @@ -245,8 +246,7 @@ func (ofs Offerings) Available() Offerings {
// Compatible returns the offerings based on the passed requirements
func (ofs Offerings) Compatible(reqs scheduling.Requirements) Offerings {
rschalo marked this conversation as resolved.
Show resolved Hide resolved
rschalo marked this conversation as resolved.
Show resolved Hide resolved
return lo.Filter(ofs, func(offering Offering, _ int) bool {
return (!reqs.Has(v1.LabelTopologyZone) || reqs.Get(v1.LabelTopologyZone).Has(offering.Zone)) &&
(!reqs.Has(v1beta1.CapacityTypeLabelKey) || reqs.Get(v1beta1.CapacityTypeLabelKey).Has(offering.CapacityType))
return reqs.Compatible(offering.Requirements, scheduling.AllowUndefinedWellKnownLabels) == nil
})
}

Expand All @@ -257,6 +257,13 @@ func (ofs Offerings) Cheapest() Offering {
})
}

// MostExpensive returns the most expensive offering from the return offerings
func (ofs Offerings) MostExpensive() Offering {
return lo.MaxBy(ofs, func(a, b Offering) bool {
return a.Price > b.Price
})
}

// NodeClaimNotFoundError is an error type returned by CloudProviders when the reason for failure is NotFound
type NodeClaimNotFoundError struct {
error
Expand Down
2 changes: 1 addition & 1 deletion pkg/controllers/disruption/consolidation.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ func (c *consolidation) computeSpotToSpotConsolidation(ctx context.Context, cand
func getCandidatePrices(candidates []*Candidate) (float64, error) {
var price float64
for _, c := range candidates {
offering, ok := c.instanceType.Offerings.Get(c.capacityType, c.zone)
offering, ok := c.instanceType.Offerings.Get(scheduling.NewLabelRequirements(c.StateNode.Labels()))
if !ok {
return 0.0, fmt.Errorf("unable to determine offering for %s/%s/%s", c.instanceType.Name, c.capacityType, c.zone)
}
Expand Down
Loading