Creating GitOps Workflow with ArgoCD, Kustomize and GitHub Actions


Author profile picture

What is GitOps ?

The term was first coined by Weaveworks in a popular article from August 2017. The problem it intends to solve was how to efficiently and safely deploy a Kubernetes application.

The main tenants of this philosophy are:

  • Use a Git repository as the single source of truth.
  • Any change is made in the form of a Git commit.
  • When the application state differ from the desired state (that is: what’s in Git), a reconciliation loop detect the drift and try to reach this state.directly from Weaveworks

The reason why GitOps is especially suited to deploy cloud native applications is that Kubernetes follows the same declarative way of doing things:

  • You submit a bunch of Kubernetes objects through a declarative document as YAML or JSON.
  • Kubernetes Operators process are endlessly evaluating the difference between the submitted objects and their real state in the cluster, adding, modifying or deleting them as needed.

When you understand the concept, you can apply the GitOps way not only to Kubernetes application but to anythings described with code, for example code infrastructure.

But what is the difference between GitOps and the final deploy step of my CICD pipeline ?

Very often your pipeline is triggered by a change in code (if not, it really should!), so it’s in fact the same starting point as GitOps. Your final pipeline step then run a command like kubectl apply. You so run an imperative command to reach the desired state.

In GitOps, you won’t do this: it’s an external tool that detect the drift in your Git repository and will run theses commands for you. You can think of it as a “pulling” way of doing things.

Let’s look into these tools.

What tools are available to implement GitOps ?

Most commonly used tools are Flux from Weaveworks and ArgoCD. You may find extensive comparisons of both tools but to sum it up:

  • Flux can only observe one repository at a time, meaning you have generally one flux instance running for each application.
  • ArgoCD may observe multiple repositories, comes with a GUI dashboard, may be federated with an identity provider: it’s more enterprise ready.

In this article we will look to implement a GitOps model using ArgoCD.

Our GitOps workflow

Tools

We will implement a GitOps scenario using:

  • ArgoCD as the GitOps tool
  • GitHub Actions as the CICD pipeline
  • Kustomize to describe application deployments

Starting point

Code is available on GitHub. You will find step by step instructions on how to make it works for you by installing a Minikube cluster, ArgoCD and setup the required security tokens.

Application

Our application is a simple Go application displaying the good old “Hello World” string.

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
)

const PORT = 8080

func main() {
	startServer(handler)
}

func startServer(handler func(http.ResponseWriter, *http.Request)){
	http.HandleFunc("/", handler)
	log.Printf("starting server...")
	http.ListenAndServe(fmt.Sprintf(":%d", PORT), nil)
}

func handler(w http.ResponseWriter, r *http.Request){
	log.Printf("received request from %s", r.Header.Get("User-Agent"))
	host, err := os.Hostname()
	if err != nil {
		host = "unknown host"
	}
	resp := fmt.Sprintf("Hello from %s", host)
	_, err = w.Write([]byte(resp))
	if err != nil {
		log.Panicf("not able to write http output: %s", err)
	}
}

The associated Dockerfile is quite simple, building the application then running it in a linux container

FROM golang:1.14 as build
WORKDIR /build
COPY . .
RUN CGO_ENABLED=0 go build -o hello-gitops cmd/main.go

FROM alpine:3.12
EXPOSE 8080
WORKDIR /app
COPY --from=build /build/hello-gitops .
CMD ["./hello-gitops"]

Code pipeline

Our pipeline use two job:

  • One for running tests, building and pushing the container on Dockerhub
  • The seconde one will edit the Kustomize patch to bump the expected container tag to the new Docker image and then commit these changes.
name: Go

on:
  push:
    branches: [ master ]

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    
    steps:
    - name: Set up Go 1.x
      uses: actions/[email protected]
      with:
        go-version: ^1.14
      
    - name: Check out code
      uses: actions/[email protected]

    - name: Test
      run: |
        CGO_ENABLED=0 go test ./...
        
    - name: Build and push Docker image
      uses: docker/[email protected]
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
        repository: ${{ secrets.DOCKER_USERNAME }}/hello-gitops
        tags: ${{ github.sha }}, latest

  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    needs: build

    steps:
    - name: Check out code
      uses: actions/[email protected]

    - name: Setup Kustomize
      uses: imranismail/[email protected]
      with:
        kustomize-version: "3.6.1"

    - name: Update Kubernetes resources
      env:
        DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
      run: |
       cd kustomize/base
       kustomize edit set image hello-gitops=$DOCKER_USERNAME/hello-gitops:$GITHUB_SHA
       cat kustomization.yaml
        
    - name: Commit files
      run: |
        git config --local user.email "[email protected]"
        git config --local user.name "GitHub Action"
        git commit -am "Bump docker tag"

    - name: Push changes
      uses: ad-m/[email protected]
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}

ArgoCD

ArgoCD must be configured to observe our Git repository. Configuration is rather straightforward and can be done in the included GUI. You need to specify the relative path of the Kustomize patch to use though.

GitOps magic

Note that at the end of the GitHub Actions pipeline, we don’t run any imperative command to deploy our application, we just changed our container version using Kustomize and auto-pushed these changes into our repository.

If you do any code change, the pipeline is triggered and a new Docker image is pushed, the container version is updated and ArgoCD should catch the change. Everything should be green by then.

Conclusion

GitOps is a powerful and intelligible way of making change to anything. I think it could be considered as the logical continuation of the “* as code” we see everywhere and the massive industry trend to move to more declarative, easier to understand models.

Previously published at https://medium.com/@emmanuel.sys/how-to-build-a-gitops-workflow-with-argocd-kustomize-and-github-actions-f919e7443295

Tags

The Noonification banner

Subscribe to get your daily round-up of top tech stories!

Don't forget to share

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *