I opened a shell inside one of my pods and asked AWS who it was.
It answered with an IAM role and a set of temporary credentials. No access keys anywhere. Not in an environment variable, not in a Kubernetes Secret, not in git.
The pod had real AWS permissions, and there was nothing I could point to and call a password.

An assumed role ARN, not a node role, not a static key. The pod proving its own identity.
That is IRSA, and it's the idea this whole build is anchored on.
The setup
ClearOps, my three-service app (frontend, API, Redis), was running on a local cluster while I proved out the Kubernetes migration.
This build takes it to production: Amazon EKS, AWS's managed Kubernetes.
The surprising part is how little changed. My Deployments, Services, ConfigMap, and autoscaler moved over almost untouched. EKS just means AWS runs the Kubernetes control plane, the API server, etcd, the scheduler, so I don't have to.
I keep the worker nodes and my manifests. Same Kubernetes, different management underneath. Same logic as using a managed database instead of running your own.
Images move to a real registry
The local cluster built images straight into its own daemon.
EKS nodes are EC2 instances that can be replaced at any time, so they pull images from a registry instead. ECR is AWS's private registry for this, the same idea as Docker Hub or Nexus, with vulnerability scanning on push turned on.

Every finding sits in the Debian base image, not my own code. The same base-image CVE pattern I'd already hardened against earlier in this build.
The one genuinely new idea
Before this, pods never needed AWS permissions, because there was no AWS in the picture.
On EKS, that becomes the central question. How does a pod get permission to do AWS things without a key baked into it somewhere?
The wrong answer is keys sitting in a Secret, a mistake I'd already caught and fixed once before. The too broad answer is a role attached to the node, because then every pod on that node inherits it, whether it needs it or not.
IRSA, IAM Roles for Service Accounts, is the actual answer. A pod proves its Kubernetes identity through the cluster's OIDC provider, and gets back temporary, auto rotating credentials scoped to just that pod. No keys stored anywhere.

An injected role ARN and a web identity token. That's the pod's entire AWS identity, and there's no key in sight.
This same pattern does the real work three separate times in this build: the load balancer controller, the app's own S3 access, and External Secrets. Each one is a pod that needs AWS permission, and each gets it the same keyless way.
Real load balancing from three lines
I'd built an Application Load Balancer by hand before. Created it, made a target group, set health checks, wired up security groups. An afternoon of console work.
The AWS Load Balancer Controller turns all of that into three lines of annotation on an Ingress. It's a pod that watches for Ingress objects and creates real ALBs to match, using its own IRSA identity for permission.

The app reachable through a real ALB, provisioned straight from my Ingress. An afternoon of manual console work, reduced to a manifest.
Secrets that never touch git
Kubernetes Secrets are only base64 encoded, not encrypted.
External Secrets Operator keeps the real value in AWS Secrets Manager and syncs it into the cluster on demand. What lives in git is only a pointer to the secret, never the value itself.

The value pulled live from Secrets Manager into a Kubernetes Secret. Nothing sensitive sitting in any manifest.
Pods with no nodes
A Fargate profile runs pods with no node of mine involved at all, billed per pod per minute. Good for short or spiky work that would otherwise mean paying for a node that mostly sits idle.
I ran a batch Job and watched it land on infrastructure that didn't exist a moment earlier.

A Job running on a Fargate node, not on my regular node group.
The part that actually needs attention: cost
EKS bills by the hour. The control plane has no free tier and no pause button, only delete.
My first cluster build failed outright, because the account was restricted to free tier instance types, and the size I picked wasn't one of them. The CloudFormation error said so plainly. Switching to an eligible size fixed it in one line.
So the whole build runs spin up and tear down in a single session. Budget alarm first, before anything else exists.
Teardown happens in strict order: the app and its Ingress first, so the controller removes the ALB on its own, confirmed gone, then the cluster, then the leftovers. Get that order wrong and the ALB orphans itself, billing on with nothing left to remove it.
A full session cost me under a dollar. A cluster left running by accident is where the scary numbers live.
The thread running through all of it
It's Kubernetes I already knew, wrapped in AWS plumbing, with IRSA as the identity glue and cost discipline as the constant.
Same manifests. Managed underneath. Keyless pod permissions. A meter you never take your eye off.
Full build, the eksctl config, the manifests, and the teardown runbook: github.com/vivianokose/nexaops-operations-lab/tree/main/11-eks
Top comments (0)