Challenge #4: Create CI/CD for Serverless Apps
Tags: aws, devops, cicd, serverless
Published: 2022-11-17 09:07:49

**This was presented as a lightning talk last Nov 17, 2022 in the 4th AWSUG F2F Meetup at the GCash Office, BGC**

In this post, we will create a basic CI/CD pipeline for the serverless application we built in the first challenge. For a quick recap, our serverless application has 3 API endpoints: for creating a loyalty card, seeing all loyalty cards, and displaying details about one loyalty card. The loyalty card data are stored in DynamoDB:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8m6n0j91n2y5v3mzip22.png)

With a CI/CD pipeline, we can deploy changes to our application in a consistent and automated way. 

To achieve that, we will create a CI/CD pipeline with AWS CodePipeline. Inside CodePipeline, we have 2 stages. One for Source and another for Build. By the end of this walkthrough, our changes will be deployed to production once it is merged to master:

- **SOURCE STAGE**: CodePipeline simply watches for any changes from the master branch in GitHub. Once a change has been pushed, our pipeline gets triggered and the code is copied for further processing
- **BUILD STAGE**: CodePipeline triggers CodeBuild and supplies it with a copy of the source code. CodeBuild is an automated build service. It relies on a file called "buildspec.yml" inside the repository. The file contains instructions on how to prepare and deploy our Serverless App. When CodeBuild is triggered, it executes these instructions.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phre4dbakk0b6lxiv2qh.png)


## Now, let's set up our CI/CD Pipeline

**[1]** Head over to CodePipeline by searching for it in the top search bar


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/grottd2oq2y5oxg08kwp.png)

Then, hit the "Create pipeline" button


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lnb26lvuu4606n40i1sb.png)

**[2]** Type the name of the pipeline and leave everything else as default. Press Next


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xngmczrareksgnq44gih.png)

**[3]**  In the Source stage, select the source provider that applies to you. For me, my code is stored in GitHub so I select the "Github (Version 2)" option.

Since my repository is outside AWS, I need to authorize CodePipeline to access it. Press "Connect to GitHub". You may have to do something similar if you select BitBucket. Aside from the two, CodePipeline supports CodeCommit as a source repository. However, Gitlab is not natively supported.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yo58r7wnabszyjlmxjvk.png)

A pop-up screen will appear. Create a name for your connection. Then, press "Connect to GitHub"

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rjogkp2grfofambsml4k.png)

If you are signed in to the GitHub account, you will see this pop-up screen in Github that requires your permission. Grant it by clicking "Authorize AWS Connector for GitHub"

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/td6jgu0jzrvuwmky7vq3.png)

**[4]** You will be redirected back to this screen. If you have done this before, you will see a pre-installed GitHub App. If there is none, hit "Install a new app". You will also have to click "Install a new app" if your GitHub repository is owned by another organization and your GitHub user was just invited to that organization.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ljyfvbls95lnnxquib8f.png)

After you have chosen the correct GitHub App, click "Connect"

**[5]** With that, the pop-up window closes and you are redirected back to the "Add source stage" screen. Choose your repository and branch name. Leave the rest as default and then, click "Next"

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rrb63ja5addiq90si4up.png)

**[6]** In the build provider, choose "AWS CodeBuild". If you haven't created your CodeBuild Project yet, hit "Create Project"

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ltwr6162z19fryxtw1ye.png)

Another pop-up screen appears for us to create our CodeBuild project. In this screen, add your project name:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a84tpkt6tyj8gybkw5gv.png)

Scroll a bit down for the environment section.

- Environment image: Managed image
- Operation System: Ubuntu
- Runtime: Standard
- Image: aws/codebuild/standard:5.0 (or whatever the latest version is)
- Image version: Always use the latest image for this runtime version
- Environment type: Linux
- Privilged: Enabled

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cc8tkctpnwoh4c5h1uxx.png)

In the previous step, we connected GitHub to our CI/CD pipeline. In this step, we created a CodeBuild project that runs a series of commands to build our application. These commands are listed under the buildspec.yml file. Here is our project's buildspec.yml file

In our project's buildspec.yml, we only used 2 phases: install and build. Phases allow us to group our build commands. For our install phase, we installed serverless framework and pip. Then, for the build phase, we ran `serverless deploy` to deploy our application.


```yml
version: 0.2

phases:
  install:
    runtime-versions:
      python: 3.8
    commands:
      - ls -a ~
      - npm install
      - npm install -g serverless
      - curl -O https://bootstrap.pypa.io/get-pip.py
      - ls -a ~
      - python3 get-pip.py --user
      - pip --version
  build:
    commands:
      - serverless deploy --region $AWS_REGION
```


Leave the rest as default and click "Continue to CodePipeline". This creates the CodeBuild project. You will then be redirected back to the CodePipeline screen, with a pre-filled form. Double-check the values. Once satisfied, click next:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3rrjub4xh5mc1jtz30h6.png)

**[7]** You will be redirected to the deploy stage. We don't need this for now. With the `serverless deploy` command in CodeBuild, we are deploying the application already. Click "Skip Deploy Stage". 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dtoafrid4l5mbbxkb6pe.png)

Then, we will be on the Review screen. Review the values and press "Create pipeline". Right after pipeline creation, it executes immediately.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gwrqh5ns1ku6y688303f.png)

The Source stage executes immediately and fetches code from GitHub. If this stage fails the first time, just click retry. Sometimes the first run malfunctions.

Once the source stage is done, the Build stage triggers. In this stage, CodeBuild executes the instructions coded inside your buildspec.yml. If you are curious as to what is happening while CodeBuild is executing, click the "details" and you will see a log of the deployment:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y7uvzz9ofnx8cty0g2oa.png)

It will take a few minutes before the build completes. Wait for a few minutes and come back to it. 

## Let's troubleshoot the deployment

**[8]** From the result of our first run, it looks like the execution failed.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n6cdp1t5n1e0gfqz872e.png)

To figure out what's wrong, scroll down and find the last few lines of the execution. You shall see the error there.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tl02etyte21awdllpmwu.png)

It looks like our Build execution failed when we tried to deploy our serverless app using the `serverless deploy` command. The error is the role that CodeBuild uses is "not authorized to perform" operations in CloudFormation. Since Serverless Frameworks apps are just CloudFormation deployments under the hood, we need to equip our CodeBuild with an IAM role that has permission to access CloudFormation and other needed resources.

**[9]** Let's update the permission of our CodeBuild role. First, let's go to the Build Project that powers our execution.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u8tzto71qa0yd85yabl7.png)

Then, under Edit, click Environment:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/20uzekzj00ju3wdfprgy.png)

You will see this screen. Copy the ARN of the Service role:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n5t0wd5sp22mfyryejrh.png)

Then, go to the AWS IAM console.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/przepa2qdz2vl0zmu4ya.png)

Click "Roles" on the left-hand side menu and search for the role

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3e6yq5zeq9xa9oo6hqgb.png)

Under Permissions, click Attach policies. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/auaprkgg88qgtpaxceel.png)

Search for the following AWS-managed policies and attach them to the role. In production, be more deliberate and granular about the permissions we grant to this role. For our exercise, this will do.

- AmazonS3FullAccess
- AWSCloudFormationFullAccess
- AmazonDynamoDBFullAccess
- AmazonAPIGatewayAdministrator
- CloudWatchLogsFullAccess
- IAMFullAccess
- AWSLambda_FullAccess

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/amo84nuzpn0ymacpo3x7.png)

Now, we're good. Let's run the CodePipeline again. Find the pipeline you created earlier and click "Release Change"

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/599mxt16b6xwtj7p0p1m.png)

On this run, our application is now deployed!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h1pdj6f24laupgx7ki58.png)

Let's see it in action on our Postman collection:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o1qx9ub46btsihooghms.png)

Now, let's try to modify the response body just a bit by adding "status": "success". Then, we commit the change to master and our CI/CD should deploy our change:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x7t8c2vf1fqd5rk4gv33.png)

Our CI/CD pipeline should automatically run. After a few minutes, our change should now reflect in our API.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/32c5bc8djenlhky57p93.png)

## That's all folks!

Photo by <a href="https://unsplash.com/@jjying?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">JJ Ying</a> on <a href="https://unsplash.com/s/photos/pipeline?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>