Skip to content

Latest commit

 

History

History
563 lines (377 loc) · 24.4 KB

continuous-delivery-aws.md

File metadata and controls

563 lines (377 loc) · 24.4 KB

GitHub Actions: Continuous Delivery with AWS

Create two deployment workflows using AWS.

Tags: GitHub Actions, Workflows, CD, Amazon Web Services (AWS), Deployment


This course is about continuous delivery, or CD, with GitHub Actions. CD is a crucial part of modern software development, and it can have a monumental impact on development projects.

Continuous Delivery is the practice of delivering software faster and with higher quality. Doing so successfully requires many different practices, behaviors, and technologies. GitHub Actions makes implementing CD easier, faster, and more reliable.

In this course, we focus on using workflows in GitHub Actions to deploy pull requests automatically to a staging environment, and to deploy all merged commits to production.

Please note: You may need a credit card to create an account with a cloud provider. If you're a student, you may also be able to take advantage of the Student Developer Pack for access to a cloud provider. If you'd like to continue with the course without an account with a cloud provider, Learning Lab will still respond, but none of the deployments will work.

What you'll learn

We'll answer common questions like:

  • What is Continuous Delivery?
  • What is a workflow file and how are workflows used with GitHub Actions?
  • What are deployment triggers and how are they used with deployments?
  • How can secrets store tokens?

And when you're done you'll be able to:

  • Use GitHub Actions and create workflow files
  • Use secrets to store tokens
  • Deploy to staging and production using GitHub Actions to implement CD

What you'll build

In this course, we'll be walking through a CD deployment using GitHub Actions, and creating a Tic Tac Toe game!

deployed app

Prerequisites

Before you start, you should be familiar with GitHub and Continuous Integration. We recommend taking the following three Learning Labs prior to this course:

Projects used

This course makes use of the following technologies:

Audience

Developers, DevOps Engineers, students, teams


Welcome to the course!

We'll learn how to create a workflow that enables Continuous Delivery. You'll:

  • create a workflow to deploy to staging based on a label
  • create a workflow to deploy to production based on merging to main

Before you start, you should be familiar with GitHub and Continuous Integration. If you aren't sure where to start, you may want to check out these two Learning Lab courses:

What is Continuous Delivery?

Martin Fowler defined Continuous Delivery very simply in a 2013 post as follows:

Continuous Delivery is a software development discipline where you build software in such a way that the software can be released to production at any time.

A lot of things go into delivering "continuously". These things can range from culture and behavior to specific automation. In this course, we're going to focus on deployment automation.

Kicking off deployments

Every deployment is kicked off by some trigger. Engineers at many companies, like at GitHub, typically use a ChatOps command as a trigger. The trigger itself isn't incredibly important. In our use case, we'll use labels. When someone applies a "stage" label to a pull request, that'll be our indicator that we'd like to deploy our application to a staging environment.

Step 1: Configure a trigger based on labels

In a GitHub Actions workflow, the on step defines what causes the workflow to run. In this case, we want the workflow to run whenever a label is applied to the pull request.

⌨️ Activity: Configure the workflow trigger based on a label being added

  1. Edit the deploy-staging.yml file on this branch, or [use this quick link]({{ repoUrl }}/edit/staging-workflow/.github/CHANGETHIS/deploy-staging.yml?) (We recommend opening the quick link in another tab)
  2. Change the name of the directory CHANGETHIS to workflows, so the title of this file with the path is .github/workflows/deploy-staging.yml
  3. Edit the contents of this file to trigger on a label

Your result should look like this:

name: Staging deployment

on: 
  pull_request:
    types: [labeled]

jobs:
  build:
    runs-on: ubuntu-latest

It looks like you took an action I didn't expect.

I expected you to {{ expected }}. Please try that to continue!

Job conditionals

GitHub Actions features powerful controls for when to execute jobs and the steps within them. One of these controls is if, which allows you run a job only when a specific condition is met. See jobs.<job_id>.if in Workflow syntax for GitHub Actions for more information.

Using information within GitHub

Workflows are part of the GitHub ecosystem, so each workflow run gives you access to a rich set of data that you can use to take fine-grained control.

We'd like to run our workflow on a specific label called stage, so we'll achieve this in a single line that packs a punch. We'll use:

  • if: is the conditional that will decide if the job will run
  • contains() is a function that allows us to determine if a value like say, a label named "stage", is contained within a set of values like say, a label array ["bug", "stage", "feature request"]
  • github.event.pull_request.labels is specifically accessing the set of labels that triggered the workflow to run. It does this by accessing the github object, and the pull_request event that triggered the workflow.
  • github.event.pull_request.labels.*.name uses object filters to filter out all information about the labels, like their color or description, and lets us focus on just the label names.

Step 2: Trigger a job on specific labels

Let's put all this together to run our job only when a labeled named "stage" is applied to the pull request.

⌨️ Activity: Choose the Ubuntu environment for our app

  1. Edit the deploy-staging.yml file on this branch, or [use this quick link]({{ repoUrl }}/edit/staging-workflow/.github/workflows/deploy-staging.yml?) (We recommend opening the quick link in another tab)
  2. Edit the contents of the file to add a conditional

Your results should look like this:

name: Staging deployment

on: 
  pull_request:
    types: [labeled]

jobs:
  build:
    runs-on: ubuntu-latest

    if: contains(github.event.pull_request.labels.*.name, 'stage')

It looks like you took an action I didn't expect.

I expected you to {{ expected }}. Please try that to continue!

Workflow steps

So far, the workflow knows what the trigger is and what environment to run in. But, what exactly is supposed to run? The "steps" section of this workflow specifies actions and scripts to be run in the Ubuntu environment when new labels are added.

Step 3: Write the steps for the staging workflow

We won't be going into detail on the steps of this workflow, but it would be a good idea to become familiar with the actions we're using. They are:

The course Using GitHub Actions for CI also teaches how to use most of these actions in details.

⌨️ Activity: Deploy a Node.js app to AWS for the first time

  1. In a new tab, create an AWS account if you don't already have one.

    Note: You may need a credit card to create an AWS account. If you're a student, you may also be able to take advantage of the Student Developer Pack for access to AWS. If you'd like to continue with the course without an AWS account, Learning Lab will still respond, but none of the deployments will work.

  2. Add a user in the IAM service with administrator permission.
  3. In the confirmation screen, copy both the Access key ID and the Secret access key to a safe space. We'll use these in the next few steps as follows:
    • Access key ID ➡️ AWS_ACCESS_KEY
    • Secret access key ️️️ ➡️ AWS_SECRET_KEY
  4. Back on GitHub, click on this repository's [Secrets]({{ repoUrl }}/settings/secrets) in the Settings tab.
  5. Click Add a new secret
  6. Name your new secret AWS_ACCESS_KEY and paste the value from the Access key ID generated on AWS.
  7. Click Add secret.
  8. Click Add a new secret again.
  9. Name the second secret AWS_SECRET_KEY and paste the value from the Secret access key generated on AWS.
  10. Click Add secret
  11. Back in this pull request, edit the .github/workflows/deploy-staging.yml file to use a new action, or [use this quick link]({{ repoUrl }}/edit/staging-workflow/.github/workflows/deploy-staging.yml?) (We recommend opening the quick link in another tab)
    - name: Deploy to AWS
      uses: github/deploy-nodejs@master
      env:
        AWS_ACCESS_KEY: {% raw %}${{ secrets.AWS_ACCESS_KEY }}{% endraw %}
        AWS_SECRET_KEY: {% raw %}${{ secrets.AWS_SECRET_KEY }}{% endraw %}

If you'd like to copy the full workflow file, it should look like this:

name: Staging deployment

on: 
  pull_request:
    types: [labeled]

jobs:
  build:
    if: contains(github.event.pull_request.labels.*.name, 'stage')

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v1
      - name: npm install and build webpack
        run: |
          npm install
          npm run build
      - uses: actions/upload-artifact@main
        with:
          name: webpack artifacts
          path: public/

  deploy:
    name: Deploy Node.js app to AWS
    needs: build
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v1

      - name: Download built artifact
        uses: actions/download-artifact@main
        with:
          name: webpack artifacts
          path: public

      - name: Deploy to AWS
        uses: github/deploy-nodejs@master
        env:
          AWS_ACCESS_KEY: {% raw %}${{ secrets.AWS_ACCESS_KEY }}{% endraw %}
          AWS_SECRET_KEY: {% raw %}${{ secrets.AWS_SECRET_KEY }}{% endraw %}

Completed Workflow

Nice job, you've done it!

It won't be "working" yet, because our next step is to work on the configuration files that AWS will need. But, the logic for this workflow is complete.

Step 4: Merge the staging workflow

⌨️ Activity: Merge this staging workflow pull request

  1. Merge this pull request
  2. Delete the staging-workflow branch

AWS Configuration - S3 Buckets

GitHub Actions is cloud agnostic, so any cloud will work. We'll show how to deploy to AWS in this course.

S3 Buckets

Amazon S3 Buckets are a very flexible type of data storage -- they can be configured to work in many different types of ways. They're popular for their security, scalability, and dependability. Our S3 Bucket will store our application, both in staging and in production.

Step 5: Confirm AWS S3 configuration

⌨️ Activity: Create an S3 bucket

  1. Navigate to the Amazon S3 service and click on Create bucket.
    • See Create a Bucket on AWS documentation for the most detailed instructions.
  2. Name the bucket whatever you'd like, and jot the name down.
  3. Select a region, and jot it down. You'll need it later. These examples use US West (Oregon), also known as us-west-2. If you'd like to choose another region, make sure to update the aws-config.yml file to match.
  4. For all other options, accept the defaults.
  5. Edit the .github/aws-config.yml file on this branch, or [use this quick link]({{ repoUrl }}/edit/aws-configuration/.github/aws-config.yml?). (We recommend opening the quick link in another tab.)
  6. Change the value of s3_bucket: to match your chosen bucket name.
  7. In the same file, confirm that the region: value matches your chosen region for the S3 bucket.
  8. Commit your changes.

I'll respond when I detect a commit on this branch.

You can find your next steps in the [next pull request]({{ url }}).

AWS Configuration files

To deploy successfully to our S3 bucket on AWS, we need a few special configuration files.

aws-config.yml

The aws-config.yml file is needed with the Deploy to AWS Action that we're using. How did we know that it's needed? In the documentation for the GitHub action, there are specific instructions about including this file, and where it needs to sit within the repository.

Whenever you're using a GitHub Action, it's important to read the documentation. There may be details like what secrets or other template files are required for the Action to work as expected.

sam-template.yml

This file is a bit trickier. The template aws-config.yml file that was documented with the action has a placeholder for this template, but doesn't specify what we should do.

This file is specific to deploying a serverless application to AWS. Specifically, an AWS SAM template tells AWS how to set up the application's architecture. Read more about it in AWS SAM Template Concepts in the AWS documentation.

In our case, we created the sam-template.yml for you. It contains information that's specific about the application's endpoints and structure.

Step 6: Approve the pull request

I've requested your approval on this pull request. Once you approve this, I will merge.

⌨️ Activity: Approve pull request adding aws-config.yml and sam-template.yml

  1. Approve this pull request

Uh oh - you reviewed this pull request, but you didn't approve it. Please approve it.

Testing the workflow

Now that the proper configuration and workflow files are present, let's test this action out!

Step 7: Test the staging action

In this pull request, there's a small change to the game. Once you add the label, you should be able to see the deployment!

⌨️ Activity: Add the proper label to this pull request

  1. On the right hand side, click Labels or the gear next to it
  2. Select the label titled stage

You can find your next steps in the [next pull request]({{ url }}).

Different triggers

Deployments to production can be manual (like through a Chat Ops command), or automated (if, say, we trust our pull request review process and we've followed continuous integration practices).

We'll trigger a deployment to the production environment whenever something is committed to main. Our main branch is protected, so the only way for commits to appear on main is for a pull request to have been created and gone through the proper review process and merged.

Step 8: Write the production deployment trigger

Let's create a new workflow that deals specifically with commits to main and handles deployments to prod.

⌨️ Activity: Write the production deployment trigger on merge to main

  1. Edit the deploy-prod.yml file on this branch, or [use this quick link]({{ repoUrl }}/edit/production-deployment-workflow/.github/CHANGETHIS/deploy-prod.yml?) (We recommend opening the quick link in another tab)
  2. Rename the file in this pull request to .github/workflows/deploy-prod.yml
  3. Add a push trigger
  4. Add branches inside the push block
  5. Add - main inside the branches block
  6. Commit your changes to this branch

The file should look like this when you're finished:

name: Production deployment

on: 
  push:
    branches:
      - main

The deployment may take a few moments but you've done the right thing. Once the deployment is successful, you'll see green check marks for each run, and you'll see a URL for your deployment.

a screenshot of the Actions logs showing a completed deployment with an Output section and a staging URL

You can wait for the deployment, or move on to the next steps in the [next pull request]({{ url }}).

If you'd like to come back and merge this once their other workflow is done, you can. 🎉

It looks like you took an action I didn't expect.

I expected you to {{ expected }}. Please try that to continue!

Great! The syntax you used tells GitHub Actions to only run that workflow when a commit is made to the main branch.

Deploying to production

Just like with the other workflow, we'll need to build our application and deploy to AWS using the same action as before because we are working with the same Node.js app.

Continuous delivery is a concept that contains many behaviors and other, more specific concepts. One of those concepts is test in production. That can mean different things to different projects and different companies, and isn't a strict rule that says you are or aren't "doing CD".

In our case, we can match our production environment to be exactly like our staging environment. This minimizes opportunities for surprises once we deploy to production.

Step 9: Complete the deployment to production workflow

⌨️ Commit the steps to the production workflow that allow you to deploy on merge to main

  1. Edit the deploy-prod.yml file on this branch, or [use this quick link]({{ repoUrl }}/edit/production-deployment-workflow/.github/workflows/deploy-prod.yml?) (We recommend opening the quick link in another tab)
  2. Add a build and deploy job to the workflow

It should look like the file below when you are finished. Note that not much has changed from our staging workflow, except for our trigger.

name: Production deployment

on: 
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v1
      - name: npm install and build webpack
        run: |
          npm install
          npm run build
      - uses: actions/upload-artifact@main
        with:
          name: webpack artifacts
          path: public/

  deploy:
    name: Deploy Node.js app to AWS
    needs: build
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v1

      - name: Download built artifact
        uses: actions/download-artifact@main
        with:
          name: webpack artifacts
          path: public

      - name: Deploy to AWS
        uses:  github/deploy-nodejs@master
        env:
          AWS_ACCESS_KEY: {% raw %}${{ secrets.AWS_ACCESS_KEY }}{% endraw %}
          AWS_SECRET_KEY: {% raw %}${{ secrets.AWS_SECRET_KEY }}{% endraw %}

Workflow steps

We'll add a final section to our production workflow that packages up our application in a Docker container and publishes it to GitHub Packages. This step is important for the traceability of your deployed artifacts.

We'll only use one new action here created by a GitHubber, which allows us to push a container to GitHub Packages.

  • mattdavis0351/actions/docker-gpr

All of this happens automatically once a pull request is merged!

Step 10: Create the Docker image and push it to GitHub Packages

⌨️ Activity: Write the steps for the production deployment workflow

  1. Edit the deploy-prod.yml file on this branch, or [use this quick link]({{ repoUrl }}/edit/production-deployment-workflow/.github/workflows/deploy-prod.yml?) (We recommend opening the quick link in another tab)
  2. Add a job to your workflow as follows:
    Build-and-Push-Docker-Image:
    runs-on: ubuntu-latest
    needs: build
    name: Docker Build, Tag, Push
    steps:
      - name: Checkout
        uses: actions/checkout@v1
    
      - name: Download built artifact
        uses: actions/download-artifact@main
        with:
          name: webpack artifacts
          path: public
    
      - name: Build, Tag, Push
        uses: mattdavis0351/actions/docker-gpr@v1
        with:
          repo-token: {% raw %}${{ secrets.GITHUB_TOKEN }}{% endraw %}
          image-name: {{user.login}}-aws-ttt
  3. Commit the workflow to this branch.

The complete workflow file should look like this:

name: Production deployment

on: 
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v1
      - name: npm install and build webpack
        run: |
          npm install
          npm run build
      - uses: actions/upload-artifact@main
        with:
          name: webpack artifacts
          path: public/

  deploy:
    name: Deploy Node.js app to AWS
    needs: build
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v1

      - name: Download built artifact
        uses: actions/download-artifact@main
        with:
          name: webpack artifacts
          path: public

      - name: Deploy to AWS
        uses:  github/deploy-nodejs@master
        env:
          AWS_ACCESS_KEY: {% raw %}${{ secrets.AWS_ACCESS_KEY }}{% endraw %}
          AWS_SECRET_KEY: {% raw %}${{ secrets.AWS_SECRET_KEY }}{% endraw %}

  Build-and-Push-Docker-Image:
    runs-on: ubuntu-latest
    needs: build
    name: Docker Build, Tag, Push
    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Download built artifact
        uses: actions/download-artifact@main
        with:
          name: webpack artifacts
          path: public

      - name: Build, Tag, Push
        uses: mattdavis0351/actions/docker-gpr@v1
        with:
          repo-token: {% raw %}${{ secrets.GITHUB_TOKEN }}{% endraw %}
          image-name: {{user.login}}-aws-ttt

Completed Workflow

Nice job, you've done it!

Step 11: Merge the production workflow

⌨️ Activity: Merge this pull request and test the production deployment workflow

  1. Merge this pull request
  2. Delete the branch

Nice work!

Now, we just have to wait for the deployment to occur, and for the package to be published to GitHub Packages. When it's completed, you should be able to see it in the [Packages]({{ repoUrl }}/packages) section of your repository. You can get the deployment URL in the [Actions]({{ repoUrl }}/actions) log, just like the staging URL.

‼️ Remember to destroy or disable the AWS services created during this course if you'd like to ensure they don't consume resources accidentally. As a reminder, the following resources were created on your AWS account:

  • IAM user
  • S3 bucket

celebrate

This course is now complete! I'll stop responding but the fun doesn't have to stop here.

Now...[what will you learn next]({{ host }}/courses)?