GitHub actions to deliver on kubernetes

22 Jan 2019 - Tags: kubernetes, github action, serverless, ci, automation

Recently GitHub released a new feature called Actions. To me, it looks like the best implementation I can think of for serverless. I used AWS Lambda and API Gateway for some basic API, and I wrote a prototype of an application capable of running functions using containers called gourmet I don’t buy the fact that it will make my code easy to manage. At least not to write API or web applications.

That’s why I like what GitHub did because they used serverless for what I think it is designed for, extensibility.

GitHub Actions just like Lambda functions on AWS are a powerful and managed way to extend their product straightforwardly.

With AWS Lambda you can hook your code to almost whatever event happens: EC2 creations, termination, route53 DNS record change and a lot more. You don’t need to run a server, you load your code, and it just works.

Jess Frazelle wrote a blog post about “The Life of a GitHub Action, and I decided to try something I had my mind since a couple of weeks but it required a CI server, and it was already too much for me.

Time to time I like the idea to have a kubernetes cluster that I can use for the testing purpose, so I created a private repository that it is not ready to be open source because it is a mess with secrets inside and so on.

In any case, to give you an idea, this is the project’s folder:

├── .github
│   ├── actions
│   │   ├── deploy
│   │   │   ├── deploy
│   │   │   └── Dockerfile
│   │   └── dryrun
│   │       ├── Dockerfile
│   │       └── dryrun
│   └── main.workflow
└── kubernetes
    ├── digitalocean.yaml
    ├── external-dns.yaml
    ├── micro.yaml
    ├── namespaces.yaml
    ├── nginx.yaml
    └── openvpn.yaml

The kubernetes directory contains all the things I would like to install in my cluster. For every new push on this repository, I would like to check if it can be applied to the kubernetes cluster with the command kubectl apply -f ./kubernetes --dryrun and when the PR is merged the changes should get applied.

So I created my workflow in .github/main.workflow: ( I left some comment to make it understandable)

## Workflow defines what we want to call a set of actions.

## For every new push check if the changes can be applied to kubernetes ## using the action called: kubectl dryrun
workflow "after a push check if they apply to kubernetes" {
  on = "push"
  resolves = ["kubectl dryrun"]
}

## When a PR is merged trigger the action: kubectl deploy. To apply the new code to master.
workflow "on merge to master deploy on kubernetes" {
  on = "pull_request"
  resolves = ["kubectl deploy"]
}

## This is the action that checks if the push can be applied to kubernetes
action "kubectl dryrun" {
  uses = "./.github/actions/dryrun"
  secrets = ["KUBECONFIG"]
}

## This is the action that applies the change to kubernetes
action "kubectl deploy" {
  uses = "./.github/actions/deploy"
  secrets = ["KUBECONFIG"]
}

The secrets are an array of environment variables that you can use to set values from the outside. If your account has GitHub Action enabled there is a new Tag inside the Settings in every repository called “Secrets.”

You can set key-value pairs usable as you see in my workflow. For this example, I set the KUBECONFIG as the base64 of a kubeconfig file that allows the GitHub Action to authorize itself to my Kubernetes cluster.

Both actions are similar the first one is in the directory .github/actions/dryrun

├── .github
    ├── actions
        └── dryrun
            ├── Dockerfile
            └── dryrun

It contains a Dockerfile

FROM alpine:latest

## The action name displayed by GitHub
LABEL "com.github.actions.name"="kubectl dryrun"
## The description for the action
LABEL "com.github.actions.description"="Check the kubernetes change to apply."
## https://developer.github.com/actions/creating-github-actions/creating-a-docker-container/#supported-feather-icons
LABEL "com.github.actions.icon"="check"
## The color of the action icon
LABEL "com.github.actions.color"="blue"

RUN     apk add --no-cache \
        bash \
        ca-certificates \
        curl \
        git \
        jq

RUN curl -L -o /usr/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/v1.13.0/bin/linux/amd64/kubectl && \
  chmod +x /usr/bin/kubectl && \
  kubectl version --client

COPY dryrun /usr/bin/dryrun
CMD ["dryrun"]

As you can see to describe an action, you need just a Dockerfile, and it works the same as in docker. The CMD dryrun is the bash script I copied here:

#!/bin/bash

main(){
    echo ">>>> Action started"
    # Decode the secret passed by the action and paste the config in a file.
    echo $KUBECONFIG | base64 -d > ./kubeconfig.yaml
    echo ">>>> kubeconfig created"
    # Check if the kubernetes directory has change
    diff=$(git diff --exit-code HEAD~1 HEAD ./kubernetes)
    if [ $? -eq 1 ]; then
        echo ">>>> Detected a change inside the kubernetes directory"
        # Apply the changes with --dryrun just to validate them
        kubectl apply --kubeconfig ./kubeconfig.yaml --dry-run -f ./kubernetes
    else
        echo ">>>> No changed detected inside the ./kubernetes folder. Nothing to do."
    fi
}

main "$@"

The second action is almost the same as this one, the Dockerfile is THE same, so I am not posting it here, but the CMD looks like this:

#!/bin/bash

main(){
    # Decode the secret passed by the action and paste the config in a file.
    echo $KUBECONFIG | base64 -d > ./kubeconfig.yaml
     # Check if it is an event generated by the PR is a merge
    merged=$(jq --raw-output .pull_request.merged "$GITHUB_EVENT_PATH")
    # Retrieve the base branch for the PR because I would like to apply only PR merged to master
    baseRef=$(jq --raw-output .pull_request.base.ref "$GITHUB_EVENT_PATH")

    if [[ "$merged" == "true" ]] && [[ "$baseRef" == "master" ]]; then
        echo ">>>> PR merged into master. Shipping to k8s!"
        kubectl apply --kubeconfig ./kubeconfig.yaml -f ./kubernetes
    else
        echo ">>>> Nothing to do here!"
    fi
}

main "$@"

That’s everything, and I am thrilled!

There is nothing more to say other than “GitHub actions are amazing!”. They look well designed since day! The workflow file has a generator that even if I didn’t use it because I don’t like colors, it seems amazing. The secrets allow us to do integration with third-party services out of the box and you can use bash to do whatever you like! Let me know what you use them for on Twitter.