Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6b2af75
add a flag: ignoreDesiredCountOnUpdate
t-kikuc Jul 8, 2024
cf3efbf
ignore desiredCount on updateService if needed
t-kikuc Jul 8, 2024
d17a9b7
Add a flag ignoreDesiredCountOnUpdate into each func
t-kikuc Jul 8, 2024
5103158
Format func params and comment
t-kikuc Jul 8, 2024
dcd7dc9
add docs
t-kikuc Jul 8, 2024
7d65824
fix tests: add a missing attribute
t-kikuc Jul 8, 2024
2c8ef1a
recover desiredCount in CreateService (mistake)
t-kikuc Jul 8, 2024
a0f3459
add mentioning driftdetection
t-kikuc Jul 11, 2024
e556faf
Revert changes of adding ignoreDesiredCountOnUpdate
t-kikuc Jul 11, 2024
8ae6647
Revert commits of adding ignoreDesiredCount"
t-kikuc Jul 11, 2024
0bf97d5
Revert "add docs"
t-kikuc Jul 11, 2024
cd310a7
Revert "Format func params and comment"
t-kikuc Jul 11, 2024
638c5f9
Revert "Add a flag ignoreDesiredCountOnUpdate into each func"
t-kikuc Jul 11, 2024
936dfb2
Revert "ignore desiredCount on updateService if needed"
t-kikuc Jul 11, 2024
eb3bdc7
Revert "add a flag: ignoreDesiredCountOnUpdate"
t-kikuc Jul 11, 2024
766debd
ignore updating desiredCount if it's 0 or not defined
t-kikuc Jul 11, 2024
aef6c41
Merge branch 'master' of https://github.com/pipe-cd/pipecd into ecs-i…
t-kikuc Jul 11, 2024
434c3af
Use pruning when 'recreate' is on
t-kikuc Jul 11, 2024
7b267a1
add docs
t-kikuc Jul 11, 2024
33985f8
refine docs
t-kikuc Jul 11, 2024
e644dc0
Merge branch 'master' into ecs-ignore-desired-count
t-kikuc Jul 12, 2024
b58fb8c
Clarify procedure of configuring
t-kikuc Jul 16, 2024
99060a0
Merge branch 'ecs-ignore-desired-count' of https://github.com/pipe-cd…
t-kikuc Jul 16, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ One of `yamlField` or `regex` is required.
| targetGroups | [ECSTargetGroupInput](#ecstargetgroupinput) | The target groups configuration, will be used to routing traffic to created task sets. | Yes (if you want to perform progressive delivery) |
| runStandaloneTask | bool | Run standalone tasks during deployments. About standalone task, see [here](https://docs.aws.amazon.com/AmazonECS/latest/userguide/ecs_run_task-v2.html). The default value is `true`. |
| accessType | string | How the ECS service is accessed. One of `ELB` or `SERVICE_DISCOVERY`. See examples [here](https://github.com/pipe-cd/examples/tree/master/ecs/servicediscovery/simple). The default value is `ELB`. |
| ignoreDesiredCountOnUpdate | bool | Whether to ignore the desiredCount of the service definition when updating the service. For example, when you use auto scaling (e.g. [Application AutoScaling](https://docs.aws.amazon.com/autoscaling/application/userguide/what-is-application-auto-scaling.html)) and set `true` for this field, the desiredCount will not be updated to the value in the serviceDefinition. The default value is `false`. |

### Restrictions of Service Definition

Expand Down
15 changes: 10 additions & 5 deletions pkg/app/piped/executor/ecs/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ func (e *deployExecutor) ensureSync(ctx context.Context) model.StageStatus {
}

recreate := e.appCfg.QuickSync.Recreate
if !sync(ctx, &e.Input, e.platformProviderName, e.platformProviderCfg, recreate, taskDefinition, servicedefinition, primary) {
ignoreDesiredCountOnUpdate := *e.appCfg.Input.IgnoreDesiredCountOnUpdate
if !sync(ctx, &e.Input, e.platformProviderName, e.platformProviderCfg, recreate, taskDefinition, servicedefinition, primary, ignoreDesiredCountOnUpdate) {
return model.StageStatus_STAGE_FAILURE
}

Expand All @@ -126,6 +127,8 @@ func (e *deployExecutor) ensurePrimaryRollout(ctx context.Context) model.StageSt
return model.StageStatus_STAGE_FAILURE
}

ignoreDesiredCountOnUpdate := *e.appCfg.Input.IgnoreDesiredCountOnUpdate

switch e.appCfg.Input.AccessType {
case config.AccessTypeELB:
primary, _, ok := loadTargetGroups(&e.Input, e.appCfg, e.deploySource)
Expand All @@ -137,12 +140,12 @@ func (e *deployExecutor) ensurePrimaryRollout(ctx context.Context) model.StageSt
return model.StageStatus_STAGE_FAILURE
}

if !rollout(ctx, &e.Input, e.platformProviderName, e.platformProviderCfg, taskDefinition, servicedefinition, primary) {
if !rollout(ctx, &e.Input, e.platformProviderName, e.platformProviderCfg, taskDefinition, servicedefinition, primary, ignoreDesiredCountOnUpdate) {
return model.StageStatus_STAGE_FAILURE
}
case config.AccessTypeServiceDiscovery:
// Target groups are not used.
if !rollout(ctx, &e.Input, e.platformProviderName, e.platformProviderCfg, taskDefinition, servicedefinition, nil) {
if !rollout(ctx, &e.Input, e.platformProviderName, e.platformProviderCfg, taskDefinition, servicedefinition, nil, ignoreDesiredCountOnUpdate) {
return model.StageStatus_STAGE_FAILURE
}
default:
Expand All @@ -163,6 +166,8 @@ func (e *deployExecutor) ensureCanaryRollout(ctx context.Context) model.StageSta
return model.StageStatus_STAGE_FAILURE
}

ignoreDesiredCountOnUpdate := *e.appCfg.Input.IgnoreDesiredCountOnUpdate

switch e.appCfg.Input.AccessType {
case config.AccessTypeELB:
_, canary, ok := loadTargetGroups(&e.Input, e.appCfg, e.deploySource)
Expand All @@ -174,12 +179,12 @@ func (e *deployExecutor) ensureCanaryRollout(ctx context.Context) model.StageSta
return model.StageStatus_STAGE_FAILURE
}

if !rollout(ctx, &e.Input, e.platformProviderName, e.platformProviderCfg, taskDefinition, servicedefinition, canary) {
if !rollout(ctx, &e.Input, e.platformProviderName, e.platformProviderCfg, taskDefinition, servicedefinition, canary, ignoreDesiredCountOnUpdate) {
return model.StageStatus_STAGE_FAILURE
}
case config.AccessTypeServiceDiscovery:
// Target groups are not used.
if !rollout(ctx, &e.Input, e.platformProviderName, e.platformProviderCfg, taskDefinition, servicedefinition, nil) {
if !rollout(ctx, &e.Input, e.platformProviderName, e.platformProviderCfg, taskDefinition, servicedefinition, nil, ignoreDesiredCountOnUpdate) {
return model.StageStatus_STAGE_FAILURE
}
default:
Expand Down
27 changes: 19 additions & 8 deletions pkg/app/piped/executor/ecs/ecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,16 @@ func applyTaskDefinition(ctx context.Context, cli provider.Client, taskDefinitio
return td, nil
}

func applyServiceDefinition(ctx context.Context, cli provider.Client, serviceDefinition types.Service) (*types.Service, error) {
// The parameter `ignoreDesiredCountOnUpdate` is only used when updating the service, not for creating it.
func applyServiceDefinition(ctx context.Context, cli provider.Client, serviceDefinition types.Service, ignoreDesiredCountOnUpdate bool) (*types.Service, error) {
found, err := cli.ServiceExists(ctx, *serviceDefinition.ClusterArn, *serviceDefinition.ServiceName)
if err != nil {
return nil, fmt.Errorf("unable to validate service name %s: %w", *serviceDefinition.ServiceName, err)
}

var service *types.Service
if found {
service, err = cli.UpdateService(ctx, serviceDefinition)
service, err = cli.UpdateService(ctx, serviceDefinition, ignoreDesiredCountOnUpdate)
if err != nil {
return nil, fmt.Errorf("failed to update ECS service %s: %w", *serviceDefinition.ServiceName, err)
}
Expand Down Expand Up @@ -253,7 +254,17 @@ func createPrimaryTaskSet(ctx context.Context, client provider.Client, service t
return nil
}

func sync(ctx context.Context, in *executor.Input, platformProviderName string, platformProviderCfg *config.PlatformProviderECSConfig, recreate bool, taskDefinition types.TaskDefinition, serviceDefinition types.Service, targetGroup *types.LoadBalancer) bool {
func sync(
ctx context.Context,
in *executor.Input,
platformProviderName string,
platformProviderCfg *config.PlatformProviderECSConfig,
recreate bool,
taskDefinition types.TaskDefinition,
serviceDefinition types.Service,
targetGroup *types.LoadBalancer,
ignoreDesiredCountOnUpdate bool,
) bool {
client, err := provider.DefaultRegistry().Client(platformProviderName, platformProviderCfg, in.Logger)
if err != nil {
in.LogPersister.Errorf("Unable to create ECS client for the provider %s: %v", platformProviderName, err)
Expand All @@ -268,7 +279,7 @@ func sync(ctx context.Context, in *executor.Input, platformProviderName string,
}

in.LogPersister.Infof("Start applying the ECS service definition")
service, err := applyServiceDefinition(ctx, client, serviceDefinition)
service, err := applyServiceDefinition(ctx, client, serviceDefinition, ignoreDesiredCountOnUpdate)
if err != nil {
in.LogPersister.Errorf("Failed to apply service %s: %v", *serviceDefinition.ServiceName, err)
return false
Expand All @@ -279,7 +290,7 @@ func sync(ctx context.Context, in *executor.Input, platformProviderName string,
// Scale down the service tasks by set it to 0
in.LogPersister.Infof("Scale down ECS desired tasks count to 0")
service.DesiredCount = 0
if _, err = client.UpdateService(ctx, *service); err != nil {
if _, err = client.UpdateService(ctx, *service, false); err != nil {
in.LogPersister.Errorf("Failed to stop service tasks: %v", err)
return false
}
Expand All @@ -293,7 +304,7 @@ func sync(ctx context.Context, in *executor.Input, platformProviderName string,
// Scale up the service tasks count back to its desired.
in.LogPersister.Infof("Scale up ECS desired tasks count back to %d", cnt)
service.DesiredCount = cnt
if _, err = client.UpdateService(ctx, *service); err != nil {
if _, err = client.UpdateService(ctx, *service, false); err != nil {
in.LogPersister.Errorf("Failed to turning back service tasks: %v", err)
return false
}
Expand All @@ -315,7 +326,7 @@ func sync(ctx context.Context, in *executor.Input, platformProviderName string,
return true
}

func rollout(ctx context.Context, in *executor.Input, platformProviderName string, platformProviderCfg *config.PlatformProviderECSConfig, taskDefinition types.TaskDefinition, serviceDefinition types.Service, targetGroup *types.LoadBalancer) bool {
func rollout(ctx context.Context, in *executor.Input, platformProviderName string, platformProviderCfg *config.PlatformProviderECSConfig, taskDefinition types.TaskDefinition, serviceDefinition types.Service, targetGroup *types.LoadBalancer, ignoreDesiredCountOnUpdate bool) bool {
client, err := provider.DefaultRegistry().Client(platformProviderName, platformProviderCfg, in.Logger)
if err != nil {
in.LogPersister.Errorf("Unable to create ECS client for the provider %s: %v", platformProviderName, err)
Expand All @@ -330,7 +341,7 @@ func rollout(ctx context.Context, in *executor.Input, platformProviderName strin
}

in.LogPersister.Infof("Start applying the ECS service definition")
service, err := applyServiceDefinition(ctx, client, serviceDefinition)
service, err := applyServiceDefinition(ctx, client, serviceDefinition, ignoreDesiredCountOnUpdate)
if err != nil {
in.LogPersister.Errorf("Failed to apply service %s: %v", *serviceDefinition.ServiceName, err)
return false
Expand Down
17 changes: 14 additions & 3 deletions pkg/app/piped/executor/ecs/rollback.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,25 @@ func (e *rollbackExecutor) ensureRollback(ctx context.Context) model.StageStatus
return model.StageStatus_STAGE_FAILURE
}

if !rollback(ctx, &e.Input, platformProviderName, platformProviderCfg, taskDefinition, serviceDefinition, primary, canary) {
ignoreDesiredCountOnUpdate := *appCfg.Input.IgnoreDesiredCountOnUpdate
if !rollback(ctx, &e.Input, platformProviderName, platformProviderCfg, taskDefinition, serviceDefinition, primary, canary, ignoreDesiredCountOnUpdate) {
return model.StageStatus_STAGE_FAILURE
}

return model.StageStatus_STAGE_SUCCESS
}

func rollback(ctx context.Context, in *executor.Input, platformProviderName string, platformProviderCfg *config.PlatformProviderECSConfig, taskDefinition types.TaskDefinition, serviceDefinition types.Service, primaryTargetGroup *types.LoadBalancer, canaryTargetGroup *types.LoadBalancer) bool {
func rollback(
ctx context.Context,
in *executor.Input,
platformProviderName string,
platformProviderCfg *config.PlatformProviderECSConfig,
taskDefinition types.TaskDefinition,
serviceDefinition types.Service,
primaryTargetGroup *types.LoadBalancer,
canaryTargetGroup *types.LoadBalancer,
ignoreDesiredCountOnUpdate bool,
) bool {
in.LogPersister.Infof("Start rollback the ECS service and task family: %s and %s to original stage", *serviceDefinition.ServiceName, *taskDefinition.Family)
client, err := provider.DefaultRegistry().Client(platformProviderName, platformProviderCfg, in.Logger)
if err != nil {
Expand All @@ -112,7 +123,7 @@ func rollback(ctx context.Context, in *executor.Input, platformProviderName stri
}

// Rollback ECS service configuration to previous state including commit-hash of the tag.
service, err := applyServiceDefinition(ctx, client, serviceDefinition)
service, err := applyServiceDefinition(ctx, client, serviceDefinition, ignoreDesiredCountOnUpdate)
if err != nil {
in.LogPersister.Errorf("Unable to rollback ECS service %s configuration to previous stage: %v", *serviceDefinition.ServiceName, err)
return false
Expand Down
6 changes: 4 additions & 2 deletions pkg/app/piped/platformprovider/ecs/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,18 @@ func (c *client) CreateService(ctx context.Context, service types.Service) (*typ
return output.Service, nil
}

func (c *client) UpdateService(ctx context.Context, service types.Service) (*types.Service, error) {
func (c *client) UpdateService(ctx context.Context, service types.Service, ignoreDesiredCount bool) (*types.Service, error) {
input := &ecs.UpdateServiceInput{
Cluster: service.ClusterArn,
Service: service.ServiceName,
DesiredCount: aws.Int32(service.DesiredCount),
EnableExecuteCommand: aws.Bool(service.EnableExecuteCommand),
PlacementStrategy: service.PlacementStrategy,
// TODO: Support update other properties of service.
// PlacementConstraints: service.PlacementConstraints,
}
if !ignoreDesiredCount {
input.DesiredCount = aws.Int32(service.DesiredCount)
}

@khanhtc1202 khanhtc1202 Jul 8, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@t-kikuc I think when users tend to ignore expressly setting the desiredCount value, they will leave it empty (or remove that line from the manifests); having this flag added can lead to some confusing configuration when users set the desiredCount value in service file but set this flag in the app configuration as true 🤔
For whatever cases, how about to change this logic as

	input := &ecs.UpdateServiceInput{
		Cluster:              service.ClusterArn,
		....
	}
	if service.DesiredCount != 0 {
		input.DesiredCount = aws.Int32(service.DesiredCount)
	}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@khanhtc1202
Thank you so much. Your code seems really simple and great, and I also think they will remove desiredCount.

However, there are two patterns we should consider.

  1. Even if a user truly wants to scale-in tasks to 0 by desiredCount: 0, desiredCount won't be 0.
    • e.g. When a user wants to keep the service but it does not need to serve.
  2. DriftDetection will be inevitable because a piped cannot judge whether to ignore desiredCount.
    • case-a.
      1. current desiredCount= 5
      2. ->Scale up to 10 by AutoScaling
      3. -> DriftDetection will reconcile to 5.
    • case-b.
      1. current desiredCount= 5
      2. -> new desiredCount= 0
      3. -> DriftDetection will try to reconcile to 0 every time (but it won't actually be 0).

That's why I think the new config attribute is required.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@t-kikuc Across the Kubernetes QuickSync configuration of pipecd, I think currently we don't support prune resource (tasks) for ECS application, which mean we can forget about the behavior when users set desiredCount to zero to remove the tasks (ref: https://pipecd.dev/docs-v0.48.x/user-guide/configuration-reference/#kubernetesquicksync).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, I understand the need to support the prune resource on deployment, but I think it should be a separate change, not this PR. Also, in such cases, we can just add a new interface (not update this UpdateService) but call the new function like PruneService. Then, in the implementation, we set the desiredCount to zero.
My point is, we should not change the interface whenever we need a change that is not actually worth; creating a new one is a better choice 😄 (If follow the OOP principles - if you're a former java dev, you know what I mean 😉 )

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@khanhtc1202

we don't support prune resource

To be precise, this is not pruning, it's scaling-in.
Of course, I know the principle and won't use UpdateService() for pruning.

You mean scaling-in to 0 tasks is not necessary?

DriftDetection

I found a simpler way based on your code.

Let's ignore the drift If

  • diff is only desiredCount
  • desiredCount in new commit = 0

Then we can support both of two patterns:

  • reconciling to desiredCount(>0) in the new commit
  • skipping reconciling when desiredCount is 0 or not defined

In case I mentioned, a user should remove desiredCount from the serviceDef.

case-b.
current desiredCount= 5
-> new desiredCount= 0
-> DriftDetection will try to reconcile to 0 every time (but it won't actually be 0).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@t-kikuc Thanks for making it clear; I forgot about the recreating case 😢
Then, how about

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@khanhtc1202
Thanks,

Let's ignore "scaling-in to 0 tasks is not necessary" cases for simplicity.
It is not a common case for production-ready services.

If needed, let's support prune feature like K8sApp.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you 🙏

output, err := c.ecsClient.UpdateService(ctx, input)
if err != nil {
return nil, fmt.Errorf("failed to update ECS service %s: %w", *service.ServiceName, err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/app/piped/platformprovider/ecs/ecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type Client interface {
type ECS interface {
ServiceExists(ctx context.Context, clusterName string, servicesName string) (bool, error)
CreateService(ctx context.Context, service types.Service) (*types.Service, error)
UpdateService(ctx context.Context, service types.Service) (*types.Service, error)
UpdateService(ctx context.Context, service types.Service, ignoreDesiredCount bool) (*types.Service, error)
WaitServiceStable(ctx context.Context, service types.Service) error
RegisterTaskDefinition(ctx context.Context, taskDefinition types.TaskDefinition) (*types.TaskDefinition, error)
RunTask(ctx context.Context, taskDefinition types.TaskDefinition, clusterArn string, launchType string, awsVpcConfiguration *config.ECSVpcConfiguration, tags []types.Tag) error
Expand Down
4 changes: 4 additions & 0 deletions pkg/config/application_ecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ type ECSDeploymentInput struct {
// - SERVICE_DISCOVERY - The service is accessed via ECS Service Discovery.
// Default is ELB.
AccessType string `json:"accessType,omitempty" default:"ELB"`
// Whether to ignore the desiredCount of the service definition when updating the service.
// If this is set as true, the desiredCount will be updated when creating the service, but will not be updated when updating the service.
// Default is false.
IgnoreDesiredCountOnUpdate *bool `json:"ignoreDesiredCountOnUpdate,omitempty" default:"false"`
}

func (in *ECSDeploymentInput) IsStandaloneTask() bool {
Expand Down
35 changes: 19 additions & 16 deletions pkg/config/application_ecs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ func TestECSApplicationConfig(t *testing.T) {
ContainerPort: 80,
},
},
LaunchType: "FARGATE",
AutoRollback: newBoolPointer(true),
RunStandaloneTask: newBoolPointer(true),
AccessType: "ELB",
LaunchType: "FARGATE",
AutoRollback: newBoolPointer(true),
RunStandaloneTask: newBoolPointer(true),
AccessType: "ELB",
IgnoreDesiredCountOnUpdate: newBoolPointer(true),
},
},
expectedError: nil,
Expand Down Expand Up @@ -97,12 +98,13 @@ func TestECSApplicationConfig(t *testing.T) {
},
},
Input: ECSDeploymentInput{
ServiceDefinitionFile: "/path/to/servicedef.yaml",
TaskDefinitionFile: "/path/to/taskdef.yaml",
LaunchType: "FARGATE",
AutoRollback: newBoolPointer(true),
RunStandaloneTask: newBoolPointer(true),
AccessType: "SERVICE_DISCOVERY",
ServiceDefinitionFile: "/path/to/servicedef.yaml",
TaskDefinitionFile: "/path/to/taskdef.yaml",
LaunchType: "FARGATE",
AutoRollback: newBoolPointer(true),
RunStandaloneTask: newBoolPointer(true),
AccessType: "SERVICE_DISCOVERY",
IgnoreDesiredCountOnUpdate: newBoolPointer(false),
},
},
expectedError: nil,
Expand Down Expand Up @@ -131,12 +133,13 @@ func TestECSApplicationConfig(t *testing.T) {
},
},
Input: ECSDeploymentInput{
ServiceDefinitionFile: "/path/to/servicedef.yaml",
TaskDefinitionFile: "/path/to/taskdef.yaml",
LaunchType: "FARGATE",
AutoRollback: newBoolPointer(true),
RunStandaloneTask: newBoolPointer(true),
AccessType: "XXX",
ServiceDefinitionFile: "/path/to/servicedef.yaml",
TaskDefinitionFile: "/path/to/taskdef.yaml",
LaunchType: "FARGATE",
AutoRollback: newBoolPointer(true),
RunStandaloneTask: newBoolPointer(true),
AccessType: "XXX",
IgnoreDesiredCountOnUpdate: newBoolPointer(false),
},
},
expectedError: fmt.Errorf("invalid accessType: XXX"),
Expand Down
1 change: 1 addition & 0 deletions pkg/config/testdata/application/ecs-app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ spec:
targetGroupArn: arn:aws:elasticloadbalancing:xyz
containerName: web
containerPort: 80
ignoreDesiredCountOnUpdate: true