AWS Code Star with Fargate — 2

Pooja G Bhat
9 min readJul 5, 2021

In the previous blog , we have seen how to setup Codestar project and integrate it with ECS to run the application.

In this blog, we will see how to setup CI/CD pipeline for multiple environments( dev, integration, staging) and have common loadbalancer for development environments and another for production.

Multiple environment setup

Once you create the codestar project, it creates only one pipeline. If you want to have CI/CD pipelines for testing, development branches, you have to create separate code pipelines and have different buildspec files in the codestar project.

We have created few of the required resources prior and used the ARN in the template.yml file. We have 2 YAML files for creating the required resources, one for Development and another for Production.

Following resources are being created using the templates Development-stack.yml and Production-stack.yml

  • Development cluster
  • ECR Repository
  • Target groups for each environment
  • Load balancer security group
  • Listeners and ALB
  • Build projects which is used in build stage of the codepipeline.

Run these 2 stacks in the Cloudformation. For this you can refer https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-console-create-stack.html

  1. Development-stack.yml
    In this template, we are adding resources for dev, intg and staging
AWSTemplateFormatVersion: 2010-09-09
Transform:
- AWS::CodeStar
Conditions:
UseSubnet: !Not [!Equals [!Ref 'SubnetId', subnet-none]]
Parameters:
VpcId:
Type: String
Description: The ID of the Amazon Virtual Private Cloud (VPC) used for the new Amazon EC2 Linux instances.
Default: vpc-<id>
SubnetId:
Type: AWS::EC2::Subnet::Id
Description: The name of the VPC subnet used for the new Amazon EC2 Linux instances launched for this project.
Default: subnet-<id1>
SubnetB:
Type: AWS::EC2::Subnet::Id
Default: subnet-<id2>
DevPort:
Type: Number
Default: 3000
IntgPort:
Type: Number
Default: 3001
StagingPort:
Type: Number
Default: 3002
ProjectId:
Type: String
Default: poc-spring-app
HealthCheckIntervalSeconds:
Type: Number
Default: 30
ServiceName:
Type: String
Default: codestar
LoadBalancerPortHTTP:
Type: Number
Default: 80
LoadBalancerPortHTTPS:
Type: Number
Default: 443
HealthCheckPath:
Type: String
Default: /health
CertificateARN:
Type: String
Description: The ARN of the Amazon Certificate Manager
Default: arn:aws:acm:us-west-2:<id>:certificate/eea725ed-77c6-47b8-bac8-730b3d29b39e
ServiceRole:
Type: String
Description: The ARN of the AWS IAM role that enables AWS CodeBuild to interact with dependent AWS services
Default: arn:aws:iam::<id>:role/CodeStarWorker-poc-spring-app-ToolChain
ArtifactEncryptionKey:
Type: String
Description: Artifact encryption key
# This value is present in codebuild project and used the same value present in codebuild project created by Codestar
Default: arn:aws:kms:us-west-2:<id>:alias/aws/s3
BuildEnvironmentComputeType:
Type: String
AllowedValues:
- BUILD_GENERAL1_SMALL
- BUILD_GENERAL1_MEDIUM
- BUILD_GENERAL1_LARGE
Default: BUILD_GENERAL1_MEDIUM
BuildEnvironmentImage:
Type: String
Default: aws/codebuild/amazonlinux2-x86_64-standard:3.0
S3Bucket:
Type: String
Default: aws-codestar-us-west-2-<id>-poc-spring-app-pipe
AccountId:
Type: String
Default: <id>
BuildSpecFilePath:
Type: String
Default: config/buildspec
Resources:
Cluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Join ['-', [!Ref ServiceName, 'development']]

DevRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Join ['-', [!Ref ProjectId, 'dev', 'repo']]
ImageScanningConfiguration:
ScanOnPush: "true"

IntgRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Join ['-', [!Ref ProjectId, 'integration', 'repo']]
ImageScanningConfiguration:
ScanOnPush: "true"
StagingRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Join ['-', [!Ref ProjectId, 'staging', 'repo']]
ImageScanningConfiguration:
ScanOnPush: "true"
ProdRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Join ['-', [!Ref ProjectId, 'production', 'repo']]
ImageScanningConfiguration:
ScanOnPush: "true"

TargetGroupDev:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: !Ref HealthCheckIntervalSeconds
# will look for a 200 status code by default unless specified otherwise
HealthCheckPath: !Ref HealthCheckPath
HealthCheckTimeoutSeconds: 5
UnhealthyThresholdCount: 2
HealthyThresholdCount: 2
Name: !Join ['-', [!Ref ServiceName, tg, 'dev']]
Port: !Ref DevPort
Protocol: HTTP
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 60 # default is 300
TargetType: ip
VpcId: !Ref VpcId
TargetGroupIntg:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: !Ref HealthCheckIntervalSeconds
# will look for a 200 status code by default unless specified otherwise
HealthCheckPath: !Ref HealthCheckPath
HealthCheckTimeoutSeconds: 5
UnhealthyThresholdCount: 2
HealthyThresholdCount: 2
Name: !Join ['-', [!Ref ServiceName, tg, 'integration']]
Port: !Ref IntgPort
Protocol: HTTP
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 60 # default is 300
TargetType: ip
VpcId: !Ref VpcId
TargetGroupStaging:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: !Ref HealthCheckIntervalSeconds
# will look for a 200 status code by default unless specified otherwise
HealthCheckPath: !Ref HealthCheckPath
HealthCheckTimeoutSeconds: 5
UnhealthyThresholdCount: 2
HealthyThresholdCount: 2
Name: !Join ['-', [!Ref ServiceName, tg, 'staging']]
Port: !Ref StagingPort
Protocol: HTTP
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 60 # default is 300
TargetType: ip
VpcId: !Ref VpcId

LoadBalancerSecurityGroup:
Type: AWS::EC2::SecurityGroup
# This security group is w.r.t Development(Dev,Integration) ALB
Properties:
GroupName: !Join ['-', [!Ref ServiceName, alb, sg, development]]
GroupDescription: !Join [' ', [!Ref ServiceName, alb, sg, development]]
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: !Ref LoadBalancerPortHTTP
ToPort: !Ref LoadBalancerPortHTTP
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: !Ref LoadBalancerPortHTTP
ToPort: !Ref LoadBalancerPortHTTP
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: !Ref LoadBalancerPortHTTPS
ToPort: !Ref LoadBalancerPortHTTPS
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: !Ref LoadBalancerPortHTTPS
ToPort: !Ref LoadBalancerPortHTTPS
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: !Ref DevPort
ToPort: !Ref DevPort
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: !Ref DevPort
ToPort: !Ref DevPort
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: !Ref IntgPort
ToPort: !Ref IntgPort
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: !Ref IntgPort
ToPort: !Ref IntgPort
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: !Ref StagingPort
ToPort: !Ref StagingPort
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: !Ref StagingPort
ToPort: !Ref StagingPort
CidrIpv6: ::/0

ListenerHTTP:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: redirect
RedirectConfig:
Protocol: HTTPS
Port: 443
Host: "#{host}"
Path: "/#{path}"
Query: "#{query}"
StatusCode: "HTTP_301"
LoadBalancerArn: !Ref LoadBalancer
Port: !Ref LoadBalancerPortHTTP
Protocol: HTTP
ListenerHTTPS:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroupDev
Type: forward
Certificates:
- CertificateArn: !Ref CertificateARN
LoadBalancerArn: !Ref LoadBalancer
Port: !Ref LoadBalancerPortHTTPS
Protocol: HTTPS
ListenerRule1:
Type: 'AWS::ElasticLoadBalancingV2::ListenerRule'
Properties:
Actions:
- Type: forward
TargetGroupArn: !Ref TargetGroupDev
Conditions:
- Field: path-pattern
PathPatternConfig:
Values:
- "*/dev/*"
ListenerArn: !Ref ListenerHTTPS
Priority: 1
ListenerRule2:
Type: 'AWS::ElasticLoadBalancingV2::ListenerRule'
Properties:
Actions:
- Type: forward
TargetGroupArn: !Ref TargetGroupIntg
Conditions:
- Field: path-pattern
PathPatternConfig:
Values:
- "*/intg/*"
ListenerArn: !Ref ListenerHTTPS
Priority: 2
ListenerRule3:
Type: 'AWS::ElasticLoadBalancingV2::ListenerRule'
Properties:
Actions:
- Type: forward
TargetGroupArn: !Ref TargetGroupStaging
Conditions:
- Field: path-pattern
PathPatternConfig:
Values:
- "*/staging/*"
ListenerArn: !Ref ListenerHTTPS
Priority: 3

LoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
LoadBalancerAttributes:
- Key: idle_timeout.timeout_seconds
Value: 60
Name: !Join ['-', [!Ref ServiceName, alb, development]]
Scheme: internet-facing
SecurityGroups:
- !Ref LoadBalancerSecurityGroup
Subnets:
- !Ref SubnetId
- !Ref SubnetB

#Creating Dev Build project
DevBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: !Join ['-', [!Ref ProjectId, 'dev']]
Description: AWS CodeStar created CodeBuild Project for the project
ServiceRole: !Ref ServiceRole
ConcurrentBuildLimit: 1
Artifacts:
Type: CODEPIPELINE
Packaging: ZIP
EncryptionKey: !Ref ArtifactEncryptionKey
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: !Ref BuildEnvironmentImage
ImagePullCredentialsType: CODEBUILD
#PrivilegedMode is required to pull the images from ECR
PrivilegedMode: true
EnvironmentVariables:
- Name: S3_BUCKET
Type: PLAINTEXT
Value: !Ref S3Bucket
- Name: WEBSITE_S3_PREFIX
Value: NoVal
Type: PLAINTEXT
- Name: WEBSITE_S3_BUCKET
Value: NoVal
Type: PLAINTEXT
- Name: PROJECT_ID
Value: !Ref ProjectId
Type: PLAINTEXT
- Name: ACCOUNT_ID
Value: !Ref AccountId
Type: PLAINTEXT
- Name: PARTITION
Value: aws
Type: PLAINTEXT
- Name: DOCKER_REPOSITORY_URI
Value: !Select [0, !Split [ '/', !GetAtt DevRepository.RepositoryUri]]
Type: PLAINTEXT
- Name: REPOSITORY_NAME
Value: !Join ['-', [!Ref ProjectId, 'dev', 'repo']]
Type: PLAINTEXT
Source:
Type: CODEPIPELINE
#Don't specify BuildSpec key if the file is named as buildspec.yml in the source code root directory
BuildSpec: !Join ['-', [!Ref BuildSpecFilePath, 'dev.yml']]
TimeoutInMinutes: 60
Tags:
- Key: awscodestar:projectArn
Value: arn:aws:codestar:us-west-2:755242612616:project/poc-spring-app

#Creating Integration Build project
IntgBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: !Join ['-', [!Ref ProjectId, 'integration']]
Description: AWS CodeStar created CodeBuild Project for the project
ServiceRole: !Ref ServiceRole
ConcurrentBuildLimit: 1
Artifacts:
Type: CODEPIPELINE
Packaging: ZIP
EncryptionKey: !Ref ArtifactEncryptionKey
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: !Ref BuildEnvironmentImage
ImagePullCredentialsType: CODEBUILD
#PrivilegedMode is required to pull the images from ECR
PrivilegedMode: true
EnvironmentVariables:
- Name: S3_BUCKET
Type: PLAINTEXT
Value: !Ref S3Bucket
- Name: WEBSITE_S3_PREFIX
Value: NoVal
Type: PLAINTEXT
- Name: WEBSITE_S3_BUCKET
Value: NoVal
Type: PLAINTEXT
- Name: PROJECT_ID
Value: !Ref ProjectId
Type: PLAINTEXT
- Name: ACCOUNT_ID
Value: !Ref AccountId
Type: PLAINTEXT
- Name: PARTITION
Value: aws
Type: PLAINTEXT
- Name: DOCKER_REPOSITORY_URI
Value: !Select [0, !Split [ '/', !GetAtt IntgRepository.RepositoryUri]]
Type: PLAINTEXT
- Name: REPOSITORY_NAME
Value: !Join ['-', [!Ref ProjectId, 'integration', 'repo']]
Type: PLAINTEXT
Source:
Type: CODEPIPELINE
#Don't specify BuildSpec key if the file is named as buildspec.yml in the source code root directory
BuildSpec: !Join ['-', [!Ref BuildSpecFilePath, 'integration.yml']]
TimeoutInMinutes: 60
Tags:
- Key: awscodestar:projectArn
Value: arn:aws:codestar:us-west-2:755242612616:project/poc-spring-app
#Creating Staging Build project
StagingBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: !Join ['-', [!Ref ProjectId, 'staging']]
Description: AWS CodeStar created CodeBuild Project for the project
ServiceRole: !Ref ServiceRole
ConcurrentBuildLimit: 1
Artifacts:
Type: CODEPIPELINE
Packaging: ZIP
EncryptionKey: !Ref ArtifactEncryptionKey
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: !Ref BuildEnvironmentImage
ImagePullCredentialsType: CODEBUILD
#PrivilegedMode is required to pull the images from ECR
PrivilegedMode: true
EnvironmentVariables:
- Name: S3_BUCKET
Type: PLAINTEXT
Value: !Ref S3Bucket
- Name: WEBSITE_S3_PREFIX
Value: NoVal
Type: PLAINTEXT
- Name: WEBSITE_S3_BUCKET
Value: NoVal
Type: PLAINTEXT
- Name: PROJECT_ID
Value: !Ref ProjectId
Type: PLAINTEXT
- Name: ACCOUNT_ID
Value: !Ref AccountId
Type: PLAINTEXT
- Name: PARTITION
Value: aws
Type: PLAINTEXT
- Name: DOCKER_REPOSITORY_URI
Value: !Select [0, !Split [ '/', !GetAtt StagingRepository.RepositoryUri]]
Type: PLAINTEXT
- Name: REPOSITORY_NAME
Value: !Join ['-', [!Ref ProjectId, 'staging', 'repo']]
Type: PLAINTEXT
Source:
Type: CODEPIPELINE
#Don't specify BuildSpec key if the file is named as buildspec.yml in the source code root directory
BuildSpec: !Join ['-', [!Ref BuildSpecFilePath, 'staging.yml']]
TimeoutInMinutes: 60
Tags:
- Key: awscodestar:projectArn
Value: arn:aws:codestar:us-west-2:755242612616:project/poc-spring-app
  • Production-stack.yml
AWSTemplateFormatVersion: 2010-09-09
Transform:
- AWS::CodeStar
Conditions:
UseSubnet: !Not [!Equals [!Ref 'SubnetId', subnet-none]]
Parameters:
VpcId:
Type: String
Description: The ID of the Amazon Virtual Private Cloud (VPC) used for the new Amazon EC2 Linux instances.
Default: vpc-<id>
SubnetId:
Type: AWS::EC2::Subnet::Id
Description: The name of the VPC subnet used for the new Amazon EC2 Linux instances launched for this project.
Default: subnet-<id1>
SubnetB:
Type: AWS::EC2::Subnet::Id
Default: subnet-<id2>
ProdPort:
Type: Number
Default: 3003
ProjectId:
Type: String
Default: poc-spring-app
HealthCheckIntervalSeconds:
Type: Number
Default: 30
ServiceName:
Type: String
Default: codestar
LoadBalancerPortHTTP:
Type: Number
Default: 80
LoadBalancerPortHTTPS:
Type: Number
Default: 443
HealthCheckPath:
Type: String
Default: /health
CertificateARN:
Type: String
Description: The ARN of the Amazon Certificate Manager
Default: arn:aws:acm:us-west-2:<id>:certificate/eea725ed-77c6-47b8-bac8-730b3d29b39e
Resources:
TargetGroupProd:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: !Ref HealthCheckIntervalSeconds
# will look for a 200 status code by default unless specified otherwise
HealthCheckPath: !Ref HealthCheckPath
HealthCheckTimeoutSeconds: 5
UnhealthyThresholdCount: 2
HealthyThresholdCount: 2
Name: !Join ['-', [!Ref ServiceName, tg, production]]
Port: !Ref ProdPort
Protocol: HTTP
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 60 # default is 300
TargetType: ip
VpcId: !Ref VpcId
LoadBalancerSecurityGroupProd:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Join ['-', [!Ref ServiceName, alb, sg, production]]
GroupDescription: !Join [' ', [!Ref ServiceName, alb, sg, production]]
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: !Ref LoadBalancerPortHTTP
ToPort: !Ref LoadBalancerPortHTTP
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: !Ref LoadBalancerPortHTTP
ToPort: !Ref LoadBalancerPortHTTP
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: !Ref LoadBalancerPortHTTPS
ToPort: !Ref LoadBalancerPortHTTPS
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: !Ref LoadBalancerPortHTTPS
ToPort: !Ref LoadBalancerPortHTTPS
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: !Ref ProdPort
ToPort: !Ref ProdPort
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: !Ref ProdPort
ToPort: !Ref ProdPort
CidrIpv6: ::/0
ListenerHTTPProd:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: redirect
RedirectConfig:
Protocol: HTTPS
Port: 443
Host: "#{host}"
Path: "/#{path}"
Query: "#{query}"
StatusCode: "HTTP_301"
LoadBalancerArn: !Ref LoadBalancerProd
Port: !Ref LoadBalancerPortHTTP
Protocol: HTTP
ListenerHTTPSProd:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroupProd
Type: forward
Certificates:
- CertificateArn: !Ref CertificateARN
LoadBalancerArn: !Ref LoadBalancerProd
Port: !Ref LoadBalancerPortHTTPS
Protocol: HTTPS
LoadBalancerProd:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
LoadBalancerAttributes:
- Key: idle_timeout.timeout_seconds
Value: 60
Name: !Join ['-', [!Ref ServiceName, alb, production]]
Scheme: internet-facing
SecurityGroups:
- !Ref LoadBalancerSecurityGroupProd
Subnets:
- !Ref SubnetId
- !Ref SubnetB
  • Run the above 2 templates in Cloudformation cli
  • Store buildspec-dev.yml file in the config folder at the root level. Add Dockerfile.dev file at the root level. So the buildspec path will be config/buildspec-dev.yml

CodePipeline changes

  • Open the codepipeline and click on clone pipeline. Update project name and service role and click on clone which will automatically trigger the build. Stop the execution and let’s do necessary changes in the pipeline. We have removed the permission boundary for the build role CodeStarWorker-<project-Id>-ToolChain
  • Edit the source stage branch name and output artifact. Click on Edit → Edit source stage
  • Edit the build stage. Update Input artifacts, build project name and build output artifact.
  • Edit deploy stage. Open GenerateChangeSet action group. Here we are going to create new stack in the cloudformation for the given branch.

Output artifact of build stage is used as input artifacts in deploy stage.
Update the stack name as awscodestar-<project-id>-infrastructure-<branch-name>
Change set name : pipeline-changeset

  • Override the parameter values specific to each branch as we have seen in the Deploy stage changes section.
  • Update the ExecutionChangeSet action group. Update the stack name and change set name which is same as GenerateChangeSet action group.
  • Save the changes and release the pipeline. This will create resources specific to each branch.

🥳Hurray!! We have created CI/CD pipeline for each environment and common ALB using codestar.

Conclusion

In this article, we have seen how we can setup CI/CD pipeline for different branches and having common application load balancer for development environments.

--

--