Secure EKS Platform with GitOps
Six-service EKS platform with Multi-AZ networking, IRSA on every workload, one shared ALB, and declarative zero-downtime delivery via Argo CD.
- CloudFormation
- EKS
- Argo CD
- IRSA

Overview
A reference implementation of a production-shaped EKS platform: six services behind one shared Application Load Balancer, deployed entirely by Argo CD from a single Git repository. The goal was to show the full path from network to running pod — Multi-AZ VPC, EKS cluster, IAM, and GitOps delivery — as a consistent, internally coherent system rather than a collection of disconnected snippets.
Every identifier in the repo is fictional (placeholder account ID, example.com
domains), but the manifests are complete and internally consistent — point them
at a real AWS account and they apply as-is.
Architecture
Three subnet tiers span three availability zones: public subnets hold the ALB
and NAT gateway, private subnets hold the EKS managed node group and VPC
interface endpoints (ECR, SSM, CloudWatch, STS), and database subnets — with no
route to NAT at all — hold RDS PostgreSQL and ElastiCache Redis. The node group
has no public IPs; the ALB reaches pods directly on their private addresses via
target-type: ip.
All six services share a single ALB — both Ingress objects declare the same
alb.ingress.kubernetes.io/group.name, so the controller merges them into one
load balancer instead of six. Every workload runs under its own IAM role via
IRSA, scoped to a single ServiceAccount and a single SSM Parameter Store
prefix, so a compromised pod can’t read another service’s secrets. Non-secret
config lives in a ConfigMap reviewable in a pull request; secrets are read at
pod start by the Secrets Store CSI driver, and no long-lived AWS keys exist
anywhere in the cluster.
Provisioning (VPC, EKS, EFS, RDS, Redis) is eleven CloudFormation stacks under
infrastructure/. Everything above that — namespaces, ingress, storage
claims, and all six service deployments — is reconciled by Argo CD from the
same repository.
Key decisions & tradeoffs
CloudFormation over Terraform. No state file to store, lock, or corrupt; drift detection and rollback come from the service itself rather than a separately-managed backend.
Immutable image tags, not latest. CI tags each image with its commit
SHA and rewrites the manifest. The deployed version is always traceable to a
specific commit, and rollback is just git revert.
CI never touches the cluster. The build pipeline authenticates to AWS via OIDC (no stored keys), pushes to ECR, and commits a one-line manifest change. Argo CD, which does hold cluster access, only ever reads from Git — so a compromised CI pipeline can push a bad image but can’t reach the cluster API.
Requests everywhere, CPU limits nowhere. CPU limits add CFS throttling, which hurts tail latency more than a noisy neighbor does. Memory is limited, because unlike CPU it’s incompressible — exceeding it has to kill the pod.
Probes tuned per workload, not copy-pasted. The long-running conversion
service tolerates minutes of unresponsiveness before a restart, because a
worker mid-job isn’t a failed worker; the web frontend uses a startupProbe
so a slow boot doesn’t trip a strict liveness check.
No service mesh, no HPA. Six services with straightforward north-south traffic don’t justify Istio or Linkerd, and autoscaling thresholds should come from observed load rather than invented numbers.
Results
What the finished design demonstrates end-to-end:
- One load balancer serving six services instead of six — a direct, measurable infra-cost reduction over the naive per-service default.
- Zero long-lived AWS credentials anywhere in the cluster — every pod-to-AWS call is scoped, short-lived IRSA access.
- A deploy is a Git commit; a rollback is
git revert— no manualkubectlonce the cluster is bootstrapped. - Full network isolation for stateful services: the database subnets have no route to the internet in either direction.