Cloudformation Template for AWS Codedeploy with Autoscaling.

Ankan-Devops
codelogicx
Published in
9 min readSep 14, 2022

BUILDING CLOUDFORMATION STACK

AWS Cloudformation Template

The Cloudformation template deploys the following resources :

  1. Network
    a. New VPC with internet gateway attached
    b. 2 public Subnet
    c. Internet Gateway
    d. Route table attached with Internet Gateway for Public subnets
  2. Security Groups
    a. Application LoadBalancer Sg
    b. Sg for Autoscaling group EC2 instances
  3. Roles
    a. EC2 instance profile for S3 access
    b. Codedeploy service role
  4. Autoscaling
    a. Launch template
    b. Autoscaling Group
  5. Application LoadBalancing for Autoscaling Group
    a. Target group attached to ASG
    b. Application LoadBalancer
    c. Listener for attaching Loadbalancer to Target group
  6. S3 Bucket for uploading source code
  7. Codedeploy
    a. Codedeploy Application
    b. Deployment Group for the Codedeploy app.
Architecture for the CF template

Things needed before creating the Cloudformation stack:

  1. Key pair
  2. AWS SSL Certificate from ACM (optional: for HTTPS through Loadbalancer)

Steps to create Stack in AWS :

  1. Go to CloudFormation in AWS and click ‘Create Stack’.
  2. Select ‘Upload a template file’ and choose the yaml file given below.

3. In the next step give a relevant stack name and enter the parameter* values.

4. In the next step give a relevant tag Name: {value} as this will be replicated on all the resources created through Cloudformation.

5. Create the stack.

Parameters

Cloudformation parameters are user defined values that can be given in cloudformation template.

In this template we used the following parameters :

  1. AMI — This is the image to be used for creating EC2 instance. The default value given is for Ubuntu image in US-EAST-1 region. Please use the relevant AMI ID for any different region.
  2. ASGCapacity — The Autoscaling group Minimum, Desired and Max capacity for EC2 instance.
  3. CodeDeployName — The Codedeploy Application & Deployment group name. (both are kept the same for simplicity)
  4. HealthCheckPath — The Health check path for ALB.
  5. InstanceTypeParameter — The EC2 instance type. Default given here is t3.micro.
  6. Key — Key pair to be used for the Autoscaling instance. Key pair should be created before deploying the template.
  7. S3BucketName — The S3 bucket to be created for uploading the source code zip file.
  8. SSHIP — The remote IP address for SSH access to EC2 instance.

Cloudformation template to create the Infrastructure in YAML:
*(also can be written in json)

#Parameters are user defined values needed to create AWS resources
Parameters:
InstanceTypeParameter:
Type: String
Default: t3.micro
Description: Enter instance size. Default is t3.micro.
AMI:
Type: AWS::EC2::Image::Id
Default: ami-08d4ac5b634553e16 #Replace this AMI ID with the #ubuntu AMI used in that particular region stack is deployed
Description: The linux AMI to use.
Key:
Type: AWS::EC2::KeyPair::KeyName
Description: The key used to access the instance.
SSH-IP:
Type: String
Description: IP for remote SSH.
S3BucketName:
Type: String
Description: S3 Bucket name for source code.
CodeDeployName:
Type: String
Description: Codedeploy Application and Deployment Group name.
ASGCapacity:
Type: List<Number>
Description: Min, Desired & Max capacity of Autoscaling Group seperated with comma.
HealthCheckPath:
Type: String
Description: Health check path for ALB.
#Resources to be created on AWS
Resources:
#Network resources to be created
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16 #Custom defined VPC CIDR block as per #user need
EnableDnsSupport: true
EnableDnsHostnames: true
InstanceTenancy: default
Tags:
- Key: Name
Value: CodeDeploy VPC
InternetGateway:
Type: AWS::EC2::InternetGateway
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC #Ref is used to get the resource ID created #before or the parameter value defined
InternetGatewayId: !Ref InternetGateway
SubnetA:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select [ 0, !GetAZs '' ] #GetAZs returns #the list of Availability zone in the Region
VpcId: !Ref VPC
CidrBlock: 10.0.0.0/24 #Custom Subnet CIDR block
MapPublicIpOnLaunch: true
SubnetB:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select [ 1, !GetAZs '' ]
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
InternetRoute:
Type: AWS::EC2::Route
DependsOn: InternetGateway
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
RouteTableId: !Ref RouteTable
SubnetARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTable
SubnetId: !Ref SubnetA
SubnetBRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTable
SubnetId: !Ref SubnetB
#Security Group to be created for ALB and Autoscaling instances
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: "ALB Security Group"
GroupDescription: "ALB Security Group"
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: '0.0.0.0/0'
- IpProtocol: tcp
FromPort: '443'
ToPort: '443'
CidrIp: '0.0.0.0/0'
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: "EC2 Security Group"
GroupDescription: "EC2 Security Group"
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '80'
ToPort: '80'
SourceSecurityGroupId: !GetAtt ALBSecurityGroup.GroupId
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: !Sub ${SSHIP}/32 #Sub is used to retrieve the #paramter value and do string concatenation
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
#IAM Roles to be created for S3 full access of the EC2 instances & #Codedeploy Service rules.
s3Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonS3FullAccess
RoleName: EC2S3Access

cdRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- codedeploy.amazonaws.com
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole
Policies:
- PolicyName: codedeploy-for-launch-template
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- iam:PassRole
- ec2:CreateTags
- ec2:RunInstances
Resource: '*'
RoleName: CodeDeployRole

#Attaching the S3 IAM Role created to the Autoscaling Instances
ServerProfile:
Type: 'AWS::IAM::InstanceProfile'
Properties:
InstanceProfileName: cdInstanceProfile
Roles:
- !Ref s3Role
#Target Group for attaching ALB to ASG
cdTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckPath: !Ref HealthCheckPath
HealthCheckProtocol: HTTP
Name: CodeDeployTargetGroup
Port: 80
Protocol: HTTP
TargetType: "instance"
Matcher:
HttpCode: "200"
VpcId: !Ref VPC
#Application LoadBalancer
cdALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: cdtest
SecurityGroups:
- !Ref ALBSecurityGroup
Subnets:
- !Ref SubnetA
- !Ref SubnetB

#ALB Listener to forward HTTP traffic to the target group
HTTPListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref cdALB
Port: 80
Protocol: "HTTP"
DefaultActions:
- Type: forward
TargetGroupArn: !Ref cdTargetGroup
#Launch template for the Autoscaling group
ServerLaunchTemplate:
Type: 'AWS::EC2::LaunchTemplate'
Properties:
LaunchTemplateName: CodeDeployASGLaunchTemplate
LaunchTemplateData:
InstanceType: !Ref InstanceTypeParameter
SecurityGroupIds:
- !Ref InstanceSecurityGroup
IamInstanceProfile:
Name: !Ref ServerProfile
ImageId: !Ref AMI
KeyName: !Ref Key
UserData: #Script to install Codedeploy agent and enable it
Fn::Base64:
!Sub |
#!/bin/bash
sudo apt-get update -y
sudo apt-get install ruby-full wget -y
cd /home/ubuntu
wget https://aws-codedeploy-${AWS::Region}.s3.${AWS::Region}.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto > /tmp/logfile
sudo service codedeploy-agent start

#Autoscaling Group for Codedeploy
cdASG:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: CodeDeployASG
LaunchTemplate:
LaunchTemplateId: !Ref ServerLaunchTemplate
Version: !GetAtt ServerLaunchTemplate.LatestVersionNumber
MaxSize: !Select [ 2, !Ref ASGCapacity ]
MinSize: !Select [ 0, !Ref ASGCapacity ]
DesiredCapacity: !Select [ 1, !Ref ASGCapacity ]
HealthCheckGracePeriod: 300
TargetGroupARNs:
- !Ref cdTargetGroup
VPCZoneIdentifier:
- !Ref SubnetA
- !Ref SubnetB
#New S3 bucket to upload source code zip file for codedeploy
cdS3:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref S3BucketName

#Codedeploy Application
cdApp:
Type: AWS::CodeDeploy::Application
Properties:
ApplicationName: !Ref CodeDeployName
ComputePlatform: Server

#Codedeploy Deployment group
cdDG:
Type: AWS::CodeDeploy::DeploymentGroup
Properties:
ApplicationName: !Ref cdApp
AutoScalingGroups:
- !Ref cdASG
DeploymentGroupName: !Ref CodeDeployName
AutoRollbackConfiguration:
Enabled: true
Events:
- DEPLOYMENT_FAILURE
- DEPLOYMENT_STOP_ON_REQUEST
ServiceRoleArn: !GetAtt cdRole.Arn #GetAtt returns a #particular attribute of the resource created before

Adding SSL to the LoadBalancer:

  1. First we need to add a parameter to the template yaml to get the AWS SSL certificate ARN already created before.
Parameters:
SSLCertificateArn:
Type: String
Description: Existing AWS SSL Certificate Arn for the domain.

2. Next, we need to add a HTTPS Listener that needs to forward traffic to the target group.
— We also need to replace the HTTP listener with the one that redirects traffic to the HTTPS listener (port 80 to 443 redirect).

Replace the HTTPListener resource on the template with the segment below:

HTTPSListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref cdALB
Port: 443
Protocol: "HTTPS"
Certificates:
- CertificateArn: !Ref SSLCertificateArn
SslPolicy: "ELBSecurityPolicy-2016-08"
DefaultActions:
- Type: forward
TargetGroupArn: !Ref cdTargetGroup
HTTPListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref cdALB
Port: 80
Protocol: "HTTP"
DefaultActions:
- Type: "redirect"
RedirectConfig:
Protocol: "HTTPS"
Port: 443
Host: "#{host}"
Path: "/#{path}"
Query: "#{query}"
StatusCode: "HTTP_301"

The complete Cloudformation template can be downloaded from the link below:
https://1drv.ms/u/s!Amstsgdh3fTwgQMKaRU3ssfr_gEt?e=5nd5B9

DEPLOYING APPLICATION REVISION TO CODEDEPLOY USING GITHUB-ACTION

In this part we will discuss how to deploy App revisions to Codedeploy through github actions.

Adding Appspec.yaml with Appspec hooks to source code

Firstly, the source code in github should contain Appspec.yaml along with Appspec ‘hooks’ if needed.

N.B :-
-
The Appspec.yaml should be in the base path of the source code files.
-
The ‘source’ path in the files section of Appspec.yaml is relative to the path of Appspec.
-
The path of the appspec hook scripts should also be relative to the path of Appspec.yaml

The source code (‘index.html’ here) is in the same path as ‘appspec.yaml’ and the Appspec hooks are inside ‘aws/scripts/*’.

Configuring OpenID Connect in AWS for Github Action

Step 1 :
Creating an Identity provider with OpenID connect for connecting with Github.

Go to Security Credentials -> Identity Provider -> Add provider -> OpenID Connect

Security Credentials -> Identity Provider -> Add provider -> OpenID Connect

Add, Provider URL : https://token.actions.githubusercontent.com
Audience : sts.amazonaws.com

Get the Thumbprint and create the Provider.

Step 2 :
Assign a new role to the Identity Provider

Inside Identity Provider created, Click on a Assign Role -> Create a new Role

Select sts.amazonaws.com as audience and keep other options as default.

On Add Permissions, select the following policies:
- AmazonS3FullAccess
-
AWSCodeDeployFullAccess

The policies attached are displayed here, Note the Role name ( ‘cdgithubaction’ here) you are using.

Enter a role name & Create Role.

Step 3 :
Go to the Role created and in the ‘Trust relationship’, edit :

Replace the “condition”: json section to -

“Condition”: {
“StringLike”: {
“token.actions.githubusercontent.com:aud”: “sts.amazonaws.com”, “token.actions.githubusercontent.com:sub”: “repo:GitHubOrg/GitHubRepo:*"
}
}

  • The GitHubOrg refers to Github Organization or Profile name.
  • The GitHubReporefers to Github Repository name.
  • Example :
    GitHubOrg/GitHubRepo: ankan-devops/codedeploytest

Note the IAM Role ARN.

Setting up the Github Actions

Step 1 :

Creating Github Action secrets.
- Go to Repository settings -> Secrets (under security) -> Actions

Add the following secrets :

Click on ‘New Repository Secrets’ to add secrets.
  1. AWS_REGION : The AWS region where the cloudformation stack is created
  2. CDNAME : The Codedeploy Application name & Deployment group name given in the parameter while creating the Cloudformation stack (only one name is used for both, for simplicity)
  3. IAMROLE_GITHUB : The IAM Role ARN of OpenID Connect created before here.
  4. S3BUCKET : The S3 Bucket name given as parameter while creating the Cloudformation stack for source code (as zip) upload.

Step 2 :

Adding the github-action.yml in .github/workflows path.

The filename could be anything followed by .yml extension. To be created in .github/workflows/ path.

A sample github-action.yml is given below :

name: github-codedeploy

on:
push:
branches:
- master

jobs:
upload:
name: upload
runs-on: ubuntu-20.04
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v3
name: Checkout Repository
- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{ secrets.IAMROLE_GITHUB }}
role-session-name: GitHub-Action-codedeploy-Role
aws-region: ${{ secrets.AWS_REGION }}
- name: Upload Artifact to s3
run: aws deploy push --application-name test --source <Source-code path in github repo> --s3-location s3://${{ secrets.S3BUCKET }}/gitcodeploytest.zip

deploy:
needs: upload
runs-on: ubuntu-20.04
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v3
- uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{ secrets.IAMROLE_GITHUB }}
role-session-name: GitHub-Action-Role
aws-region: ${{ secrets.AWS_REGION }}
- run: aws deploy create-deployment --application-name ${{ secrets.CDNAME }} --deployment-group-name ${{ secrets.CDNAME }} --s3-location bucket=${{ secrets.S3BUCKET }},key=gitcodeploytest.zip,bundleType=zip --ignore-application-stop-failures --file-exists-behavior OVERWRITE
  • The <Source-code path in github repo> refers to the base path of source code and appspec.yaml.
  • The key=gitcodeploytest.zip refers to the zipped file name of the source code. This is the also the name of the object in the S3 bucket under which the source code is zipped.

A sample source code repo containing :

- github-action.yml in .github/workflows/ path
- Appspec.yaml and hook scripts (aws/scripts/ path)

is given below :
https://github.com/ankan-devops/codedeploytest.git

--

--