Skip to content

Commit

Permalink
feat: support fargate spot (aws#2188)
Browse files Browse the repository at this point in the history
<!-- Provide summary of changes -->

Closes aws#2162

<!-- Issue number, if available. E.g. "Fixes aws#31", "Addresses aws#42, 77" -->

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
  • Loading branch information
SoManyHs authored and thrau committed Dec 9, 2022
1 parent fc9fc65 commit 1ea20a8
Show file tree
Hide file tree
Showing 14 changed files with 721 additions and 107 deletions.
18 changes: 16 additions & 2 deletions internal/pkg/deploy/cloudformation/stack/backend_svc.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,22 @@ func (s *BackendService) Template() (string, error) {
if err != nil {
return "", fmt.Errorf("convert the sidecar configuration for service %s: %w", s.name, err)
}
autoscaling, err := convertAutoscaling(&s.manifest.Count.Autoscaling)

advancedCount, err := convertAdvancedCount(&s.manifest.Count.AdvancedCount)
if err != nil {
return "", fmt.Errorf("convert the Auto Scaling configuration for service %s: %w", s.name, err)
return "", fmt.Errorf("convert the advanced count configuration for service %s: %w", s.name, err)
}

var autoscaling *template.AutoscalingOpts
var desiredCountOnSpot *int
var capacityProviders []*template.CapacityProviderStrategy

if advancedCount != nil {
autoscaling = advancedCount.Autoscaling
desiredCountOnSpot = advancedCount.Spot
capacityProviders = advancedCount.Cps
}

storage, err := convertStorageOpts(s.manifest.Name, s.manifest.Storage)
if err != nil {
return "", fmt.Errorf("convert storage options for service %s: %w", s.name, err)
Expand All @@ -105,6 +117,8 @@ func (s *BackendService) Template() (string, error) {
NestedStack: outputs,
Sidecars: sidecars,
Autoscaling: autoscaling,
CapacityProviders: capacityProviders,
DesiredCountOnSpot: desiredCountOnSpot,
ExecuteCommand: convertExecuteCommand(&s.manifest.ExecuteCommand),
WorkloadType: manifest.BackendServiceType,
HealthCheck: s.manifest.BackendServiceConfig.ImageConfig.HealthCheckOpts(),
Expand Down
10 changes: 6 additions & 4 deletions internal/pkg/deploy/cloudformation/stack/backend_svc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,11 @@ Outputs:
"failed parsing Auto Scaling template": {
setUpManifest: func(svc *BackendService) {
testBackendSvcManifestWithBadAutoScaling := manifest.NewBackendService(baseProps)
badRange := manifest.Range("badRange")
testBackendSvcManifestWithBadAutoScaling.Count.Autoscaling = manifest.Autoscaling{
Range: &badRange,
badRange := manifest.IntRangeBand("badRange")
testBackendSvcManifestWithBadAutoScaling.Count.AdvancedCount = manifest.AdvancedCount{
Range: &manifest.Range{
Value: &badRange,
},
}
svc.manifest = testBackendSvcManifestWithBadAutoScaling
},
Expand All @@ -127,7 +129,7 @@ Outputs:
Value: hello`,
}
},
wantedErr: fmt.Errorf("convert the Auto Scaling configuration for service frontend: %w", errors.New("invalid range value badRange. Should be in format of ${min}-${max}")),
wantedErr: fmt.Errorf("convert the advanced count configuration for service frontend: %w", errors.New("invalid range value badRange. Should be in format of ${min}-${max}")),
},
"failed parsing svc template": {
setUpManifest: func(svc *BackendService) {
Expand Down
17 changes: 15 additions & 2 deletions internal/pkg/deploy/cloudformation/stack/lb_web_svc.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,20 @@ func (s *LoadBalancedWebService) Template() (string, error) {
if err != nil {
return "", fmt.Errorf("convert the sidecar configuration for service %s: %w", s.name, err)
}
autoscaling, err := convertAutoscaling(&s.manifest.Count.Autoscaling)

advancedCount, err := convertAdvancedCount(&s.manifest.Count.AdvancedCount)
if err != nil {
return "", fmt.Errorf("convert the Auto Scaling configuration for service %s: %w", s.name, err)
return "", fmt.Errorf("convert the advanced count configuration for service %s: %w", s.name, err)
}

var autoscaling *template.AutoscalingOpts
var desiredCountOnSpot *int
var capacityProviders []*template.CapacityProviderStrategy

if advancedCount != nil {
autoscaling = advancedCount.Autoscaling
desiredCountOnSpot = advancedCount.Spot
capacityProviders = advancedCount.Cps
}

storage, err := convertStorageOpts(s.manifest.Name, s.manifest.Storage)
Expand All @@ -134,6 +145,8 @@ func (s *LoadBalancedWebService) Template() (string, error) {
LogConfig: convertLogging(s.manifest.Logging),
DockerLabels: s.manifest.ImageConfig.DockerLabels,
Autoscaling: autoscaling,
CapacityProviders: capacityProviders,
DesiredCountOnSpot: desiredCountOnSpot,
ExecuteCommand: convertExecuteCommand(&s.manifest.ExecuteCommand),
WorkloadType: manifest.LoadBalancedWebServiceType,
HTTPHealthCheck: convertHTTPHealthCheck(&s.manifest.HealthCheck),
Expand Down
16 changes: 10 additions & 6 deletions internal/pkg/deploy/cloudformation/stack/lb_web_svc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,18 +307,22 @@ func TestLoadBalancedWebService_Parameters(t *testing.T) {
Port: 80,
}
testLBWebServiceManifest := manifest.NewLoadBalancedWebService(baseProps)
testLBWebServiceManifestRange := manifest.Range("2-100")
testLBWebServiceManifestRange := manifest.IntRangeBand("2-100")
testLBWebServiceManifest.Count = manifest.Count{
Value: aws.Int(1),
Autoscaling: manifest.Autoscaling{
Range: &testLBWebServiceManifestRange,
AdvancedCount: manifest.AdvancedCount{
Range: &manifest.Range{
Value: &testLBWebServiceManifestRange,
},
},
}
testLBWebServiceManifestWithBadCount := manifest.NewLoadBalancedWebService(baseProps)
testLBWebServiceManifestWithBadCountRange := manifest.Range("badCount")
testLBWebServiceManifestWithBadCountRange := manifest.IntRangeBand("badCount")
testLBWebServiceManifestWithBadCount.Count = manifest.Count{
Autoscaling: manifest.Autoscaling{
Range: &testLBWebServiceManifestWithBadCountRange,
AdvancedCount: manifest.AdvancedCount{
Range: &manifest.Range{
Value: &testLBWebServiceManifestWithBadCountRange,
},
},
}
testLBWebServiceManifestWithSidecar := manifest.NewLoadBalancedWebService(baseProps)
Expand Down
91 changes: 90 additions & 1 deletion internal/pkg/deploy/cloudformation/stack/transformers.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ const (
defaultSidecarPort = "80"
)

// Supported capacity providers for Fargate services
const (
capacityProviderFargateSpot = "FARGATE_SPOT"
capacityProviderFargate = "FARGATE"
)

// Validation errors when rendering manifest into template.
var (
errNoFSID = errors.New(`volume field efs/id cannot be empty`)
Expand All @@ -45,6 +51,7 @@ var (
errUIDWithNonManagedFS = errors.New("UID and GID cannot be specified with non-managed EFS")
errInvalidUIDGIDConfig = errors.New("set managed filesystem access point creation info: must specify both UID and GID, or neither")
errReservedUID = errors.New("set managed filesystem access point creation info: UID must not be 0")
errInvalidSpotConfig = errors.New(`"count.spot" and "count.range" cannot be specified together`)
)

// convertSidecar converts the manifest sidecar configuration into a format parsable by the templates pkg.
Expand Down Expand Up @@ -95,12 +102,94 @@ func parsePortMapping(s *string) (port *string, protocol *string, err error) {
}
}

func convertAdvancedCount(a *manifest.AdvancedCount) (*template.AdvancedCount, error) {
if a == nil {
return nil, nil
}

if a.IsEmpty() {
return nil, nil
}

autoscaling, err := convertAutoscaling(a)
if err != nil {
return nil, err
}

cps, err := convertCapacityProviders(a)
if err != nil {
return nil, err
}

return &template.AdvancedCount{
Spot: a.Spot,
Autoscaling: autoscaling,
Cps: cps,
}, nil
}

// convertCapacityProviders transforms the manifest fields into a format
// parsable by the templates pkg.
func convertCapacityProviders(a *manifest.AdvancedCount) ([]*template.CapacityProviderStrategy, error) {
if a.IsEmpty() {
return nil, nil
}

if a.Spot != nil && a.Range != nil {
return nil, errInvalidSpotConfig
}

// return if autoscaling range specified without spot scaling
if a.Range != nil && a.Range.Value != nil {
return nil, nil
}

var cps []*template.CapacityProviderStrategy

// if Spot specified as count, then weight on Spot CPS should be 1
cps = append(cps, &template.CapacityProviderStrategy{
Weight: aws.Int(1),
CapacityProvider: capacityProviderFargateSpot,
})

// Return if only spot is specifed as count
if a.Range == nil {
return cps, nil
}

// Scaling with spot
rc := a.Range.RangeConfig
if !rc.IsEmpty() {
spotFrom := aws.IntValue(rc.SpotFrom)
min := aws.IntValue(rc.Min)

// If spotFrom value is not equal to the autoscaling min, then
// the base value on the Fargate Capacity provider must be set
// to one less than spotFrom
if spotFrom > min {
base := spotFrom - 1
fgCapacity := &template.CapacityProviderStrategy{
Base: aws.Int(base),
Weight: aws.Int(0),
CapacityProvider: capacityProviderFargate,
}
cps = append(cps, fgCapacity)
}
}

return cps, nil
}

// convertAutoscaling converts the service's Auto Scaling configuration into a format parsable
// by the templates pkg.
func convertAutoscaling(a *manifest.Autoscaling) (*template.AutoscalingOpts, error) {
func convertAutoscaling(a *manifest.AdvancedCount) (*template.AutoscalingOpts, error) {
if a.IsEmpty() {
return nil, nil
}
if a.Spot != nil {
return nil, nil
}

min, max, err := a.Range.Parse()
if err != nil {
return nil, err
Expand Down
Loading

0 comments on commit 1ea20a8

Please sign in to comment.