How To Deploy a 3-Tier Application in EKS and Implement CI-CD with GitHub and ArgoCD.

Sami Banerjee
8 min readApr 17, 2023

--

Introduction:

“Containerization is this trend that’s taking over the world to allow people to run all kinds of different applications in a variety of different environments. When they do that, they need an orchestration solution in order to keep track of all of those containers and schedule them and orchestrate them. Kubernetes is an increasingly popular way to do that.” — Dan Kohn (Cloud Native Computing Foundation (CNCF), in a podcast with Gordon Haff)

WHAT ARE KUBERNETES AND EKS?🤔
Kubernetes or K8S is an open-source container orchestration tool it helps to deploy, manage, and scale containerized applications.
So, EKS is a managed Kubernetes service to run Kubernetes in the AWS cloud.

WHAT DOES A 3-TIER APPLICATION MEAN?
An application that has a presentation tier or user interface; the application tier, where data is processed; and the data tier, where the data associated with the application is stored and managed.

Now Let’s move on to our agenda, how to deploy our 3-tier application in EKS and implement CICD with GitHub Actions and ArgoCD. You can see the diagram below.

So first we need to create an application and containerized it and store that container images in a container image registry (like — Dockerhub, ECR, etc. Here I use DockerHub). Then we create an EKS cluster in AWS and then we automate the deployment process with CI/CD (GithubActions/ArgoCD).

Step 1:
Here I created a simple application using ReactJS(as front-end), NodeJs(Express as back-end), and MongoDB (as Database). This application has some basic features like login, register, update, and delete.
We containerize this application with Docker.

# back-end container
FROM node:16.20
WORKDIR /app
COPY . .
RUN cp .env.example.docker .env
RUN npm install
RUN npm install pm2 -g
EXPOSE 7770
ENTRYPOINT ["pm2-runtime", "index.js"]
# front-end container
FROM node:16.20 AS node-build
WORKDIR /app
COPY . .
RUN cp .env.example .env
RUN yarn
RUN yarn build

FROM nginx:alpine
WORKDIR /usr/share/nginx/html
RUN rm -rf ./*
COPY --from=node-build /app/build/ .
COPY default.conf /etc/nginx/conf.d/
ENTRYPOINT ["nginx", "-g", "daemon off;"]

Here is the GitHub repo — fe, be for reference.

Step 2:
Create all the necessary manifest files which were going to use for Kubernetes deployment inside a separate repo, later we will use this repo for CD (Continuous Deployment) purposes. I named this repo gitops, because we going to follow the well-known GitOps method here.

Step 3: Create an EKS cluster in AWS
Install AWS-CLI, and Kubectl, eksctl in your local system.

step A:
Create an IAM user with programmatic access.

I attached a custom policy (eks_full_access) that this user can access AWS EKS console.

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"eks:*"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "*",
"Condition": {
"StringEquals": {
"iam:PassedToService": "eks.amazonaws.com"
}
}
}
]
}

then create an Access-Key and Secret-Access-Key

use this commands to configure aws cli —

aws configure set aws_access_key_id AJFJVDYF.....
aws configure set aws_secret_access_key 2ttfdGD/uivIH.....
aws configure set default.region ap-northeast-1

step B:
Create two Roles
1. for the EKS cluster

2. for EKS node_group

step C: Create an EKS cluster

step D: Create NodeGroup

step E: Access EKS throuch you Terminal

aws eks update-kubeconfig --region ap-northeast-1 --name Test_Node
kubectl get ns

step F: Install add-ons in EKS cluster
Ingress-nginx controller:

Bofre this stape you have to issue a wildcard certificate from AWS ACM for your domain. Here in my case I issued a wildcard certificate for my domain
*.yourdomain.com (i.g *.showyrskills.in)

1. Download the deploy.yaml template:
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/aws/nlb-with-tls-termination/deploy.yaml
2. Edit the file and change the VPC CIDR in use for the Kubernetes cluster:
proxy-real-ip-cidr: XXX.XXX.XXX/XX
3. Change the AWS Certificate Manager (ACM) ID as well:
arn:aws:acm:us-west-2:XXXXXXXX:certificate/XXXXXX-XXXXXXX-XXXXXXX-XXXXXXXX
4. Deploy the manifest:
kubectl apply -f deploy.yaml




** reference: https://kubernetes.github.io/ingress-nginx/deploy/#aws

Now check in AWS>EC2>LoadBalancer you will see a load balancer created by NGINX ingress-controller type Network. Now point your domain to this DNS from AWS Route53 or CPanel or where your domain is for hosting.

Here I have 3 domains -
showyrskills.in => front end
api.showyrskills.in => back end
argo.showyrskills.in => argocd console

ArgoCD:

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

apiVersion: v1
kind: Service
metadata:
name: argo-service
namespace: argocd
spec:
type: NodePort
selector:
app.kubernetes.io/name: argocd-server
ports:
- name: http
port: 80 # Service Port
targetPort: 8080 # Container Port
nodePort: 31880
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
name: argocd-ingress
namespace: argocd
spec:
rules:
- host: argo.showyrskills.in
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 80
kubectl apply -f argo-svc-ingress.yaml

try to log in to ArgoCD console;
username: admin
password: kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d #for linux user

kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" 

# open https://www.base64decode.org/ > and enter the return value.
# for windows system.

Step 4: Configure CI/CD:

CI -
Add .github/workflows/ci.yml in fe and be repo.

#fe/.github/workflows/ci.yaml
name: Build and Push to Docker-Hub

on:
push:
branches:
- fe-ci

jobs:
build:
name: Build Docker Image and Push into Docker-Hub
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/e-commerce-front-end:${{ github.sha }}

- name: The current paths
run:
echo $PWD
- name: Checkout GitOps
uses: actions/checkout@v3
with:
repository: TryToLearnProgramming/gitops
path: ./gitops
ref: k-argo
token: ${{ secrets.REPO_PAT }}

- name: update kustomization in gitops
run: |
cd gitops
sed -Ei "/\-\ name\:\ sami203\/e\-commerce\-front\-end$/{n;s/\w+$/${{ github.sha }}/}" ./deployments/node-app/kustomization.yaml
git config --global user.email "samivivake@gmail.com"
git config --global user.name "gitops"
git add .
git commit -m "Deploying latest image"
git pull && git push
#be.github/workflows/ci.yaml
name: Build and Push to Docker-Hub

on:
push:
branches:
- be-ci
# paths:
# - .github/**
# - simpleapp/**

jobs:
build:
name: Build Docker Image and Push into Docker-Hub
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./dockerfile.back-end
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/e-commerce-back-end:${{ github.sha }}

- name: The current paths
run:
echo $PWD
- name: Checkout GitOps
uses: actions/checkout@v3
with:
repository: TryToLearnProgramming/gitops
path: ./gitops
ref: k-argo
token: ${{ secrets.REPO_PAT }}

- name: update kustomization in gitops
run: |
cd gitops
sed -Ei "/\-\ name\:\ sami203\/e\-commerce\-back\-end$/{n;s/\w+$/${{ github.sha }}/}" ./deployments/node-app/kustomization.yaml
git config --global user.email "samivivake@gmail.com"
git config --global user.name "gitops"
git add .
git commit -m "Deploying latest image"
git pull && git push

you can change this accordingly.

then configure Actions secrets and then wait to push your code to the repo.

GitHub action will build Docker Image and then push it into DockerHub then it will update the image tag in deployments/node-app/kustomization.yaml

CD —
As previously discussed, we going to use gitops as our CD repo, ArgoCD will read the changes and deploy the changes into the EKS. So how it will detect the changes? for this we use kustomization. You can read more about Kustomize on there official page (click_here).

# deployment kustomization.yaml

namespace: node-app
resources:
- ../../base/apps/be
- ../../base/apps/fe
- ../../base/apps/mongo
- be-ingress.yaml
- fe-ingress.yaml
- be-cm.yaml
patchesStrategicMerge:
- be-deploy.yaml
- fe-deploy.yaml
images:
- name: sami203/e-commerce-front-end
newTag: df9ed947fc47811d1a7c827cf42772d98418e184
- name: sami203/e-commerce-back-end
newTag: 701e32a54ffa0fe3435e368d3f470c8e0290d0d1

and inside every resource directory, there are different kustomization.yaml files

# kustomization.yaml
resources:
- deploy.yaml
- service.yaml

push this repo in github, then run the command to create the argocd application.

kubectl apply -f deployments/node-app/app.yaml
# this is a ArgoCD application manifest file.

you can see the application in the ArgoCD console -

*** Now push the code into fe and be the repo that you created earlier. Go to the Actions tab, you will see that the pipeline is running

let's wait till complete.

Go to ArgCD and you will see the deployment process is started automatically.

Wait for some time then try to access your front-end and back-end sites.

register a user and you are able to log in to the app.

Congratulations!!! You successfully deploy an Application in EKS 🎉🥳🎉.

But one thing, in starting I said this is a 3-tier application right? so, where is the database ??? 🙄. Don’t worry we already have a database pod running, and the manifest files are inside gitops/base/apps/mongo/. In base/app, we have basic application manifests deployment, service, secrets, pv, pvc. And in gitops/deployments/node-app/ we have application manifests ingress, configmap, argocd-application which can be changed quickly if required.

For a better understanding please follow the GitHub repositories.

Thank You

follow for more content and keep reading 😊.

--

--