Someone recently asked me “What is GitOps?”
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
- Install by running
- Minikube
- Install by running
brew install minikube
- Install by running
- 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
- Install by running
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.