Infrastructure-as-Code for a self-healing, observable Kubernetes platform on AWS EKS — provisioned end-to-end with Terraform, deployed via GitHub Actions, and monitored with the Prometheus/Grafana stack out of the box.
This is a portfolio reference implementation. It is written to be cloned and adapted, not run as-is against a real AWS account: every account-specific value (state bucket, role ARNs, cluster name) is left as a placeholder that you supply via .tfvars, backend.hcl, or repository/environment variables — none of it is hardcoded or committed.
| Skill area | Where |
|---|---|
| Infrastructure as Code | terraform/ — modular VPC + EKS stack, remote state with locking, no manual console steps |
| Container orchestration | k8s/ — namespaced workloads, health probes, topology-aware scheduling, Helm-managed add-ons |
| Monitoring & observability | k8s/monitoring/ — Prometheus Operator, Alertmanager routing, provisioned Grafana dashboards, ServiceMonitor-based scrape discovery |
| CI/CD | .github/workflows/ — OIDC-authenticated pipelines for both infra (terraform.yml) and application (deploy-app.yml) delivery |
| Security-conscious defaults | private subnets for workloads, no static AWS keys, no committed secrets, least-privilege IAM per component |
See architecture/architecture.md for the full diagram and design rationale. In short:
- A VPC with public subnets (NAT/IGW only) and private subnets (all compute).
- An EKS cluster with a managed node group, provisioned entirely by the
terraform/modules/eksmodule. kube-prometheus-stackdeployed via the Terraform Helm provider into a dedicatedmonitoringnamespace.- A sample application in its own namespace, instrumented with a
ServiceMonitorso it's scraped automatically. - Two GitHub Actions pipelines: one plans/applies infrastructure changes, the other builds and rolls out the application — both authenticate to AWS via OIDC, with zero long-lived credentials.
.
├── terraform/ # IaC: VPC, EKS, and the monitoring Helm release
│ ├── modules/
│ │ ├── vpc/
│ │ └── eks/
│ ├── main.tf / variables.tf / outputs.tf / providers.tf
│ ├── terraform.tfvars.example
│ └── backend.hcl.example
├── k8s/
│ ├── namespaces.yaml
│ ├── monitoring/ # Prometheus + Grafana Helm values
│ └── sample-app/ # Example instrumented workload
├── .github/workflows/
│ ├── terraform.yml # plan on PR, apply on merge to main
│ └── deploy-app.yml # build image, deploy, verify rollout
├── docs/runbook.md # secrets handling, DR, troubleshooting
└── architecture/architecture.md # diagram + design notes
Prerequisites: Terraform ≥ 1.7, an AWS account,
kubectl, andhelminstalled locally if you want to interact with the cluster outside of CI.
-
Configure remote state (never commit this):
cp terraform/backend.hcl.example terraform/backend.hcl # edit terraform/backend.hcl with your own bucket/table -
Set your variables:
cp terraform/terraform.tfvars.example terraform/terraform.tfvars # edit values for your environment — none are sensitive by design -
Provision the platform:
cd terraform terraform init -backend-config=backend.hcl terraform plan terraform apply -
Point kubectl at the new cluster (Terraform prints this command as an output):
aws eks update-kubeconfig --name <cluster_name> --region <aws_region>
-
Deploy the sample app and confirm it's being scraped:
kubectl apply -f k8s/namespaces.yaml kubectl apply -f k8s/sample-app/ kubectl -n monitoring port-forward svc/kube-prometheus-stack-grafana 3000:80 # open http://localhost:3000 — dashboards are pre-provisioned
Both workflows in .github/workflows/ use GitHub's OIDC provider to assume a scoped IAM role — set the following as repository/environment configuration rather than secrets where possible:
| Name | Type | Purpose |
|---|---|---|
AWS_DEPLOY_ROLE_ARN |
secret | IAM role the pipeline assumes (ARN only, not credentials) |
AWS_REGION |
variable | Target region |
EKS_CLUSTER_NAME |
variable | Cluster to deploy the sample app into |
terraform.yml runs plan on every pull request touching terraform/** and apply on merge to main. deploy-app.yml builds and pushes an image to GHCR, then rolls it out with kubectl set image and waits on rollout status.
See docs/runbook.md for secret handling, credential rotation, common failure modes, and disaster-recovery notes.
This reference focuses on a single-cloud (AWS) setup for clarity, but the same patterns extend to hybrid/multi-cloud:
- Swap the
vpc/eksmodules for akubernetes_clusterabstraction (e.g. via Cluster API) that can target on-prem vSphere/bare-metal alongside cloud providers. - Keep the monitoring layer (
kube-prometheus-stack) identical — Prometheus federation or Thanos/Mimir can aggregate metrics across on-prem and cloud clusters into one Grafana. - Route CI/CD through a self-hosted runner for on-prem targets while keeping cloud-hosted GitHub runners for cloud targets, sharing the same workflow files via environment-scoped variables.
MIT — use freely as a reference or starting point.