Skip to content

Commit 88a7507

Browse files
authored
Remove mechanism for filling the instance distribution w/ compatibles (#979)
1 parent 7acea1e commit 88a7507

File tree

4 files changed

+19
-103
lines changed

4 files changed

+19
-103
lines changed

cli/cmd/lib_cluster_config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ func getClusterUpdateConfig(cachedClusterConfig clusterconfig.Config, awsCreds A
225225
userClusterConfig.Spot = cachedClusterConfig.Spot
226226

227227
if userClusterConfig.Spot != nil && *userClusterConfig.Spot {
228-
err = userClusterConfig.AutoFillSpot(awsClient)
228+
err = userClusterConfig.FillEmptySpotFields(awsClient)
229229
if err != nil {
230230
return nil, err
231231
}

docs/cluster-management/spot-instances.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ _WARNING: you are on the master branch, please refer to the docs on the branch t
1111
spot: false
1212

1313
spot_config:
14-
# additional instances with identical or better specs than the primary instance type (defaults to 2 instances sorted by price)
14+
# additional instances with identical or better specs than the primary instance type (defaults to only the primary instance)
1515
instance_distribution: [similar_instance_type_1, similar_instance_type_2]
1616

1717
# minimum number of on demand instances (default: 0)
@@ -31,7 +31,7 @@ spot_config:
3131
on_demand_backup: true
3232
```
3333
34-
Spot instances are not guaranteed to be available. The chances of getting spot instances can be improved by providing `instance_distribution`, a list of alternative instance types to the primary `instance_type` you specified. If left blank, Cortex will autofill `instance_distribution` with up to 2 other similar instances. Cortex defaults the `max_price` to the on-demand price of the primary instance.
34+
Spot instances are not guaranteed to be available. The chances of getting spot instances can be improved by providing `instance_distribution`, a list of alternative instance types to the primary `instance_type` you specified. If left blank, Cortex will only include the primary instance type in the `instance_distribution`. Cortex defaults the `max_price` to the on-demand price of the primary instance.
3535

3636
Spot instances can be mixed with on-demand instances by configuring `on_demand_base_capacity` and `on_demand_percentage_above_base_capacity`. `on_demand_base_capacity` enforces the minimum number of nodes that will be fulfilled by on-demand instances as your cluster is scaling up. `on_demand_percentage_above_base_capacity` defines the percentage of instances that will be on-demand after the base capacity has been fulfilled (the rest being spot instances). `instance_pools` is the number of pools per availability zone to allocate your instances from. See [here](https://docs.aws.amazon.com/autoscaling/ec2/APIReference/API_InstancesDistribution.html) for more details.
3737

pkg/types/clusterconfig/clusterconfig.go

Lines changed: 15 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package clusterconfig
1919
import (
2020
"fmt"
2121
"regexp"
22-
"sort"
2322
"strings"
2423

2524
"github.com/aws/amazon-vpc-cni-k8s/pkg/awsutils"
@@ -412,11 +411,12 @@ func (cc *Config) Validate(awsClient *aws.Client) error {
412411
return ErrorS3RegionDiffersFromCluster(cc.Bucket, bucketRegion, *cc.Region)
413412
}
414413

415-
if _, ok := aws.InstanceMetadatas[*cc.Region][*cc.InstanceType]; !ok {
416-
return errors.Wrap(ErrorInstanceTypeNotSupportedInRegion(*cc.InstanceType, *cc.Region), InstanceTypeKey)
414+
primaryInstanceType := *cc.InstanceType
415+
if _, ok := aws.InstanceMetadatas[*cc.Region][primaryInstanceType]; !ok {
416+
return errors.Wrap(ErrorInstanceTypeNotSupportedInRegion(primaryInstanceType, *cc.Region), InstanceTypeKey)
417417
}
418418

419-
if err := awsClient.VerifyInstanceQuota(*cc.InstanceType); err != nil {
419+
if err := awsClient.VerifyInstanceQuota(primaryInstanceType); err != nil {
420420
// Skip AWS errors, since some regions (e.g. eu-north-1) do not support this API
421421
if _, ok := errors.CauseOrSelf(err).(awserr.Error); !ok {
422422
return errors.Wrap(err, InstanceTypeKey)
@@ -428,44 +428,29 @@ func (cc *Config) Validate(awsClient *aws.Client) error {
428428
}
429429

430430
if cc.Spot != nil && *cc.Spot {
431-
cc.AutoFillSpot(awsClient)
432-
chosenInstance := aws.InstanceMetadatas[*cc.Region][*cc.InstanceType]
433-
compatibleSpots := CompatibleSpotInstances(awsClient, chosenInstance, cc.SpotConfig.MaxPrice, _spotInstanceDistributionLength)
434-
if len(compatibleSpots) == 0 {
435-
return errors.Wrap(ErrorNoCompatibleSpotInstanceFound(chosenInstance.Type), InstanceTypeKey)
436-
}
431+
cc.FillEmptySpotFields(awsClient)
437432

438-
compatibleInstanceCount := 0
433+
primaryInstance := aws.InstanceMetadatas[*cc.Region][primaryInstanceType]
439434
for _, instanceType := range cc.SpotConfig.InstanceDistribution {
440-
if instanceType == *cc.InstanceType {
435+
if instanceType == primaryInstanceType {
441436
continue
442437
}
443438
if _, ok := aws.InstanceMetadatas[*cc.Region][instanceType]; !ok {
444-
return errors.Wrap(ErrorInstanceTypeNotSupportedInRegion(instanceType, *cc.Region), InstanceDistributionKey)
439+
return errors.Wrap(ErrorInstanceTypeNotSupportedInRegion(instanceType, *cc.Region), SpotConfigKey, InstanceDistributionKey)
445440
}
446441

447442
instanceMetadata := aws.InstanceMetadatas[*cc.Region][instanceType]
448-
err := CheckSpotInstanceCompatibility(chosenInstance, instanceMetadata)
443+
err := CheckSpotInstanceCompatibility(primaryInstance, instanceMetadata)
449444
if err != nil {
450-
return errors.Wrap(err, InstanceDistributionKey)
445+
return errors.Wrap(err, SpotConfigKey, InstanceDistributionKey)
451446
}
452447

453448
spotInstancePrice, awsErr := awsClient.SpotInstancePrice(instanceMetadata.Region, instanceMetadata.Type)
454449
if awsErr == nil {
455-
if err := CheckSpotInstancePriceCompatibility(chosenInstance, instanceMetadata, cc.SpotConfig.MaxPrice, spotInstancePrice); err != nil {
456-
return errors.Wrap(err, InstanceDistributionKey)
450+
if err := CheckSpotInstancePriceCompatibility(primaryInstance, instanceMetadata, cc.SpotConfig.MaxPrice, spotInstancePrice); err != nil {
451+
return errors.Wrap(err, SpotConfigKey, InstanceDistributionKey)
457452
}
458453
}
459-
460-
compatibleInstanceCount++
461-
}
462-
463-
if compatibleInstanceCount == 0 {
464-
suggestions := []string{}
465-
for _, compatibleInstance := range compatibleSpots {
466-
suggestions = append(suggestions, compatibleInstance.Type)
467-
}
468-
return ErrorAtLeastOneInstanceDistribution(*cc.InstanceType, suggestions[0], suggestions[1:]...)
469454
}
470455

471456
if cc.SpotConfig.OnDemandBaseCapacity != nil && *cc.SpotConfig.OnDemandBaseCapacity > *cc.MaxInstances {
@@ -524,50 +509,8 @@ func CheckSpotInstancePriceCompatibility(target aws.InstanceMetadata, suggested
524509
return nil
525510
}
526511

527-
func CompatibleSpotInstances(awsClient *aws.Client, targetInstance aws.InstanceMetadata, maxPrice *float64, numInstances int) []aws.InstanceMetadata {
528-
compatibleInstances := []aws.InstanceMetadata{}
529-
instanceMap := aws.InstanceMetadatas[targetInstance.Region]
530-
availableInstances := []aws.InstanceMetadata{}
531-
532-
for instanceType, instanceMetadata := range instanceMap {
533-
if instanceType == targetInstance.Type {
534-
continue
535-
}
536-
availableInstances = append(availableInstances, instanceMetadata)
537-
}
538-
539-
sort.Slice(availableInstances, func(i, j int) bool {
540-
return availableInstances[i].Price < availableInstances[j].Price
541-
})
542-
543-
for _, instanceMetadata := range availableInstances {
544-
if err := CheckCortexSupport(instanceMetadata); err != nil {
545-
continue
546-
}
547-
548-
if err := CheckSpotInstanceCompatibility(targetInstance, instanceMetadata); err != nil {
549-
continue
550-
}
551-
552-
spotInstancePrice, awsErr := awsClient.SpotInstancePrice(instanceMetadata.Region, instanceMetadata.Type)
553-
if awsErr == nil {
554-
if err := CheckSpotInstancePriceCompatibility(targetInstance, instanceMetadata, maxPrice, spotInstancePrice); err != nil {
555-
continue
556-
}
557-
}
558-
559-
compatibleInstances = append(compatibleInstances, instanceMetadata)
560-
561-
if len(compatibleInstances) == numInstances {
562-
break
563-
}
564-
}
565-
566-
return compatibleInstances
567-
}
568-
569512
func AutoGenerateSpotConfig(awsClient *aws.Client, spotConfig *SpotConfig, region string, instanceType string) error {
570-
chosenInstance := aws.InstanceMetadatas[region][instanceType]
513+
primaryInstance := aws.InstanceMetadatas[region][instanceType]
571514
cleanedDistribution := []string{instanceType}
572515
for _, spotInstance := range spotConfig.InstanceDistribution {
573516
if spotInstance != instanceType {
@@ -576,19 +519,8 @@ func AutoGenerateSpotConfig(awsClient *aws.Client, spotConfig *SpotConfig, regio
576519
}
577520
spotConfig.InstanceDistribution = cleanedDistribution
578521

579-
if len(spotConfig.InstanceDistribution) == 1 {
580-
compatibleSpots := CompatibleSpotInstances(awsClient, chosenInstance, spotConfig.MaxPrice, _spotInstanceDistributionLength)
581-
if len(compatibleSpots) == 0 {
582-
return errors.Wrap(ErrorNoCompatibleSpotInstanceFound(chosenInstance.Type), InstanceTypeKey)
583-
}
584-
585-
for _, instance := range compatibleSpots {
586-
spotConfig.InstanceDistribution = append(spotConfig.InstanceDistribution, instance.Type)
587-
}
588-
}
589-
590522
if spotConfig.MaxPrice == nil {
591-
spotConfig.MaxPrice = &chosenInstance.Price
523+
spotConfig.MaxPrice = &primaryInstance.Price
592524
}
593525

594526
if spotConfig.OnDemandBaseCapacity == nil {
@@ -614,7 +546,7 @@ func AutoGenerateSpotConfig(awsClient *aws.Client, spotConfig *SpotConfig, regio
614546
return nil
615547
}
616548

617-
func (cc *Config) AutoFillSpot(awsClient *aws.Client) error {
549+
func (cc *Config) FillEmptySpotFields(awsClient *aws.Client) error {
618550
if cc.SpotConfig == nil {
619551
cc.SpotConfig = &SpotConfig{}
620552
}

pkg/types/clusterconfig/errors.go

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func ErrorIncompatibleSpotInstanceTypeGPU(target aws.InstanceMetadata, suggested
102102
func ErrorSpotPriceGreaterThanTargetOnDemand(suggestedSpotPrice float64, target aws.InstanceMetadata, suggested aws.InstanceMetadata) error {
103103
return errors.WithStack(&errors.Error{
104104
Kind: ErrSpotPriceGreaterThanTargetOnDemand,
105-
Message: fmt.Sprintf("%s will not be allocated because its current spot price is $%g which is greater than than %s's on-demand price of $%g", suggested.Type, suggestedSpotPrice, target.Type, target.Price),
105+
Message: fmt.Sprintf("%s will not be allocated because its current spot price is $%g which is greater than %s's on-demand price of $%g", suggested.Type, suggestedSpotPrice, target.Type, target.Price),
106106
})
107107
}
108108

@@ -120,22 +120,6 @@ func ErrorInstanceTypeNotSupported(instanceType string) error {
120120
})
121121
}
122122

123-
func ErrorAtLeastOneInstanceDistribution(instanceType string, suggestion string, suggestions ...string) error {
124-
allSuggestions := append(suggestions, suggestion)
125-
message := strings.Join(allSuggestions, ", ")
126-
return errors.WithStack(&errors.Error{
127-
Kind: ErrAtLeastOneInstanceDistribution,
128-
Message: fmt.Sprintf("at least one compatible instance type other than %s must be specified (suggestions: %s)", instanceType, message),
129-
})
130-
}
131-
132-
func ErrorNoCompatibleSpotInstanceFound(instanceType string) error {
133-
return errors.WithStack(&errors.Error{
134-
Kind: ErrNoCompatibleSpotInstanceFound,
135-
Message: fmt.Sprintf("unable to find compatible spot instance types for %s", instanceType),
136-
})
137-
}
138-
139123
func ErrorConfiguredWhenSpotIsNotEnabled(configKey string) error {
140124
return errors.WithStack(&errors.Error{
141125
Kind: ErrConfiguredWhenSpotIsNotEnabled,

0 commit comments

Comments
 (0)