GitHub Actions CICD in AWS

manu muraleedharan
CodeX

--

Github Actions is a CICD platform from Github, with tight integration to Github. Being a SasS product, this frees the team from having to setup and manage a separate application for doing the CICD. If your code is on Github, this product makes a lot of sense to add to your toolkit.

Using Github Actions have below advantages:

Event-driven — You can trigger your CICD workflows based on events on your Github repo.

Distributed Execution — Github manages CICD pipeline execution on its own fleet of virtual machines (Runners) You can use unix, windows, mac, ARM and even containers for this purpose.

Visualization — See and manage your pipeline live on the Github Actions console right inside Github and share status quickly.

Support — Github Actions is supported by an active developer community & all major vendors.

Matrix Build — Test your code on many combinations of target systems.

For public repositories, Github Actions is free to run. For others there is a X number of minutes free per year and Runners are billed per minute

In this article, we will look at how to use GitHub Actions when your application target

environment is AWS.

Setup Github Actions to work on AWS

Enable GitHub Actions from the GitHub console

On the Github repository in the web console, do the steps as given on the Github docs:

https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository

Set permissions and access in the GitHub console

Set Read and Write access for your workflow:

https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository

In order for GitHub Actions to do anything on AWS, it needs to be assigned a role. For this:

Create an OIDC Identity Provider which points to the GitHub.

Create a Role in IAM which has all the permissions required for the Github Actions workflow.

Edit the role’s trust relationship so that your Github Repo workflow can assume the role.

Full steps for doing this are given here:

https://aws.amazon.com/blogs/security/use-iam-roles-to-connect-github-actions-to-actions-in-aws/

Now lets understand the basic concepts of GitHub Actions.

Github Actions Workflow, Jobs, Steps

Workflow — the top-most component, which represents a CICD pipeline.

Job — Under one workflow we can have multiple Jobs, which might have dependency on one another. Eg: one job for CI, one job for CD.

Step — Each job will in turn have multiple steps in them. These steps are actual actions that take place. For eg. you want to check out code. You can use the action actions/checkout@v3 in the step. This is an “Action” provided by Github to check out your code in a step.

You can either use such actions provided by GitHub, AWS or other providers, or write your own script (we will expand on this later)

You can pass variables between jobs, or between steps. You can set default variables. You can read and write to the Github repository, and use GitHub secrets to store your secret information.

GitHub workflow file is stored as a YAML under the default directory .github/workflows/

Lets create a very basic workflow.

A Very Basic Github Actions Workflow

name: Hello World
on:
push:
branches:
- main # Replace with your repository’s main branch name
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Print Hello World
run: echo “Hello, World”

Save this as main.yml under .github/workflows/ in your repository.

On: This is the trigger section. When does your workflow get triggered? When someone pushes to the branch given.

Jobs: Contains the jobs. We have one job — build.

Runs-on: This is the virtual machine on which workflow will run. Each job runs on different virtual machines by default that do not share the data. Our job is going to run on an ubuntu virtual machine. These VMs are called “Runners” by github.

Eg: ubuntu-latest, windows-latest, macos-latest.

The list of runners is given here: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners

These are standard runners with 7GB-30GB of RAM and 2 core to 12 core CPUs depending on the type. Team and Enterprise customers can get “large runners” which have more memory and CPU, and can come with auto-scaling and IPs.

Under the job section we have steps. We have 2 steps here. Checkout code and Print Hello World. Checkout Code uses the standard GitHub-provided action actions/checkout@v3.

@v3 here means we are using the version 3 of the Action. All actions are open-source, for example:

https://github.com/actions/checkout

In the Print Hello World step, you can see we have used the linux command echo to print Hello World to the console. Run is the keyword used to run commands or scripts.

Connect to AWS From GitHub Actions

We can use the AWS supplied action, aws-actions/configure-aws-credentials to connect to AWS and assume the role we defined. It looks like this:

name: Configure AWS credentials from AWS account
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::AWS_ACCOUNT_ID:role/github_actions_role role-session-name: GitHub_to_AWS_via_FederatedOIDC
aws-region: ‘us-east-1’

Here, role-to-assume should have the ARN for the role that you created for GitHub Actions workflow. Role-session-name will set the name for the session created on login. This allows you to search in CloudWatch or CloudTrail for the role session.

Use Terraform in GitHub Actions

Setting up the popular IaC tool Terraform is easy in Github Actions, using a HashiCorp supplied action:

name: Setup Terraform
uses: hashicorp/setup-terraform@v2

Example Workflow for Continuous Integration

Let’s assume an application, which is deployed as a docker container. Continuous Integration in this case would be creating the docker image as an when there is a change to the code. Let’s see how we can do that, with the job below:

jobs:
docker-creation:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
working-directory: .
steps:
- name: Git checkout
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::644107485976:role/github_actions_role #change to reflect your IAM role’s ARN
role-session-name: GitHub_to_AWS_via_FederatedOIDC
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1.7.0
with:
mask-password: ‘true’
- name: Build and push image to Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: nodeapp
IMAGE_TAG: latest
run: |
cat src/index.ts
echo $ECR_REGISTRY && echo $ECR_REPOSITORY && echo $IMAGE_TAG
docker build -t $ECR_REPOSITORY .
docker tag $ECR_REPOSITORY $ECR_REGISTRY/$ECR_REPOSITORY
docker push $ECR_REGISTRY/$ECR_REPOSITORY

This workflow would run on an Ubuntu runner, and will have bash as its shell. First step, uses the standard actions/checkout@v3 action to checkout the code from your repository. Let’s say your repository contains all the code for your application and the Dockerfile that would create the docker image.

Next step uses the standard aws-actions/configure-aws-credentials@v2 action to setup your AWS credentials using the OIDC provider and AWS Role.

We want to push the docker image when created, to AWS ECR. So in the next step we login to the ECR using again a standard action, aws-actions/amazon-ecr-login@v1.7.0

The last step uses run command to run unix commands, and we are running the unix docker commands to build, tag and push the image to ECR with proper repository name and tag.

Example Workflow for Infrastructure Provisioning

We are going to use Terraform in this example as the IaC tool. We have written terraform configurations to create an EC2 instance and open requisite ports using security groups. Now we want to call the terraform using our Github Actions workflow. Lets see the code:

Salient points to note:

needs: [docker-creation] — This in the job section means that this job depends on the docker-creation job to be complete before this can start. This way we can create dependencies between jobs in a workflow.

Again, we would use checkout action to checkout the code (which includes the Terraform .tf files) and connect to AWS using aws action. Then we can use Hashicorp action to setup Terraform.

Then, we can use the run command to run the terraform commands to format, plan and apply.

Note during terraform init, we are asking to save the state in an S3 bucket and we are pasing S3 bucket information as environment variables, using env command.

- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform fmt
id: fmt
run: terraform fmt
continue-on-error: true
- name: Terraform Init
id: init
env:
AWS_BUCKET_NAME: “tf-state-manu16082023”
AWS_BUCKET_KEY_NAME: “remote-state”
run: terraform init -backend-config=”bucket=${AWS_BUCKET_NAME}” -backend-config=”key=${{env.AWS_BUCKET_KEY_NAME}}” -backend-config=”region=${{env.AWS_REGION}}”
- name: Terraform Validate
id: validate
run: terraform validate -no-color
- name: Terraform Plan
id: plan
run: terraform plan -no-color
if: github.event_name == ‘pull_request’
continue-on-error: true
- name: Terraform Plan Status
if: steps.plan.outcome == ‘failure’
run: exit 1
- name: Terraform Apply
if: github.ref == ‘refs/heads/main’ && github.event_name == ‘push’
run: terraform apply -auto-approve -input=false

--

--

manu muraleedharan
CodeX
Writer for

AWS/Devops Enthusiast, Books&Movies Lover, FrenchToast Creator.