
Amazon EKS (Elastic Kubernetes Service)AuthorCloudBrum editorial teamLast reviewedReading time~12 minLevelBeginner to advanced
Amazon Web Services · Containers · Managed Kubernetes
Kubernetes where AWS runs the control plane and you run the rest. EKS gives you a standard Kubernetes API server and etcd, spread over three Availability Zones and kept patched by AWS. Everything your pods run on is still your decision: managed node groups, Fargate, Karpenter or the newer Auto Mode. Two things catch teams out. Every Kubernetes version has a support end date, and a cluster left past it starts costing six times as much for the control plane.
On this page (17 sections)
Mind map: Amazon EKS at a glance
Free to share. The downloaded image includes the CloudBrum link.
Key features (summary)
- A control plane you never patchAWS runs the Kubernetes API servers and etcd across three Availability Zones, replaces unhealthy control plane nodes and applies security fixes. You get a normal Kubernetes endpoint and talk to it with kubectl, Helm or any other client.
- Four ways to get worker nodesManaged node groups run EC2 instances that AWS patches and drains for you. Fargate runs each pod on its own small VM. Karpenter picks instance types as pods appear. Auto Mode goes one step further and has AWS run Karpenter, the load balancer controller and storage drivers for you.
- IAM for people and for podsAccess entries map IAM roles and users to Kubernetes permissions without editing a ConfigMap. EKS Pod Identity gives one service account an IAM role, so a pod can call S3 or DynamoDB without long-lived keys.
- Pods with real VPC addressesThe default VPC CNI plugin gives each pod an IP address from your subnets, so security groups, flow logs and routing work the way they do for EC2. The price is IP addresses: a busy cluster can drain a small subnet.
- Add-ons with versionsCoreDNS, kube-proxy, the VPC CNI, the EBS CSI driver and the Pod Identity agent are installed as EKS add-ons. EKS shows which version fits your cluster and can update them in place.
- Defined support windowsA Kubernetes minor version gets about 14 months of standard support and then 12 months of extended support. After standard support ends the control plane fee goes from $0.10 to $0.60 per cluster-hour, unless you upgrade first.
- Private clusters and encryptionTurn the public API endpoint off and reach the cluster from your VPC only. Add a KMS key for envelope encryption of Kubernetes secrets, and ship audit logs to CloudWatch.
- Runs beyond one RegionEKS Anywhere and hybrid nodes let you run Kubernetes on your own hardware while still managing it through the EKS API, which suits data that can’t leave a building.
Basic concepts
- Cluster
- One EKS control plane plus the nodes registered to it. Most teams run a few clusters, not one per application.
- Control plane
- The Kubernetes API server, scheduler, controllers and etcd. AWS runs it in its own account and shows you only the endpoint and the logs.
- Managed node group
- An Auto Scaling group of EC2 instances that EKS creates and updates. You choose instance types, size limits and an AMI family.
- Fargate profile
- A rule that says which namespaces or labelled pods run on Fargate. Each pod gets its own isolated compute, with no nodes to patch.
- Karpenter
- An open-source autoscaler that watches for pods that can’t be scheduled and launches the cheapest instance that fits them. It replaces the Cluster Autoscaler on many clusters.
- EKS Auto Mode
- A cluster setting where AWS also runs the nodes, the load balancer controller, the block storage driver and node scaling. You pay a management fee per instance on top of EC2.
- Access entry
- A cluster record that connects an IAM principal to Kubernetes permissions. An access policy such as AmazonEKSViewPolicy supplies the rights.
- EKS Pod Identity
- Lets a Kubernetes service account assume an IAM role through an agent on the node. The role’s trust policy names pods.eks.amazonaws.com.
- IRSA
- The older way to do the same job, using an OIDC provider and a web identity token. It still works, and you’ll meet it in older clusters and on Fargate.
- VPC CNI
- The networking plugin that attaches VPC addresses to pods. Settings such as prefix delegation change how many pods fit on a node.
- Add-on
- A component managed through the EKS API, such as coredns, kube-proxy, vpc-cni or aws-ebs-csi-driver, with its own version.
- Upgrade policy
- A cluster setting, STANDARD or EXTENDED, that decides what happens when the version leaves standard support.
- Cluster endpoint
- The URL of the Kubernetes API. It can be public, private or both, and the public side can be limited to chosen CIDR ranges.
- Pod Disruption Budget
- A Kubernetes object that limits how many replicas can be down at once. Node upgrades and drains respect it, so write one for anything you can’t lose.
How to create & configure it
Create an EKS cluster with the AWS CLI
- Pick the Region and the networkChoose a Region close to your users (Mumbai or Hyderabad for India). You need a VPC with subnets in at least two Availability Zones, and plenty of free addresses in them, because pods use them too.
- Create the cluster roleEKS needs an IAM role that the service can assume, with the AmazonEKSClusterPolicy managed policy attached. Create it once and reuse it for every cluster.
- Create the clusterRun aws eks create-cluster with the role, the subnets and the access mode API. Switch the public endpoint off if your build agents run inside the VPC, and add --upgrade-policy supportType=STANDARD if you never want extended-support pricing by surprise.
- Wait until it’s activeCreation takes around ten minutes. aws eks wait cluster-active blocks until the control plane answers.
- Add worker capacityCreate a managed node group with its own node role, two or three zones of subnets, and a size range. Start with a modest instance type and let the autoscaler or Karpenter do the rest.
- Install the add-ons you needThe VPC CNI, CoreDNS and kube-proxy arrive with the cluster. Add eks-pod-identity-agent for pod permissions and aws-ebs-csi-driver if any workload needs a persistent volume.
- Give people accessCreate access entries for the IAM roles your team uses, and attach an access policy at cluster or namespace scope. Skip the old aws-auth ConfigMap on new clusters.
- Connect and deployaws eks update-kubeconfig writes the kubeconfig. Apply your namespace, deployment, service, autoscaler and disruption budget, then check that the pods are ready on more than one node.
- Plan the first upgrade nowLook up the support end date of your version, put it in a calendar, and run Upgrade Insights before you change anything. Control plane first, add-ons next, nodes last.
Tip: eksctl and the Terraform EKS module wrap most of these steps, and either one is a better home for a real cluster than a shell script. The CLI version is useful because it shows every moving part once. See the code references.
Common issues after configuration
| Symptom | Likely cause | What to check / fix |
|---|---|---|
| error: You must be logged in to the server (Unauthorized) | Your IAM principal has no access entry, or kubectl is using another AWS profile than you think | Run aws sts get-caller-identity, then check aws eks list-access-entries. Add an entry for the role and attach an access policy. Also check the kubeconfig’s exec block uses the right profile and Region. |
| kubectl times out on a private cluster | The API endpoint is private and your laptop is outside the VPC | Connect through a VPN, Direct Connect, a bastion or a CI runner inside the VPC. If you must open it temporarily, enable the public endpoint with a narrow publicAccessCidrs list. |
| Nodes never join the cluster | The node role is missing a policy, the subnet can’t reach the API endpoint or the registries, or the AMI doesn’t match the node group | Check the node role has the worker node, CNI and ECR pull policies. Confirm the subnet has a route to NAT, a VPC endpoint or both. Read the node group’s health issues in the console. |
| Pods stuck in Pending with 0/3 nodes are available: Too many pods | Each instance type has a limit on network interfaces and addresses, so only so many pods fit | Turn on prefix delegation for the VPC CNI, use larger instances, or let Karpenter place pods. Don’t solve it by adding the same small instance type forever. |
| Pods stuck in Pending, insufficient cpu or didn’t match Pod’s node affinity | Requests are larger than any free node, or a selector, taint or zone constraint rules the nodes out | Run kubectl describe pod, read the scheduler’s last event, then compare requests with node capacity. Check for taints and a persistent volume stuck in another zone. |
| AccessDenied from the AWS SDK inside a pod | The pod uses the node’s role, or the Pod Identity association points at a different service account or namespace | Confirm the pod’s service account name, check aws eks list-pod-identity-associations, and make sure the add-on eks-pod-identity-agent is running on that node. Restart the pod after you change the association. |
| ImagePullBackOff from ECR | The node role can’t pull from the repository, or the subnet can’t reach ECR | Attach AmazonEC2ContainerRegistryPullOnly to the node role. For nodes without internet access add the ECR and S3 VPC endpoints. |
| Persistent volume claims stay Pending | The EBS CSI driver isn’t installed or its role is missing | Install the aws-ebs-csi-driver add-on with a role that can manage volumes, and make sure the storage class exists. |
| The load balancer is never created | The AWS Load Balancer Controller isn’t running or the subnets lack the required tags | Install the controller (or use Auto Mode), then tag public subnets kubernetes.io/role/elb and private subnets kubernetes.io/role/internal-elb. |
| An upgrade fails or hangs on a node group | A Pod Disruption Budget blocks draining, or an add-on version doesn’t support the new Kubernetes version | Check the budgets with kubectl get pdb -A, look at Upgrade Insights for removed APIs, and update add-ons before the nodes. Don’t set force until you know why a node refuses to drain. |
| The control plane bill suddenly grew by $365 a month | The cluster version passed the end of standard support and moved to extended support | Upgrade the cluster, or set the upgrade policy to STANDARD where you’d rather be forced up than billed. Add the end date to your team calendar. |
Use cases: when and where to use it
Good fit
- Microservice platforms with a dozen or more services that need rolling updates, autoscaling and service discovery
- Teams that already know Kubernetes and want to keep manifests and Helm charts portable
- Platform teams building a shared cluster for many product teams, with namespaces and quotas
- Machine learning and batch jobs that need GPUs, spot capacity and a scheduler
- Moving self-managed Kubernetes off EC2 so nobody patches etcd any more
- Hybrid setups where some workloads stay on premises but share one control plane
Consider something else
- One or two containers with no need for Kubernetes → Amazon ECS with Fargate, or AWS App Runner, which carry far less to learn
- Short event-driven code → AWS Lambda, which has no cluster at all
- A single virtual server → Amazon EC2 is simpler and cheaper
- Teams without anybody to own cluster upgrades and policies → start on a platform that hides more, such as App Runner or ECS
- The same job on other clouds → compare Azure Kubernetes Service and Google Kubernetes Engine
Pros: why consider it?
Why teams choose it
- A conformant Kubernetes API, so tools and manifests move with you
- No control plane to patch, back up or scale
- Close ties to IAM, VPC, CloudWatch, KMS and load balancers
- Several compute models in one cluster: managed nodes, Fargate and Karpenter
- Access entries and Pod Identity remove most of the old credential plumbing
- A huge pool of add-ons, operators and community answers
Trade-offs to weigh
- You still run nodes, add-ons, upgrades and policies unless you pick Auto Mode
- The control plane has a fixed monthly fee even when the cluster is idle
- Pod networking uses VPC addresses, which makes IP planning a real task
- Upgrades are your job, one minor version at a time, and you can’t go back
- Costs hide in load balancers, NAT gateways, cross-zone traffic and logs
- Kubernetes itself is a lot to learn, and EKS doesn’t change that
Common threats, vulnerabilities & risks
| Risk | Severity | Why it matters | Mitigation |
|---|---|---|---|
| A public API endpoint open to the whole internet | High | Anyone can reach the login endpoint and try stolen credentials or unpatched flaws. | Make the endpoint private, or limit the public one to known CIDR ranges, and use access entries with least privilege. |
| Cluster admin handed out widely | High | A single mistaken command or stolen role can delete every workload. | Give people namespace-scoped access policies, keep admin to a few roles, and review access entries each quarter. |
| Pods using the node role | High | Every pod on the node can then reach whatever the node can, including ECR and sometimes much more. | Use Pod Identity or IRSA per service account, and block pod access to the instance metadata service. |
| Running past the end of support | Medium | You pay the extended-support price, and after the extended window AWS upgrades the control plane for you at its own pace. | Track end dates, upgrade at least twice a year, and set the upgrade policy deliberately. |
| Upgrades that break deprecated APIs | Medium | Manifests that use removed API versions fail after the control plane moves. | Run Upgrade Insights, scan manifests with a linter, and test in a staging cluster first. |
| Subnets that run out of addresses | Medium | Pods stay Pending even though there is CPU left, and fixing it later means new subnets. | Size subnets for pods, not only nodes, and consider prefix delegation or a secondary CIDR range. |
| No resource requests and limits | Medium | One greedy pod starves others, and the autoscaler can’t make good decisions. | Set requests on every container, use quotas per namespace and review with real usage data. |
| Secrets stored as plain Kubernetes secrets | Low–Med | They are only base64 encoded, and anyone with read access to the namespace can see them. | Encrypt with KMS, limit who can read secrets, and keep real secrets in Secrets Manager with the CSI driver. |
| Cost drift from idle nodes and load balancers | Low–Med | Forgotten test clusters, oversized nodes and one load balancer per service pile up quietly. | Tag by team, share ingress, use Karpenter consolidation, and delete test clusters on a schedule. |
Performance optimisation
- Set CPU and memory requests from observed usage. Requests decide scheduling and autoscaling, so guessing high wastes money and guessing low causes evictions.
- Use the Horizontal Pod Autoscaler for replicas and Karpenter or Auto Mode for nodes, and give new nodes time to join by keeping a little headroom.
- Spread replicas over zones with topology spread constraints, and add a Pod Disruption Budget so a drain never takes them all.
- Pick Graviton instance types for services whose images support arm64. They usually give more work per dollar.
- Keep readiness probes honest. A pod that reports ready too early gets traffic it can’t serve.
- Watch CoreDNS: a busy cluster can overload it. Scale it, enable node-local caching and avoid long search paths in application DNS names.
- Put heavy east-west traffic in one zone where you can, since cross-zone traffic adds latency and a per-GB charge.
- Use VPC CNI prefix delegation or larger instance types when pod density is the limit rather than CPU.
Reduce monthly cost
FinOps is the habit of managing cloud spend together with engineering. These are the FinOps checks that matter most for Amazon EKS.
- Remember the control plane fee of $0.10 per cluster-hour (about $73 a month). Fewer, larger, well-governed clusters cost less than one per team.
- Upgrade before standard support ends, because extended support costs $0.60 per cluster-hour for the same cluster.
- Let Karpenter consolidate nodes, and allow Spot capacity for stateless and batch workloads with a sensible interruption plan.
- Right-size requests with the Vertical Pod Autoscaler recommendations or your metrics. Oversized requests mean oversized nodes.
- Buy Savings Plans for the steady base load, and leave the spiky part on-demand or Spot.
- Share load balancers through one ingress or gateway instead of one per service.
- Add VPC endpoints for ECR and S3 if image pulls through NAT are large, since NAT gateways charge per GB processed.
- Cut log volume. Control plane audit logs and verbose application logs in CloudWatch can cost more than a small node group.
- Shut down development clusters at night or delete them after a branch is merged.
- Tag clusters, node groups and namespaces by team, and check Auto Mode’s per-instance fee against what Karpenter would cost you to run yourself.
The prices in this section are AWS list prices as we read them in October 2026, and the extended-support rate comes from AWS's own announcement. Region, instance type, discounts and later changes all move your bill, so check the live pricing page (see Pricing).
Security optimisation
- Keep the API endpoint private, or restrict the public endpoint to known CIDR ranges.
- Use access entries with narrow access policies instead of cluster-admin for people, and set the cluster’s authentication mode to API.
- Give every workload its own service account and IAM role through Pod Identity or IRSA, and don’t let pods fall back to the node role.
- Turn on envelope encryption of secrets with a KMS key you control, and keep real secrets in Secrets Manager or Parameter Store.
- Apply Pod Security Standards per namespace (restricted for most application namespaces), and run containers as non-root with a read-only root file system where you can.
- Add network policies, starting with default deny, and use security groups for pods where a workload needs its own firewall.
- Send control plane logs (api, audit, authenticator) to CloudWatch, and alert on unusual exec, role binding and secret reads.
- Scan images in Amazon ECR, pin tags or digests, and allow only your registry with an admission policy.
- Keep nodes patched by rolling the node group to the latest AMI release, or by using Bottlerocket or Auto Mode, which replace nodes regularly.
- Use GuardDuty EKS protection and AWS Config or Security Hub rules to catch drift and suspicious behaviour.
Governance & compliance
Guardrails
- Tagging rules for clusters, node groups and namespaces (owner, environment, cost centre) enforced at creation
- A service control policy that denies public API endpoints and unencrypted secrets in production accounts
- A shared Terraform module for clusters, with private endpoint, logging, access mode and upgrade policy already set
- An approved list of Kubernetes versions with a retirement date for each, and a rule that production never runs the oldest
Continuous checks (AWS Config and Security Hub)
- “eks-endpoint-no-public-access”
- “eks-secrets-encrypted”
- “eks-cluster-logging-enabled”
- “eks-cluster-supported-version”
- “eks-cluster-oldest-supported-version”
AWS CloudTrail records who created, changed or deleted clusters and node groups, EKS audit logs record what happens inside the Kubernetes API, AWS Config keeps the history and the rules above, and AWS Artifact holds compliance reports. The split of duties is the usual one for managed Kubernetes: AWS runs and patches the control plane, while you own node configuration, workload permissions, network policy, images and upgrades on time.
Reduce carbon footprint
- Right-size requests and nodes. Idle capacity draws power all day.
- Consolidate with Karpenter or Auto Mode so lightly used nodes are removed.
- Prefer Graviton instances where your images support them. They deliver more work per watt.
- Run batch and test workloads when you need them, and delete the cluster afterwards.
- Share clusters and load balancers across teams instead of duplicating whole stacks.
- Keep traffic inside a zone and compress what you log, so less data moves and is stored.
- The AWS Customer Carbon Footprint Tool gives an account-level estimate. Check what it covers for container services before you quote a number.
Pricing
Purchase options
Control plane Per cluster-hour
$0.10 per hour on a supported Kubernetes version, about $73 a month, and $0.60 per hour while a version is in extended support.
Nodes and Fargate Per instance-second or vCPU-second
Managed node groups and Karpenter nodes are billed as ordinary EC2 instances. Fargate bills each pod for the vCPU and memory it requests.
EKS Auto Mode Per instance, on top of EC2
A management fee is added for each instance Auto Mode launches. AWS publishes the rate per instance type, and Savings Plans don’t reduce it.
Everything around it Varies
Load balancers, NAT gateways, EBS and EFS storage, data transfer between zones and out to the internet, CloudWatch logs and any EKS add-ons from the marketplace.
What you actually pay for
- The control plane for every cluster, per hour, at the standard or extended rate
- EC2 instances or Fargate resources that run your pods
- Auto Mode’s per-instance fee if you use it
- Load balancers, NAT gateways and other VPC resources the cluster needs
- Storage, snapshots, logs and data transfer between zones, Regions and to the internet
Rates vary by Region and change over time. EKS doesn't sit in the always-free allowance, so a test cluster bills from the first hour. AWS gives new accounts credits that may cover it, so read the terms for your account. See the official Amazon EKS pricing page and the AWS Pricing Calculator for current figures, and AWS's note on extended support pricing for the version rules.
Important code references
A private three-replica web service called orders-api. The CLI tab builds the cluster with a managed node group, Pod Identity and an access entry, then applies the Kubernetes manifest (namespace, service account, deployment, service, autoscaler, disruption budget and network policy) with kubectl. Terraform builds the same cluster. The Python tab is the program inside the pod: it writes a receipt to S3 with the credentials Pod Identity hands out, with no keys anywhere. The C# tab runs from a laptop or a pipeline and waits for the rollout to finish. Replace the subnets, account number, bucket and Region with your own. A cluster bills from the moment it exists, so delete the test one when you’re done.
# Placeholders: replace the subnets, account and bucket with your own.
REGION=ap-south-1
CLUSTER=orders-prod
ACCOUNT=111122223333
SUBNETS=subnet-0aaa1111bbbb2222c,subnet-0ddd3333eeee4444f
BUCKET=orders-receipts-example
# 1. Roles: one for the control plane, one for the nodes, one that pods can assume.
aws iam create-role --role-name eks-orders-cluster --assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{"Effect": "Allow", "Principal": {"Service": "eks.amazonaws.com"}, "Action": "sts:AssumeRole"}]}'
aws iam attach-role-policy --role-name eks-orders-cluster --policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
aws iam create-role --role-name eks-orders-nodes --assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{"Effect": "Allow", "Principal": {"Service": "ec2.amazonaws.com"}, "Action": "sts:AssumeRole"}]}'
for P in AmazonEKSWorkerNodePolicy AmazonEKS_CNI_Policy AmazonEC2ContainerRegistryPullOnly; do
aws iam attach-role-policy --role-name eks-orders-nodes --policy-arn arn:aws:iam::aws:policy/$P
done
aws iam create-role --role-name orders-api-pod --assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{"Effect": "Allow", "Principal": {"Service": "pods.eks.amazonaws.com"}, "Action": ["sts:AssumeRole", "sts:TagSession"]}]}'
aws iam put-role-policy --role-name orders-api-pod --policy-name receipts-write --policy-document '{
"Version": "2012-10-17",
"Statement": [{"Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::'$BUCKET'/receipts/*"}]}'
# 2. The cluster: access entries only (no aws-auth ConfigMap), private endpoint, audit logs, no accidental extended support.
aws eks create-cluster --name $CLUSTER --region $REGION \
--role-arn arn:aws:iam::$ACCOUNT:role/eks-orders-cluster \
--resources-vpc-config subnetIds=$SUBNETS,endpointPrivateAccess=true,endpointPublicAccess=false \
--access-config authenticationMode=API,bootstrapClusterCreatorAdminPermissions=true \
--upgrade-policy supportType=STANDARD \
--deletion-protection \
--logging '{"clusterLogging":[{"types":["api","audit","authenticator"],"enabled":true}]}'
aws eks wait cluster-active --name $CLUSTER --region $REGION
# 3. Nodes: three Graviton instances across the subnets, replaced one at a time during updates.
aws eks create-nodegroup --cluster-name $CLUSTER --region $REGION --nodegroup-name general \
--node-role arn:aws:iam::$ACCOUNT:role/eks-orders-nodes --subnets ${SUBNETS//,/ } \
--instance-types m7g.large --ami-type AL2023_ARM_64_STANDARD --capacity-type ON_DEMAND \
--scaling-config minSize=2,maxSize=6,desiredSize=3 --update-config maxUnavailable=1
aws eks wait nodegroup-active --cluster-name $CLUSTER --nodegroup-name general --region $REGION
# 4. Add-ons and permissions: the Pod Identity agent, then the link between the service account and its role.
aws eks create-addon --cluster-name $CLUSTER --region $REGION --addon-name eks-pod-identity-agent
aws eks create-pod-identity-association --cluster-name $CLUSTER --region $REGION \
--namespace orders --service-account orders-api --role-arn arn:aws:iam::$ACCOUNT:role/orders-api-pod
# 5. A read-only entry for the support team, limited to one namespace.
aws eks create-access-entry --cluster-name $CLUSTER --region $REGION \
--principal-arn arn:aws:iam::$ACCOUNT:role/support-readonly --type STANDARD
aws eks associate-access-policy --cluster-name $CLUSTER --region $REGION \
--principal-arn arn:aws:iam::$ACCOUNT:role/support-readonly \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy \
--access-scope type=namespace,namespaces=orders
# 6. Deploy. Run this from inside the VPC, because the endpoint is private.
aws eks update-kubeconfig --name $CLUSTER --region $REGION
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
name: orders
labels:
pod-security.kubernetes.io/enforce: restricted
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: orders-api
namespace: orders
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-api
namespace: orders
spec:
replicas: 3
selector:
matchLabels: {app: orders-api}
strategy:
rollingUpdate: {maxSurge: 1, maxUnavailable: 0}
template:
metadata:
labels: {app: orders-api}
spec:
serviceAccountName: orders-api
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels: {app: orders-api}
securityContext:
runAsNonRoot: true
seccompProfile: {type: RuntimeDefault}
containers:
- name: api
image: 111122223333.dkr.ecr.ap-south-1.amazonaws.com/orders-api:1.4.2
ports:
- containerPort: 8080
env:
- {name: RECEIPT_BUCKET, value: orders-receipts-example}
resources:
requests: {cpu: 250m, memory: 256Mi}
limits: {memory: 512Mi}
readinessProbe:
httpGet: {path: /healthz, port: 8080}
periodSeconds: 5
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: {drop: ["ALL"]}
---
apiVersion: v1
kind: Service
metadata:
name: orders-api
namespace: orders
spec:
selector: {app: orders-api}
ports:
- port: 80
targetPort: 8080
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: orders-api
namespace: orders
spec:
scaleTargetRef: {apiVersion: apps/v1, kind: Deployment, name: orders-api}
minReplicas: 3
maxReplicas: 12
metrics:
- type: Resource
resource:
name: cpu
target: {type: Utilization, averageUtilization: 70}
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: orders-api
namespace: orders
spec:
minAvailable: 2
selector:
matchLabels: {app: orders-api}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: orders-api-ingress
namespace: orders
spec:
podSelector:
matchLabels: {app: orders-api}
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels: {kubernetes.io/metadata.name: ingress}
ports:
- {port: 8080, protocol: TCP}
EOF
kubectl -n orders rollout status deployment/orders-api
# 7. Later: see what an upgrade would break, then move the control plane one minor version.
aws eks list-insights --cluster-name $CLUSTER --region $REGION
# aws eks update-cluster-version --name $CLUSTER --kubernetes-version <next minor> --region $REGION
# Placeholders: pass in your own subnet IDs and bucket name.
variable "subnet_ids" {
type = list(string)
}
variable "receipt_bucket" {
type = string
default = "orders-receipts-example"
}
data "aws_caller_identity" "me" {}
data "aws_iam_policy_document" "eks_trust" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["eks.amazonaws.com"]
}
}
}
data "aws_iam_policy_document" "node_trust" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
data "aws_iam_policy_document" "pod_trust" {
statement {
actions = ["sts:AssumeRole", "sts:TagSession"]
principals {
type = "Service"
identifiers = ["pods.eks.amazonaws.com"]
}
}
}
resource "aws_iam_role" "cluster" {
name = "eks-orders-cluster"
assume_role_policy = data.aws_iam_policy_document.eks_trust.json
}
resource "aws_iam_role_policy_attachment" "cluster" {
role = aws_iam_role.cluster.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
}
resource "aws_iam_role" "nodes" {
name = "eks-orders-nodes"
assume_role_policy = data.aws_iam_policy_document.node_trust.json
}
resource "aws_iam_role_policy_attachment" "nodes" {
for_each = toset([
"AmazonEKSWorkerNodePolicy",
"AmazonEKS_CNI_Policy",
"AmazonEC2ContainerRegistryPullOnly",
])
role = aws_iam_role.nodes.name
policy_arn = "arn:aws:iam::aws:policy/${each.key}"
}
resource "aws_eks_cluster" "orders" {
name = "orders-prod"
role_arn = aws_iam_role.cluster.arn
deletion_protection = true
enabled_cluster_log_types = ["api", "audit", "authenticator"]
vpc_config {
subnet_ids = var.subnet_ids
endpoint_private_access = true
endpoint_public_access = false
}
access_config {
authentication_mode = "API"
bootstrap_cluster_creator_admin_permissions = true
}
upgrade_policy {
support_type = "STANDARD"
}
depends_on = [aws_iam_role_policy_attachment.cluster]
}
resource "aws_eks_node_group" "general" {
cluster_name = aws_eks_cluster.orders.name
node_group_name = "general"
node_role_arn = aws_iam_role.nodes.arn
subnet_ids = var.subnet_ids
instance_types = ["m7g.large"]
ami_type = "AL2023_ARM_64_STANDARD"
scaling_config {
min_size = 2
desired_size = 3
max_size = 6
}
update_config {
max_unavailable = 1
}
depends_on = [aws_iam_role_policy_attachment.nodes]
}
resource "aws_eks_addon" "pod_identity" {
cluster_name = aws_eks_cluster.orders.name
addon_name = "eks-pod-identity-agent"
depends_on = [aws_eks_node_group.general]
}
resource "aws_iam_role" "orders_pod" {
name = "orders-api-pod"
assume_role_policy = data.aws_iam_policy_document.pod_trust.json
}
resource "aws_iam_role_policy" "orders_pod" {
name = "receipts-write"
role = aws_iam_role.orders_pod.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "s3:PutObject"
Resource = "arn:aws:s3:::${var.receipt_bucket}/receipts/*"
}]
})
}
resource "aws_eks_pod_identity_association" "orders" {
cluster_name = aws_eks_cluster.orders.name
namespace = "orders"
service_account = "orders-api"
role_arn = aws_iam_role.orders_pod.arn
depends_on = [aws_eks_addon.pod_identity]
}
resource "aws_eks_access_entry" "support" {
cluster_name = aws_eks_cluster.orders.name
principal_arn = "arn:aws:iam::${data.aws_caller_identity.me.account_id}:role/support-readonly"
type = "STANDARD"
}
resource "aws_eks_access_policy_association" "support" {
cluster_name = aws_eks_cluster.orders.name
principal_arn = aws_eks_access_entry.support.principal_arn
policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy"
access_scope {
type = "namespace"
namespaces = ["orders"]
}
}
# Runs inside the orders-api pod. No keys anywhere: with an EKS Pod Identity association, the agent on the node
# serves temporary credentials, and boto3's default chain finds them (AWS_CONTAINER_CREDENTIALS_FULL_URI).
import json
import os
import uuid
from datetime import datetime, timezone
import boto3
from botocore.config import Config
BUCKET = os.environ["RECEIPT_BUCKET"]
s3 = boto3.client("s3", config=Config(retries={"max_attempts": 5, "mode": "standard"}))
def save_receipt(order_id, total):
body = json.dumps({"order": order_id, "total": total, "at": datetime.now(timezone.utc).isoformat()})
key = "receipts/%s/%s.json" % (datetime.now(timezone.utc).strftime("%Y/%m/%d"), order_id)
s3.put_object(Bucket=BUCKET, Key=key, Body=body.encode(), ContentType="application/json",
ServerSideEncryption="AES256")
return key
def who_am_i():
# Handy in a pod that gets AccessDenied: this shows whether it's the pod role or the node role.
return boto3.client("sts").get_caller_identity()["Arn"]
if __name__ == "__main__":
print("running as", who_am_i())
print("saved", save_receipt(str(uuid.uuid4()), 1249.50))
// dotnet add package KubernetesClient
// Waits for the orders-api rollout, then lists any pod that is not ready. The default config reads your
// kubeconfig (the exec plugin calls "aws eks get-token"), or the service account inside a pod.
using k8s;
using k8s.Models;
var client = new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig());
const string ns = "orders", name = "orders-api";
var give_up = DateTime.UtcNow.AddMinutes(5);
while (true)
{
V1Deployment d = await client.AppsV1.ReadNamespacedDeploymentStatusAsync(name, ns);
int want = d.Spec.Replicas ?? 1;
bool done = d.Status.ObservedGeneration >= d.Metadata.Generation
&& (d.Status.UpdatedReplicas ?? 0) == want
&& (d.Status.AvailableReplicas ?? 0) == want
&& (d.Status.Replicas ?? 0) == want;
Console.WriteLine($"{name}: {d.Status.AvailableReplicas ?? 0}/{want} available");
if (done) break;
if (DateTime.UtcNow > give_up)
{
V1PodList pods = await client.CoreV1.ListNamespacedPodAsync(ns, labelSelector: "app=" + name);
foreach (var p in pods.Items.Where(p => p.Status.Phase != "Running"
|| (p.Status.ContainerStatuses ?? new List<V1ContainerStatus>()).Any(c => !c.Ready)))
{
var waiting = p.Status.ContainerStatuses?.FirstOrDefault()?.State?.Waiting?.Reason;
Console.Error.WriteLine($" {p.Metadata.Name}: {p.Status.Phase} {waiting}");
}
Console.Error.WriteLine("Rollout did not finish in 5 minutes. Run: kubectl -n orders describe deploy " + name);
return 1;
}
await Task.Delay(TimeSpan.FromSeconds(5));
}
Console.WriteLine("Rollout complete.");
return 0;
Best videos to explore
Short walkthrough videos for this service are being prepared. They will appear here and load only when you press play. Until then, follow CloudBrum on YouTube for new videos.
Interview questions
BasicWhat is Amazon EKS?
It’s AWS’s managed Kubernetes service. AWS runs and patches the Kubernetes control plane (the API servers and etcd) across three Availability Zones, and you bring the worker nodes, either as EC2 instances, Fargate pods or through EKS Auto Mode. You talk to it with the usual Kubernetes tools.
BasicWhat is the difference between EKS and ECS?
ECS is AWS’s own container orchestrator with its own API and far fewer concepts. EKS is standard Kubernetes, so it brings more to learn but also portable manifests, Helm charts and a very large community. Teams with a Kubernetes skill base or a multi-cloud plan usually pick EKS; small teams with a few services often find ECS faster.
BasicWhat does EKS cost?
The control plane is $0.10 per cluster-hour on a supported Kubernetes version and $0.60 per hour on extended support. On top of that you pay for the nodes or Fargate resources, load balancers, storage, NAT gateways, logs and data transfer. Auto Mode adds a per-instance fee.
IntermediateHow do people and pods get permissions in EKS?
People use IAM through access entries: an entry links an IAM role to the cluster, and an access policy such as AmazonEKSViewPolicy grants Kubernetes rights at cluster or namespace scope. Pods use EKS Pod Identity (or IRSA on older setups), which ties a service account to an IAM role so the pod gets short-lived AWS credentials.
IntermediateManaged node groups, Fargate, Karpenter or Auto Mode: how do you choose?
Managed node groups are the plain default and suit steady workloads. Karpenter fits changing, mixed workloads and Spot, at the price of running one more controller. Fargate removes nodes for pods that don’t need daemonsets or GPUs, but costs more per unit. Auto Mode hands node scaling, load balancing and storage drivers to AWS for a fee, which suits small platform teams.
IntermediateWhy do pods stay Pending even when the nodes have free CPU?
The scheduler looks at more than CPU. The instance type may have run out of network interface addresses for pods, a taint or node selector may exclude the node, a volume may be stuck in another zone, or requests may be larger than the free space on any one node. kubectl describe pod shows the scheduler’s reason.
AdvancedHow would you upgrade a production EKS cluster from one minor version to the next?
Read the version’s release notes and run Upgrade Insights to find removed APIs, test in a staging cluster, then upgrade the control plane, update the add-ons that need a newer release, and finally roll the node groups with maxUnavailable set and Pod Disruption Budgets in place. You go up one minor version at a time and you can’t roll back, so keep manifests in Git and the old node group until the new one is healthy.
AdvancedA team runs out of IP addresses in its subnets. What are the options?
Add a secondary CIDR range to the VPC and use VPC CNI custom networking so pods get addresses from it, turn on prefix delegation so each network interface hands out /28 blocks, or move pods to a larger subnet. For the long term, size subnets for the number of pods, and consider IPv6 clusters where pods don’t compete for IPv4 space.
AdvancedHow do you stop one pod from using another pod’s AWS permissions?
Give each workload its own service account and a Pod Identity association with a narrow role, so credentials are per pod. Block access to the instance metadata service from pods (hop limit of 1 and a network policy), and don’t attach broad policies to the node role. Check aws sts get-caller-identity from inside the pod to see which role it really has.
AdvancedHow would you design a multi-tenant cluster with sensible boundaries?
Give each team namespaces with resource quotas, limit ranges and default-deny network policies, scope access entries to those namespaces, and enforce Pod Security Standards. Add admission policies for images, labels and resource requests, and separate sensitive or noisy tenants onto their own node groups with taints. Namespaces are a soft boundary, so use separate clusters or accounts for tenants that must be isolated hard.
Similar or alternative services on other clouds
| Capability | AWS | Azure | Google Cloud |
|---|---|---|---|
| Service | Amazon EKS | Azure Kubernetes Service (AKS) | Google Kubernetes Engine (GKE) |
| Control plane fee | $0.10 per cluster-hour, $0.60 in extended support | Free tier without an SLA; Standard about $0.10 per hour; Premium about $0.60 | $0.10 per cluster-hour, with a monthly free credit that covers one cluster |
| Hands-off mode | EKS Auto Mode | AKS Automatic | GKE Autopilot |
| Pod permissions | EKS Pod Identity or IRSA | Microsoft Entra Workload ID | Workload Identity Federation for GKE |
| Node autoscaling | Karpenter, Cluster Autoscaler, Auto Mode | Node auto-provisioning (Karpenter), Cluster Autoscaler | Autopilot or node auto-provisioning, Cluster Autoscaler |
| Cluster access for people | IAM through access entries | Microsoft Entra ID with Azure RBAC | IAM with Kubernetes RBAC |
| Pod networking | VPC CNI (pods get VPC addresses) | Azure CNI Overlay or Azure CNI with VNet addresses | VPC-native with alias IP ranges, Dataplane V2 |
| Version support | About 14 months standard plus 12 months extended | Community support window; Long-Term Support on Premium | Release channels, with an Extended channel for Standard clusters |
If you only need to run a few containers without Kubernetes, look at Amazon ECS, AWS App Runner or the equivalents on the other clouds. For event-driven code with no servers use AWS Lambda. Databases for the applications on a cluster usually sit outside it, for example on Amazon RDS, and files go to Amazon S3.
Did you find this article helpful?
Your answer helps us improve this page for the next reader.
Sources and further reading
CloudBrum wrote this page in its own words, using the provider’s public documentation listed below as the reference. Features, limits, names and prices change, so confirm anything that matters on the provider’s own pages before you decide.
- What is Amazon EKS?Amazon Web Services
- Understand the Kubernetes version lifecycle on EKSAmazon Web Services
- Amazon EKS extended support for Kubernetes versions pricingAmazon Web Services
- Grant IAM users access to Kubernetes with EKS access entriesAmazon Web Services
- Learn how EKS Pod Identity grants pods access to AWS servicesAmazon Web Services
- Automate cluster infrastructure with EKS Auto ModeAmazon Web Services
- Security in Amazon EKSAmazon Web Services
- Amazon EKS pricingAmazon Web Services
- Terraform AWS provider: aws_eks_clusterHashiCorp
