Assignment 4
Continuous Integration and Continuous Delivery
Due Saturday, March 9 at 11:59pm
For this assignment, you will build an end to end CI/CD pipeline using GitHub Actions and AWS Elastic Container Registry (ECR) that results in all commits to the Yoctogram backend being immediately deployed to the Fargate cluster. Specifically, we will create an ECR repository, configure GitHub Actions to push new images to this repository, modify the CDK to pull from this ECR repository, and automatically redeploy the Fargate cluster.
As with previous assignments, all the assignment work should take place on your development EC2 virtual machine since it has all the tools you need. This assignment builds on Assignment 2; however, it can be done independently of Assignment 3.
Creating the ECR Repository
Run the command
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin ACCOUNTID.dkr.ecr.us-west-2.amazonaws.com
aws ecr create-repository --repository-name cs40
to create the ECR repository we’ll use for the rest of the assignment. This repository will be used to store the container images used to host Yoctogram. You can verify the repository was created successfully by navigating to ECR in the AWS console and verifying the cs40
repo is there.
Push an Initial Image to ECR
While not strictly necessary, this section will prevent errors (some of which may be difficult to recover from) when deploying the CDK. Note that all of the work here will eventually have to be done in the GitHub Action.
Navigate to the top level app
directory (the one containing a Dockerfile
), in the same tree you’ve used to complete assignments 2 and 3. You need to build the Docker image from the Dockerfile
and, more importantly, you must tag the image using a specific format. The tag must look like:
ECR_REGISTRY/ECR_REPOSITORY:latest
where ECR_REGISTRY
is the url ACCOUNTID.dkr.ecr.us-west-2.amazonaws.com
(ACCOUNTID
is your twelve digit AWS account ID) and ECR_REPOSITORY
is the name of the repository you created earlier (cs40
if you named it as this spec does).
An example might look like
123456789012.dkr.ecr.us-west-2.amazonaws.com/cs40:latest
Once you have built the docker image, you can push it to ECR with the following command:
docker push ECR_REGISTRY/ECR_REPOSITORY:latest
where ECR_REGISTRY
and ECR_REPOSITORY
are as earlier. Now, you should be able to navigate to the ECR repository through the AWS console and see your Docker image tagged latest
. It should be around 105 MB. If the image is not there, you made a mistake and should double check your commands. If it is, continue to the next section.
It is convention that the most recent image should be called latest
. However, using the same syntax, you can tag images any way you prefer. For example,
ECR_REGISTRY/ECR_REPOSITORY:foo
would push a new image tagged as foo
to ECR.
Clone Yoctogram Backend Code
First, import the Yoctogram backend repository into a private repository. Log into GitHub and click “Import repository”, as you did for Assignment 2.
Use https://github.com/infracourse/yoctogram-app
for “Your old repository’s clone URL”. You can name the new repository anything under your personal account; we suggest something like yoctogram-app
. Remember the repo name as it will be used in the CDK later.
Make sure to keep the repository private. If you’re working with a partner, make sure to add them as a collaborator in Settings.
On your EC2 instance, clone the Yoctogram backend code on the same level as your assignment 2 repo (not inside of it): git clone --recursive [email protected]:yourusername/yoctogram-app
.
Modify CDK to Pull from ECR and Configure GitHub OIDC
First, in compute_stack.py
add
aws_ecr as ecr,
aws_iam as iam
to the existing long list of imports from aws_cdk.
Pulling from ECR
There are two changes you should make to your ECS Fargate task definition. Both changes should be at most a few lines of code.
First, you need to import the container registry from the one you created earlier. To do so, you can use the ecr.Repository.from_repository_name
CDK construct.
Then, in the Yoctogram app container, where you previously used from_asset
to import the container image from the local filesystem, you should instead import the ECS container image from the ECR repository. Use the latest
tag.
Configuring GitHub OIDC
For the latter part of this assignment, GitHub will need the ability to authenticate to your AWS account in order to push images to ECR and redeploy the compute stack.
First, you will need to configure the GitHub OIDC provider. To do this, paste the following block at the bottom of your compute stack code:
gh_actions_provider = iam.OpenIdConnectProvider(
self,
f"{settings.PROJECT_NAME}-gh-actions-provider",
url="https://token.actions.githubusercontent.com",
client_ids=["sts.amazonaws.com"],
)
Now, you will need to configure an IAM role to be assumed by GitHub. Configure the role to be assumed by a WebIdentityPrincipal
specifying the Amazon Resource Name (ARN) of the GitHub OIDC provider. You’ll also have to specify
{
"token.actions.githubusercontent.com:sub": "repo:your-github-account/your-yoctogram-app-repo:ref:refs/heads/main",
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
}
as StringLike
conditions for the principal. This limits which repositories have access to the role to just your Yoctogram backend repository.
Attach the AmazonEC2ContainerRegistryFullAccess
and AmazonECS_FullAccess
managed policies (policies created by AWS) to the role, and ensure a session length of one hour maximum.
A grade4.sh
script to allow you to check your work will be made available shortly. However, if at this stage you made these modifications, can redeploy the compute stack, and the deploy completes successfully, then the ECR configuration likely worked. If something is wrong with your role, it will be obvious in the following stage because you’ll get errors about being unable to assume a role or about insufficient permissions.
Configuring GitHub Actions to Push to ECR
First, allow actions in your GitHub repository by navigating to Settings
in your repository’s menu, and selecting General
under Actions
. Ensure that Allow all actions and reusable workflows
is selected.
Then, add a GitHub Repository Secret to your repo with the name AWS_ACCOUNT_ID
and the value as your 12-digit AWS account ID. Looking at your repository in a browser, you can do this by clicking Settings –> Secrets and variables –> Actions (Settings is in the top bar alongside Code, Issues, Pull Requests, etc).
Now, copy this starter code to .github/workflows/main.yml
in the repository you just cloned.
name: CI/CD Pipeline
on:
push:
branches: [ main ]
permissions: write-all
env:
AWS_REGION: us-west-2
ECR_REGISTRY: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.us-west-2.amazonaws.com
ECR_REPOSITORY: cs40
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
# COMPLETED FOR YOU
- name: 1 Retrieve Timestamp
id: timestamp
run: echo "::set-output name=timestamp::$(date +%s%3N)"
# COMPLETED FOR YOU
- name: 2 Checkout code
uses: actions/checkout@v2
# FILLMEIN: replace ROLE_NAME_HERE with the name of the role you defined in CDK
- name: 3 Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ROLE_NAME_HERE
aws-region: us-west-2
# COMPLETED FOR YOU
- name: 4 Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
# COMPLETED FOR YOU
- name: 5 Set up Docker Buildx
uses: docker/setup-buildx-action@v1
# FILLMEIN: configure the action to build the docker image but *NOT* push it to ECR
- name: 6 Build Docker images
uses: docker/build-push-action@v2
with:
# FILLMEIN: Remove the latest tag from all existing images
- name: 7 Remove latest Tag From Old latest
run: COMMAND_HERE
# FILLMEIN: Push to ECR
- name: 8 Push Docker images
uses: docker/build-push-action@v2
with:
# FILLMEIN: populate the correct values for cluster name and service
# This is a hack for the purposes of this assignment. Don't do this in production.
- name: 9 Force a Fargate Redeploy
run: aws ecs update-service --cluster <cluster-name> --service <service-name> --force-new-deployment --region us-west-2
Note that .github/workflows
is a folder containing YAML files, each of which represent an independent GitHub action.
- Step 3 should use the name of the role you defined earlier in CDK. You can find this in your AWS console –> IAM –> Roles (note, the list has multiple pages, so you may have to click through to the second page).
- Step 6 should build and tag the Docker images as you did earlier in the assignment, except instead of using raw Docker commands it should use the Docker
GitHub action (you just need to populate the
with:
fields properly). You should include two tags –latest
and the timestamp retrieved earlier. You can reference the timestamp using${{ steps.timestamp.outputs.timestamp }}
ℹ️Make sure to specify the platform aslinux/arm64
and to not push the images. - Step 7 should use the AWS command-line interface (CLI) to remove the tag
latest
from all existing images, so that when we push our image we don’t have duplicates. - Step 8 should be identical to step 6, except it should specify
push: true
. - Step 9 is done for you except it needs two values, the name of your ECS cluster and the name of the task definition (this is the
--service flag
). Both of these values are likely very long. For Cody the full command isaws ecs update-service \ --cluster yoctogram-compute-stack-yoctogramcluster2BA83DB9-HZYMbQe0NRT0 \ --service yoctogram-compute-stack-yoctogramfargateserviceServiceDB9282DF-ajLuzi20W8Sk --force-new-deployment \ --region us-west-2
To verify if your GitHub action worked, make a commit and verify that when the workflow is complete, there is a new container image in your ECR repo and there is a task in your ECS cluster indicating a redeploy.
Submission and Grading
Local Sanity Check
Copy and paste the following file into your assignment2
root directory. Call the file grade4.sh
, and mark it executable by running chmod +x grade4.sh
:
#!/bin/bash
set -euxo pipefail
echo "RUN THIS SCRIPT FROM YOUR assignment2 ROOT DIRECTORY. MAKE SURE YOU HAVE SOURCED YOUR venv"
pip3 install pyyaml jinja2
pushd cdk
cdk synth
popd
git clone https://github.com/infracourse/assignment4-autograder autograder
opa eval -b autograder/rules/ -i <(jq -s 'reduce .[] as $item ({}; .Resources += $item.Resources) | del(.Resources.CDKMetadata)' cdk/cdk.out/yoctogram-network-stack.template.json cdk/cdk.out/yoctogram-data-stack.template.json cdk/cdk.out/yoctogram-compute-stack.template.json) -f json 'data.rules.main' | jq -r .result[].expressions[].value.violations[]
python3 autograder/grade_action.py $1
rm -rf autograder
When you run this script, make sure to activate your Python virtual environment, and pass in the path to your GitHub Actions workflow YAML file. For example, one run might look like
./grade4.sh ~/yoctogram-app/.github/workflows/main.yml
If there are no errors, you should be good to go and you should receive a full score when submitting. There are no additional checks against your live site.
Submission
To submit, copy the .github/workflows/main.yml
file from your yoctogram-app
directory to the assignment2
root directory (i.e., the one with the SUNET
and possibly FLAG
files) and push this to GitHub. Upload your submission from GitHub to Gradescope as usual.
The filestructure should look like:
.
├── app/
├── cdk/
├── cdk.out/
├── compression/
├── FLAG
├── grade.sh
├── grade3.sh
├── grade4.sh # create this file by copying the script above
├── main.yml # copy the action from your private github repository
├── README.md
├── SUNET
└── web/
The point distribution for this assignment is as follows:
- 100 points: Static Open Policy Agent evaluation against our OPA policies to ensure your CDK (synthesized to CloudFormation) meets our specification.
- 50 points: Static checks on your GitHub Actions YAML file to ensure it meets our specification.
All checks are locally runnable using grade4.sh
.