Skip to content

Latest commit

 

History

22 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Counter Service (EKS + Helm + Terraform)

What this project is

Counter Service is a small HTTP API that maintains a single integer counter and exposes:

  • GET /count → current counter value
  • POST /count → increments the counter by 1
  • GET /metrics → Prometheus metrics

The service is designed to run on AWS EKS and be deployed via Helm.
Persistence is pluggable:

  • Redis (recommended; default for the Helm chart) — supports multiple replicas and rolling updates safely.
  • File (PVC-backed) — useful for local/dev, but not suitable for multi-replica HA with a single RWO volume.

Architecture (high level)

  1. Developer pushes code to GitHub.
  2. CI (GitHub Actions):
    • runs tests (pytest)
    • builds a Docker image
    • pushes to Amazon ECR with tags: {commit_sha} and latest
  3. CD (GitHub Actions):
    • triggers on successful CI run on main
    • selects the environment (prod by default)
    • deploys to EKS using helm upgrade --install
  4. Runtime (EKS):
    • Deployment runs 1+ pods
    • Service exposes port 80 in-cluster
    • Ingress (AWS Load Balancer Controller) exposes the app externally
    • optional: HPA, PDB, ServiceMonitor

Data path

  • If storageBackend=redis:
    • app connects to ElastiCache/Redis via COUNTER_REDIS_URL and stores the counter under a key (default counter)
  • If storageBackend=file:
    • app reads/writes {dataDir}/{filename} and requires a PVC (or emptyDir for ephemeral mode)

Repository layout

.
├── app/                          # FastAPI application code
│   ├── main.py                   # API + /metrics
│   ├── settings.py               # env-driven configuration
│   └── storage/
│       ├── file_store.py         # file persistence
│       └── redis_store.py        # redis persistence
├── tests/                        # pytest tests
├── helm/counter-service/         # Helm chart
│   ├── Chart.yaml
│   ├── values.yaml               # defaults
│   ├── values-dev.yaml           # environment overrides
│   ├── values-staging.yaml
│   └── values-prod.yaml
└── terraform/
    ├── bootstarp/                # IAM role for Terraform execution (bootstrap)
    ├── ci-pipeline/              # ECR + GitHub OIDC role for Actions
    ├── eks_cluster/              # VPC + EKS + addons + controllers + monitoring
    │   └── envs/{dev,staging,prod}
    └── redis/                    # ElastiCache/Redis (recommended persistence)

Prerequisites

Local tooling:

  • aws CLI
  • terraform (1.x)
  • kubectl
  • helm
  • Docker (for local builds)

AWS-side prerequisites:

  • An AWS account with permissions to create IAM, VPC, EKS, ECR, ElastiCache.
  • An S3 bucket (per environment) for Terraform remote state (see terraform/eks_cluster/envs/*/backend.tf).

GitHub prerequisites:

  • GitHub Environments: dev, staging, prod (recommended)
  • Secrets configured per environment (see below)

Running locally (optional)

1) File backend (simple local run)

python -m pip install -r requirements.txt
export COUNTER_STORAGE_BACKEND=file
export COUNTER_DATA_DIR="$(pwd)/.tmpdata"
export COUNTER_COUNTER_FILENAME="counter.txt"
mkdir -p "$COUNTER_DATA_DIR"
uvicorn app.main:app --host 0.0.0.0 --port 8080

Test it:

curl -sS http://127.0.0.1:8080/count && echo
curl -sS -X POST http://127.0.0.1:8080/count && echo
curl -sS http://127.0.0.1:8080/metrics >/dev/null && echo "OK metrics"

Infrastructure provisioning (order of operations)

The recommended provisioning order is:

  1. Bootstrap (Terraform execution role)
  2. CI pipeline infra (ECR + GitHub OIDC role)
  3. Redis (ElastiCache)
  4. EKS cluster (VPC + EKS + addons/controllers + monitoring)

1) Bootstrap: terraform/bootstarp/

Purpose:

  • Creates the Terraform execution role used by later stacks.

Files to update:

  • terraform/bootstarp/terraform.tfvars
    • aws_region
    • terraform_role_name
    • terraform_state_bucket_name (naming convention / shared state bucket)

Commands:

cd terraform/bootstarp
terraform init
terraform apply
terraform output

Important output:

  • terraform_role_arn — referenced by other Terraform stacks.

2) CI pipeline infra: terraform/ci-pipeline/

Purpose:

  • Creates the ECR repository (counter-service)
  • Creates GitHub OIDC provider + IAM role for GitHub Actions (AWS_ROLE_ARN)

Files to update:

  • terraform/ci-pipeline/terraform.tfvars
    • aws_region
    • ecr_repository_name
    • github_org / github_repo (OIDC trust policy subject)

Commands:

cd terraform/ci-pipeline
terraform init
terraform apply
terraform output

Important outputs:

  • ecr_repository_url
  • github_actions_role_arn ← put this into GitHub Secrets as AWS_ROLE_ARN

3) Redis: terraform/redis/

Purpose:

  • Creates an ElastiCache Redis Replication Group (recommended backend for HA)

Files to update:

  • terraform/redis/terraform.tfvars
    • aws_region
    • environment
    • vpc_id (VPC where Redis is created)
    • eks_node_security_group_id (node SG allowed to connect to Redis)
    • allowed_cidr_blocks (optional internal access controls)
    • subnet_ids / azs must match your VPC design

Commands:

cd terraform/redis
terraform init
terraform apply
terraform output

Important output:

  • Redis endpoint (hostname + port). Use it in Helm values as:
    • app.redisUrl: "redis://<endpoint>:6379/0" (non-TLS)
    • or rediss://... if you enforce TLS on your Redis.

4) EKS cluster: terraform/eks_cluster/envs/<env>

Purpose:

  • Creates VPC and EKS cluster
  • Creates a managed node group
  • Installs key components:
    • AWS Load Balancer Controller (for Ingress)
    • kube-prometheus-stack (Prometheus/Grafana) where enabled
    • CloudWatch observability components where configured

Files to update:

  • terraform/eks_cluster/envs/<env>/terraform.tfvars
    • aws_region
    • environment
    • cluster_name
    • terraform_role_arn (from bootstrap)
    • admin_role_arn (your admin role)
    • github_actions_role_arn (from ci-pipeline output)
    • VPC CIDR, subnets/AZs, node group sizing, instance type, etc.
  • terraform/eks_cluster/envs/<env>/backend.tf
    • Ensure the S3 bucket exists and is in the correct region.

Commands (example for dev):

cd terraform/eks_cluster/envs/dev
terraform init
terraform apply
terraform output

After creation:

aws eks update-kubeconfig --name <cluster_name> --region <aws_region>
kubectl get nodes
kubectl get ns

Helm chart (deployment)

The Helm chart lives in helm/counter-service.

Key parameters

  • Image:
    • image.repository (ECR URL)
    • image.tag (commit SHA)
  • Storage mode:
    • app.storageBackend: redis or file
    • Redis:
      • app.redisUrl (required when redis)
      • app.redisKey (defaults to counter)
    • File:
      • app.file.dataDir, app.file.filename
      • persistence.enabled, persistence.size, persistence.storageClassName

Environment values files

  • values-dev.yaml
  • values-staging.yaml
  • values-prod.yaml

Each file typically sets:

  • replicaCount
  • ingress.hosts[0].host
  • app.storageBackend
  • app.redisUrl (for Redis mode)
  • optional autoscaling/pdb

Rendering locally

If you render without an env overrides file, values.yaml defaults to storageBackend=redis but does not include app.redisUrl, so templating will fail by design.

Correct examples:

helm template counter-service helm/counter-service -f helm/counter-service/values-dev.yaml

or for file backend:

helm template counter-service helm/counter-service \
  --set app.storageBackend=file \
  --set persistence.enabled=false

GitHub Actions (CI / CD)

CI workflow: CI - Build, Test and Push Image (.github/workflows/ci.yml)

What it does:

  1. Installs Python dependencies
  2. Runs pytest with a file backend (isolated temp directory)
  3. On push (not PR):
    • assumes AWS role via OIDC (secrets.AWS_ROLE_ARN)
    • logs in to ECR
    • builds and pushes Docker image:
      • ECR:{sha}
      • ECR:latest

Required GitHub secret:

  • AWS_ROLE_ARN (created by terraform/ci-pipeline)

CD workflow: CD - Deploy to EKS (Helm) (.github/workflows/cd.yml)

Triggers:

  • workflow_run after CI succeeded on main (deploys to prod)
  • workflow_dispatch for manual deploy to dev/staging/prod with optional image_tag

What it does:

  1. Chooses environment (prod by default)
  2. Picks Helm values file (values-<env>.yaml)
  3. Determines image tag:
    • workflow_run → CI head_sha
    • manual → input image_tag or GITHUB_SHA
  4. Deploys via Helm (upgrade --install)

Verification / evidence (commands to run)

These are the typical checks evaluators look for: pods healthy, endpoints work, persistence survives restart, and ingress works.

1) Cluster objects

kubectl -n prod get deploy,po,svc,ingress
kubectl -n prod describe deploy counter-service | sed -n '/Image:/,/Conditions:/p'

2) In-cluster API test (bypasses ingress)

kubectl -n prod run curl-check --rm -i --restart=Never --image=curlimages/curl -- \
  sh -lc 'set -e;
  echo "GET /metrics"; curl -m 5 -sS http://counter-service.prod.svc.cluster.local/metrics >/dev/null; echo "OK metrics";
  echo "GET /count";   curl -m 5 -sS http://counter-service.prod.svc.cluster.local/count; echo;
  echo "POST /count";  curl -m 5 -sS -X POST http://counter-service.prod.svc.cluster.local/count; echo;
  echo "GET /count";   curl -m 5 -sS http://counter-service.prod.svc.cluster.local/count; echo;
  echo "OK counter"'

3) Persistence test (Redis)

# bump counter a few times
kubectl -n prod run curl-bump --rm -i --restart=Never --image=curlimages/curl -- \
  sh -lc 'set -e; for i in 1 2 3; do curl -sS -X POST http://counter-service.prod.svc.cluster.local/count; echo; done; curl -sS http://counter-service.prod.svc.cluster.local/count; echo'

# restart deployment
kubectl -n prod rollout restart deploy/counter-service
kubectl -n prod rollout status deploy/counter-service --timeout=5m

# confirm value persisted
kubectl -n prod run curl-after --rm -i --restart=Never --image=curlimages/curl -- \
  sh -lc 'set -e; echo "AFTER RESTART:"; curl -sS http://counter-service.prod.svc.cluster.local/count; echo'

4) Ingress check

kubectl -n prod get ingress counter-service -o wide
# then curl the external hostname / DNS you configured:
curl -sS http://<your-domain>/count && echo

What you should customize before running

  1. Regions: ensure Terraform (aws_region) and GitHub Actions (AWS_REGION) match your target region.
  2. Domains / ingress hostnames: update helm/counter-service/values-*.yaml.
  3. Redis endpoint: update app.redisUrl in the environment values file (or inject via secrets).
  4. Terraform tfvars: fill in your AWS account IDs, role ARNs, and state bucket names.

License

Internal / educational project.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages