From 9df631ebde565e3ad324be9e7493b9f529684f04 Mon Sep 17 00:00:00 2001 From: FUJIWARA Shunichiro Date: Wed, 4 Dec 2019 17:42:26 +0900 Subject: [PATCH 1/8] update to aws-sdk-go v1.25.47 Support to Farget Spot! --- go.mod | 2 +- go.sum | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index fba1d42b..7224cce6 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.12 require ( github.com/Songmu/prompter v0.0.0-20150725163906-b5721e8d5566 github.com/alecthomas/kingpin v1.3.8-0.20190930021037-0a108b7f5563 - github.com/aws/aws-sdk-go v1.25.44 + github.com/aws/aws-sdk-go v1.25.47 github.com/kayac/go-config v0.1.0 github.com/kr/pretty v0.1.0 // indirect github.com/mattn/go-isatty v0.0.10 diff --git a/go.sum b/go.sum index 61131b12..492a9b67 100644 --- a/go.sum +++ b/go.sum @@ -8,12 +8,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5Vpd github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/aws/aws-sdk-go v1.25.26 h1:hMrxW3feteaGcP32oKaFdCQsCEWYf9zF12g73C0AcbI= -github.com/aws/aws-sdk-go v1.25.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.25.37 h1:gBtB/F3dophWpsUQKN/Kni+JzYEH2mGHF4hWNtfED1w= -github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.25.44 h1:n9ahFoiyn66smjF34hYr3tb6/ZdBcLuFz7BCDhHyJ7I= -github.com/aws/aws-sdk-go v1.25.44/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.47 h1:Y13LHLosjP35FPWae95teJC4eQH2YeKD0I0dVFZ4CUM= +github.com/aws/aws-sdk-go v1.25.47/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -54,7 +50,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 83203c87da9bf986be5b940d6cec8350e803d803 Mon Sep 17 00:00:00 2001 From: FUJIWARA Shunichiro Date: Wed, 4 Dec 2019 18:18:42 +0900 Subject: [PATCH 2/8] update service attributes on deploy/rollback. Enable to change CapacityProviderStrategy, NetworkConfiguration, HealthCheckGracePeriodSeconds and PlatformVersion of the service. --- deploy.go | 35 ++++++++++++++++++++--------------- rollback.go | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/deploy.go b/deploy.go index 57fdcdb3..6289b669 100644 --- a/deploy.go +++ b/deploy.go @@ -100,7 +100,7 @@ func (d *App) Deploy(opt DeployOption) error { } // rolling deploy (ECS internal) - if err := d.UpdateService(ctx, tdArn, count, *opt.ForceNewDeployment, sv); err != nil { + if err := d.UpdateService(ctx, tdArn, count, *opt.ForceNewDeployment); err != nil { return errors.Wrap(err, "failed to update service") } @@ -118,7 +118,12 @@ func (d *App) Deploy(opt DeployOption) error { return nil } -func (d *App) UpdateService(ctx context.Context, taskDefinitionArn string, count *int64, force bool, sv *ecs.Service) error { +func (d *App) UpdateService(ctx context.Context, taskDefinitionArn string, count *int64, force bool) error { + svd, err := d.LoadServiceDefinition(d.config.ServiceDefinitionPath) + if err != nil { + return err + } + msg := "Updating service" if force { msg = msg + " with force new deployment" @@ -126,19 +131,19 @@ func (d *App) UpdateService(ctx context.Context, taskDefinitionArn string, count msg = msg + "..." d.Log(msg) - _, err := d.ecs.UpdateServiceWithContext( - ctx, - &ecs.UpdateServiceInput{ - Service: aws.String(d.Service), - Cluster: aws.String(d.Cluster), - TaskDefinition: aws.String(taskDefinitionArn), - DesiredCount: count, - ForceNewDeployment: &force, - NetworkConfiguration: sv.NetworkConfiguration, - HealthCheckGracePeriodSeconds: sv.HealthCheckGracePeriodSeconds, - PlatformVersion: sv.PlatformVersion, - }, - ) + in := &ecs.UpdateServiceInput{ + Service: aws.String(d.Service), + Cluster: aws.String(d.Cluster), + CapacityProviderStrategy: svd.CapacityProviderStrategy, + TaskDefinition: aws.String(taskDefinitionArn), + DesiredCount: count, + ForceNewDeployment: &force, + NetworkConfiguration: svd.NetworkConfiguration, + HealthCheckGracePeriodSeconds: svd.HealthCheckGracePeriodSeconds, + PlatformVersion: svd.PlatformVersion, + } + d.DebugLog("UpdateServiceInput: " + in.String()) + _, err = d.ecs.UpdateServiceWithContext(ctx, in) return err } diff --git a/rollback.go b/rollback.go index 284e88c1..9c96f5a4 100644 --- a/rollback.go +++ b/rollback.go @@ -32,7 +32,7 @@ func (d *App) Rollback(opt RollbackOption) error { return nil } - if err := d.UpdateService(ctx, targetArn, sv.DesiredCount, false, sv); err != nil { + if err := d.UpdateService(ctx, targetArn, sv.DesiredCount, false); err != nil { return errors.Wrap(err, "failed to update service") } From 682e9c6f20a93484c2f02bcb528c554e057306b2 Mon Sep 17 00:00:00 2001 From: FUJIWARA Shunichiro Date: Thu, 5 Dec 2019 11:32:25 +0900 Subject: [PATCH 3/8] set service attributes to run task input. --- ecspresso.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ecspresso.go b/ecspresso.go index 8b1d25cb..3a295de5 100644 --- a/ecspresso.go +++ b/ecspresso.go @@ -633,6 +633,10 @@ func (d *App) RunTask(ctx context.Context, tdArn string, sv *ecs.Service, ov *ec LaunchType: sv.LaunchType, Overrides: ov, Count: aws.Int64(count), + CapacityProviderStrategy: sv.CapacityProviderStrategy, + PlacementConstraints: sv.PlacementConstraints, + PlacementStrategy: sv.PlacementStrategy, + PlatformVersion: sv.PlatformVersion, }, ) if err != nil { From 2795b648dbd546bb4d0520f73b0cf6994e7c7db9 Mon Sep 17 00:00:00 2001 From: FUJIWARA Shunichiro Date: Thu, 5 Dec 2019 11:33:01 +0900 Subject: [PATCH 4/8] add deploy --update-service option to update service attributes by service definition. UpdateService() function becomes to split to UpdateServiceTasks() and UpdateServiceAttributes(). --- cmd/ecspresso/main.go | 5 +-- deploy.go | 74 ++++++++++++++++++++++++++++++------------- options.go | 1 + rollback.go | 3 +- 4 files changed, 58 insertions(+), 25 deletions(-) diff --git a/cmd/ecspresso/main.go b/cmd/ecspresso/main.go index f816d643..e1fc64b8 100644 --- a/cmd/ecspresso/main.go +++ b/cmd/ecspresso/main.go @@ -33,6 +33,7 @@ func _main() int { NoWait: deploy.Flag("no-wait", "exit ecspresso immediately after just deployed without waiting for service stable").Bool(), SuspendAutoScaling: deploy.Flag("suspend-auto-scaling", "set suspend to auto-scaling attached with the ECS service").IsSetByUser(&isSetSuspendAutoScaling).Bool(), RollbackEvents: deploy.Flag("rollback-events", " rollback when specified events happened (DEPLOYMENT_FAILURE,DEPLOYMENT_STOP_ON_ALARM,DEPLOYMENT_STOP_ON_REQUEST,...) CodeDeploy only.").String(), + UpdateService: deploy.Flag("update-service", "update service attributes by service definition").Bool(), } refresh := kingpin.Command("refresh", "refresh service. equivalent to deploy --skip-task-definiton --force-new-deployment") @@ -57,9 +58,9 @@ func _main() int { rollback := kingpin.Command("rollback", "rollback service") rollbackOption := ecspresso.RollbackOption{ - DryRun: rollback.Flag("dry-run", "dry-run").Bool(), + DryRun: rollback.Flag("dry-run", "dry-run").Bool(), DeregisterTaskDefinition: rollback.Flag("deregister-task-definition", "deregister rolled back task definition").Bool(), - NoWait: rollback.Flag("no-wait", "exit ecspresso immediately after just rollbacked without waiting for service stable").Bool(), + NoWait: rollback.Flag("no-wait", "exit ecspresso immediately after just rollbacked without waiting for service stable").Bool(), } delete := kingpin.Command("delete", "delete service") diff --git a/deploy.go b/deploy.go index 6289b669..3a916584 100644 --- a/deploy.go +++ b/deploy.go @@ -77,6 +77,12 @@ func (d *App) Deploy(opt DeployOption) error { if count != nil { d.Log("desired count:", *count) } + if *opt.UpdateService { + sv, err = d.UpdateServiceAttributes(ctx, opt) + if err != nil { + return errors.Wrap(err, "failed to update service attributes") + } + } if *opt.DryRun { d.Log("DRY RUN OK") return nil @@ -100,8 +106,8 @@ func (d *App) Deploy(opt DeployOption) error { } // rolling deploy (ECS internal) - if err := d.UpdateService(ctx, tdArn, count, *opt.ForceNewDeployment); err != nil { - return errors.Wrap(err, "failed to update service") + if err := d.UpdateServiceTasks(ctx, tdArn, count, opt); err != nil { + return errors.Wrap(err, "failed to update service tasks") } if *opt.NoWait { @@ -109,7 +115,6 @@ func (d *App) Deploy(opt DeployOption) error { return nil } - time.Sleep(delayForServiceChanged) // wait for service updated if err := d.WaitServiceStable(ctx, time.Now()); err != nil { return errors.Wrap(err, "failed to wait service stable") } @@ -118,33 +123,58 @@ func (d *App) Deploy(opt DeployOption) error { return nil } -func (d *App) UpdateService(ctx context.Context, taskDefinitionArn string, count *int64, force bool) error { - svd, err := d.LoadServiceDefinition(d.config.ServiceDefinitionPath) - if err != nil { - return err +func (d *App) UpdateServiceTasks(ctx context.Context, taskDefinitionArn string, count *int64, opt DeployOption) error { + in := &ecs.UpdateServiceInput{ + Service: aws.String(d.Service), + Cluster: aws.String(d.Cluster), + TaskDefinition: aws.String(taskDefinitionArn), + DesiredCount: count, + ForceNewDeployment: opt.ForceNewDeployment, } - - msg := "Updating service" - if force { + msg := "Updating service tasks" + if *opt.ForceNewDeployment { msg = msg + " with force new deployment" } msg = msg + "..." d.Log(msg) + d.DebugLog(in.String()) + + _, err := d.ecs.UpdateServiceWithContext(ctx, in) + if err != nil { + return err + } + time.Sleep(delayForServiceChanged) // wait for service updated + return nil +} +func (d *App) UpdateServiceAttributes(ctx context.Context, opt DeployOption) (*ecs.Service, error) { in := &ecs.UpdateServiceInput{ - Service: aws.String(d.Service), - Cluster: aws.String(d.Cluster), - CapacityProviderStrategy: svd.CapacityProviderStrategy, - TaskDefinition: aws.String(taskDefinitionArn), - DesiredCount: count, - ForceNewDeployment: &force, - NetworkConfiguration: svd.NetworkConfiguration, - HealthCheckGracePeriodSeconds: svd.HealthCheckGracePeriodSeconds, - PlatformVersion: svd.PlatformVersion, + Service: aws.String(d.Service), + Cluster: aws.String(d.Cluster), } - d.DebugLog("UpdateServiceInput: " + in.String()) - _, err = d.ecs.UpdateServiceWithContext(ctx, in) - return err + if *opt.UpdateService { + svd, err := d.LoadServiceDefinition(d.config.ServiceDefinitionPath) + if err != nil { + return nil, err + } + in.CapacityProviderStrategy = svd.CapacityProviderStrategy + in.NetworkConfiguration = svd.NetworkConfiguration + in.HealthCheckGracePeriodSeconds = svd.HealthCheckGracePeriodSeconds + in.PlatformVersion = svd.PlatformVersion + } + if *opt.DryRun { + d.Log("update service input:", in.String()) + return nil, nil + } + d.Log("Updateing service attributes...") + d.DebugLog(in.String()) + + out, err := d.ecs.UpdateServiceWithContext(ctx, in) + if err != nil { + return nil, err + } + time.Sleep(delayForServiceChanged) // wait for service updated + return out.Service, nil } func (d *App) DeployByCodeDeploy(ctx context.Context, taskDefinitionArn string, count *int64, sv *ecs.Service, opt DeployOption) error { diff --git a/options.go b/options.go index e4004a3b..426302c2 100644 --- a/options.go +++ b/options.go @@ -14,6 +14,7 @@ type DeployOption struct { NoWait *bool SuspendAutoScaling *bool RollbackEvents *string + UpdateService *bool } type StatusOption struct { diff --git a/rollback.go b/rollback.go index 9c96f5a4..4982d108 100644 --- a/rollback.go +++ b/rollback.go @@ -32,7 +32,8 @@ func (d *App) Rollback(opt RollbackOption) error { return nil } - if err := d.UpdateService(ctx, targetArn, sv.DesiredCount, false); err != nil { + f := false // Set ForceNewDeployment to false + if err := d.UpdateServiceTasks(ctx, targetArn, sv.DesiredCount, DeployOption{ForceNewDeployment: &f}); err != nil { return errors.Wrap(err, "failed to update service") } From f7db287b6c2924d942f6c265ebc62eb6fbc34485 Mon Sep 17 00:00:00 2001 From: FUJIWARA Shunichiro Date: Thu, 5 Dec 2019 11:41:27 +0900 Subject: [PATCH 5/8] the if statement is meaningless --- deploy.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/deploy.go b/deploy.go index 3a916584..3934a219 100644 --- a/deploy.go +++ b/deploy.go @@ -148,19 +148,17 @@ func (d *App) UpdateServiceTasks(ctx context.Context, taskDefinitionArn string, } func (d *App) UpdateServiceAttributes(ctx context.Context, opt DeployOption) (*ecs.Service, error) { - in := &ecs.UpdateServiceInput{ - Service: aws.String(d.Service), - Cluster: aws.String(d.Cluster), + svd, err := d.LoadServiceDefinition(d.config.ServiceDefinitionPath) + if err != nil { + return nil, err } - if *opt.UpdateService { - svd, err := d.LoadServiceDefinition(d.config.ServiceDefinitionPath) - if err != nil { - return nil, err - } - in.CapacityProviderStrategy = svd.CapacityProviderStrategy - in.NetworkConfiguration = svd.NetworkConfiguration - in.HealthCheckGracePeriodSeconds = svd.HealthCheckGracePeriodSeconds - in.PlatformVersion = svd.PlatformVersion + in := &ecs.UpdateServiceInput{ + Service: aws.String(d.Service), + Cluster: aws.String(d.Cluster), + CapacityProviderStrategy: svd.CapacityProviderStrategy, + NetworkConfiguration: svd.NetworkConfiguration, + HealthCheckGracePeriodSeconds: svd.HealthCheckGracePeriodSeconds, + PlatformVersion: svd.PlatformVersion, } if *opt.DryRun { d.Log("update service input:", in.String()) From 7a46590d3c84d5f0e6d13817cf977b4dfd228ac9 Mon Sep 17 00:00:00 2001 From: FUJIWARA Shunichiro Date: Thu, 5 Dec 2019 12:20:34 +0900 Subject: [PATCH 6/8] update readme --- README.md | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 11acd29b..5136563b 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ task_definition: myTask.json timeout: 5m ``` -ecspresso works as below. +ecspresso deploy works as below. - Register a new task definition from JSON file. - JSON file is allowed both of formats as below. @@ -107,7 +107,8 @@ ecspresso works as below. - If "FOO" is not defined, replaced by "bar" - Replace ```{{ must_env `FOO` }}``` syntax in the JSON file to environment variable "FOO". - If "FOO" is not defined, abort immediately. -- Update a service. +- Update a service tasks. + - When `--update-service` option set, update service attributes by service definition. - Wait a service stable. ## Example of deployment @@ -178,7 +179,7 @@ $ ecspresso deploy --config config.yaml --tasks 10 --skip-task-definition ## Example of create -escpresso can create service by `service_definition` JSON file and `task_definition`. +escpresso can create a service by `service_definition` JSON file and `task_definition`. ```console $ ecspresso create --config config.yaml @@ -215,6 +216,7 @@ Keys are same format as `aws ecs describe-services` output. - placementConstraint - placementStrategy - role +- etc. ## Example of run task @@ -224,6 +226,8 @@ $ ecspresso run --config config.yaml --task-def=db-migrate.json When `--task-def` is not set, use a task definition included in a service. +Other options for RunTask API are set by service attributes(CapacityProviderStrategy, LaunchType, PlacementConstraints, PlacementStrategy and PlatformVersion). + # Notes ## Deploy to Fargate @@ -275,6 +279,28 @@ For service-definition, } ``` +## Fargate Spot support + +1. Set capacityProviders and defaultCapacityProviderStrategy to ECS cluster. +1. If you hope to migrate existing service to use Fargate Spot, define capacityProviderStrategy into service definition as below. `ecspresso deploy --update-service` applies the settings to the service. + +```json5 +{ + "capacityProviderStrategy": [ + { + "base": 1, + "capacityProvider": "FARGATE", + "weight": 1 + }, + { + "base": 0, + "capacityProvider": "FARGATE_SPOT", + "weight": 1 + } + ], + # ... +``` + # LICENCE MIT From bdc93388b54251f9ad5905202ad8fa74fdf6e356 Mon Sep 17 00:00:00 2001 From: FUJIWARA Shunichiro Date: Fri, 6 Dec 2019 10:55:57 +0900 Subject: [PATCH 7/8] ForceNewDeployment requied to update some attribultes. --- deploy.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy.go b/deploy.go index 3934a219..886d0b0c 100644 --- a/deploy.go +++ b/deploy.go @@ -155,10 +155,12 @@ func (d *App) UpdateServiceAttributes(ctx context.Context, opt DeployOption) (*e in := &ecs.UpdateServiceInput{ Service: aws.String(d.Service), Cluster: aws.String(d.Cluster), + DeploymentConfiguration: svd.DeploymentConfiguration, CapacityProviderStrategy: svd.CapacityProviderStrategy, NetworkConfiguration: svd.NetworkConfiguration, HealthCheckGracePeriodSeconds: svd.HealthCheckGracePeriodSeconds, PlatformVersion: svd.PlatformVersion, + ForceNewDeployment: opt.ForceNewDeployment, } if *opt.DryRun { d.Log("update service input:", in.String()) From e6e9ded99fabb902a469cb1ebddc7f5cd1455c78 Mon Sep 17 00:00:00 2001 From: FUJIWARA Shunichiro Date: Fri, 6 Dec 2019 12:30:43 +0900 Subject: [PATCH 8/8] enforce not nil --- cmd/ecspresso/main.go | 1 + deploy.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/ecspresso/main.go b/cmd/ecspresso/main.go index e1fc64b8..91f8da92 100644 --- a/cmd/ecspresso/main.go +++ b/cmd/ecspresso/main.go @@ -42,6 +42,7 @@ func _main() int { SkipTaskDefinition: boolp(true), ForceNewDeployment: boolp(true), NoWait: refresh.Flag("no-wait", "exit ecspresso immediately after just deployed without waiting for service stable").Bool(), + UpdateService: boolp(false), } create := kingpin.Command("create", "create service") diff --git a/deploy.go b/deploy.go index 886d0b0c..801f70cd 100644 --- a/deploy.go +++ b/deploy.go @@ -77,7 +77,7 @@ func (d *App) Deploy(opt DeployOption) error { if count != nil { d.Log("desired count:", *count) } - if *opt.UpdateService { + if opt.UpdateService != nil && *opt.UpdateService { sv, err = d.UpdateServiceAttributes(ctx, opt) if err != nil { return errors.Wrap(err, "failed to update service attributes")