ArgoCD (GitOps) + K8s + CI/CD: How to deploy application on top of Kubernetes using GitOps.

Sarvjeet Jain
10 min readMar 28, 2023

WELCOME

Here, we’ll look at How to use ArgoCD and GitLab to deploy our application on top of a Kubernetes cluster.

GitOps is considered a developer-centric experience that allows team members to manage and control the internal capabilities of cloud-native applications. It provides developers with an application development strategy that can be used to initiate pull requests as a means of accelerating and simplifying infrastructure changes. GitOps functions as a tool to help businesses deliver new features by automating the Continuous Delivery of cloud-native applications.

CONTENTS:

  • Architecture
  • Application Code Repository
  • GitOps Repository
  • Kubernetes cluster part
  • ArgoCD Setup
  • GitOps Setup
  • Trigger

ARCHITECTURE:

The architecture that will be illustrated in this article is shown in the photo below.

Let’s quickly comprehend this architecture.

  • APPLICATION CODE REPOSITORY: Our developers push the application code to this GitLab repository, and the DevOps team created a Pipeline to build and push a docker image of our application to the GitLab Container Registry.
  • GITOPS REPOSITORY: The ArgoCD repository where our Kubernetes manifest files are located (Also we can it Promotional Branch Strategy).

Like the youngster in the above picture who also not understood what does this Repo Contains, I understand that you are perplexed ……

Don’t worry will discuss it in more detail below.

  • ARGOCD: Using the Helm chart, I installed Argocd on top of the K8s cluster and linked it to the GitOps repository that will be used to deploy the application.
  • AWS EKS: Kuberenetes Cluster managed by AWS Cloud.

APPLICATION CODE REPOSITORY:

  • Create one Repo named as “Application” in your GitLab Project.
  • Ask your Developers to push the Application code in this Repository in main branch.

For testing purpose you can use this simple nodejs application, it just displays “Welcome”:

  • Create one DockerFile in the main branch to build a Docker Image of your application (It varys according to the Language that we use to create application) .Like for the above nodejs application below is the Dockerfile you can use:
FROM node:18
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 4000
CMD [ "npm", "run", "dev" ]

It’s time to build the Docker image now that the Dockerfile has been created. To accomplish this, a CI/CD pipeline must be set up in GitLab Repository. Pipeline definition in GitLab CI is stored in .gitlab-ci.yml file in the main branch of the repository.

GitLab also provides us the Container Registry where we can store our Docker images, in this practical we will see how to use it.

Purpose of CI/CD: Every time when one of our developers pushes, commits, or merges code into the main branch of our repository, we want the Docker image to automatically create and push to the container registry. We must set up a CI/CD pipeline for this automation, which will start when the repository’s main branch triggers.

  • Create “.gitlab-ci.yml” file in the main branch of this repo. Here we will add one stage in this file named as “build-push”, which will build the Dockerfile and push the image to the GitLab Container Registry.
stages:
- build-push

Here is the “build-push stage” configuration:

image: docker:19.03.12

variables:
DOCKER_HOST: tcp://docker:2375/
DOCKER_TLS_CERTDIR: ""

services:
- docker:19.03.12-dind

build:
stage: build-push
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:latest
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
  • $CI_REGISTRY_USER, $CI_REGISTRY_PASSWORD, $CI_REGISTRY_IMAGE and $CI_REGISTRY, these are the GitLab default variables, we don’t have to define this in our GitLab.

Once you created this file, you can see in “Gitlab Repo” >> “CI/CD” >> “Pipelines” one pipeline is running.

Pipeline GitLab

If there are no errors, then you can see the Pipeline status as “passed”.

Once pipeline is succeeded you can see your Docker image inside “GitLab Repo” >> “Packages and registries” >> “Container Registry” .

You’re done with your Application Repo; let’s move on to the GitOps Repo.

GITOPS REPOSITORY:

Let’s clear the confusion of that Youngster Boy and you too……

  • Create one Repo names as “GitOps” inside your GitLab Project.
  • Inside this Repo we gonna create three directories or folders named as “base”, “envs” and “variants”.

BASE folder:

In the “base” directory, we will put the common manifest file of k8s + the kustomization.yaml.

Below are the files that are present in base folder:

GitOps Base Directory
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nodejs
template:
metadata:
labels:
app: nodejs
spec:
containers:
- name: application-container
image: registry.gitlab.com/<GITLAB-PROJECT>/<IMAGE-NAME>:latest
ports:
- containerPort: 80
imagePullSecrets:
- name: gitlab-creds-secret
  • service.yaml:
apiVersion: v1
kind: Service
metadata:
name: test
spec:
type: LoadBalancer
selector:
app: nodejs
ports:
- protocol: TCP
port: 80
targetPort: 3000
  • kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./deployment.yaml
- ./service.yaml

This are the common configuration of all the files, we can change replicas, image, namespace , etc according to our environment to environment, but not in this base directory.

Kustomize: Kustomize provides a way to overlay changes onto a base set of Kubernetes YAML files, allowing for customization without having to edit the base YAML files. This makes it easier to manage changes and ensures that changes made to the YAML files are versioned and tracked through Git. Kustomize is a part of the Kubernetes project and is included in the kubectl tool. It can be used to manage and customize Kubernetes resources, such as deployments, services, and config maps, as well as create custom Kubernetes resources.

ENVS Folder:

  • Inside the “envs” folder we can define all the environments that we want to add for our application, like in this blog I created three environments “dev”, “prod” and “test”. So I created three folders inside the “envs” folder names as “dev”, “prod” and “test”.
GitOps envs directory

Now, much like in a development environment, I want one K8s replica of my application, and I also want to deploy using a different Docker image. Without altering the common manifest files that are already present in the base directory, this can be accomplished using kustomization.

  • Create below files inside the “envs/dev” folder:
GitOps Dev environment
  • replicas.yml: Define replicas
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-deployment
spec:
replicas: 1
  • version.yml: Define image
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-deployment
spec:
template:
spec:
containers:
- name: application-container
image: registry.gitlab.com/<GITLAB-PROJECT>/<IMAGE>-dev:latest
  • kustomization.yml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: example-dev
namePrefix: dev-

resources:
- ../../base

#components:
# - ../../variants/prod
# - ../../variants/asia

patchesStrategicMerge:
- replicas.yml
- version.yml

You can see that we defined the “resources” block here inside the Kustomization.yml file, which means that it will take the common manifest from the “../../base” folder and update on the fly with the files “replicas.yml” and “version.yml” that we defined in “patchesStrategicMerge” during deploying. Additionally, we define the namespace and namePrefix of the pod in this section, where we intend to deploy the development application.

Now, much like in a production environment, I want two K8s replica of my application, and I also want to deploy using a different Docker image. Without altering the common manifest files that are already present in the base directory, this can be accomplished using kustomization.

  • Create below files inside the “envs/prod” folder:
  • replicas.yml: Define replicas
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-deployment
spec:
replicas: 2
  • version.yml: Define image
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-deployment
spec:
template:
spec:
containers:
- name: application-container
image: registry.gitlab.com/<GITLAB-PROJECT>/<IMAGE>:latest

Here I used the image that we already created above it application repo using the Pipeline.

  • kustomization.yml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: example-prod
namePrefix: prod-

resources:
- ../../base

#components:
# - ../../variants/prod
# - ../../variants/asia

patchesStrategicMerge:
- replicas.yml
- version.yml

VARIANTS FOLDER:

We can specify the many variants where we want the identical changes, such as “prod” and “non-prod,” in the variants folder. Our production environment can be included in prod, while our development and test environments can be included in non-prod.

For this article I’m skipping the variants section but it contains same files like the above but for different-different variants.

You’re done with your GitOps Repo; let’s move on to the Kubernetes part.

KUBERNETES CLUSTER PART:

Prior to deploying the aforementioned environments, we must first have to create the namespaces and the k8s Secret named as “gitlab-creds-secret” in the same namespace, which provides the Docker login information for our gitlab container registry and enables our pods to pull the image.

  • Run below command to create the namespaces
~> kubectl create ns example-dev
~> kubectl create ns example-prod
  • Now we have to create a cred Secret.

Please refer to this blog to create Secret:

MAKE SURE TO CREATE IT IN BOTH ABOVE CREATED NAMESPACES WITH THE NAME “gitlab-creds-secret”

ARGOCD SETUP:

Here, we’ll deploy Argocd on top of our EKS cluster using the Helm chart.

  • Run below commands:
~> kubectl create namespace argocd
~> helm repo add argo https://argoproj.github.io/argo-helm
~> helm install argocd -n argocd argo/argocd - values values.yaml

You can get Values.yaml from the below link:

  • You can see running pods inside the argocd namespace:
  • Now either you can create the ingress to expose the argocd if you have to DNS or you can expose the argocd service to the “LoadBalancer”.
  • Edit the Serivce named as “argocd-server” and update the type with “LoadBalancer”.
  • You will get the “external-IP” after doing this.

Now you access argocd GUI from the browser using the link :
http://<external-ip>:80

You can see the page like above.

To get the password, run below command:

kubectl -n argocd get secret argocd-secret -o jsonpath="{.data.clearPassword}" | base64 -d

“Username”: “admin”

You’re done with Argocd Setup

GITOPS SETUP:

It’s time to deploy the application using Argocd after you’ve set it up.

  • Go inside “Argocd” >> “Settings” >> “Repositories”
  • Here you want to add your gitlab repository to connect it with ArgoCD.

Add your Repository URL, Username and Password (Access Token, that you used in to create Docker creds secret in above steps).

  • Click on connect.

Once everything is okay, you can see the “CONNECTION STATUS” as “Successfull”.

  • Now go to “Argocd” >> “Applications” and click on “+ NEW APP”.

“Application Name”: such as example-dev-app,

“Project Name”: default

“SYNC POLICY”: Automatic and select both the below ticks.

“SYNC OPTIONS”: Select the above ticks.

“Repository URL”: Automatic comes up if you added it before in Argocd

“Path”: Automatic comes up all the paths of your GitOps repo, select the one which you wanna use, here for now I have used “envs/dev”.

“Cluster URL”: Automatic comes up if you deployed argocd on top of your cluster.

“Namespace”: Define the namespace where you wanna deploy this app, we already created this ns in above steps.

  • Click on “CREATE”.

It will start deploying your application using the configuration you provided in “envs/dev” folder. Once all done you can see the output like below:

Run below commands to see the application is deployed or not.

~> kubectl get pods -n example-dev
~> kubectl get svc -n example-dev

Once everything is okay, you can use your service LoadBalancer to access your app in browser.

Now you can create the different app in argocd to deploy the prod environment too, just you have to change the “path” to “envs/prod”.

You’re done with GitOps Setup

TRIGGER:

Now that the setup is complete. The GitOps Repo must now be manually activated each time your Pipeline triggers in the application repository in order to re-deploy the application with the most recent changes. After your GitOps repository is triggered, ArgoCD will begin syncing it automatically.

AUTOMATIC TRIGGER:

  • For automatic trigger of GitOps repo using the Pipeline, you have to add one more stage in your “.gitlab-ci.yml”:
deploy-dev:
stage: deploy-dev
image: alpine:3.8
before_script:
- apk add --no-cache git curl bash
- curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
- mv kustomize /usr/local/bin/
- git remote set-url origin @gitlab.com/<USER>/gitops.git">https://${CI_USERNAME}:${CI_PUSH_TOKEN}@gitlab.com/<USER>/gitops.git
- git config --global user.email "gitlab@gitlab.com"
- git config --global user.name "GitLab CI/CD"
script:
- git checkout -B master
- cd deployment/dev
- kustomize edit set image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- cat kustomization.yaml
- git commit -am '[skip ci] DEV image update'
- git push origin main
only:
- main

THANK YOU SO MUCH FOR READING THIS ARTICLE. I HOPE IT HELPED YOU TO UNDERSTAND HOW GITOPS WORKS.

PLEASE ADD COMMENTS OR PING ME IN LINKDIN IF ANY CONCERNS YOU HAVE.

Linkdin Profile:-

https://www.linkedin.com/in/sarvjeet-jain-50a3671ab

--

--