Golden Gate Bridge

Automating workflows with github actions!

Determining the best way to automate your CI/CD workflows can be a daunting task. There are so many tools out there that can help you with this, narrowing down the best one for your use case can be difficult. In this post, I will walkthrough how I like to set up my CI/CD workflows using Github Actions. In this post we'll cover a simple implementation of a CI/CD workflow for a Go application using golangs standard testing library and focusing on the Continuous Delivery aspecet of CD. Deployment can vary widely depending on your use case, so we will only covert the testing and delivery of a Go application to a docker registry.

For me, Github Actions provide a lot of flexibility and there are tons of pre-built actions that you can use to help you get started. The best part to me, is the proximity to the code. You can define your workflows in the same repository as your code, which makes it easy to maintain and understand. Being so close to the code allows you to really catch any issues that may arise from changes in the codebase and catch them before they make it into your deployment branch. Among other benefits, Github Actions allow me to maintain controll of everything that goes on in my CI/CD pipeline and not have to worry about things like build containers being public. Github Actions are not without their drawbacks, there are some limitations to the free tier and from time to time the service does go down. However, having a solid break glass plan in place can help mitigate these issues.

Getting Started with Github Actions

To get started with Github Actions, you will need to create a new file in your repository called .github/workflows/ci.yml. This file will contain the definition of our CI workflow. The first thing you will need to do is define the name of your workflow and the events that will trigger it. I like for the file name to match the name of the job so it's eaiser to debug any issues that may arise. In this example, we will use the push event to trigger our workflow. This means that every time we push to our repository, our workflow will run. Here is an example of what that might look like:

  

---
name: ci

on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: run go tests
        run: |
          go test -v ./...

  

In this example, we are defining a workflow called ci that will run every time we push to our repository. This setp can be further configured to run on specific triggers. A trigger can be a push, pull request, or a schedule. This workflow contains a single job called build that will run on the latest version of ubuntu. The job contains a single step that checks out our code and then runs our go tests. This is a very simple example, but it should give you a good idea of how to get started with Github Actions. You can add as many jobs and steps as you need to get your workflow up and running.

Continuous Delivery

Continuous Delivery is the practice of automating the delivery of your software to a specific destination, not to be confused with it's cousin continuous deployment. This can be a docker registry, a package manager, or even a server. In this example, we will focus on delivering our Go application to a docker registry. To do this, we will need to create a new file in our repository called .github/workflows/cd.yml. This file will contain the definition of our CD workflow. The first thing you will need to do is define the name of your workflow and the events that will trigger it. Since we won't want this action to run until the code has at least been tested. We will need to add some kind of logic to In this example to only run when our ci workflow has completed. We can do this by using the workflow_run declaration on our workflow. Under workflow_run we'll set the workflow we want to trigger our CD workflow and the type of event that will trigger it. In the event of a successful run of the ci workflow, we will trigger our cd workflow. We will dig into our the rest of the job after we see what the file will look like.

  

---
name: cd

on:
  workflow_run:
    workflows: ["ci"]
    types:
      - completed

jobs:
  docker-build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          platforms: linux/arm/v7
          file: ./Dockerfile
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/go-site-gin:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  

Now let's focus on the docker-build job. Here we are setting up our Docker build job and we will use the runs_on field to tell our action that we want to run the following steps the latest available version of an ubuntu container. Our first step is to checkout our code. For this step we will use a pre-built action called actions/checkout@v3. You can kind of think of these pre-built actions as the FROM section of a Dockerfile. They are pre-built and ready to use. This step requires no configuration, and it's pretty self-explanitory, it checkout out or code. Next we will login to our Docker Hub account. The docker/login-action@v2 action is another pre-built solution provided by the Docker team. You could, if you were so incliend to, use the run field to run the docker login command, but this action is a little more legible this way. Hear, we will supply our service accounts username and password. The username and token DOCKERHUB_USERNAME DOCKERHUB_TOKEN will be stored as a key value pair in our repository's secrets. This will allow multiple users to reuse the same configuration without having to worry about sharing sensitive information. It is good to note that with things like Amazon's ECR, you can configure trusted IAM roles to allow your actions to push to your registry without having to use a username and password.

Now that we have logged into our Docker Hub account, let's start builing our container image. First we will need to set up docker/setup-buildx-action@v2 to take some of the heavy lifting off of us when building and pushing the container. Once we have our buildx environment set up, we will be using it for the remainder of the steps in our continuous delivery workflow. On to actual delivery part of this workflow. Using docker/build-push-action@v4 the final image. There are a lot of interesting things the docker/build-push-action can do for us. For instance, in our Build and push step, we are telling Docker where our Dockerfile is located with teh context declaration. What platforms we want to build for, in this case this image is for my k3s cluster so I'll need an image based on the arm arichitecture. Confgured in the tags declaration, we are telling Docker where our registry is and what the final image should be tagged as. In the past, I've run into many issue where the latest tag is not always honored, so I like to use the commit hash as the tag. This has the added benefit of forcing kubernets to pull the latest image when the deployment is updated, but it also provides a way to tie a specific deployment to a point in time in the codebase. When we get into continuous deployment, we'll see that constantly updating a semantically versioned tag can be a bit of a pain. As always, it's a bit of a decision you'll have to make for yourself. The rest of the configuration is for caching the build and push steps. This is comes in handy when you really optimize you container builds and you don't want to have to rebuild the entire container every time you make a change (A topic for later).

This is a very simple example of how to get started with Github Actions. There are many more things you can do with Github Actions, and I encourage you to check out the documentation and explore some of the configis other developers are using. As discussed above, you can find their exampels in the .github/workflows directory of their repositories. I hope this post has been helpful and cleared up somethings about GitHub Actions. Good luck automating your workflows!

Posts + Projects