Using Github to Manage Kubernetes

After writing Making the Leap into DevOps and Using Github Actions To Test Before You Deploy, I decided that using Github to manage Kubernetes was my next step. Up until this point, I had a collection of random YAML files sitting on a Linux machine that was backed up.

Trying to maintain random YAML files was not working anymore so I needed a different way to manage my Kubernetes. I decided to move my configurations into Github. I then used Github Actions to make my changes live in my Kubernetes cluster.

Kubernetes Deployment

This part was pretty simple. I started with just a simple Ubuntu instance running in my default namespace. The deployment YAML is below

apiVersion: apps/v1
kind: Deployment
metadata:
  generation: 1
  labels:
    apps: ubuntu
  name: ubuntu
  namespace: default
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: ubuntu
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: ubuntu
      namespace: default
    spec:
      containers:
      - command:
        - /bin/sleep
        - 3651d
        env:
        - name: MY_SERVICE_NAME
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: spec.serviceAccountName
        image: ubuntu:latest
        imagePullPolicy: Always
        name: ubuntu
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      securityContext: {}
      terminationGracePeriodSeconds: 30

This is a simple deployment that will deploy ubuntu and run the sleep command for 3651 days. This is one of those random hacks I use that maybe I should document similar to my Using Docker Instead of Virtual Environments (venv) post but that’s for another day. In short, it allows me to have a Linux machine in Kubernetes in case I need to test out anything.

I called this file default-ubuntu_deployment.yaml and added it to the config directory of my Github repo. I’m using this naming convention to help my Github Action figure out what to do with the YAML. The naming convention used here is <K8 Namespace>-<K8 Name>_<K8 Type>.yaml so that I can use a central Github Action to manage all of Kubernetes deployments.

Setting Up Github Actions

The Kubernetes YAML is pretty simple so now we move onto the Github Actions.

Reusable Github Action

I started with a Github Action that I can reuse for other deployments in the future. I called this file do-k8-apply.yml and placed it in the .github/workflows directory of my repo.

name: Build, push, and deploy

# Controls when the action will run.
on:
  workflow_call:
    inputs:
      k8-name:
        required: true
        type: string
      k8-type:
        required: true
        type: string
      k8-namespace:
        required: false
        type: string
        default: default
    secrets:
      local_cluster_name:
        required: true
      local_token:
        required: true


# A workflow run is made up of one or more jobs that can run sequentially or in parallel.
jobs:
  # This workflow contains a single job called "build".
  build:
    # The type of runner that the job will run on.
    runs-on: ubuntu-latest

    defaults:
      run:
        working-directory: ${{ inputs.working-directory }}
    
    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:

    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it.
    - name: Checkout master
      uses: actions/checkout@main

    # Install doctl.
    - name: Install doctl
      uses: digitalocean/action-doctl@v2
      with:
        token: ${{ secrets.LOCAL_TOKEN }}
    
    - name: Save DigitalOcean kubeconfig with short-lived credentials
      run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 ${{ secrets.LOCAL_CLUSTER_NAME }}
    
    - name: Deploy to Kubernetes
      id: k8-deploy
      run: kubectl apply -f $GITHUB_WORKSPACE/config/${{ inputs.K8-NAMESPACE }}-${{ inputs.K8-NAME }}_${{ inputs.K8-TYPE }}.yaml

    - name: Verify deployment
      run: kubectl -n ${{ inputs.K8-NAMESPACE }} rollout status ${{ inputs.K8-TYPE }}/${{ inputs.K8-NAME }}

This workflow makes use of three input variables

  • k8-name
  • k8-type
  • k8-namespace

I’ve already set the default value of k8-namespace to default so it isn’t a required input. The other two variables have no default value so they are required. I also changed the run lines for Deploy to Kubernetes and Verify deployment to leverage the variables that I’ve defined. Other than these changes, the Github Action is similar to what was already provided in Enable Push-to-Deploy on DigitalOcean Kubernetes Using GitHub Actions.

Deployment Specific Github Action

The next step was to create another Action that will be triggered whenever I make changes to my Ubuntu deployment. I called this file ubuntu.yml and placed it in the .github/workflows of the repo.

name: Build, push, and deploy

# Controls when the action will run.
on:
  # Triggers the workflow on push request on the main branch for changes in the specified paths.
  push:
    branches:
      - main
    paths:
      - 'config/default-ubuntu_deployment.yaml'
      - '.github/workflows/ubuntu.yml'

# A workflow run is made up of one or more jobs that can run sequentially or in parallel.
jobs:
  # This workflow contains a single job called "build".
  deploy:
    uses: ./.github/workflows/do-k8-apply.yml
    with:
      k8-name: ubuntu
      k8-type: deployment
    secrets:
      local_token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
      local_cluster_name: ${{ secrets.CLUSTER_NAME }}

This starts with an on statement that looks for any pushes to the main branch. These pushes would require changes to the paths specified. In short, this will execute whenever I change this yml file or the default-ubuntu_deployment.yaml that I created above.

The jobs section contains a job definition called deploy that makes use of the do-k8-apply.yml created above. We supply that with values for the k8-name and k8-type variables.

Putting It All Together

Committing a change to the main branch of my repo that makes a change to either of the following files:

  • config/default-ubuntu_deployment.yaml
  • .github/workflows/ubuntu.yml

will trigger the following commands

  1. kubectl apply -f config/default-ubuntu_deployment.yaml
  2. kubectl -n default rollout status deployment/ubuntu

It’s a simplistic way of managing my Kubernetes in Github but now I can build upon this for future deployments.