DynamicDesiredCountAction: Metadata: 'aws:copilot:description': "A custom resource returning the ECS service's running task count" Type: Custom::DynamicDesiredCountFunction Properties: ServiceToken: !GetAtt DynamicDesiredCountFunction.Arn Cluster: Fn::ImportValue: !Sub '${AppName}-${EnvName}-ClusterId' App: !Ref AppName Env: !Ref EnvName Svc: !Ref WorkloadName DefaultDesiredCount: !Ref TaskCount # We need to force trigger this lambda function on all deployments, so we give it a random ID as input on all event types. UpdateID: {{ randomUUID }} DynamicDesiredCountFunction: Type: AWS::Lambda::Function Properties: {{- with $cr := index .CustomResources "DynamicDesiredCountFunction" }} Code: S3Bucket: {{$cr.Bucket}} S3Key: {{$cr.Key}} {{- end }} Handler: "index.handler" Timeout: 600 MemorySize: 512 Role: !GetAtt 'DynamicDesiredCountFunctionRole.Arn' Runtime: nodejs16.x DynamicDesiredCountFunctionRole: Metadata: 'aws:copilot:description': "An IAM Role {{- if .PermissionsBoundary}} with permissions boundary {{.PermissionsBoundary}} {{- end}} for describing number of running tasks in your ECS service" Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole {{- if .PermissionsBoundary}} PermissionsBoundary: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/{{.PermissionsBoundary}}' {{- end}} Policies: - PolicyName: "DelegateDesiredCountAccess" PolicyDocument: Version: '2012-10-17' Statement: - Sid: ECS Effect: Allow Action: - ecs:DescribeServices Resource: "*" Condition: ArnEquals: 'ecs:cluster': Fn::Sub: - arn:${AWS::Partition}:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterName} - ClusterName: Fn::ImportValue: !Sub '${AppName}-${EnvName}-ClusterId' - Sid: ResourceGroups Effect: Allow Action: - resource-groups:GetResources Resource: "*" - Sid: Tags Effect: Allow Action: - "tag:GetResources" Resource: "*" AutoScalingRole: Metadata: 'aws:copilot:description': 'An IAM role {{- if .PermissionsBoundary}} with permissions boundary {{.PermissionsBoundary}} {{- end}} for container auto scaling' Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole' {{- if .PermissionsBoundary}} PermissionsBoundary: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/{{.PermissionsBoundary}}' {{- end}} AutoScalingTarget: Metadata: 'aws:copilot:description': "An autoscaling target to scale your service's desired count" Type: AWS::ApplicationAutoScaling::ScalableTarget Properties: MinCapacity: {{.Autoscaling.MinCapacity}} MaxCapacity: {{.Autoscaling.MaxCapacity}} ResourceId: Fn::Join: - '/' - - 'service' - Fn::ImportValue: !Sub '${AppName}-${EnvName}-ClusterId' - !GetAtt Service.Name ScalableDimension: ecs:service:DesiredCount ServiceNamespace: ecs RoleARN: !GetAtt AutoScalingRole.Arn {{if .Autoscaling.CPU}} AutoScalingPolicyECSServiceAverageCPUUtilization: Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: PolicyName: !Join ['-', [!Ref WorkloadName, ECSServiceAverageCPUUtilization, ScalingPolicy]] PolicyType: TargetTrackingScaling ScalingTargetId: !Ref AutoScalingTarget TargetTrackingScalingPolicyConfiguration: PredefinedMetricSpecification: PredefinedMetricType: ECSServiceAverageCPUUtilization {{- if .Autoscaling.CPUCooldown.ScaleInCooldown}} ScaleInCooldown: {{.Autoscaling.CPUCooldown.ScaleInCooldown}} {{- else}} ScaleInCooldown: 120 {{- end}} {{- if .Autoscaling.CPUCooldown.ScaleOutCooldown}} ScaleOutCooldown: {{.Autoscaling.CPUCooldown.ScaleOutCooldown}} {{- else}} ScaleOutCooldown: 60 {{- end}} TargetValue: {{.Autoscaling.CPU}} {{- end}} {{if .Autoscaling.Memory}} AutoScalingPolicyECSServiceAverageMemoryUtilization: Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: PolicyName: !Join ['-', [!Ref WorkloadName, ECSServiceAverageMemoryUtilization, ScalingPolicy]] PolicyType: TargetTrackingScaling ScalingTargetId: !Ref AutoScalingTarget TargetTrackingScalingPolicyConfiguration: PredefinedMetricSpecification: PredefinedMetricType: ECSServiceAverageMemoryUtilization {{- if .Autoscaling.MemCooldown.ScaleInCooldown}} ScaleInCooldown: {{.Autoscaling.MemCooldown.ScaleInCooldown}} {{- else}} ScaleInCooldown: 120 {{- end}} {{- if .Autoscaling.MemCooldown.ScaleOutCooldown}} ScaleOutCooldown: {{.Autoscaling.MemCooldown.ScaleOutCooldown}} {{- else}} ScaleOutCooldown: 60 {{- end}} TargetValue: {{.Autoscaling.Memory}} {{- end}} {{- if .Autoscaling.QueueDelay }} BacklogPerTaskCalculatorLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: Fn::Join: - '/' - - '/aws' - 'lambda' - Fn::Sub: "${BacklogPerTaskCalculatorFunction}" RetentionInDays: 3 BacklogPerTaskCalculatorFunction: Metadata: 'aws:copilot:description': "A Lambda function to emit BacklogPerTask metrics to CloudWatch" Type: AWS::Lambda::Function Properties: {{- with $cr := index .CustomResources "BacklogPerTaskCalculatorFunction" }} Code: S3Bucket: {{$cr.Bucket}} S3Key: {{$cr.Key}} {{- end }} Handler: "index.handler" Timeout: 600 MemorySize: 512 Role: !GetAtt BacklogPerTaskCalculatorRole.Arn Runtime: nodejs16.x Environment: Variables: CLUSTER_NAME: Fn::ImportValue: !Sub '${AppName}-${EnvName}-ClusterId' SERVICE_NAME: !Ref Service NAMESPACE: !Sub '${AppName}-${EnvName}-${WorkloadName}' QUEUE_NAMES: Fn::Join: - ',' - - !GetAtt EventsQueue.QueueName {{- if .Subscribe }} {{- range $topic := .Subscribe.Topics }} {{- if $topic.Queue }} - !GetAtt {{logicalIDSafe $topic.Service}}{{logicalIDSafe $topic.Name}}EventsQueue.QueueName {{- end }} {{- end }} {{- end }} BacklogPerTaskCalculatorRole: Metadata: 'aws:copilot:description': 'An IAM role {{- if .PermissionsBoundary}} with permissions boundary {{.PermissionsBoundary}} {{- end}} for BacklogPerTaskCalculatorFunction' Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole {{- if .PermissionsBoundary}} PermissionsBoundary: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/{{.PermissionsBoundary}}' {{- end}} Path: / Policies: - PolicyName: "BacklogPerTaskCalculatorAccess" PolicyDocument: Version: '2012-10-17' Statement: - Sid: ECS Effect: Allow Action: - ecs:DescribeServices Resource: "*" Condition: ArnEquals: 'ecs:cluster': Fn::Sub: - arn:${AWS::Partition}:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterName} - ClusterName: Fn::ImportValue: !Sub '${AppName}-${EnvName}-ClusterId' - Sid: SQS Effect: Allow Action: - sqs:GetQueueAttributes - sqs:GetQueueUrl Resource: - !GetAtt EventsQueue.Arn {{- if .Subscribe }} {{- range $topic := .Subscribe.Topics}} {{- if $topic.Queue}} - !GetAtt {{logicalIDSafe $topic.Service}}{{logicalIDSafe $topic.Name}}EventsQueue.Arn {{- end }} {{- end }} {{- end }} ManagedPolicyArns: - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole BacklogPerTaskScheduledRule: Metadata: 'aws:copilot:description': "A trigger to invoke the BacklogPerTaskCalculator Lambda function every minute" DependsOn: - BacklogPerTaskCalculatorLogGroup # Ensure log group is created before invoking. Type: AWS::Events::Rule Properties: ScheduleExpression: "rate(1 minute)" State: "ENABLED" Targets: - Arn: !GetAtt BacklogPerTaskCalculatorFunction.Arn Id: "BacklogPerTaskCalculatorFunctionTrigger" PermissionToInvokeBacklogPerTaskCalculatorLambda: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref BacklogPerTaskCalculatorFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt BacklogPerTaskScheduledRule.Arn {{- $acceptableBacklog := .Autoscaling.QueueDelay.AcceptableBacklogPerTask }} {{- $queueDelayCooldown := .Autoscaling.QueueDelayCooldown }} AutoScalingPolicyEventsQueue: Metadata: 'aws:copilot:description': "An autoscaling policy to maintain {{$acceptableBacklog}} messages/task for EventsQueue" Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: PolicyName: !Join ['-', [!Ref WorkloadName, BacklogPerTask, !GetAtt EventsQueue.QueueName]] PolicyType: TargetTrackingScaling ScalingTargetId: !Ref AutoScalingTarget TargetTrackingScalingPolicyConfiguration: {{- if $queueDelayCooldown.ScaleInCooldown}} ScaleInCooldown: {{$queueDelayCooldown.ScaleInCooldown}} {{- else}} ScaleInCooldown: 120 {{- end}} {{- if $queueDelayCooldown.ScaleOutCooldown}} ScaleOutCooldown: {{$queueDelayCooldown.ScaleOutCooldown}} {{- else}} ScaleOutCooldown: 60 {{- end}} CustomizedMetricSpecification: Namespace: !Sub '${AppName}-${EnvName}-${WorkloadName}' MetricName: BacklogPerTask Statistic: Average Dimensions: - Name: QueueName Value: !GetAtt EventsQueue.QueueName Unit: Count TargetValue: {{$acceptableBacklog}} {{- if .Subscribe }} {{- range $topic := .Subscribe.Topics}} {{- if $topic.Queue}} AutoScalingPolicy{{logicalIDSafe $topic.Service}}{{logicalIDSafe $topic.Name}}EventsQueue: Metadata: 'aws:copilot:description': "An autoscaling policy to maintain {{$acceptableBacklog}} messages/task for {{logicalIDSafe $topic.Service}}{{logicalIDSafe $topic.Name}}EventsQueue" Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: PolicyName: !Join ['-', [!Ref WorkloadName, BacklogPerTask, !GetAtt {{logicalIDSafe $topic.Service}}{{logicalIDSafe $topic.Name}}EventsQueue.QueueName]] PolicyType: TargetTrackingScaling ScalingTargetId: !Ref AutoScalingTarget TargetTrackingScalingPolicyConfiguration: {{- if $queueDelayCooldown.ScaleInCooldown}} ScaleInCooldown: {{$queueDelayCooldown.ScaleInCooldown}} {{- else}} ScaleInCooldown: 120 {{- end}} {{- if $queueDelayCooldown.ScaleOutCooldown}} ScaleOutCooldown: {{$queueDelayCooldown.ScaleOutCooldown}} {{- else}} ScaleOutCooldown: 60 {{- end}} CustomizedMetricSpecification: Namespace: !Sub '${AppName}-${EnvName}-${WorkloadName}' MetricName: BacklogPerTask Statistic: Average Dimensions: - Name: QueueName Value: !GetAtt {{logicalIDSafe $topic.Service}}{{logicalIDSafe $topic.Name}}EventsQueue.QueueName Unit: Count TargetValue: {{$acceptableBacklog}} {{- end }}{{/* if $topic.Queue */}} {{- end }}{{/* range $topic := .Subscribe.Topics */}} {{- end }}{{/* if .Subscribe */}} {{- end }}{{/* if .Autoscaling.QueueDelay */}} {{- if .Autoscaling.Requests}} AutoScalingPolicyALBSumRequestCountPerTarget: Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: PolicyName: !Join ['-', [!Ref WorkloadName, ALBSumRequestCountPerTarget, ScalingPolicy]] PolicyType: TargetTrackingScaling ScalingTargetId: !Ref AutoScalingTarget TargetTrackingScalingPolicyConfiguration: CustomizedMetricSpecification: Dimensions: - Name: LoadBalancer {{- if eq .WorkloadType "Backend Service"}} Value: !GetAtt EnvControllerAction.InternalLoadBalancerFullName {{- else}} Value: !GetAtt EnvControllerAction.PublicLoadBalancerFullName {{- end}} - Name: TargetGroup Value: !GetAtt TargetGroup.TargetGroupFullName MetricName: RequestCountPerTarget Namespace: AWS/ApplicationELB Statistic: Sum {{- if .Autoscaling.ReqCooldown.ScaleInCooldown}} ScaleInCooldown: {{.Autoscaling.ReqCooldown.ScaleInCooldown}} {{- else}} ScaleInCooldown: 120 {{- end}} {{- if .Autoscaling.ReqCooldown.ScaleOutCooldown}} ScaleOutCooldown: {{.Autoscaling.ReqCooldown.ScaleOutCooldown}} {{- else}} ScaleOutCooldown: 60 {{- end}} TargetValue: {{.Autoscaling.Requests}} {{- end}} {{- if .Autoscaling.ResponseTime}} AutoScalingPolicyALBAverageResponseTime: Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: PolicyName: !Join ['-', [!Ref WorkloadName, ALBAverageResponseTime, ScalingPolicy]] PolicyType: TargetTrackingScaling ScalingTargetId: !Ref AutoScalingTarget TargetTrackingScalingPolicyConfiguration: CustomizedMetricSpecification: Dimensions: - Name: LoadBalancer {{- if eq .WorkloadType "Backend Service"}} Value: !GetAtt EnvControllerAction.InternalLoadBalancerFullName {{- else}} Value: !GetAtt EnvControllerAction.PublicLoadBalancerFullName {{- end}} - Name: TargetGroup Value: !GetAtt TargetGroup.TargetGroupFullName MetricName: TargetResponseTime Namespace: AWS/ApplicationELB Statistic: Average {{- if .Autoscaling.RespTimeCooldown.ScaleInCooldown}} ScaleInCooldown: {{.Autoscaling.RespTimeCooldown.ScaleInCooldown}} {{- else}} ScaleInCooldown: 120 {{- end}} {{- if .Autoscaling.RespTimeCooldown.ScaleOutCooldown}} ScaleOutCooldown: {{.Autoscaling.RespTimeCooldown.ScaleOutCooldown}} {{- else}} ScaleOutCooldown: 60 {{- end}} TargetValue: {{.Autoscaling.ResponseTime}} {{- end}}