3-Tier Language Voting App on EKS : A Full Guide πŸ˜„

3-Tier Language Voting App on EKS : A Full Guide πŸ˜„

Β·

12 min read

In this project we will deploy a 3-Tier Language-Vote-App using AWS EKS.


Prerequisites

Before starting the project you should have these things in your system :-

  • Account on AWS

  • Account on GitHub

  • Code (we will use code from this repository) : click here for code


STEP 1: Launch Instance

  • Create AWS EC2 instance

Name

Launch

Instance

  • Connect this instance through SSH

  • After successfully connecting to the EC2 instance, it will look like this

Connect Interface


STEP 2: Git

  • Now clone git repo, for this use command as follow :-

git clone https://github.com/sudhajobs0107/EKS-voting-app-3-tier.git

Git-Clone


STEP 3: Build & Push API Image To ECR

  • Now we will build API image. So first we will install docker for this use command :-

sudo apt install docker.io -y
sudo usermod -aG docker $USER
sudo reboot
  • Now we will make Dockerfile, so go to API directory and write command :-

vim Dockerfile
  • Now write code as shown below :-

# Use an official Golang runtime as a parent image
FROM golang:1.17 as build

# Set the working directory inside the container
WORKDIR /app

# Copy the local package files to the container's workspace
COPY go.mod .
COPY go.sum .

# Download and install any required dependencies
RUN go mod download

# Copy the rest of your application source code
COPY . .

# Build the Go application
RUN go build -o main

# Expose the port your application will listen on
EXPOSE 8080

# Command to run your application
CMD ["/app/main"]
  • Now we have to build "Docker Image" from "Dockerfile", for this we use command as shown below:-

docker build . -t voting-app-api

API-Dockerfile

  • Now docker image build see in image given below :-

API-image

  • Now we will store our image to ECR or DockerHub, so for ECR : Go to ECR β†’ Create repository β†’ select Public β†’ name of repository β†’ Create repository. Now commands to push image :-

(run command where API dockerfile is)
aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/g1z2u9v4

docker tag voting-app-api:latest public.ecr.aws/g1z2u9v4/voting-app-api:latest

docker push public.ecr.aws/g1z2u9v4/voting-app-api:latest
  • We pushed our API image to ECR succesfully.

ECR

ECR-Login

API-Push

API-ECR


STEP 4: Install AWS CLI v2 (run this command in home directory)

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
sudo apt install unzip
unzip awscliv2.zip
sudo ./aws/install -i /usr/local/aws-cli -b /usr/local/bin --update

Install AWS

  • Now we have to configure AWS CLI, for this we need IAM user. So to make IAM user, Go To IAM β†’ Create user β†’ Username β†’ Next β†’ Attach policies directly β†’ select AdministratorAccess β†’ Next β†’ Create User. Now we will make security credentials. Why? Because our AWS CLI can do identify my account. Now go to User β†’ Security Credentials β†’ Create access key β†’ select CLI β†’ Next β†’ Create access key. So we will get Access key and Secret access key. Now go to instance and write command given below :-

aws configure
  • Now paste Access key and Secret access key.

AWS-Version

  • Now to control K8s Cluster on EKS we will need to install kubectl tool. To install kubectl use command (run this command in home directory) :-

curl -o kubectl https://amazon-eks.s3.us-west-2.amazonaws.com/1.19.6/2021-01-05/bin/linux/amd64/kubectl
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin
kubectl version --short --client

Install-Kubectl

  • Now to make K8s Cluster on EKS we will need to install eksctl tool. To install eksctl use command (run this command in home directory) :-

curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv /tmp/eksctl /usr/local/bin
eksctl version

Install-EKS

  • Now to setup EKS Cluster use command :-

eksctl create cluster --name voting-app-eks-cluster --region us-west-2 --node-type t2.medium --nodes-min 2 --nodes-max 2

Create-Cluster

Cluster

  • Now go cluster β€œAdd-ons” β†’ Click β€œGet more add-ons” β†’ select "Amazon EBS CSI Driver"

Get-More-Ons

Add-On

  • Now click β€œNext” β†’ again β€œNext” β†’ Click β€œCreate”.

  • Now add 1 policy in Node IAM role. Go to node and click β€œIAM role” β†’ click "Attach Policy" β†’ search "AmazonEBSCSIDriverPolicy" and add this Policy.

Node-Policy

  • Now check nodes, use command :-

kubectl get nodes
  • We will get a refused error because we haven’t set up the context yet. Lets set up context, for this use command :-

aws eks update-kubeconfig --name voting-app-eks-cluster --region us-west-2

EKS-Update

kubectl get nodes

Nodes

  • Our EKS Cluster and Nodes are working fine.


STEP 5: Setup & Run Mongo

  • Now we will create a Mongo stateful set yaml file but before that we will create a namespace so for this use command :-

kubectl create namespace sudha

NS

  • Now when we want to work within a specific namespace for our Kubernetes operations. We have to set our namespace as current, for this use command :-

kubectl config set-context --current --namespace sudha
  • After running this command, any subsequent kubectl commands we execute will be scoped to the sudha namespace, unless we specify a different namespace explicitly in our commands. This can be particularly useful when we have multiple namespaces in our Kubernetes cluster and we want to ensure that our operations are isolated to a specific namespace.

NS-Context

vim mongo-statefulset.yaml
# mongo-stateful set yaml file
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongo
  namespace: sudha
spec:
  serviceName: mongo
  replicas: 3
  selector:
    matchLabels:
      role: db
  template:
    metadata:
      labels:
        role: db
        env: demo
        replicaset: rs0.main
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: replicaset
                      operator: In
                      values:
                        - rs0.main
                topologyKey: kubernetes.io/hostname
      terminationGracePeriodSeconds: 10
      containers:
        - name: mongo
          image: mongo:4.2
          command:
            - "numactl"
            - "--interleave=all"
            - "mongod"
            - "--wiredTigerCacheSizeGB"
            - "0.1"
            - "--bind_ip"
            - "0.0.0.0"
            - "--replSet"
            - "rs0"
          ports:
            - containerPort: 27017
          volumeMounts:
            - name: mongodb-persistent-storage-claim
              mountPath: /data/db
  volumeClaimTemplates:
    - metadata:
        name: mongodb-persistent-storage-claim
      spec:
        accessModes:
          - ReadWriteOnce
        storageClassName: gp2
        resources:
          requests:
            storage: 1Gi
  • Now to apply a Mongo stateful set with Persistent volumes, run the command :-

#to apply mongo-statefulset manifest file
kubectl apply -f mongo-statefulset.yaml

Mongo-State

#to check pods 
kubectl get pods

Mongo-Pod

  • Now go to Aws console and click on nodes and storage. We will see now new 1Gb storage has been added to both nodes.

1GB-Attach

  • Check whether persistent volumes are created or not

kubectl get pvc

Mongo-PVC

  • Now we will create a mongo-service yaml file, write command :-

vim mongo-service.yaml
# mongo-service yaml file
apiVersion: v1
kind: Service
metadata:
  name: mongo
  namespace: sudha
  labels:
    role: db
    env: demo
spec:
  ports:
  - port: 27017
    targetPort: 27017
  selector:
    role: db
  • Now to apply a mongo-service yaml file, run the command :-

kubectl apply -f mongo-service.yaml
kubectl get svc -n sudha

Mongo-SVC

  • Now let’s go inside the mongo-0 pod and we have to initialise the Mongo database Replica set :-

kubectl get pods
kubectl exec -it mongo-0 -- mongo

Mongo-exec

  • In the terminal run the following command :-

rs.initiate();
sleep(2000);
rs.add("mongo-1.mongo:27017");
sleep(2000);
rs.add("mongo-2.mongo:27017");
sleep(2000);
cfg = rs.conf();
cfg.members[0].host = "mongo-0.mongo:27017";
rs.reconfig(cfg, {force: true});
sleep(5000);
  • Note: Wait until this command completes successfully, it typically takes 10-15 seconds to finish and completes with the message: bye

  • Load the Data in the database by running this command :-

use langdb

Mongo-langdb

Lang-Switched

  • Now paste given below data in the terminal :-

db.languages.insert({"name" : "csharp", "codedetail" : { "usecase" : "system, web, server-side", "rank" : 5, "compiled" : false, "homepage" : "https://dotnet.microsoft.com/learn/csharp", "download" : "https://dotnet.microsoft.com/download/", "votes" : 0}});
db.languages.insert({"name" : "python", "codedetail" : { "usecase" : "system, web, server-side", "rank" : 3, "script" : false, "homepage" : "https://www.python.org/", "download" : "https://www.python.org/downloads/", "votes" : 0}});
db.languages.insert({"name" : "javascript", "codedetail" : { "usecase" : "web, client-side", "rank" : 7, "script" : false, "homepage" : "https://en.wikipedia.org/wiki/JavaScript", "download" : "n/a", "votes" : 0}});
db.languages.insert({"name" : "go", "codedetail" : { "usecase" : "system, web, server-side", "rank" : 12, "compiled" : true, "homepage" : "https://golang.org", "download" : "https://golang.org/dl/", "votes" : 0}});
db.languages.insert({"name" : "java", "codedetail" : { "usecase" : "system, web, server-side", "rank" : 1, "compiled" : true, "homepage" : "https://www.java.com/en/", "download" : "https://www.java.com/en/download/", "votes" : 0}});
db.languages.insert({"name" : "nodejs", "codedetail" : { "usecase" : "system, web, server-side", "rank" : 20, "script" : false, "homepage" : "https://nodejs.org/en/", "download" : "https://nodejs.org/en/download/", "votes" : 0}});
  • Now to see data in proper way use command given below :-

db.languages.find().pretty();

Lang-Added

  • Now to exit from mongo-0 pod use command :-

exit #exit from conatiner

Exit-Mongo

  • To confirm run this in the terminal:

kubectl exec -it mongo-0 -- mongo --eval "rs.status()" | grep "PRIMARY\|SECONDARY"

Pri-Sec-Sec

  • Now we will create a mongo-secret yaml file, write command :-

vim mongo-secret.yaml
# mongo-secret yaml file
apiVersion: v1
kind: Secret
metadata:
  name: mongodb-secret
  namespace: sudha
data:
  username: c3VkaGEK   
  password: eWFkYXYK
  • In mongo-secret.yaml file we will write encryted password so how we can make encrypted password. Write echo 'sudha' | base64 and enter and we get our encryted username or password. Now how we can decode it? For this write echo 'encrypted username or password' | base64 --decode and we will get our real username or password.

  • Now to apply a mongo-secret yaml file, run the command :-

kubectl apply -f mongo-sercet.yaml

Mongo-Secret


STEP 6: Setup & Run API

  • Now we will create a API deployment yaml file so for this use command :-

vim api-deployment.yaml
# api-deployment yaml file
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  namespace: sudha
  labels:
    role: api
    env: demo
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 25%
  selector:
    matchLabels:
      role: api
  template:
    metadata:
      labels:
        role: api
    spec:
      containers:
      - name: api
        image: sudhajobs0107/voting-app-api:latest #change image name
        imagePullPolicy: Always
        env:
          - name: MONGO_CONN_STR
            value: mongodb://mongo-0.mongo,mongo-1.mongo,mongo-2.mongo:27017/langdb?replicaSet=rs0
          - name: MONGO_USERNAME
            valueFrom:
              secretKeyRef:
                name: mongodb-secret
                key: username
          - name: MONGO_PASSWORD
            valueFrom:
              secretKeyRef:
                name: mongodb-secret
                key: password
        ports:
        - containerPort: 8080
        livenessProbe:
          httpGet:
            path: /ok
            port: 8080
          initialDelaySeconds: 2
          periodSeconds: 5
        readinessProbe:
          httpGet:
             path: /ok
             port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
          successThreshold: 1
  • Now to apply a api deployment file, run the command :-

#to apply manifest file
kubectl apply -f api-deployment.yaml
#to check pods 
kubectl get pods  (or) 
kubectl get all

API-Pod

  • Now we will create a api-service yaml file, write command :-

vim api-service.yaml
# api-service yaml file
#Imperative command to create service to expose frontend deployment 
#kubectl expose deploy api \ --name=api \ --type=LoadBalancer \ --port=80 \ --target-port=8080
apiVersion: v1
kind: Service
metadata:
  name: api
  labels:
    app: api
spec:
  selector:
    app: api
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  type: LoadBalancer
  • Now we can apply api-service yaml file or directly expose, run the command :-

kubectl apply -f api-service.yaml (and)
kubectl get svc

API-lb

  • Now we will see 1 Load Balancer will be created in your AWS account.

API-lb-AWS

  • Now set the environment variable:-

{
API_ELB_PUBLIC_FQDN=$(kubectl get svc api -ojsonpath="{.status.loadBalancer.ingress[0].hostname}")
until nslookup $API_ELB_PUBLIC_FQDN >/dev/null 2>&1; do sleep 2 && echo waiting for DNS to propagate...; done
curl $API_ELB_PUBLIC_FQDN/ok
echo
}

API-lb-OK

API-lb-OK1


STEP 7: Setup & Run Frontend

Now we will make Dockerfile for frontend, so go to frontend directory and write command :-

vim Dockerfile

And we are in Dockerfile. Write code as shown below :-

# Use Node.js 16 image
FROM node:16 as build

WORKDIR /app
COPY package*.json ./
RUN npm install --legacy-peer-deps
COPY . .
RUN npm run build

# Stage 2: Serve the React app using NGINX
FROM nginx:alpine

# Remove the default NGINX welcome page
RUN rm -rf /usr/share/nginx/html/*

# Copy the built React app from the previous stage
COPY --from=build /app/build /usr/share/nginx/html

# Expose port 80 (default HTTP port)
EXPOSE 80

# Start NGINX
CMD ["nginx", "-g", "daemon off;"]

Frontend-Dockerfile

Now we have to build "Docker Image" from "Dockerfile", for this we use command as shown below :-

docker build . -t voting-app-frontend

Frontend-Image

  • Now same as api we will store our image to ECR or DockerHub, so for ECR: Go to ECR β†’ Create repository β†’ select Public β†’ name of repository β†’ Create repository and push image to ECR.

  • See here we pushed our frontend image succesfully.

Frontend-ECR

  • Now we will create a Frontend deployment yaml file so for this use command :-

vim frontend-deployment.yaml
# api-deployment yaml file
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: sudha
  labels:
    role: frontend
    env: demo
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 25%
  selector:
    matchLabels:
      role: frontend
  template:
    metadata:
      labels:
        role: frontend
    spec:
      containers:
      - name: frontend
        image: sudhajobs0107/voting-app-frontend:latest  #change image name
        imagePullPolicy: Always
        env:
          - name: REACT_APP_APIHOSTPORT
            value: aa00164c0518041098c478e090c33bc6-783999949.us-east-2.elb.amazonaws.com #Here add your API_EXTERNAL_IP manually
        ports:
        - containerPort: 80
  • Now to apply a frontend deployment file, run the command :-

kubectl apply -f frontend-deployment.yaml

Frontend-Apply

#to check pods 
kubectl get pods  (or) 
kubectl get all

Frontend-Pod

  • Now we will create a frontend-service yaml file, write command :-

vim frontend-service.yaml
# frontend-service yaml file
#Imperative command to create service to expose frontend deployment 
#kubectl expose deploy frontend \ --name=frontend \ --type=LoadBalancer \ --port=80 \ --target-port=80
apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    app: frontend
spec:
  selector:
    app: frontend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer
  • Now we can apply frontend-service yaml file or directly expose, run the command :-

kubectl apply -f frontnend-service.yaml
kubectl get svc -n sudha

Frontend-Expose

Frontend-SVC

  • Next, set the environment variable:-

{
FRONTEND_ELB_PUBLIC_FQDN=$(kubectl get svc frontend -ojsonpath="{.status.loadBalancer.ingress[0].hostname}")
until nslookup $FRONTEND_ELB_PUBLIC_FQDN >/dev/null 2>&1; do sleep 2 && echo waiting for DNS to propagate...; done
curl -I $FRONTEND_ELB_PUBLIC_FQDN
}

Frontend-lb-OK

  • Now generate the Frontend URL for browsing. In the terminal run the following command:-

echo http://$FRONTEND_ELB_PUBLIC_FQDN

Frontend-Url

  • Now cpoy URL and paste it into the browser. We will get our application running :-

App-Running

  • After the voting application has loaded successfully, vote by clicking on several of the +1 buttons. This will generate AJAX traffic which will be sent back to the API via the API’s assigned ELB.

  • Query the MongoDB database directly to observe the updated vote data. In the terminal execute the following command:

kubectl exec -it mongo-0 -- mongo langdb --eval "db.languages.find().pretty()"

Vote-Data-Save


Our 3-Tier-Language-Vote-App Project is completed πŸ˜„

🌟I've put a lot of effort into making the app user-friendly and efficient. Take a look at the project on GitHub and let me know what you think! Your feedback means a lot. Do Repost on Linkedin to share this valuable Project to your Connections.🌟

πŸ”— GitHub Repository :https://github.com/sudhajobs0107/EKS-voting-app-3-tier

Let's connect and grow on Linkedin :Click Here

Let's connect and grow on Hashnode :Click Here

Let's connect and grow on Twitter :Click Here

Happy Reading!!!!!

HappyLearning!!!!!

Sudha Yadav

Β