Kubernetes Project: Deploy Cloud Native Voting Application on EKS

This Blog is inspired by Cloudchamp’s YouTube video

APP REPO https://github.com/Aj7Ay/K8s-voting-app.git

Navigate to Your Aws Console

Click the “Search” field and search For EKS or select directly Elastic Kubernetes Service on the Recently visited tab

Click “Add cluster”

Click “Create”

Click the “Name” field and enter a unique name for the cluster that is anything you want. For example, I used Cloud and version 1.27

Click on Amazon EKS User Guide for New IAM role creation.

You will get the below webpage and

Click “console.aws.amazon.com/iam

You will be redirected to the IAM dashboard

Click “Roles”

Click “Create role”

Click “Allow AWS services like EC2, Lambda, or others to perform actions in this account.”

Click “Choose a service or use case”

Type “EKS”

Click this radio button with EKS-Cluster

Click “Next” and you will directly redirect to policy and click Next ( we have only one policy for it and it selects by default for EKS) that is AmazonEKSClusterPolicy

Click the “Role name” field and provide the name (myAmazonEKSClusterRole)

Click “Create role”

Click “myAmazonEKSClusterRole” that is created at Cluster Service Role.

Click “Next”

Click “Select security groups” and Use the existing security group or create a new security Group

Click “Next”

Click “Next”

No changes Click “Next” (Default no need to change anything)

No changes Click “Next” (Default no need to change anything)

Click “Create”

In your cluster Click “Add-ons”

Click “Get more add-ons”

Click this checkbox. with Amazon EBS CSI Driver

No changes Click “Next” (Default no need to change anything)

No changes Click “Next” (Default no need to change anything)

Click “Create”

Once your Cluster up to active status

Click “Compute”

Click on “Add node group”

Click the “Name” field.

Write any Name you want (NodeGroup)

Click “Select role” and click on the IAM console

Click “Create role”

Click “Allow AWS services like EC2, Lambda, or others to perform actions in this account.”

Click “Choose a service or use case”

Click “EC2”

Click “Next”

Click the “Search” field.

Search these Policy Names and make it check

AmazonEC2ContainerRegistryReadOnly

AmazonEKS_CNI_Policy

AmazonEBSCSIDriverPolicy

AmazonEKSWorkerNodePolicy

Click “Next”

Click the “Role name” field.

Add Role name as myAmazonNodeGroupPolicy

Click “Create role”

Add a role that was created before “myAmazonNodeGroupPolicy

Click “Next”

On the next page remove t3.medium and add t2.medium as instance type.

Select t2.medium

Click “Next”

Click “Next”

Click “Create”

Node Groups will take some time to create, Click “EC2” or Search for Ec2

Click “Launch instance”

Add Name and AMI as Amazon Linux

Take instance type as t2.micro and select keypair with default security Group.

Click “Advanced details”

Click on the IAM instance Profile and Create a New IAM profile.

Click “Create role”

Click “Choose a service or use case”

Click “EC2”

Click the “Search” field.

Type “EBS”

Click this checkbox with the policy name AmazonEBSCSIDriverPolicy.

Click “Next”

Click the “Role name” field and provide the name as EKSaccess.

Click “Create role”

Click on the newly created role “EKSaccess”

Click “Add permissions”

Click “Create inline policy”

Click “JSON”

REMOVE EVERYTHING FROM THE POLICY EDITOR

And add this

{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Action": [
            "eks:DescribeCluster",
            "eks:ListClusters",
            "eks:DescribeNodegroup",
            "eks:ListNodegroups",
            "eks:ListUpdates",
            "eks:AccessKubernetesApi"
        ],
        "Resource": "*"
    }]
}

Click “Next”

Click the “Policy name” field and add the name as eksaccesspolicy

click on create policy.

Add That Role to your instance and launch the instance

Once the instance comes up copy the SSH client to connect to Putty.

ssh -i "m.pem" ec2-user@<public-ip>

Install git on the instance

sudo yum install git -y

Once Git is installed, install Kubectl on the instance

#installs kubectl on instance 
curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.24.11/2023-03-17/bin/linux/amd64/kubectl
chmod +x ./kubectl
sudo cp ./kubectl /usr/local/bin
export PATH=/usr/local/bin:$PATH
#to check version
kubectl version --client

Install AWS CLI on the instance

#install awscli
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

check awscli version

aws --version

Now check whether nodes are up or not

kubectl get nodes

You will get a refused error because we haven’t set up the context yet. lets set context

aws eks update-kubeconfig --name EKS_CLUSTER_NAME --region CLUSTER_REGION
aws eks update-kubeconfig --name Cloud --region ap-south-1

The aws eks update-kubeconfig command updates the Kubernetes configuration file with details for connecting to an Amazon EKS cluster.

Specifically:

  • --name EKS_CLUSTER_NAME provides the name of the EKS cluster to configure access for. This should be replaced with your actual cluster name.
  • --Region us-west-2 specifies the AWS region where the EKS cluster is located. You should update this if your cluster is in a different region.
  • This will update your local kubeconfig file (usually located at ~/.kube/config) with the endpoint and certificate authority data to allow kubectl to communicate with your EKS cluster.

Let’s check again whether nodes are up or not from instance.

kubectl get nodes

We will get an error that You must be logged in to the server (unauthorized)

The error message “You must be logged in to the server (Unauthorized)” in Kubernetes indicates that the user or service account trying to access the cluster does not have the necessary permissions. This error typically occurs when the authentication and authorization mechanisms in Kubernetes deny access.

Let’s Resolve the issue

Go to Aws console

Click on the AWS cloud shell icon on the top right

click on connect

First set context by providing the following command

aws eks update-kubeconfig --name EKS_CLUSTER_NAME --region CLUSTER_REGION
aws eks update-kubeconfig --name Cloud --region ap-south-1

Edit the config map for access

kubectl edit configmap aws-auth --namespace kube-system

Go to your Iam roles and copy the arn of iam role of ec2 instance that is attached

Add your Role arn to the config map

EKSaccess role is added to the Instance (While creating the instance)

- rolearn: arn:aws:iam::XXXXXXXXXXXX:role/testrole  #change arn of your instance role arn  
    username: testrole    #rolename 
    groups:
      - system:masters
#example
- rolearn: arn:aws:iam::672618677785:role/EKSaccess  #change arn
    username: EKSaccess    # iam role name 
    groups:
      - system:masters

save and exit

Esc --> shift+:
wq!

You will get this output (aws-auth edited)

Check now whether nodes are up or not

(Not only in cloud shell you will get output like this in putty also)

kubectl get nodes

Let’s clone our Project Repository

git clone <repo-url>

Go inside the K8s-voting app once it is cloned.

cd K8s-voting-app
cd manifests
ls
cat api-deployment.yaml

In the API deployment, we used a namespace as cloudchamp. By default we get 4 namespaces only, we have to create cloudchamp namespace.

default namespaces

kubectl get ns

Let’s create our cloudchamp namespace.

kubectl create ns cloudchamp
kubectl get ns

when you want to work within a specific namespace for your Kubernetes operations.

We have to set our namespace as current

kubectl config set-context --current --namespace cloudchamp
  • kubectl config set-context: This is the main command for configuring your kubectl context.
  • --Current: This flag indicates that you want to modify the current context.
  • --namespace cloudchamp: This specifies the namespace you want to set as the current namespace for your kubectl context. In this case, it sets the namespace to “cloudchamp.”

After running this command, any subsequent kubectl commands you execute will be scoped to the “cloudchamp” namespace, unless you specify a different namespace explicitly in your commands. This can be particularly useful when you have multiple namespaces in your Kubernetes cluster, and you want to ensure that your operations are isolated to a specific namespace.

To create a Mongo stateful set with Persistent volumes, run the command in the manifests folder:

#to apply manifest file
kubectl apply -f mongo-statefulset.yaml
#to check pods 
kubectl get pods  (or)
kubectl get pods -n cloudchamp -w
kubectl get all

Go to Aws console and click on nodes and storage You can see now new 1Gb storage has been added to both nodes

Check whether persistent volumes are created or not

kubectl get pv -n cloudchamp 
kubectl get pvc -n cloudchamp

create Mongo Service

kubectl apply -f mongo-service.yaml
kubectl get 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

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
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}});
db.languages.find().pretty();
exit #exit from conatiner

To confirm run this in the terminal:

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

Create Mongo secret:

kubectl apply -f mongo-secret.yaml

Create GO API deployment by running the following command:

kubectl apply -f api-deployment.yaml
kubectl get all

Expose API deployment through service using the following command:

kubectl expose deploy api \
 --name=api \
 --type=LoadBalancer \
 --port=80 \
 --target-port=8080
kubectl get svc

One load Balancer will be created in your AWS account

Next, 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
}

Test and confirm that the API route URL /languages, and /languages/{name} endpoints can be called successfully. In the terminal run any of the following commands:

<api loadbalancer ip/languages> #in browser

In the browser, you have to use your external IP of Api to see this output

If everything works fine, go ahead with the Frontend setup.

kubectl apply -f frontend-deployment.yaml
kubectl get pods
kubectl get all

Expose API deployment through service using the following command:

kubectl expose deploy frontend \
 --name=frontend \
 --type=LoadBalancer \
 --port=80 \
 --target-port=8080
kubectl get 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
}

Generate the Frontend URL for browsing. In the terminal run the following command:

echo http://$FRONTEND_ELB_PUBLIC_FQDN

Test the full end-to-end cloud-native application

#frontend external ip in browser
<frontend service external-ip>

If you get output like this, Delete the service of frontend and deployment

kubectl delete -f frontend-service.yaml
kubectl delete -f frontend-deployment.yaml

Now copy your API External service ip

kubectl get svc

Now open your frontend-deployment. yaml file

sudo vi frontend-deployment.yaml

Update the frontend-deployment.yaml file with your api-ip

esc --shift+:
wq!

Now again deploy the frontend

kubectl apply -f frontend-deployment.yaml

and now expose the frontend-service

kubectl expose deploy frontend \
 --name=frontend \
 --type=LoadBalancer \
 --port=80 \
 --target-port=8080

Copy your external ip of the frontend service and paste it into the browser You will get an application like this

Using your local workstation’s browser – browse to the URL created in the previous output.

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()"

Thanks for Reading my Blog.

mrcloudbook.com avatar

Ajay Kumar Yegireddi is a DevSecOps Engineer and System Administrator, with a passion for sharing real-world DevSecOps projects and tasks. Mr. Cloud Book, provides hands-on tutorials and practical insights to help others master DevSecOps tools and workflows. Content is designed to bridge the gap between development, security, and operations, making complex concepts easy to understand for both beginners and professionals.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *