Skip to main content

What is GitOps?

·1362 words·7 mins
Gitops Kubernetes Argocd Ci/Cd

Someone recently asked me “What is GitOps?”

First of all, when we talk about GitOps we usually do this in combination with Kubernetes. This is what I will do in this article. However, GitOps could be applied to anything - not just Kubernetes.

GitOps is a deployment model. To understand what is new with GitOps I will first describe a traditional deployment model. In a traditional deployment model we usually work in an imperative way. We most likely set up a CI/CD pipeline with a number of steps that must happen in a certain order for our code to end up deployed in the target cluster. If the steps happen in a different order then nothing makes sense.

GitOps on the other hand replaces the CD part of CI/CD. It is a declarative deployment model. In GitOps we install a component in our cluster that handles deployments for us. Our goal is to describe (declaratively) what we want our applications to look like, and the GitOps component in our cluster makes sure to fulfill our wishes.

When a Kubernetes cluster is the destination for our application, then our goal is to create the Kubernetes manifests that describe our application and to make sure these end up in a git repository. Then our GitOps component has the responsibility of watching our git repository and act on any changes that it sees. Its job is to make sure what is deployed in the cluster exactly matches what it sees in the repository.

In this article I will use Argo CD as the GitOps component.

Get started with GitOps in 5(ish) minutes
#

Prerequisites
#

To follow along, there are a few requirements that must be in place. To simplify my own life I will assume that you are using macOS. Install the following tools

  • GitHub CLI
    • Install by running brew install gh
    • Authenticate by running gh auth login
  • Minikube
    • Install by running brew install minikube
  • Docker
    • Install by following the guide for Intel chip or Apple silicon
  • kubectl
    • Install by following the guide for Intel chip or Apple silicon
  • Argo CD CLI
    • Install by running brew install argocd

Set up a git repository
#

The first part of the word “GitOps” is “Git”. So that seems like a good place to start. I begin by creating a new empty git repository on my local computer

$ mkdir get-started-with-gitops
$ cd get-started-with-gitops
$ git init --initial-branch main

Next, I add the Kubernetes manifests that define my application. To keep things simple my application will consist of a deployment and a service. I will start with the deployment:

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: server
  labels:
    app: server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: server
  template:
    metadata:
      labels:
        app: server
    spec:
      containers:
        - name: server
          image: nginx:1.22.1
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: "250m"
              memory: "64Mi"
            limits:
              cpu: "500m"
              memory: "128Mi"

This deployment will create three replicas (replicas: 3) for my server container (name: server) that runs Nginx (image: nginx:1.22.1). Each replica exposes port 80 for HTTP traffic (containerPort: 80). I have added resource requests and limits, because my VS Code suggested this was a good idea.

Next I add my service:

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: webservice
spec:
  selector:
    app: server
  ports:
    - protocol: TCP
      port: 80

This service will target my server deployment (selector.app: server). It will target port 80. When a user accesses my service the traffic will be forwarded to one of my three pods.

I add both of these files (deployment.yaml and service.yaml) to a directory called dev. My repository now looks like this:

$ tree .
.
└── dev
    ├── deployment.yaml
    └── service.yaml

1 directory, 2 files

I use the GitHub CLI to create my repository on GitHub and push my code:

$ git add .
$ git commit -m "Initial commit"

$ gh repo create get-started-with-gitops \
  --public \
  --remote origin \
  --description "Getting started with GitOps" \
  --source . \
  --push

Set up a cluster and install Argo CD
#

I use minikube to set up a local kubernetes cluster:

$ minikube start

That was easy! Now I install Argo CD in my cluster:

$ kubectl create namespace argocd
$ kubectl apply -n argocd -f kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

That was also easy! See, this might take 5 minutes after all.

Configure Argo CD
#

To communicate with the Argo CD API server it must be exposed in some way. To simplify for myself I set an environment variable that adds required flags to all argocd commands I run:

$ export ARGOCD_OPTS='--port-forward --port-forward-namespace argocd'

The username to access Argo CD is admin, but to know the password I look at the contents of a secret that Argo CD set up for me:

PASSWORD=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo)

Now I am ready to login to Argo CD using the CLI:

$ argocd login \
    --name gitops \
    --port-forward \
    --username admin \
    --password $PASSWORD

Create an application in Argo CD
#

Time to activate GitOps! I create an application named my-app that uses my GitHub repository and my local minikube cluster, and I activate auto synchronization so that Argo CD automatically deploys my application and any new version of it in the future:

$ REPO_URL=$(gh repo view --json url --jq .url)
$ argocd app create my-app \
    --repo $REPO_URL \
    --path dev \
    --dest-namespace default \
    --dest-server https://kubernetes.default.svc \
    --auto-prune \
    --self-heal \
    --sync-policy auto

I view the status of my application with:

$ argocd app get my-app

I get the following output:

Name:               my-app
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          default
URL:                https://127.0.0.1:49296/applications/my-app
Repo:               https://github.com/<github username>/get-started-with-gitops
Target:
Path:               dev
SyncWindow:         Sync Allowed
Sync Policy:        Automated (Prune)
Sync Status:        Synced to  (6840386)
Health Status:      Healthy

GROUP  KIND        NAMESPACE  NAME        STATUS  HEALTH   HOOK  MESSAGE
       Service     default    webservice  Synced  Healthy        service/webservice created
apps   Deployment  default    server      Synced  Healthy        deployment.apps/server created

From the output I see that my Sync Status is Synced to (6840386) where the number in the parenthesis is the first seven characters of the commit SHA. My Health Status is Healthy.

I can also see that I have three instances of my Nginx pod in my cluster:

$ kubectl get pods

I get the following output:

NAME                     READY   STATUS    RESTARTS   AGE
server-6cbf7888c-8cwcx   1/1     Running   0          1m6s
server-6cbf7888c-h5cmn   1/1     Running   0          1m6s
server-6cbf7888c-stvkm   1/1     Running   0          1m6s

Three pods, just like I wanted. It seems like GitOps is working!

Deploy a new version
#

I will make a simple change to my application. Instead of running three pods I would like to run five pods. Can you imaging? Five pods! I make the required change in dev/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: server
  labels:
    app: server
spec:
  replicas: 5 # <--- this is the update
  # ...
  # the rest of the file is the same as before
  # ...

I commit my changes to main:

$ git add . && git commit -m "Run five pods" && git push

Argo CD syncs my application every three minutes by default. I said this guide would take five minutes, and now we might have to wait three minutes for an update? Outrageous! You can configure this behavior or trigger Argo CD to run a sync. For now I will just use the magic of editing and see if something has happened:

$ kubectl get pods

Now I get the following response:

server-6cbf7888c-4n2w9   1/1     Running   0          38s
server-6cbf7888c-8cwcx   1/1     Running   0          4m
server-6cbf7888c-h5cmn   1/1     Running   0          4m
server-6cbf7888c-mb2zk   1/1     Running   0          38s
server-6cbf7888c-stvkm   1/1     Running   0          4m

It worked, I now have five running pods! GitOps!

Clean up
#

I remove my application from Argo CD:

$ argocd app delete my-app --yes

I shut down my minikube cluster:

$ minikube stop

Closing thoughts
#

What we have seen in action is the basic principles of GitOps. This article describes the happy-path of GitOps where we skip many of the issues related to actually running this in production. Those issues vary from application to application, and we will not try to cover that in a general way here. I might revisit the GitOps topic in future articles.

Mattias Fjellström
Author
Mattias Fjellström
Cloud architect consultant and an HashiCorp Ambassador