Amazon CloudFormation is a service provided by AWS cloud that aids in the modeling and configuration of your AWS resources so that you may spend more time concentrating on your AWS-based applications and less time managing those resources. The AWS resources you require (such as Amazon EC2 instances or Amazon RDS DB instances) are listed in a template that you build, and CloudFormation handles the provisioning and configuration of those resources on your behalf. CloudFormation takes care of the creation, configuration, and identification of the dependencies between AWS resources, so you don’t have to.
In this blog, we will use CloudFormation to create an AWS CodePipeline to deploy a react application to an EC2 instance.
Prerequisites:
- An S3 bucket to store the build artifacts.
- A CodeCommit repository to store the react code.
- A key-pair file for SSH to EC2 instance.
Step1: Launch an EC2 instance using Cloudformation template
- Use the following CloudFormation template for launching the EC2 instance.
- Here we are using Amazon Linux AMI in the Ohio region.
- EC2 instance requires access to the S3 bucket so we will attach a required IAM policy to it so that it can access it.
- We will also need to install the CodeDeploy agent in the EC2 instance. We have used User data to install it.
AWSTemplateFormatVersion: 2010-09-09 Description: EC2 Instance with access to S3 buckets Parameters: KeyName: Type: String Default: ohio-keypair myInstanceType: Type: String Default: t2.micro myAMI: Type: String Default: ami-0f924dc71d44d23e2 Resources: EC2InstanceProfileRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: 'sts:AssumeRole' Path: / Policies: - PolicyName: EC2InstanceProfilePolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 's3:Get*' - 's3:List*' Resource: '*' MyInstanceProfile: Type: 'AWS::IAM::InstanceProfile' Properties: Path: / Roles: - !Ref EC2InstanceProfileRole MySecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: EC2 CF security group SecurityGroupIngress: - IpProtocol: tcp FromPort: '22' ToPort: '22' CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: '80' ToPort: '80' CidrIp: 0.0.0.0/0 MyEC2Instance: Type: AWS::EC2::Instance Properties: ImageId: !Ref myAMI InstanceType: !Ref myInstanceType KeyName: !Ref KeyName Tags: - Key: 'Name' Value: 'chkmt' SecurityGroups: - !Ref MySecurityGroup IamInstanceProfile: !Ref MyInstanceProfile UserData: Fn::Base64: | #!/bin/bash sudo yum -y update sudo yum -y install ruby sudo yum -y install wget sudo amazon-linux-extras install nginx1 curl -sL https://rpm.nodesource.com/setup_16.x | sudo bash - sudo yum install -y nodejs cd /home/ec2-user wget https://aws-codedeploy-us-east-2.s3.amazonaws.com/latest/install chmod +x ./install sudo ./install auto
- Go to the CloudFormation console and upload this file to create the stack.
Step2: Push code to CodeCommit repository
- Go to your project folder.
- Create a “buildspec.yaml” file and paste the following code there.
# Do not change version. This is the version of aws buildspec, not the version of your buldspec file. version: 0.2 phases: pre_build: commands: #installs dependencies into the node_modules/ directory - npm install build: commands: - echo Build started on `date` - echo Compiling - npm run build post_build: commands: - echo Build completed on `date` # Include only the files required for your application to run. artifacts: files: - build/**/* - appspec.yml - scripts/before_install.sh - scripts/start_server.sh
- Next, create a file with name “appspec.yml” and paste the following code there.
version: 0.0
os: linux
files:
- source: build/
destination: /usr/share/nginx/html/
hooks:
BeforeInstall:
- location: scripts/before_install.sh
timeout: 300
runas: root
ApplicationStart:
- location: scripts/start_server.sh
timeout: 300
runas: root
- Next, create a scripts folder in which we will create script files.
- Create a script file ‘before_install.sh’ file and paste the following there.
#!/bin/bash rm -rf /usr/share/nginx/html/*
- Now create ‘start_server.sh‘ file and paste following there.
#!/bin/bash systemctl restart nginx
- Commit the project to the CodeCommit repository.
Step3: Create the CodePipeline using Cloudformation template
- Use the following CloudFormation template the create the CodePipeline.
AWSTemplateFormatVersion: 2010-09-09
Description: Create a CodePipeline to deploy a rest application.
Parameters:
ReactRepository:
Type: String
Description: React Application Repository.
Default: cf-node
ArtifactS3Bucket:
Type: String
Description: S3 bucket to store artifacts.
Default: reactapp-cicd
Resources:
CodeBuildRole:
Type: "AWS::IAM::Role"
Properties:
RoleName:
Fn::Sub: CodeBuildRole-${AWS::StackName}
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "codebuild.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: /service-role/
Policies:
- PolicyName: "CodeBuildCICDAccessPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "codecommit:GitPull"
Resource:
- Fn::Sub: arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${ReactRepository}
- Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource:
- Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/*
- Effect: "Allow"
Action:
- "s3:PutObject"
- "s3:GetObject"
- "s3:GetObjectVersion"
- "s3:ListBucket"
Resource:
- Fn::Sub: arn:aws:s3:::codepipeline-${AWS::Region}-*
- Fn::Sub: arn:aws:s3:::${ArtifactS3Bucket}/*
- Fn::Sub: arn:aws:s3:::${ArtifactS3Bucket}
BuildReactAppCFNProject:
Type: AWS::CodeBuild::Project
Properties:
Name: !Sub BuildReactAppCFN-${AWS::StackName}
ServiceRole: !GetAtt [ CodeBuildRole, Arn ]
Artifacts:
Type: S3
Location: !Ref ArtifactS3Bucket
Name: !Sub BuildReactAppCFN-${AWS::StackName}
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:2.0
Source:
Location: !Sub https://git-codecommit.us-east-2.amazonaws.com/v1/repos/${ReactRepository}
Type: CODECOMMIT
TimeoutInMinutes: 15
Tags:
- Key: Name
Value: !Sub BuildReactAppCFN-${AWS::StackName}
CodeDeployServiceRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName:
Fn::Sub: CodeDeployServiceRole-${AWS::StackName}
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- codedeploy.amazonaws.com
Action: 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: "CodeDeployCICDAccessPolicy"
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'ec2:DescribeInstances'
- 'ec2:DescribeInstanceStatus'
- 'ec2:TerminateInstances'
- 'tag:GetTags'
- 'tag:GetResources'
Resource: '*'
CodeDeployApplication:
Type: AWS::CodeDeploy::Application
Properties:
ApplicationName: !Sub ReactApp-${AWS::StackName}
ComputePlatform: Server
CodeDeployDeploymentGroup:
Type: AWS::CodeDeploy::DeploymentGroup
Properties:
ApplicationName: !Ref CodeDeployApplication
Ec2TagFilters:
- Key: Name
Value: chkmt
Type: "KEY_AND_VALUE"
ServiceRoleArn: !GetAtt [ CodeDeployServiceRole, Arn ]
DeploymentGroupName: !Sub DeploymentGroup-${AWS::StackName}
CodePipelineRole:
Type: "AWS::IAM::Role"
Properties:
RoleName:
Fn::Sub: CodePipelineRole-${AWS::StackName}
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "codepipeline.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: /
Policies:
- PolicyName: "CodePipelineCICDAccessPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action:
- "s3:DeleteObject"
- "s3:GetObject"
- "s3:GetObjectVersion"
- "s3:ListBucket"
- "s3:PutObject"
- "s3:GetBucketPolicy"
Resource:
- Fn::Sub: arn:aws:s3:::${ArtifactS3Bucket}
- Fn::Sub: arn:aws:s3:::${ArtifactS3Bucket}/*
- Effect: "Allow"
Action:
- "sns:Publish"
Resource: '*'
- Effect: "Allow"
Action:
- "codecommit:ListBranches"
- "codecommit:ListRepositories"
- "codecommit:BatchGetRepositories"
- "codecommit:Get*"
- "codecommit:GitPull"
- "codecommit:UploadArchive"
Resource:
- Fn::Sub: arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${ReactRepository}
- Effect: "Allow"
Action:
- "codebuild:StartBuild"
- "codebuild:BatchGetBuilds"
Resource:
- Fn::Sub: arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/BuildReactAppCFN-${AWS::StackName}
- Fn::Sub: arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:build/BuildReactAppCFN-${AWS::StackName}:*
- Effect: Allow
Action:
- 'codedeploy:CreateDeployment'
- 'codedeploy:GetApplicationRevision'
- 'codedeploy:GetDeployment'
- 'codedeploy:GetDeploymentConfig'
- 'codedeploy:RegisterApplicationRevision'
Resource: '*'
DeployPipeline:
Type: "AWS::CodePipeline::Pipeline"
Properties:
Name: !Sub CICDPipe-${AWS::StackName}
RoleArn: !GetAtt [ CodePipelineRole, Arn ]
Stages:
- Name: Source
Actions:
- Name: ApplicationSource
ActionTypeId:
Category: Source
Owner: AWS
Version: 1
Provider: CodeCommit
OutputArtifacts:
- Name: ApplicationOutput
Configuration:
BranchName: master
RepositoryName:
Ref: ReactRepository
RunOrder: 1
- Name: Build
Actions:
- Name: CodeBuild
InputArtifacts:
- Name: ApplicationOutput
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
OutputArtifacts:
- Name: ReactAppArtifact
Configuration:
ProjectName: !Ref BuildReactAppCFNProject
RunOrder: 1
- Name: DeployToEC2
Actions:
- Name: CodeDeploy
InputArtifacts:
- Name: ReactAppArtifact
ActionTypeId:
Category: Deploy
Owner: AWS
Version: 1
Provider: CodeDeploy
Configuration:
ApplicationName: !Ref CodeDeployApplication
DeploymentGroupName: !Ref CodeDeployDeploymentGroup
RunOrder: 1
ArtifactStore:
Type: S3
Location: !Ref ArtifactS3Bucket
- Go to CodePipeline console. You will see that your application is being deployed.
- Now you can access your application using the Public IP of your EC2 instance.
Author Details:
This blog is written by Checkmate Global Technologies, Cloud Engineer. Please contact our technical consultants if you have anything related to cloud infrastructure to be discussed.