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

Fargate Spot support. #70

Merged
merged 8 commits into from
Dec 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
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
32 changes: 29 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -215,6 +216,7 @@ Keys are same format as `aws ecs describe-services` output.
- placementConstraint
- placementStrategy
- role
- etc.

## Example of run task

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions cmd/ecspresso/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -41,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")
Expand All @@ -57,9 +59,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")
Expand Down
75 changes: 55 additions & 20 deletions deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ func (d *App) Deploy(opt DeployOption) error {
if count != nil {
d.Log("desired count:", *count)
}
if opt.UpdateService != nil && *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
Expand All @@ -100,16 +106,15 @@ func (d *App) Deploy(opt DeployOption) error {
}

// rolling deploy (ECS internal)
if err := d.UpdateService(ctx, tdArn, count, *opt.ForceNewDeployment, sv); 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 {
d.Log("Service is deployed.")
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")
}
Expand All @@ -118,28 +123,58 @@ 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 {
msg := "Updating service"
if force {
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 tasks"
if *opt.ForceNewDeployment {
msg = msg + " with force new deployment"
}
msg = msg + "..."
d.Log(msg)
d.DebugLog(in.String())

_, 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,
},
)
return err
_, 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) {
svd, err := d.LoadServiceDefinition(d.config.ServiceDefinitionPath)
if err != nil {
return nil, err
}
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())
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 {
Expand Down
4 changes: 4 additions & 0 deletions ecspresso.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 2 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
1 change: 1 addition & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type DeployOption struct {
NoWait *bool
SuspendAutoScaling *bool
RollbackEvents *string
UpdateService *bool
}

type StatusOption struct {
Expand Down
3 changes: 2 additions & 1 deletion rollback.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ func (d *App) Rollback(opt RollbackOption) error {
return nil
}

if err := d.UpdateService(ctx, targetArn, sv.DesiredCount, false, sv); 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")
}

Expand Down