Network Stack#
Let's create a network stack:
- VPC with 2 public and 2 private subnets
- Security groups for alb and fargate
- NAT gateway
AWSTemplateFormatVersion: '2010-09-09'#------------------------------------------------------# Mappings#------------------------------------------------------Mappings:CidrMappings:public-subnet-1:CIDR: 10.0.0.0/24public-subnet-2:CIDR: 10.0.2.0/24private-subnet-1:CIDR: 10.0.1.0/24private-subnet-2:CIDR: 10.0.3.0/24#------------------------------------------------------# Parameters#------------------------------------------------------Parameters:CidrBlock:Type: StringDescription: CidrBlockDefault: 10.0.0.0/16InternetCidrBlock:Type: StringDescription: UserCidrBlockDefault: 0.0.0.0/0#------------------------------------------------------# Resources: VPC, Subnets, NAT, Routes#------------------------------------------------------Resources:VPC:Type: AWS::EC2::VPCProperties:CidrBlock: !Ref CidrBlockEnableDnsSupport: trueEnableDnsHostnames: trueTags:- Key: NameValue: !Sub ${AWS::StackName}-vpc#------------------------------------------------------# Resources: internet gateway#------------------------------------------------------InternetGateway:Type: AWS::EC2::InternetGatewayProperties:Tags:- Key: NameValue: !Sub ${AWS::StackName}-igAttachGateway:Type: AWS::EC2::VPCGatewayAttachmentProperties:VpcId: !Ref VPCInternetGatewayId: !Ref InternetGateway#------------------------------------------------------# Resources: public and private subnets#------------------------------------------------------PublicSubnet1:Type: AWS::EC2::SubnetProperties:MapPublicIpOnLaunch: falseAvailabilityZone:Fn::Select:- 0- Fn::GetAZs:Ref: AWS::RegionVpcId: !Ref VPCCidrBlock:Fn::FindInMap:- CidrMappings- public-subnet-1- CIDRTags:- Key: NameValue: !Sub ${AWS::StackName}-public-subnet-1PublicSubnet2:Type: AWS::EC2::SubnetProperties:MapPublicIpOnLaunch: falseAvailabilityZone:Fn::Select:- 1- Fn::GetAZs:Ref: AWS::RegionVpcId: !Ref VPCCidrBlock:Fn::FindInMap:- CidrMappings- public-subnet-2- CIDRTags:- Key: NameValue: !Sub ${AWS::StackName}-public-subnet-2PrivateSubnet1:Type: AWS::EC2::SubnetProperties:MapPublicIpOnLaunch: falseAvailabilityZone:Fn::Select:- 0- Fn::GetAZs:Ref: AWS::RegionVpcId: !Ref VPCCidrBlock:Fn::FindInMap:- CidrMappings- private-subnet-1- CIDRTags:- Key: NameValue: !Sub ${AWS::StackName}-private-subnet-1PrivateSubnet2:Type: AWS::EC2::SubnetProperties:MapPublicIpOnLaunch: falseAvailabilityZone:Fn::Select:- 1- Fn::GetAZs:Ref: AWS::RegionVpcId: !Ref VPCCidrBlock:Fn::FindInMap:- CidrMappings- private-subnet-2- CIDRTags:- Key: NameValue: !Sub ${AWS::StackName}-private-subnet-2#------------------------------------------------------# Resources: nat gateway#------------------------------------------------------NatGatewayEIP:Type: AWS::EC2::EIPProperties:Domain: vpcNatGateway:Type: AWS::EC2::NatGatewayProperties:AllocationId:Fn::GetAtt: [NatGatewayEIP, AllocationId]SubnetId: !Ref PublicSubnet1Tags:- Key: NameValue: !Sub ${AWS::StackName}-nat-gateway#------------------------------------------------------# Resources: public route table#------------------------------------------------------PublicRouteTable:Type: AWS::EC2::RouteTableProperties:VpcId: !Ref VPCTags:- Key: NameValue: !Sub ${AWS::StackName}-public-rtRouteInternetGateway:Type: AWS::EC2::RouteDependsOn: AttachGatewayProperties:RouteTableId: !Ref PublicRouteTableDestinationCidrBlock: !Ref InternetCidrBlockGatewayId: !Ref InternetGateway#------------------------------------------------------# Resources: private route table#------------------------------------------------------PrivateRouteTable:Type: AWS::EC2::RouteTableProperties:VpcId: !Ref VPCTags:- Key: NameValue: !Sub ${AWS::StackName}-private-rtPrivateRoute:Type: AWS::EC2::RouteProperties:RouteTableId: !Ref PrivateRouteTableDestinationCidrBlock: !Ref InternetCidrBlockNatGatewayId: !Ref NatGateway#------------------------------------------------------# Resources: routeable subnet associations#------------------------------------------------------PublicSubnet1RouteTableAssociation:Type: AWS::EC2::SubnetRouteTableAssociationProperties:SubnetId: !Ref PublicSubnet1RouteTableId: !Ref PublicRouteTablePublicSubnet2RouteTableAssociation:Type: AWS::EC2::SubnetRouteTableAssociationProperties:SubnetId: !Ref PublicSubnet2RouteTableId: !Ref PublicRouteTablePrivateSubnet1RouteTableAssociation:Type: AWS::EC2::SubnetRouteTableAssociationProperties:SubnetId: !Ref PrivateSubnet1RouteTableId: !Ref PrivateRouteTablePrivateSubnet2RouteTableAssociation:Type: AWS::EC2::SubnetRouteTableAssociationProperties:SubnetId: !Ref PrivateSubnet2RouteTableId: !Ref PrivateRouteTable#------------------------------------------------------# Security Group:#------------------------------------------------------ECSFargateSecurityGroup:Type: AWS::EC2::SecurityGroupProperties:GroupDescription: Communication between the control plane and worker nodegroupsVpcId: !Ref VPCGroupName: !Sub ${AWS::StackName}-ecs-fargate-sgECSFargateSecurityGroupIngress:Type: AWS::EC2::SecurityGroupIngressProperties:GroupId: !Ref ECSFargateSecurityGroupIpProtocol: -1SourceSecurityGroupId: !Ref ECSFargateSecurityGroupSourceSecurityGroupOwnerId: !Ref AWS::AccountIdECSFargateSecurityGroupIngressALB:Type: AWS::EC2::SecurityGroupIngressProperties:IpProtocol: tcpFromPort: 3000ToPort: 3000SourceSecurityGroupId: !Ref ALBSecurityGroupGroupId: !Ref ECSFargateSecurityGroupALBSecurityGroup:Type: AWS::EC2::SecurityGroupProperties:GroupDescription: ALB Security GroupVpcId: !Ref VPCGroupName: !Sub ${AWS::StackName}-alb-sgALBSecurityGroupIngress:Type: AWS::EC2::SecurityGroupIngressProperties:IpProtocol: tcpFromPort: 80ToPort: 80CidrIp: !Ref InternetCidrBlockGroupId: !Ref ALBSecurityGroup#------------------------------------------------------# Export:#------------------------------------------------------Outputs:VPC:Value: !Ref VPCExport:Name: !Sub ${AWS::StackName}-vpcPublicSubnet1:Value: !Ref PublicSubnet1Export:Name: !Sub ${AWS::StackName}-public-subnet-1PublicSubnet2:Value: !Ref PublicSubnet2Export:Name: !Sub ${AWS::StackName}-public-subnet-2PrivateSubnet1:Value: !Ref PrivateSubnet2Export:Name: !Sub ${AWS::StackName}-private-subnet-1PrivateSubnet2:Value: !Ref PrivateSubnet2Export:Name: !Sub ${AWS::StackName}-private-subnet-2PrivateRouteTable:Value: !Ref PrivateRouteTableExport:Name: !Sub ${AWS::StackName}-private-route-tableInternetGateway:Value: !Ref InternetGatewayExport:Name: !Sub ${AWS::StackName}-igwECSFargateSecurityGroup:Value: !Ref ECSFargateSecurityGroupExport:Name: !Sub ${AWS::StackName}-ecs-fargate-sgALBSecurityGroup:Value: !Ref ALBSecurityGroupExport:Name: !Sub ${AWS::StackName}-alb-sg
Load Balancer#
Let's create a load balancer stack:
- Application load balancer
- Blue and green target group
- Listener on port 80
AWSTemplateFormatVersion: '2010-09-09'Parameters:NetworkStackName:Description: Stack name of the network stackType: StringDefault: cfn-networkResources:LoadBalancer:Type: AWS::ElasticLoadBalancingV2::LoadBalancerProperties:Subnets:- Fn::ImportValue: !Sub ${NetworkStackName}-public-subnet-1- Fn::ImportValue: !Sub ${NetworkStackName}-public-subnet-2SecurityGroups:- Fn::ImportValue: !Sub ${NetworkStackName}-alb-sgTargetGroupBlue:Type: AWS::ElasticLoadBalancingV2::TargetGroupProperties:VpcId:Fn::ImportValue: !Sub ${NetworkStackName}-vpcPort: 80Protocol: HTTPTargetType: ipMatcher:HttpCode: 200-299HealthCheckIntervalSeconds: 10HealthCheckPath: /HealthCheckProtocol: HTTPHealthCheckTimeoutSeconds: 5HealthyThresholdCount: 2TargetGroupGreen:Type: AWS::ElasticLoadBalancingV2::TargetGroupProperties:VpcId:Fn::ImportValue: !Sub ${NetworkStackName}-vpcPort: 80Protocol: HTTPTargetType: ipMatcher:HttpCode: 200-299HealthCheckIntervalSeconds: 10HealthCheckPath: /HealthCheckProtocol: HTTPHealthCheckTimeoutSeconds: 5HealthyThresholdCount: 2Listener:Type: AWS::ElasticLoadBalancingV2::ListenerProperties:LoadBalancerArn: !Ref LoadBalancerPort: 81Protocol: HTTPDefaultActions:- Type: forwardTargetGroupArn: !Ref TargetGroupBlue# Export outputOutputs:LoadBalancer:Description: Load balancerValue: !Ref LoadBalancerExport:Name: !Sub ${AWS::StackName}-load-balancerTargetGroupBlue:Description: Target group blueValue: !Ref TargetGroupBlueExport:Name: !Sub ${AWS::StackName}-target-group-blueTargetGroupGreen:Description: Target group greenValue: !Ref TargetGroupGreenExport:Name: !Sub ${AWS::StackName}-target-group-greenTargetGroupBlueName:Description: Target group name blueValue: !GetAtt TargetGroupBlue.TargetGroupNameExport:Name: !Sub ${AWS::StackName}-target-group-blue-nameTargetGroupGreenName:Description: Target group name greenValue: !GetAtt TargetGroupGreen.TargetGroupNameExport:Name: !Sub ${AWS::StackName}-target-group-green-nameListenerArns:Description: Listener ARNsValue: !GetAtt Listener.ListenerArnExport:Name: !Sub ${AWS::StackName}-alb-listener-arn
CodeCommit and ECR#
Let's create a CodeCommit repository and a ECR repository. We need to build a container image from a local machine and push to this ECR repository before using a CI/CD piepline.
AWSTemplateFormatVersion: '2010-09-09'Parameters:CodeCommitRepoName:Type: StringDescription: The name of the CodeCommit repository to create.Default: ecs-blue-greenECRRepoName:Type: StringDescription: The name of the Elastic Container Registry to create.Default: go-appCodePipelineS3Bucket:Type: StringDescription: The name of the S3 bucket to store the deployment code inDefault: codepipeline-us-west-2-27062024Resources:# A CodeCommit repository to store your codeCodeCommitRepo:Type: AWS::CodeCommit::RepositoryProperties:RepositoryName: !Ref CodeCommitRepoName# An Elastic Container Registry to store Docker imagesECRRepo:Type: AWS::ECR::RepositoryProperties:RepositoryName: !Ref ECRRepoName# S3 bucket for storing templates and pipeline artifactsS3Bucket:Type: AWS::S3::BucketProperties:BucketName: !Ref CodePipelineS3Bucket
ECS Cluster#
Let's create an ecs cluster:
- Fargate capacity provider
- Enable logs
AWSTemplateFormatVersion: '2010-09-09'# ParametersParameters:ClusterName:Type: StringDescription: 'ImageId to be used to create an EC2 instance.'Default: 'demo'# Create an ecs clusterResources:ecscluster:Type: AWS::ECS::ClusterProperties:ClusterName: !Ref ClusterNameClusterSettings:- Name: containerInsightsValue: enabledCapacityProviders:- FARGATE- FARGATE_SPOTDefaultCapacityProviderStrategy:- CapacityProvider: FARGATEWeight: 4Base: 1- CapacityProvider: FARGATE_SPOTWeight: 1Base: 0# ExportOutputs:ecscluster:Value: !Ref ecsclusterExport:Name: !Sub '${AWS::StackName}-ecscluster'
ECS Task#
Let's create a task definition:
- Allocate cpu and memory
- Specify container definition
AWSTemplateFormatVersion: '2010-09-09'Description: ECS Task DefinitionParameters:EcrRepoName:Type: StringDescription: Name of the ecr repoDefault: go-appResources:TaskRoleBook:Type: AWS::IAM::RoleProperties:RoleName: 'ECSTaskRoleForBook'AssumeRolePolicyDocument:Statement:- Action:- 'sts:AssumeRole'Effect: AllowPrincipal:Service:- ecs-tasks.amazonaws.comPolicies:- PolicyName: rootPolicyDocument:Version: '2012-10-17'Statement:- Effect: AllowAction:- 'ssmmessages:CreateControlChannel'- 'ssmmessages:CreateDataChannel'- 'ssmmessages:OpenControlChannel'- 'ssmmessages:OpenDataChannel'Resource: '*'ManagedPolicyArns:- 'arn:aws:iam::aws:policy/AmazonS3FullAccess'TaskExecutionRoleBook:Type: AWS::IAM::RoleProperties:RoleName: 'ECSTaskExecutionRoleForBook'AssumeRolePolicyDocument:Statement:- Action:- 'sts:AssumeRole'Effect: AllowPrincipal:Service:- ecs-tasks.amazonaws.comManagedPolicyArns:- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicyBookTaskDefinition:Type: AWS::ECS::TaskDefinitionProperties:Family: !Ref EcrRepoNameTaskRoleArn: !GetAtt TaskRoleBook.ArnExecutionRoleArn: !GetAtt TaskExecutionRoleBook.ArnNetworkMode: awsvpcRequiresCompatibilities:- FARGATECpu: 256Memory: 512ContainerDefinitions:- Name: book-containerImage: !Sub- '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EcrRepoName}:latest'- EcrRepoName: !Ref EcrRepoNamePortMappings:- ContainerPort: 3000Privileged: falseLogConfiguration:LogDriver: awslogsOptions:awslogs-group: !Ref LogGroupawslogs-region: !Ref AWS::Regionawslogs-stream-prefix: !Ref EcrRepoNameLogGroup:Type: AWS::Logs::LogGroupDeletionPolicy: DeleteUpdateReplacePolicy: DeleteProperties:LogGroupName: !Sub- '/ecs/${EcrRepoName}'- EcrRepoName: !Ref EcrRepoNameRetentionInDays: 7# Export outputOutputs:taskDefinition:Description: Task DefinitionValue: !Ref BookTaskDefinitionExport:Name: !Sub '${AWS::StackName}-book-task-def'
ECS Service#
Let's create a service and expose it via the load balancer created above.
AWSTemplateFormatVersion: '2010-09-09'Description: ECS ServiceParameters:NetworkStackName:Type: StringDescription: Name of the network stackDefault: 'cfn-network'ClusterStackName:Type: StringDescription: Name of the ECS cluster stack to create the serviceDefault: 'cfn-ecs-cluster'ALBStackName:Type: StringDescription: Name of the ALB stack to associate with the serviceDefault: 'cfn-alb-blue-green'TaskDefinitionStackName:Type: StringDescription: Name of the task definition stackDefault: 'cfn-task-def-book'DesiredCount:Type: NumberDescription: Desired number of tasksDefault: 1ContainerName:Type: StringDescription: Name of the containerDefault: 'book-container'ServiceName:Type: StringDescription: Name of the serviceDefault: 'book-service-blue-green'Resources:ECSService:Type: AWS::ECS::ServiceProperties:ServiceName: !Ref ServiceNameTaskDefinition:Fn::ImportValue: !Sub ${TaskDefinitionStackName}-book-task-defCluster:Fn::ImportValue: !Sub ${ClusterStackName}-ecsclusterLaunchType: FARGATEDesiredCount: !Ref DesiredCountDeploymentController:Type: CODE_DEPLOYNetworkConfiguration:AwsvpcConfiguration:# AssignPublicIp: ENABLEDSecurityGroups:- Fn::ImportValue: !Sub ${NetworkStackName}-ecs-fargate-sgSubnets:- Fn::ImportValue: !Sub ${NetworkStackName}-private-subnet-1- Fn::ImportValue: !Sub ${NetworkStackName}-private-subnet-2LoadBalancers:- ContainerName: !Ref ContainerNameContainerPort: 3000TargetGroupArn:Fn::ImportValue: !Sub ${ALBStackName}-target-group-blueHealthCheckGracePeriodSeconds: 60ServiceScalingTarget:Type: AWS::ApplicationAutoScaling::ScalableTargetProperties:MaxCapacity: 4MinCapacity: 1ResourceId:Fn::Join:- /- - service- Fn::ImportValue: !Sub ${ClusterStackName}-ecscluster- !Ref ServiceNameRoleARN: !GetAtt AutoScalingRole.ArnScalableDimension: ecs:service:DesiredCountServiceNamespace: ecsServiceScalingPolicy:Type: AWS::ApplicationAutoScaling::ScalingPolicyProperties:PolicyName: AverageCPUUtilizationPolicyPolicyType: TargetTrackingScalingScalingTargetId: !Ref ServiceScalingTargetTargetTrackingScalingPolicyConfiguration:TargetValue: 80.0ScaleInCooldown: 60ScaleOutCooldown: 60PredefinedMetricSpecification:PredefinedMetricType: ECSServiceAverageCPUUtilizationAutoScalingRole:Type: AWS::IAM::RoleProperties:AssumeRolePolicyDocument:Version: '2012-10-17'Statement:- Effect: AllowPrincipal:Service: ecs.application-autoscaling.amazonaws.comAction: 'sts:AssumeRole'Policies:- PolicyName: MyAWSApplicationAutoscalingECSServicePolicyPolicyDocument:Version: '2012-10-17'Statement:- Effect: AllowAction:- ecs:DescribeServices- ecs:UpdateService- cloudwatch:PutMetricAlarm- cloudwatch:PutMetricAlarm- cloudwatch:PutMetricAlarmResource: '*'
CI/CD Pipeline#
Let's create a CI/CD pipeline:
- CodeCommit source
- ECR respository
- CodeBuild project
- CodeDeploy application and deployment
- S3 artifact bucket already created from the codecommit stack above
AWSTemplateFormatVersion: '2010-09-09'Parameters:AlbStackName:Type: StringDescription: The name of the ALB stackDefault: cfn-alb-blue-greenCodeBuildProjectName:Type: StringDescription: CodeBuild project nameDefault: code-build-blue-greenCodePipelineS3Bucket:Type: StringDescription: The name of the S3 bucket to store the deployment code inDefault: codepipeline-us-west-2-27062024EcrRepoName:Type: StringDescription: The name of the ECR repository to store the deployment image inDefault: go-appEcrArtifactOutput:Type: StringDescription: The name of the ECR artifact outputDefault: ecr-artifactCodeCommitArtifactOutput:Type: StringDescription: The name of the CodeBuild artifact outputDefault: codecommit-artifactCodeBuildArtifactOutput:Type: StringDescription: The name of the CodeBuild artifact outputDefault: codebuild-artifactCodeDeployAppName:Type: StringDescription: The name of the CodeDeploy applicationDefault: blue-green-deploy-appDeploymentGroupName:Type: StringDescription: The name of the Deployment GroupDefault: blue-green-deploy-groupEcsClusterName:Type: StringDescription: The name of the ECS clusterDefault: demoServiceName:Type: StringDescription: The name of the ECS serviceDefault: book-service-blue-greenCodeCommitRepoName:Type: StringDescription: The name of the CodeCommit repository to useDefault: ecs-blue-greenCodePipelineName:Type: StringDescription: The name of the CodePipelineDefault: pipeline-blue-greenResources:# IAM role for codepipelineRoleForCodePipeline:Type: AWS::IAM::RoleProperties:AssumeRolePolicyDocument:Statement:- Action: sts:AssumeRoleEffect: AllowPrincipal:Service: codepipeline.amazonaws.comPolicies:- PolicyName: !Sub ${AWS::StackName}-CodePipelinePolicyBlueGreenPolicyDocument: |{"Statement": [{"Action": ["iam:PassRole"],"Resource": "*","Effect": "Allow","Condition": {"StringEqualsIfExists": {"iam:PassedToService": ["cloudformation.amazonaws.com","elasticbeanstalk.amazonaws.com","ec2.amazonaws.com","ecs-tasks.amazonaws.com"]}}},{"Action": ["codecommit:CancelUploadArchive","codecommit:GetBranch","codecommit:GetCommit","codecommit:GetRepository","codecommit:GetUploadArchiveStatus","codecommit:UploadArchive"],"Resource": "*","Effect": "Allow"},{"Action": ["ecr:*"],"Resource": "*","Effect": "Allow"},{"Action": ["codedeploy:CreateDeployment","codedeploy:GetApplication","codedeploy:GetApplicationRevision","codedeploy:GetDeployment","codedeploy:GetDeploymentConfig","codedeploy:RegisterApplicationRevision"],"Resource": "*","Effect": "Allow"},{"Action": ["codestar-connections:UseConnection"],"Resource": "*","Effect": "Allow"},{"Action": ["elasticbeanstalk:*","ec2:*","elasticloadbalancing:*","autoscaling:*","cloudwatch:*","s3:*","sns:*","cloudformation:*","rds:*","sqs:*","ecs:*"],"Resource": "*","Effect": "Allow"},{"Action": ["lambda:InvokeFunction","lambda:ListFunctions"],"Resource": "*","Effect": "Allow"},{"Action": ["opsworks:CreateDeployment","opsworks:DescribeApps","opsworks:DescribeCommands","opsworks:DescribeDeployments","opsworks:DescribeInstances","opsworks:DescribeStacks","opsworks:UpdateApp","opsworks:UpdateStack"],"Resource": "*","Effect": "Allow"},{"Action": ["cloudformation:CreateStack","cloudformation:DeleteStack","cloudformation:DescribeStacks","cloudformation:UpdateStack","cloudformation:CreateChangeSet","cloudformation:DeleteChangeSet","cloudformation:DescribeChangeSet","cloudformation:ExecuteChangeSet","cloudformation:SetStackPolicy","cloudformation:ValidateTemplate"],"Resource": "*","Effect": "Allow"},{"Action": ["codebuild:BatchGetBuilds","codebuild:StartBuild","codebuild:BatchGetBuildBatches","codebuild:StartBuildBatch"],"Resource": "*","Effect": "Allow"},{"Effect": "Allow","Action": ["devicefarm:ListProjects","devicefarm:ListDevicePools","devicefarm:GetRun","devicefarm:GetUpload","devicefarm:CreateUpload","devicefarm:ScheduleRun"],"Resource": "*"},{"Effect": "Allow","Action": ["servicecatalog:ListProvisioningArtifacts","servicecatalog:CreateProvisioningArtifact","servicecatalog:DescribeProvisioningArtifact","servicecatalog:DeleteProvisioningArtifact","servicecatalog:UpdateProduct"],"Resource": "*"},{"Effect": "Allow","Action": ["cloudformation:ValidateTemplate"],"Resource": "*"},{"Effect": "Allow","Action": ["ecr:DescribeImages"],"Resource": "*"},{"Effect": "Allow","Action": ["states:DescribeExecution","states:DescribeStateMachine","states:StartExecution"],"Resource": "*"},{"Effect": "Allow","Action": ["appconfig:StartDeployment","appconfig:StopDeployment","appconfig:GetDeployment"],"Resource": "*"}],"Version": "2012-10-17"}# IAM role for codebuildRoleForCodeBuild:Type: AWS::IAM::RoleProperties:AssumeRolePolicyDocument:Statement:- Action: sts:AssumeRoleEffect: AllowPrincipal:Service: codebuild.amazonaws.comPolicies:- PolicyName: !Sub ${AWS::StackName}-CodeBuildPolicyBlueGreenPolicyDocument: !Sub- |{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Resource": ["arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/debug","arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/debug:*"],"Action": ["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"]},{"Effect": "Allow","Resource": ["arn:${AWS::Partition}:s3:::${CodePipelineS3Bucket}/*"],"Action": ["s3:PutObject","s3:GetObject","s3:GetObjectVersion","s3:GetBucketAcl","s3:GetBucketLocation"]},{"Effect": "Allow","Resource": ["arn:${AWS::Partition}:s3:::${CodePipelineS3Bucket}/*"],"Action": ["s3:*"]},{"Effect": "Allow","Action": ["codebuild:CreateReportGroup","codebuild:CreateReport","codebuild:UpdateReport","codebuild:BatchPutTestCases","codebuild:BatchPutCodeCoverages"],"Resource": ["arn:${AWS::Partition}:codebuild:${AWS::Region}:${AWS::AccountId}:report-group/debug-*"]},{"Action": ["ecr:*"],"Resource": "*","Effect": "Allow"},{"Action": ["codecommit:CancelUploadArchive","codecommit:GetBranch","codecommit:GetCommit","codecommit:GetRepository","codecommit:GetUploadArchiveStatus","codecommit:UploadArchive"],"Resource": "*","Effect": "Allow"}]}- CodePipelineS3Bucket: !Ref CodePipelineS3Bucket# IAM role for codedeployRoleForCodeDeploy:Type: AWS::IAM::RoleProperties:AssumeRolePolicyDocument:Statement:- Action: sts:AssumeRoleEffect: AllowPrincipal:Service: codedeploy.amazonaws.comPolicies:- PolicyName: !Sub ${AWS::StackName}-CodeDeployPolicyBlueGreenPolicyDocument: |{"Version": "2012-10-17","Statement": [{"Action": ["ecs:DescribeServices","ecs:CreateTaskSet","ecs:UpdateServicePrimaryTaskSet","ecs:DeleteTaskSet","elasticloadbalancing:DescribeTargetGroups","elasticloadbalancing:DescribeListeners","elasticloadbalancing:ModifyListener","elasticloadbalancing:DescribeRules","elasticloadbalancing:ModifyRule","lambda:InvokeFunction","cloudwatch:DescribeAlarms","sns:Publish","s3:GetObject","s3:GetObjectVersion"],"Resource": "*","Effect": "Allow"},{"Action": ["iam:PassRole"],"Effect": "Allow","Resource": "*","Condition": {"StringLike": {"iam:PassedToService": ["ecs-tasks.amazonaws.com"]}}}]}# Codebuild projectCodeBuildProject:Type: AWS::CodeBuild::ProjectProperties:Name: !Ref CodeBuildProjectNameServiceRole: !Ref RoleForCodeBuildArtifacts:Type: S3Packaging: NONELocation: !Ref CodePipelineS3BucketEnvironment:Type: LINUX_CONTAINERImage: aws/codebuild/amazonlinux2-x86_64-standard:5.0ComputeType: BUILD_GENERAL1_SMALLPrivilegedMode: falseLogsConfig:CloudWatchLogs:Status: ENABLEDGroupName: /aws/codebuild/debugSource:Type: CODECOMMITLocation: !Sub- https://git-codecommit.${AWS::Region}.amazonaws.com/v1/repos/${EcrRepoName}- EcrRepoName: !Ref EcrRepoNameBuildSpec: !Sub- |version: 0.2env:shell: bashvariables:REPO_NAME: ${EcrRepoName}CODEBUILD_RESOLVED_SOURCE_VERSION: $CODEBUILD_RESOLVED_SOURCE_VERSIONTAG_NAME: "latest"phases:pre_build:commands:- export TAG_NAME=$(date +%s)build:commands:- docker build -t $REPO_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION-$TAG_NAME .- docker tag $REPO_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION-$TAG_NAME ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/$REPO_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION-$TAG_NAME- printf '{"ImageURI":"%s"}' ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EcrRepoName}:$REPO_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION-$TAG_NAME > imageDetail.json- cat imageDetail.json- cat taskdef.json- cat appspec.yamlpost_build:commands:- aws ecr get-login-password --region ${AWS::Region} | docker login --username AWS --password-stdin ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com- docker push ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/$REPO_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION-$TAG_NAMEartifacts:files:- imageDetail.json- taskdef.json- appspec.yamlname: ${CodeBuildArtifactOutput}- CodeBuildArtifactOutput: !Ref CodeBuildArtifactOutputEcrRepoName: !Ref EcrRepoNameCache:Type: NO_CACHETags:- Key: nameValue: !Ref CodeBuildProjectName# CodeDeploy ApplicationCodeDeployApplication:Type: AWS::CodeDeploy::ApplicationProperties:ApplicationName: !Ref CodeDeployAppNameComputePlatform: ECS# Deploy DeploymentGroupDeploy:Type: AWS::CodeDeploy::DeploymentGroupProperties:ApplicationName: !Ref CodeDeployAppNameServiceRoleArn: !GetAtt [RoleForCodeDeploy, Arn]AlarmConfiguration:Enabled: falseAutoRollbackConfiguration:Enabled: falseDeploymentConfigName: CodeDeployDefault.ECSAllAtOnceDeploymentGroupName: !Ref DeploymentGroupNameDeploymentStyle:DeploymentOption: WITH_TRAFFIC_CONTROLDeploymentType: BLUE_GREENBlueGreenDeploymentConfiguration:DeploymentReadyOption:ActionOnTimeout: CONTINUE_DEPLOYMENTWaitTimeInMinutes: 0TerminateBlueInstancesOnDeploymentSuccess:Action: TERMINATETerminationWaitTimeInMinutes: 5ECSServices:- ServiceName: !Ref ServiceNameClusterName: !Ref EcsClusterNameLoadBalancerInfo:TargetGroupPairInfoList:- ProdTrafficRoute:ListenerArns:- !ImportValueFn::Sub: ${AlbStackName}-alb-listener-arnTargetGroups:- Name: !ImportValueFn::Sub: ${AlbStackName}-target-group-blue-name- Name: !ImportValueFn::Sub: ${AlbStackName}-target-group-green-name# CodePipelineCodePipeline:Type: AWS::CodePipeline::PipelineProperties:Name: !Ref CodePipelineName# ExecutionMode: SUPERSEDEDRoleArn:Fn::GetAtt:- RoleForCodePipeline- ArnArtifactStore:Type: S3Location: !Ref CodePipelineS3BucketStages:- Name: SourceActions:- Name: ECRSourceActionTypeId:Owner: AWSCategory: SourceVersion: '1'Provider: ECRConfiguration:RepositoryName: !Ref EcrRepoNameImageTag: latestOutputArtifacts:- Name: !Ref EcrArtifactOutput- Name: CodeCommitSourceActionTypeId:Owner: AWSCategory: SourceVersion: '1'Provider: CodeCommitConfiguration:RepositoryName: !Ref CodeCommitRepoNameBranchName: mainOutputArtifacts:- Name: !Ref CodeCommitArtifactOutput- Name: BuildActions:- Name: CodeBuildActionTypeId:Owner: AWSCategory: BuildVersion: '1'Provider: CodeBuildRunOrder: 1Configuration:ProjectName: !Ref CodeBuildProjectPrimarySource: !Ref CodeCommitArtifactOutputInputArtifacts:- Name: !Ref CodeCommitArtifactOutputOutputArtifacts:- Name: !Ref CodeBuildArtifactOutput- Name: DeployActions:- Name: DeployActionTypeId:Owner: AWSCategory: DeployVersion: '1'Provider: CodeDeployToECSConfiguration:AppSpecTemplateArtifact: !Ref CodeCommitArtifactOutputApplicationName: !Ref CodeDeployAppNameDeploymentGroupName: !Ref DeploymentGroupNameImage1ArtifactName: !Ref EcrArtifactOutputImage1ContainerName: IMAGE1_NAMETaskDefinitionTemplatePath: taskdef.jsonAppSpecTemplatePath: appspec.yamlTaskDefinitionTemplateArtifact: !Ref CodeCommitArtifactOutputInputArtifacts:- Name: !Ref CodeCommitArtifactOutput- Name: !Ref EcrArtifactOutput
Blue Green Deployment#
We need to provide some additional files.
taskdef.json
{"containerDefinitions": [{"name": "book-container","image": "<IMAGE1_NAME>","portMappings": [{"containerPort": 3000,"hostPort": 3000,"protocol": "tcp"}],"essential": true,"environment": [{"name": "ENV","value": "DEPLOY"}]}],"family": "latest","taskRoleArn": "arn:aws:iam::<ACCOUNT_ID>:role/ECSTaskRoleForBook","executionRoleArn": "arn:aws:iam::<ACCOUNT_ID>:role/ECSTaskExecutionRoleForBook","networkMode": "awsvpc","placementConstraints": [],"compatibilities": ["EC2", "FARGATE"],"requiresCompatibilities": ["FARGATE"],"cpu": "2048","memory": "4096","runtimePlatform": {"cpuArchitecture": "X86_64","operatingSystemFamily": "LINUX"}}
appspec.yaml
version: 0.0Resources:- TargetService:Type: AWS::ECS::ServiceProperties:TaskDefinition: <TASK_DEFINITION>LoadBalancerInfo:ContainerName: 'book-container'ContainerPort: 3000PlatformVersion: 'LATEST'
imageDetail.json
{"ImageURI": "<ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/dk-image-repo@sha256:example3"}
imagedefinitions.json
[{"name": "book-container","imageUri": "<ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/go-app:latest"}]