This project demonstrates a cloud-native deployment of the Example Voting App on AWS using Terraform and Kubernetes (EKS).
The application is deployed on an Amazon EKS cluster in the eu-west-2 region.
- Infrastructure as Code: Terraform is used to provision the VPC, EKS Cluster, and ECR repositories.
- Container Registry: AWS ECR stores the Docker images for the Vote, Result, and Worker applications.
- Orchestration: Kubernetes (EKS) manages the application workloads.
- CI/CD: GitHub Actions handles the build and deployment process.
- Vote App (Python): Frontend for users to cast votes. Exposed via LoadBalancer.
- Redis: In-memory queue for incoming votes.
- Worker (.NET): Background processor that consumes votes from Redis and stores them in PostgreSQL.
- DB (PostgreSQL): Persistent storage for votes.
- Result App (Node.js): Frontend to view real-time results. Exposed via LoadBalancer.
- AWS Account
- GitHub Repository
- Terraform >= 1.3.2
- AWS CLI
- kubectl
The CI/CD pipeline is managed by GitHub Actions. It triggers automatically on a push to the main branch.
In your GitHub repository settings (Settings > Secrets and variables > Actions), add the following repository secrets:
AWS_ACCESS_KEY_ID: Your AWS Access Key ID.AWS_SECRET_ACCESS_KEY: Your AWS Secret Access Key.
(Note: The pipeline uses the region eu-west-2 and EKS cluster talent-904976121950 defined in deploy.yaml env vars)
Push changes to the main branch to trigger the pipeline:
git add .
git commit -m "Update application"
git push origin mainThe pipeline will:
- Build Docker images for Vote, Result, and Worker apps.
- Push images to AWS ECR with the commit SHA as the tag.
- Update Kubernetes manifests with the new image tag.
- Apply the manifests to the EKS cluster.
-
Check Pods:
kubectl get pods
Ensure all pods are in
Runningstate. -
Access Applications: Get the LoadBalancer DNS names:
kubectl get svc vote result
- Access the Vote App at the
voteservice External-IP (port 80). - Access the Result App at the
resultservice External-IP (port 80).
- Access the Vote App at the
-
Infrastructure:
- Choice: Terraform was chosen for IaC as preferred by the requirements.
- State Management: Local backend was used instead of S3/DynamoDB to avoid permission issues and complexity in the restricted sandbox environment. In production, remote backend with locking is mandatory.
- Networking: Used
terraform-aws-modules/vpcfor a production-ready VPC setup with public/private subnets and NAT Gateways. - EKS: Used a managed Node Group (
t3.xlarge) for cost-efficiency and simplicity in this scale.
-
CI/CD:
- Tool: GitHub Actions was chosen for its seamless integration with the code repository.
- Authentication: Used AWS Access Keys for simplicity in the sandbox. In a real-world scenario, OIDC (OpenID Connect) would be preferred for better security (no long-lived credentials).
- Deployment Strategy:
kubectl applywith dynamic image tag replacement in the pipeline. This ensures the deployed version matches the commit. Manifests in the repo use a placeholderIMAGE_TAG.
-
Security:
- Least Privilege: Terraform uses a specific assumed role (
talent_role). - Network: Database and Redis are internal services, not exposed to the public internet. Only Vote and Result apps have LoadBalancers.
- Image Scanning: ECR scan-on-push is enabled to detect vulnerabilities.
- Least Privilege: Terraform uses a specific assumed role (
To destroy the infrastructure and avoid costs:
-
Delete Kubernetes Resources:
kubectl delete -f k8s-specifications/
-
Destroy Terraform Resources:
cd terraform terraform destroy -auto-approve(Note: Ensure you have the correct AWS credentials configured)
- VPC:
talent-904976121950(10.0.0.0/16) - Subnets: 3 Public, 3 Private, 3 Intra
- NAT Gateway: 1 (Single NAT Gateway for cost saving)
- Internet Gateway: 1
- EKS Cluster:
talent-904976121950(v1.31) - EKS Node Group:
talent-904976121950-ng(t3.xlarge, desired: 2) - ECR Repositories:
bion-talent-904976121950-voting-appbion-talent-904976121950-result-appbion-talent-904976121950-worker
- IAM Roles (Assumed/Used):
talent_role(Sandbox default)
