Devops on Three tier architecture.


This article is explanation of my recent project on Continuous Integration, Continuous Deployment. I have utilized various kinds of tools like Github, Github actions, Docker, ArgoCD, Kubernetes[Minikube].
Pre-requisite :
Github, Github actions.
Docker.
Helm chart installation and commands.
Kubernetes and yaml files.
Minikube configuration.
Table of contents :
Checking out code from Github repository.
Build and Update Docker image.
Deep diving Kubernetes manifests.
ArgoCD configuration.
Github actions.
1 . Checking out code from Github repository.
I have forked a repository which consists code of MERN stack application [Chat-app], created by Md. Afzal Hassan Ehsani. Then started adding helm chart and other required files and folders.
2 . Building and Updating Docker image.
Docker files for both frontend and backend are already written by owner, so I have utilized the same. First and foremost step is to build and push image to Docker Hub.
Frontend Dockerfile
The Dockerfile of frontend is stored in frontend folder, open command prompt, move to frontend folder and build the frontend image using the following command :
docker build -t <Name-of-your-docker-hub-rep>:<Name-of-the image> <Path-to-Dockerfile>
In my case the command was something like this :
docker build -t nithin8/chat-app-frontend .
Push it to Docker Hub using the below command :
docker push <Name-of-your-docker-hub-rep>:<Name-of-the-image>:<Version>
In my case the command was something like this :
docker push nithin8:chat-app-frontend:latest
Backend Dockerfile
The Dockerfile of backend is stored in backend folder, open command prompt, move to backend folder and build the backend image using the following command :
docker build -t <Name-of-your-docker-hub-rep>:<Name-of-the image> <Path-to-Dockerfile>
In my case the command was something like this :
docker build -t nithin8/chat-app-backend.
Push it to Docker Hub using the below command :
docker push <Name-of-your-docker-hub-rep>:<Name-of-the-image>:<Version>
In my case the command was something like this :
docker push nithin8:chat-app-backend:latest
In case you have queries regarding Docker refer to this blog .
2 . Deep diving Kubernetes manifests.
Kubernetes manifests are being included in the existing forked repository, but I have used helm chart for configuring and maintenance of manifest files, refer my repository for more information.
Each micro-service has their own deployment, service yaml files, lets deep dive into each file to understand the concept behind them.
frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: chat-app
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: nithin8/chat-app-frontend:{{.Values.image.tag}}
ports:
- containerPort: 80
Explanation :
apiVersion: refers which version of kubenetes API is used to create the resource of kind deployment.
kind: refers to the type of resource created.
metadata: name here points to name of the deployment, name space is where the deployment will reside.
spec: defines the desired state of the deployment.
replicas: are the number of pods that the deployment has to maintain.
mathcLables: app helps in identification of pods that belong to this deployment.
labels: inside the template is used for labeling the pod created by deployment.
containers: list of containers that run inside the pod.
name: is name of the container.
image: is the path of image of container in docker hub.
ports: specifies the port where container listens.
The same is for backend-deployment and mongo-deployment also.
service.yaml
apiVersion: v1
kind: Service
metadata:
name: backend
namespace: chat-app
spec:
selector:
app: backend
ports:
- protocol: TCP
port: 5001
targetPort: 5001
Explanation:
name: refers to the name of the resource being created.
app in selector: is the name of pods to which service routes the traffic.
ports: lists the ports that service exposes.
protocol: is the type of protocol that service uses.
port: specifies the port that service listens in the cluster.
targetPort: specifies where the traffic is forwarded to.
The same is for frontend-service and mongo-service as well.
namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: chat-app
- name: is the name of the name space where the pods, nodes and services reside.
I have used minikube cluster to experiment on the project.
Commands used to create and manage helm chart :
- helm install <name-of-chart> <path-of-chart> → helm install chat-app-helm chat-app-helm/
Then you can see your services, pods and cluster up and running.
3 . ArgoCD configuration.
Install argocd inside the kubernetes cluster using the documentation.
Steps after entering into the argocd UI :
Username is admin.
Get password by running the command
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
.Click on New app on left corner of the web page.
Enter application name, select project name as default, select sync policy to automatic.
Use your repository url which consists of k8 manifests or my repository.
It automatically detects the helm path.
Fill the name space field with “chat-app“.
And click on create.
And you are done integrating ArgoCD with kubernetes cluster, verify it by clicking on the app in the home of ArgoCD UI.
ArgoCD believes github as single source of truth and gets to desired state whenever changes are made to github repo.
4 . Github actions.
I had configured Githb actions to trigger the pipeline job when ever the push event is made to the repository. The ci.yaml file is located in .github/workflows folder inside my repository.
name: CI/CD
on:
push:
branches:
- main
paths-ignore:
- 'chat-app-helm/**'
- ''
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push frontend Docker image
uses: docker/build-push-action@v6
with:
context: ./frontend # Set context to the frontend directory
file: ./frontend/Dockerfile # Path to Dockerfile inside the frontend directory
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/chat-app-frontend:${{ github.run_id }}
- name: Build and Push backend Docker image
uses: docker/build-push-action@v6
with:
context: ./backend # Use the root of the repo as context for backend
file: ./backend/Dockerfile # Path to Dockerfile inside the backend directory
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/chat-app-backend:${{ github.run_id }}
Update-NewTag-in-Helm-Chart:
runs-on: ubuntu-latest
needs: docker
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{secrets.TOKEN}}
- name: Update tag in helm chart
run: |
sed -i 's/tag: .*/tag: "${{github.run_id}}"/' chat-app-helm/values.yaml
- name: Commit and push changes
run: |
git config --global user.email "nithinkasturi8@gmail.com"
git config --global user.name "Nithin kasturi"
git add chat-app-helm/values.yaml
git commit -m "Update tag in helm chart"
git push
Explanation:
name: specifies the name of the pipeline.
on: specifies when to trigger the pipeline.
jobs: defines the jobs that are to be performed on tiggering.
docker: is the name of the stage inside the jobs, it can be any thing.
runs-on: is the operating system where this stage runs on.
steps: are used to build and push the docker image.
Set the secrets by navigating to settings→secrets and variables→actions.
Update-NewTag-in-Helm-Chart: is another job which updates the image tag in values.yaml and pushes the changes to github.
So, whenever push event occurs on github source code other than the chat-app-helm folder the jobs are triggered.
Finally we have successfully implemented the Continuous Integration and Continuous deployment of a three tier architecture.
Subscribe to my newsletter
Read articles from Kasturi Nithin directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Kasturi Nithin
Kasturi Nithin
Exploring something new.