A production-ready voting application demonstrating cloud-native architecture patterns on Amazon EKS. Users can vote for their preferred programming language from six options: C#, Python, JavaScript, Go, Java, and Node.js.
This application follows a microservices architecture with three main components:
- Frontend: React-based web interface (2 replicas)
- API: Go-based REST API backend (2 replicas)
- Database: MongoDB replica set (3 nodes) with persistent storage
All components are deployed on Amazon EKS with LoadBalancer services for external access.
| Component | Technology | Details |
|---|---|---|
| Frontend | React + JavaScript | Responsive UI for voting interface |
| Backend API | Go (Golang) | REST API handling vote requests |
| Database | MongoDB 4.2 | Replica set with 3 nodes for HA |
| Orchestration | Kubernetes (EKS) | Container orchestration and management |
| Storage | AWS EBS (gp2) | Persistent volumes for MongoDB data |
| Networking | EBS CSI Driver | Dynamic volume provisioning with IAM roles |
- Namespace:
cloudchamp- Isolated environment for all application components - Deployments: Frontend and API with rolling update strategy
- StatefulSet: MongoDB replica set with stable network identities
- Services:
- Headless service for MongoDB inter-pod communication
- LoadBalancer services for frontend and API external access
- Secrets: MongoDB credentials stored securely
- PersistentVolumeClaims: 0.5Gi storage per MongoDB pod
- StorageClass: gp2 for dynamic EBS volume provisioning
- High Availability: Multi-replica deployments with pod anti-affinity rules
- Data Persistence: MongoDB replica set with persistent storage
- Health Checks: Liveness and readiness probes on all containers
- Rolling Updates: Zero-downtime deployments with controlled surge/unavailability
- Security: Kubernetes secrets for credential management
- Scalability: Independently scalable frontend, API, and database tiers
- EKS Auto Mode: Simplified cluster management with automatic node provisioning
- EBS CSI Integration: IAM roles for secure EBS volume access
This project demonstrates:
- Containerization: Docker-based application packaging
- Kubernetes Orchestration: Deployment, scaling, and management of containerized workloads
- Microservices Architecture: Decoupled, independently deployable services
- Database Replication: MongoDB replica set for redundancy and high availability
- Secrets Management: Secure credential handling in Kubernetes
- Stateful Applications: Managing stateful workloads with StatefulSets
- Persistent Storage: Kubernetes storage provisioning and management
- Service Discovery: DNS-based inter-service communication
- EKS Auto Mode: Automated cluster operations and node management
- IAM Integration: IRSA (IAM Roles for Service Accounts) for secure AWS resource access
- AWS Account with appropriate permissions
- EKS cluster with Auto Mode enabled
- kubectl installed and configured
- AWS CLI configured
- git
Create an EKS cluster with Auto Mode enabled:
aws eks create-cluster \
--name voting-app-cluster \
--version 1.29 \
--region us-west-2 \
--compute-config computeType=ec2,autoScalingGroupConfigs=[{minSize=1,maxSize=5}] \
--logging '{"clusterLogging":[{"enabled":true,"types":["api","audit","authenticator","controllerManager","scheduler"]}]}'Configure kubectl:
aws eks update-kubeconfig --name voting-app-cluster --region us-west-2
kubectl get nodesCreate an IAM policy for EBS CSI:
cat > ebs-csi-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:CreateSnapshot",
"ec2:AttachVolume",
"ec2:DetachVolume",
"ec2:ModifyVolume",
"ec2:DescribeAvailabilityZones",
"ec2:DescribeCreateVolumePermissions",
"ec2:DescribeInstanceAttribute",
"ec2:DescribeInstanceTypes",
"ec2:DescribeInstances",
"ec2:DescribeSnapshots",
"ec2:DescribeTags",
"ec2:DescribeVolumes",
"ec2:DescribeVolumesModifications"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ec2:CreateTags"
],
"Resource": [
"arn:aws:ec2:*:*:volume/*",
"arn:aws:ec2:*:*:snapshot/*"
],
"Condition": {
"StringEquals": {
"ec2:CreateAction": [
"CreateVolume",
"CreateSnapshot"
]
}
}
},
{
"Effect": "Allow",
"Action": [
"ec2:DeleteTags"
],
"Resource": [
"arn:aws:ec2:*:*:volume/*",
"arn:aws:ec2:*:*:snapshot/*"
]
}
]
}
EOFCreate the IAM policy:
aws iam create-policy \
--policy-name EBSCSIDriverPolicy \
--policy-document file://ebs-csi-policy.jsonCreate an IAM role for the EBS CSI driver:
cat > trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:kube-system:ebs-csi-controller-sa"
}
}
}
]
}
EOFReplace ACCOUNT_ID and the OIDC provider ID with your actual values:
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
OIDC_ID=$(aws eks describe-cluster --name voting-app-cluster --region us-west-2 --query 'cluster.identity.oidc.issuer' --output text | cut -d '/' -f 5)
sed -i "s/ACCOUNT_ID/$ACCOUNT_ID/g" trust-policy.json
sed -i "s/EXAMPLED539D4633E53DE1B716D3041E/$OIDC_ID/g" trust-policy.jsonCreate the role:
aws iam create-role \
--role-name EBSCSIDriverRole \
--assume-role-policy-document file://trust-policy.jsonAttach the policy to the role:
aws iam attach-role-policy \
--role-name EBSCSIDriverRole \
--policy-arn arn:aws:iam::$ACCOUNT_ID:policy/EBSCSIDriverPolicyAdd the EBS CSI driver to your cluster:
aws eks create-addon \
--cluster-name voting-app-cluster \
--addon-name aws-ebs-csi-driver \
--service-account-role-arn arn:aws:iam::$ACCOUNT_ID:role/EBSCSIDriverRole \
--region us-west-2git clone https://github.com/N4si/K8s-voting-app.git
cd K8s-voting-app
# Create and switch to cloudchamp namespace
kubectl create ns cloudchamp
kubectl config set-context --current --namespace cloudchampDeploy the MongoDB StatefulSet with persistent storage:
kubectl apply -f manifests/mongo-statefulset.yaml
kubectl apply -f manifests/mongo-service.yamlVerify DNS resolution for MongoDB pods:
kubectl run --rm utils -it --image praqma/network-multitool -- bash
# Inside the pod:
for i in {0..2}; do nslookup mongo-$i.mongo; done
exitInitialize the MongoDB replica set:
cat << EOF | kubectl exec -it mongo-0 -- mongo
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);
EOFVerify replica set status:
kubectl exec -it mongo-0 -- mongo --eval "rs.status()" | grep "PRIMARY\|SECONDARY"Load initial data:
cat << EOF | kubectl exec -it mongo-0 -- mongo
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();
EOFCreate MongoDB secret:
kubectl apply -f manifests/mongo-secret.yamlDeploy the Go API:
kubectl apply -f manifests/api-deployment.yaml
kubectl apply -f manifests/api-service.yamlWait for the API LoadBalancer to get an external hostname and test connectivity:
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..."; done
curl $API_ELB_PUBLIC_FQDN/okTest API endpoints:
curl -s $API_ELB_PUBLIC_FQDN/languages | jq .
curl -s $API_ELB_PUBLIC_FQDN/languages/go | jq .
curl -s $API_ELB_PUBLIC_FQDN/languages/java | jq .Deploy the React frontend:
kubectl apply -f manifests/frontend-deployment.yaml
kubectl apply -f manifests/frontend-service.yamlWait for the frontend LoadBalancer and verify it's ready:
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..."; done
curl -I $FRONTEND_ELB_PUBLIC_FQDNGet the application URL:
echo "Application URL: http://$FRONTEND_ELB_PUBLIC_FQDN"- Open the frontend URL in your browser
- Click the +1 buttons to vote for programming languages
- Verify votes are recorded in MongoDB:
kubectl exec -it mongo-0 -- mongo langdb --eval "db.languages.find().pretty()"- EBS volumes not provisioning: Verify the EBS CSI driver addon is installed and the IAM role has correct permissions
- Pods not starting: Check logs with
kubectl logs <pod-name> -n cloudchamp - DNS not resolving: Wait a few minutes for AWS to propagate DNS records
- MongoDB replica set issues: Verify all three pods are running with
kubectl get pods -n cloudchamp - Frontend can't reach API: Ensure the API LoadBalancer hostname is correctly set
To remove all resources:
# Delete the application
kubectl delete ns cloudchamp
# Delete the EBS CSI addon
aws eks delete-addon --cluster-name voting-app-cluster --addon-name aws-ebs-csi-driver --region us-west-2
# Delete the cluster
aws eks delete-cluster --name voting-app-cluster --region us-west-2
# Delete IAM resources
aws iam detach-role-policy --role-name EBSCSIDriverRole --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/EBSCSIDriverPolicy
aws iam delete-role --role-name EBSCSIDriverRole
aws iam delete-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/EBSCSIDriverPolicyThis deployment demonstrates a complete cloud-native application stack on EKS Auto Mode with:
- Multi-tier microservices architecture
- Stateful database with replica set
- High availability through multiple replicas
- Persistent data storage with EBS CSI driver
- Secure credential management
- External access via LoadBalancer services
- Automated cluster operations with EKS Auto Mode
- IAM-based security for AWS resource access