Amazon Elastic Kubernetes Service is the AWS service people most often misunderstand at the level of its name. "Managed Kubernetes" reads like AWS runs Kubernetes for you. What AWS actually runs is the control plane: the API server, etcd, the scheduler, and the controller manager, spread across three Availability Zones, patched and backed up and monitored, for a flat $0.10 per cluster per hour. Everything else that makes Kubernetes do work - the nodes your pods run on, the CNI that gives them IPs, the add-ons, the autoscaler, the version upgrades, the IAM wiring - was, for most of EKS's life, yours to operate. EKS is not managed Kubernetes. It is a managed control plane sold next to a menu of how much of the data plane you are willing to hand back to AWS. This article is about reading that menu correctly, because the difference between the cheapest and the most-managed way to run the exact same pod is a factor you will feel on both the invoice and the pager.
What it actually is
EKS gives you a conformant, upstream Kubernetes API endpoint that AWS operates. You never SSH into a master node because there are no master nodes you can see. You point kubectl at the endpoint, and AWS guarantees the control plane is available, scaled, and secured to its side of the shared responsibility line. In exchange you get a genuinely standard Kubernetes: the same manifests, the same kubectl, the same Helm charts, the same operators you would run on any other Kubernetes. That portability is the entire reason to pick EKS over a proprietary AWS container service. You are buying Kubernetes-the-API, running on infrastructure you do not have to babysit, with the escape hatch that your YAML would run somewhere else if you had to move.
The catch is that a control plane with no data plane runs nothing. A fresh EKS cluster is an empty API server. It schedules pods onto nodes, and until recently you had to bring those nodes, keep them patched, install the networking and storage and load-balancing add-ons, run an autoscaler, and upgrade all of it on Kubernetes's release cadence. That is the part "managed" never covered, and it is where most EKS pain lives.
The model
The primitive is the cluster: one control plane, one Kubernetes API, one version. Under it sits the data plane, and EKS now gives you three ways to provide it. You can run self-managed nodes, EC2 instances you launch and own end to end. You can run managed node groups, where EKS provisions and lifecycles EC2 instances for you but you still choose instance types and own the scaling policy. Or you can run Fargate, serverless pods with no nodes at all, one microVM per pod. As of late 2024 there is a fourth option that reframes all of them: EKS Auto Mode, where AWS provisions, scales, patches, and upgrades the compute, networking, and storage for you, using a managed Karpenter to pick right-sized instances and drain them on a schedule. Auto Mode is AWS finally managing the data plane, and it is the option that makes "managed Kubernetes" closer to true.
The consistency and durability contract is Kubernetes's, not something EKS invents. etcd is the source of truth, and AWS runs it across three AZs with automated backups. Your responsibility is the workloads and their configuration. AWS's responsibility is that the API server answers, that etcd does not lose your objects, and that the control plane survives an AZ failure. The line is clean: control plane is AWS, data plane is a spectrum you choose a point on.
The piece that trips up newcomers is identity. Pods frequently need to call AWS APIs - read from S3, write to DynamoDB, pull a secret. Kubernetes has its own identity system (service accounts) and AWS has its own (IAM roles), and the bridge between them is a first-class EKS feature. The original bridge, IAM Roles for Service Accounts (IRSA), maps a Kubernetes service account to an IAM role through an OIDC identity provider: every role that a pod can assume has to carry the cluster's OIDC issuer URL in its trust policy. The newer bridge, EKS Pod Identity (shipped late 2023, and the default recommendation for new clusters by 2026), decouples the two. An agent runs on each node, exposes a link-local endpoint the AWS SDK queries like instance metadata, and you create one association resource linking a namespace and service account to a role. The role's trust policy points at the EKS service and never changes again. For a new cluster, Pod Identity is the less painful path, and it is what we will use below.
When to use it, when not to
EKS is the right answer when you already have Kubernetes skills or manifests, when you need the Kubernetes ecosystem (operators, service meshes, GitOps tooling), or when workload portability across clouds is a real requirement and not a slide. It is the wrong answer when you have a handful of containers and no Kubernetes on staff, because you will pay the $0.10-per-hour control plane fee plus the operational tax of running Kubernetes to do what a simpler service does for free.
Here is the honest comparison against the three services people weigh EKS against.
Dimension | EKS | ECS | Self-managed k8s on EC2 |
|---|---|---|---|
Control plane | AWS-run, $0.10/hr per cluster | AWS-run, free | You run it, you pay for the EC2 and the pager |
API surface | Upstream Kubernetes, portable | AWS-proprietary, not portable | Upstream Kubernetes, portable |
Data plane | EC2, Fargate, or Auto Mode | EC2 or Fargate | EC2 you fully own |
Ecosystem | Full Kubernetes ecosystem | AWS-native only | Full Kubernetes ecosystem |
Operational load | Medium (Auto Mode) to high (self-managed nodes) | Low | Highest |
Best when | You want Kubernetes and its ecosystem | You want containers without Kubernetes | You need control the managed service will not give |
The row that decides most real cases is "ecosystem versus operational load." If you do not need the Kubernetes ecosystem, ECS gives you containers with a free control plane and less to operate, and picking EKS is buying complexity you will not use. If you do need it, EKS with Auto Mode gets you most of ECS's low operational load while keeping the portable API. Self-managing Kubernetes on raw EC2 is almost never right in 2026 unless you have a requirement EKS structurally cannot meet, and even then you should be able to name it.
What it costs
The control plane is the easy part: $0.10 per cluster per hour, about $72 a month, flat, regardless of how big the cluster gets. The number that surprises people is the extended-support cliff. A Kubernetes version gets 14 months of standard support in EKS. When that ends, the version rolls into extended support at $0.60 per cluster per hour, six times the standard rate, for up to 12 more months. AWS's own pricing example spells out the arithmetic: run a cluster for 26 months without upgrading and you average $0.33 an hour, more than triple the base. The extended-support fee is not a penalty AWS advertises loudly; it is a clock that starts the day you create the cluster and turns into a 6x bill if you ignore the upgrade treadmill. Budget for upgrades, or budget for the fee.
The data plane is billed separately and is where the real money is. With managed or self-managed nodes you pay standard EC2 and EBS prices for whatever you run, and the control plane fee is almost a rounding error next to a fleet of instances. With Auto Mode you pay a management fee on top of the EC2 price: roughly 12% of the On-Demand rate for each instance Auto Mode launches, billed per second with a one-minute minimum. AWS's example puts $1.434 an hour of EC2 under a $0.172 Auto Mode fee, which is the 12% made concrete. Two things bite here. First, the Auto Mode fee is charged at the On-Demand rate even if the underlying instance is on a Savings Plan, a Reserved Instance, or Spot, so it does not shrink when your compute commitment does. Second, if you plan to run Auto Mode past 150 nodes across your organization, AWS wants you to talk to your account team, which is the polite way of saying the list price stops applying at scale. (As of July 2026 AWS cut the Auto Mode management fee for GPU instances by up to 60%, so if you run accelerators, re-check the current rate rather than assuming a flat 12%.)
There are newer line items worth knowing exist. EKS Provisioned Control Plane lets you pre-buy control plane capacity in tiers (XL at $1.65/hr up to 8XL at $13.90/hr) for clusters big enough to hit control plane limits, which is a real problem at thousands of nodes and irrelevant below that. EKS Capabilities bills hourly for managed Argo CD, ACK, and KRO. And the free tier reality: there is no meaningful free tier for the control plane. The moment a cluster exists it costs $0.10 an hour. Do not leave test clusters running.
The limits that bite
The upgrade treadmill is the first one, and it is structural, not a bug. Kubernetes ships a minor version roughly every four months, and EKS keeps several recent minor versions in standard support at a time (as of writing in July 2026, the latest is 1.36, and the older supported versions run back through the 1.3x line). Standard support is 14 months per version, after which a version drops into extended support for another 12 months before it is gone. That means if you never upgrade, you have a little over a year before the extended-support fee starts, and about two years before the version is gone entirely. Upgrades are not free of risk either: they move the API server forward, deprecated APIs get removed, and a manifest that worked on 1.28 can break on 1.32. The cluster you stand up and forget is the cluster that surprises you with a 6x bill and a forced migration a year later.
The second limit is networking, specifically IP exhaustion. The default CNI (the Amazon VPC CNI) gives each pod a real VPC IP address from your subnets. That is great for native VPC networking and terrible if you sized your subnets for a handful of instances and then scheduled a few thousand pods. Running out of IPs shows up as pods stuck in ContainerCreating with no obvious cause, and the fix (prefix delegation, bigger subnets, or custom networking) is much easier to design in than to retrofit. Size your subnets for pods, not for nodes.
The third is that "managed" node groups still leave you holding the upgrade of the nodes themselves, the add-on versions, and the autoscaler tuning, unless you are on Auto Mode or Fargate. Managed node groups manage the EC2 lifecycle, not the Kubernetes-shaped work on top of it. Auto Mode is the option that actually removes that class of work, which is why, for a greenfield cluster in 2026, it is usually the right default.
Build it
We are going to stand up an Auto Mode cluster, deploy a workload, and give that workload scoped AWS access with Pod Identity, so you can see exactly where the "managed" line falls. Auto Mode means we never touch a node. Pod Identity means the pod gets an IAM role without an OIDC dance.
Prerequisites: an AWS account with admin-ish permissions to create an EKS cluster, IAM roles, and the associated VPC resources. Locally you need the AWS CLI v2 configured (aws configure), kubectl, and eksctl version 0.195.0 or newer (Auto Mode support landed there). The IAM permissions you need are broad for cluster creation - eks:*, plus the ability to create the cluster and node IAM roles and pass them - so do this from an account you control, not a tightly scoped CI role. Estimated cost of following along: the control plane is $0.10/hr, Auto Mode will launch one small instance (roughly $0.04-$0.10/hr of EC2 plus the ~12% Auto Mode fee), and an Application Load Balancer if you expose the service. Run the whole thing for an hour and clean up and you are looking at well under a dollar. Leave it running overnight and it is a few dollars a day.
Step one, create the cluster with Auto Mode enabled. This single command provisions the control plane, the Auto Mode data plane, the node IAM role, and the default node pools:
eksctl create cluster \
--name sl-eks-demo \
--region eu-west-3 \
--enable-auto-mode
This takes ten to fifteen minutes. When it finishes, eksctl has already written your kubeconfig. Confirm the control plane answers and that Auto Mode has nodes ready:
kubectl get nodes
kubectl get pods -A
You did not create those nodes. Auto Mode did, and it will resize and recycle them under you. That is the data plane being managed.
Step two, create an IAM role the pod will assume, scoped to exactly one thing: reading a single S3 bucket. First the trust policy that lets EKS Pod Identity assume the role:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "pods.eks.amazonaws.com" },
"Action": ["sts:AssumeRole", "sts:TagSession"]
}]
}
Create the role and attach a read-only S3 policy (use a real bucket you own in the permission policy for a tighter scope; the managed read-only policy is fine for the demo):
aws iam create-role \
--role-name sl-eks-pod-s3-read \
--assume-role-policy-document file://trust.json
aws iam attach-role-policy \
--role-name sl-eks-pod-s3-read \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
Step three, associate that role with a Kubernetes service account using Pod Identity. This is the whole bridge, one command, no OIDC provider to register:
aws eks create-pod-identity-association \
--cluster-name sl-eks-demo \
--namespace default \
--service-account s3-reader \
--role-arn arn:aws:iam::<ACCOUNT_ID>:role/sl-eks-pod-s3-read
Step four, deploy a pod that uses that service account and prove it can reach S3. Create the service account and a pod running the AWS CLI image:
apiVersion: v1
kind: ServiceAccount
metadata:
name: s3-reader
namespace: default
---
apiVersion: v1
kind: Pod
metadata:
name: s3-check
namespace: default
spec:
serviceAccountName: s3-reader
containers:
- name: awscli
image: public.ecr.aws/aws-cli/aws-cli:latest
command: ["sleep", "3600"]
Apply it with kubectl apply -f pod.yaml. Wait for the pod to reach Running (Auto Mode may need a minute to scale a node for it), then verify the identity actually flows through.
Verify it works
Exec into the pod and ask AWS who the pod is:
kubectl exec -it s3-check -- aws sts get-caller-identity
The Arn in the output should be an assumed-role ARN containing sl-eks-pod-s3-read, not your user, not the node's role. That is Pod Identity working: the SDK inside the pod found the local credentials endpoint, exchanged the service account token for temporary IAM credentials, and is now acting as the scoped role. Now confirm the scope holds in both directions:
kubectl exec -it s3-check -- aws s3 ls
kubectl exec -it s3-check -- aws dynamodb list-tables
The s3 ls should succeed. The dynamodb list-tables should fail with an AccessDenied, because the role only grants S3 read. A pod that can list your buckets but is refused DynamoDB is the contract you wanted: least privilege, enforced by IAM, delivered to a Kubernetes pod without a long-lived key anywhere. If both of those behave as described, the build worked.
When it breaks
If get-caller-identity returns the node's role instead of sl-eks-pod-s3-read, the association did not take. The usual cause is a name mismatch: the --service-account and --namespace in the association must match the pod's serviceAccountName and namespace exactly, and the pod must have been created after the association existed. Delete and recreate the pod so it picks up the injected credentials.
If the pod sits in Pending forever, Auto Mode has not given it a node. Check kubectl describe pod s3-check for scheduling events; on a brand-new cluster the first workload can wait a minute or two while Auto Mode provisions capacity. If it never schedules, you may have a resource request no available instance type can satisfy.
If pods elsewhere get stuck in ContainerCreating with IP-related errors, you have hit VPC IP exhaustion, the classic EKS failure. The subnets are out of addresses for pods. That is a design fix (prefix delegation or larger subnets), not a restart.
If kubectl itself returns Unauthorized right after creation, your local AWS identity is not in the cluster's access config. With the newer EKS access-entry model you grant your principal access with aws eks create-access-entry plus an access policy association; the older path was editing the aws-auth ConfigMap. On a cluster you created with eksctl, your creating identity is admin by default, so this usually bites teammates, not you.
And if you see a line item climbing on the bill months from now, check the cluster's Kubernetes version. A cluster left on a version past its 14-month standard-support window is quietly paying the $0.60/hr extended-support rate. That is the upgrade treadmill collecting what it is owed.
Cleanup
Auto Mode makes teardown genuinely one command, because there are no node groups to drain and delete by hand. Delete the workload, the association, the role, and the cluster:
kubectl delete pod s3-check
kubectl delete serviceaccount s3-reader
aws eks delete-pod-identity-association \
--cluster-name sl-eks-demo \
--association-id <ASSOCIATION_ID>
aws iam detach-role-policy \
--role-name sl-eks-pod-s3-read \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
aws iam delete-role --role-name sl-eks-pod-s3-read
eksctl delete cluster --name sl-eks-demo --region eu-west-3
The eksctl delete cluster call tears down the cluster, the Auto Mode-managed compute, and the CloudFormation stacks eksctl created for the VPC and roles. Confirm the cluster is gone with aws eks list-clusters and check the EC2 console for any stray instances or load balancers, because an ALB created by a Kubernetes service can outlive the cluster if its finalizer did not run. Once list-clusters is empty and no instances remain, the $0.10/hr clock has stopped and the bill for this exercise is closed.
The mental model to keep: EKS never sold you managed Kubernetes. It sold you a managed control plane and a dial for how much of the data plane you delegate. Turn the dial to Auto Mode and it feels like managed Kubernetes. Leave it on self-managed nodes and you are running Kubernetes yourself with a nicer API endpoint. The invoice, the pager, and the upgrade calendar all follow from where you set the dial, and the one setting AWS never lets you skip is keeping the version current.

