Amazon ECS Deployment with CloudFormation
External
Internal
Procedure
Declare a set of configuration parameters that abstract out operational details, such as project name, etc. Then declare the task definition:
Prerequisites
Parameters: ProjectID: Type: String Default: themyscira Description: | The key that uniquely identifies a resource consumer (service, tool that requires AWS resources, etc.). The project ID is used as root when assembling the names of associated resources. Image: Type: String Tag: Type: String
AWS::ECS::TaskDefinition
Resources: ... TaskDefinition: Type: AWS::ECS::TaskDefinition Properties: Family: !Ref ProjectID RequiresCompatibilities: ['FARGATE'] TaskRoleArn: !GetAtt TaskRole.Arn ExecutionRoleArn: !GetAtt TaskExecutionRole.Arn NetworkMode: 'awsvpc' Memory: '4096' Cpu: '2048' ContainerDefinitions: - Name: !Sub '${ProjectID}-container' Image: !Sub ${Image}:${Tag} Essential: 'true' Memory: '4096' Cpu: '2048' PortMappings: - HostPort: 10002 ContainerPort: 10002 Environment: - Name: SPRING_PROFILES_ACTIVE Value: 'something' LogConfiguration: LogDriver: "awslogs" Options: awslogs-group: !Ref ServiceLogGroup awslogs-region: !Sub ${AWS::Region} awslogs-stream-prefix: 'task'
TaskRole and TaskExecutionRole, a service-specific ServiceLogGroup will also have to be declared, see Dependencies below.
Container Image Tag Considerations
If "latest" is used as container image tag above (as ${Tag}), I've noticed that under circumstances that yet have to be elucidated, the task does not get recycled during a CodePipeline deployment phase, as if the image change does not get detected. This happens even if the underlying image tagged as "latest" undeniably changes from the current version used by the task. The workaround is to use a unique tag ID for each image, as the one provided the CodeBuild's CODEBUILD_RESOLVED_SOURCE_VERSION. This is the essence of it:
1) Tag the image with CODEBUILD_RESOLVED_SOURCE_VERSION at build phase, and pass CODEBUILD_RESOLVED_SOURCE_VERSION to the deployment phase as part of the TemplateConfiguration.
...
phases:
install:
commands:
...
- if [ -z "${CODEBUILD_RESOLVED_SOURCE_VERSION}" ]; then echo "required CODEBUILD_RESOLVED_SOURCE_VERSION variable not set" 1>&2; exit 1; else echo "CODEBUILD_RESOLVED_SOURCE_VERSION=${CODEBUILD_RESOLVED_SOURCE_VERSION}"; fi
...
post_build:
commands:
...
- docker tag ${ECR_REPOSITORY_URI}:latest ${ECR_REPOSITORY_URI}:${CODEBUILD_RESOLVED_SOURCE_VERSION}
- docker push ${ECR_REPOSITORY_URI}:${CODEBUILD_RESOLVED_SOURCE_VERSION}
...
- echo "{\"Parameters\":{... \"CodebuildResolvedSourceVersion\":\"${CODEBUILD_RESOLVED_SOURCE_VERSION}\"}}" > ${DEPLOYMENT_STACK_CONFIG_FILE}
artifacts:
files:
- ${DEPLOYMENT_STACK_CONFIG_FILE}
2) Declare as deployment stack parameter and used it in the Task definition:
...
Parameters:
...
CodebuildResolvedSourceVersion:
Type: String
Description: >
The identifier for the version of a build's source code. For GitHub, it is the commit ID.
...
Resources:
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
...
ContainerDefinitions:
- Name: !Sub ${ApplicationName}-container
...
Image: !Sub ${EcrRepositoryUri}:${CodebuildResolvedSourceVersion}
AWS::ECS::Service
Resources: ... ServiceDefinition: Type: AWS::ECS::Service DependsOn: - LoadBalancerListener Properties: ServiceName: !Ref ProjectID LaunchType: FARGATE Cluster: 'playground' TaskDefinition: !Ref TaskDefinition DesiredCount: 1 HealthCheckGracePeriodSeconds: 60 LoadBalancers: - ContainerName: !Sub '${ProjectID}-container' ContainerPort: 10002 TargetGroupArn: !Ref TargetGroup NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: DISABLED SecurityGroups: - !Ref ServiceSecurityGroup Subnets: - 'blue-subnet' - 'green-subnet' ServiceRegistries: - RegistryArn: !GetAtt ServiceDiscovery.Arn
The service depends on load balancing infrastructure.
Dependencies
TODO:
- TaskRole
- TaskExecutionRole
- ServiceLogGroup
- Load balancing infrastructure
Create a Cluster
Resources: Cluster: Type: AWS::ECS::Cluster Properties: ClusterName: <cluster-name>