Set up AWS CodePipeline to deploy react application using CloudFormation

Share

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.

 Cloudformation

  • 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.

 Cloudformation

 Cloudformation

 Cloudformation

  • 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.

Leave a Reply

Your email address will not be published.

*