Amazon EKS runs the Kubernetes control plane for you. Everything past that, node groups, networking, IAM, autoscaling, still lands on your plate. This tutorial builds a working AWS EKS cluster from a blank AWS account: eksctl setup, a managed node group, IRSA and EKS Pod Identity, core add-ons, Karpenter autoscaling, and a real application deployed behind a load balancer. Budget about 90 minutes. AWS starts billing the moment your cluster hits ACTIVE status, at $0.10 per cluster per hour for standard support.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What Amazon EKS Actually Does, and Why Teams Still Pick It in 2026
Amazon EKS is AWS’s managed control plane for Kubernetes. AWS runs the API server, etcd, and scheduler across multiple availability zones and patches them for you. You still own everything below that line: worker nodes, networking, IAM wiring, and whatever workloads you deploy. That split is the entire pitch. Teams that tried running kubeadm on raw EC2 instances know how much operational weight disappears once someone else owns the control plane.
EKS gives you four ways to run the data plane: self-managed EC2 nodes, managed node groups, AWS Fargate, or EKS Auto Mode. Managed node groups are the default for most teams because AWS handles provisioning and lifecycle actions (draining, replacing, patching AMIs) while you still pick instance types and scaling ranges. This guide uses managed node groups for the main walkthrough, then covers Fargate and Auto Mode in the advanced section near the end.
None of this is free. A running EKS cluster bills $0.10 per cluster per hour for standard support, on top of whatever EC2, EBS, and networking resources your nodes consume. That pricing model shapes several decisions later in this tutorial, including when to tear the cluster down.
One more architectural detail worth understanding before you create anything: every EKS cluster exposes its API server through an endpoint you control, set to public, private, or both. Public access lets kubectl reach the cluster from anywhere once IAM permissions check out, which is what this tutorial assumes throughout. Private-only access restricts API traffic to inside the VPC, which is more secure for production but means kubectl needs a VPN, bastion host, or similar path to reach it. Get this setting wrong on a private-only cluster and you will see the exact kubectl timeout symptom covered in the troubleshooting section further down.
Prerequisites: Accounts, Tools, and Exact Versions You Need
Before touching eksctl create cluster, get these four things in place. Skipping any of them is the fastest way to burn an hour on setup errors instead of Kubernetes.
- An AWS account with billing enabled and an IAM user or role with permissions across EKS, EC2, IAM, and CloudFormation (eksctl provisions your VPC and node groups through CloudFormation stacks behind the scenes).
- A terminal on Linux, macOS, or Windows with WSL. Every command in this tutorial assumes a bash-compatible shell.
- Roughly 30 minutes of unattended wait time built into your 90-minute budget, since cluster and node group creation both block on AWS provisioning.
- A willingness to delete the cluster when you’re done. Step 12 covers teardown, and skipping it is the single most common way this tutorial ends up costing more than expected.
| Tool | Minimum Version | What It Does | Check Command |
|---|---|---|---|
| AWS CLI | v2 | Authenticates to AWS and generates your kubeconfig | aws --version |
| eksctl | 0.215.0 or later | Creates and manages the cluster and node groups | eksctl version |
| kubectl | Within one minor version of your cluster | Talks to the Kubernetes API once the cluster exists | kubectl version --client |
| Helm | 3.x | Installs Karpenter later in this tutorial | helm version |
AWS’s own eksctl documentation specifies version 0.215.0 or later for current workflows. At the time of writing, 0.229.0 is circulating through package managers like Chocolatey, so run eksctl version after installing and update if you land on something older.
Step 1: Configure Your AWS Account, IAM Access, and CLI
Start by confirming your AWS CLI is authenticated as the identity you expect. Run aws configure if you haven’t already, then verify.
aws configure
aws sts get-caller-identity
A working setup returns something like this:
{
"UserId": "AIDAEXAMPLE123456",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/denis"
}
For a first cluster, an IAM user with broad permissions is fine. For anything touching production, scope this down before you start: EKS, EC2, IAM role creation, and CloudFormation are the services eksctl actually calls. The identity that creates the cluster automatically becomes its first administrator, which matters later if you hand access to teammates.
Step 2: Install kubectl, eksctl, and Helm
Install each tool through your platform’s package manager (Homebrew on macOS, Chocolatey on Windows, or the official install scripts on Linux), then confirm all three respond before moving on.
eksctl version
kubectl version --client
helm version
If eksctl version returns anything older than 0.215.0, update it now. Version mismatches between eksctl and newer EKS features (Pod Identity, EKS Auto Mode, access entries) cause confusing failures that look like permission problems but are actually just an outdated binary.
Step 3: Design Your VPC and Subnet Layout
EKS needs a VPC spanning at least two availability zones, and three is better for production resilience. You have two options here: let eksctl build the VPC automatically (the default when you don’t specify a vpc block in your cluster config), or bring your own. This tutorial uses the automatic path, which creates public and private subnets across three AZs along with a NAT gateway for outbound traffic from private subnets.
One detail trips up almost everyone on their first cluster: subnets need specific tags before any Kubernetes Service of type LoadBalancer will work. Public subnets need kubernetes.io/role/elb set to 1, and private subnets need kubernetes.io/role/internal-elb set to 1. eksctl applies these automatically when it builds your VPC. If you bring your own VPC, you have to tag subnets yourself, and this shows up again in the troubleshooting section below.
Avoid the default VPC for anything beyond a quick test. Default VPCs often ship with smaller subnet CIDR blocks, and Kubernetes pods consume IP addresses fast once you’re running dozens of them across multiple nodes.
Large clusters eventually hit IP exhaustion even with three properly sized subnets, since the VPC CNI assigns each pod a routable VPC IP address by default. AWS’s fix is a secondary CIDR block, an additional non-overlapping range such as 100.64.0.0/16, attached to the same VPC purely for pod IPs, leaving your primary CIDR for nodes and load balancers. A first cluster doesn’t need this, but it’s worth knowing it exists before you hit a wall around pod 800 and can’t figure out why new pods refuse to schedule.
Step 4: Create Your EKS Cluster With eksctl
eksctl accepts a YAML config file describing the whole cluster, which is easier to version-control and rerun than a long string of CLI flags. Save this as cluster.yaml.
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: ti-demo-cluster
region: us-east-1
# Check docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html
# for the current supported version list before you pick one.
version: "1.34"
availabilityZones:
- us-east-1a
- us-east-1b
- us-east-1c
managedNodeGroups:
- name: ng-general
instanceType: m5.large
amiFamily: AmazonLinux2023
desiredCapacity: 3
minSize: 2
maxSize: 5
volumeSize: 20
privateNetworking: true
labels:
role: general
tags:
Environment: tutorial
iam:
withOIDC: true
The withOIDC: true line matters more than it looks. It associates an IAM OIDC provider with the cluster during creation, which Step 8 needs for IRSA. Skipping it now just means running one extra command later, but there’s no reason to skip it.
eksctl create cluster -f cluster.yaml
Expect this to run for 15 to 20 minutes. eksctl builds a CloudFormation stack for the VPC, waits for the control plane to reach ACTIVE, then builds a second stack for the node group. Output looks roughly like this, trimmed for length:
[ℹ] eksctl version 0.229.0
[ℹ] using region us-east-1
[ℹ] setting availability zones to [us-east-1a us-east-1b us-east-1c]
[ℹ] creating VPC stack "eksctl-ti-demo-cluster-cluster"
[ℹ] building cluster stack "eksctl-ti-demo-cluster-cluster"
[ℹ] waiting for CloudFormation stack "eksctl-ti-demo-cluster-cluster"
[ℹ] creating managed nodegroup "ng-general"
[ℹ] waiting for at least 2 node(s) to become ready in "ng-general"
[✔] EKS cluster "ti-demo-cluster" in "us-east-1" region is ready
That final line is your green light. If the command exits before that, jump to the troubleshooting section rather than rerunning it blind, most failures at this stage trace back to IAM permissions or subnet capacity.
Step 5: Create a Cluster From the AWS Console (The Manual Path)
Some teams prefer clicking through the console for a first cluster, either to see every option explicitly or because eksctl isn’t approved yet in their environment. AWS’s own Amazon EKS documentation walks through this path directly. You start simply: “Open the Amazon EKS console.“
From there, the flow follows the same shape as the eksctl path, just spread across console screens instead of one YAML file. As AWS’s documentation puts it, you “choose Add cluster and then choose Create,” then, on the configuration screen, “under Configuration options select Custom configuration” if you want control over networking, logging, and access settings instead of accepting every default.
The console path takes longer for the same result and still requires a follow-up step to install eksctl or the AWS CLI for anything past basic cluster creation, node groups, add-ons, and IRSA all still involve CLI or YAML work. Most teams use the console once to understand the options, then switch to eksctl or Terraform for anything repeatable.
Step 6: Point kubectl at Your New Cluster
Whichever path you used, kubectl doesn’t know your cluster exists yet. Generate a kubeconfig entry with the AWS CLI.
aws eks update-kubeconfig --name ti-demo-cluster --region us-east-1
kubectl get nodes
Added new context arn:aws:eks:us-east-1:123456789012:cluster/ti-demo-cluster to /home/user/.kube/config
NAME STATUS ROLES AGE VERSION
ip-192-168-45-12.ec2.internal Ready <none> 4m v1.34.0-eks-abcdef1
ip-192-168-78-34.ec2.internal Ready <none> 4m v1.34.0-eks-abcdef1
ip-192-168-91-201.ec2.internal Ready <none> 3m v1.34.0-eks-abcdef1
Three nodes showing Ready confirms the control plane, the node group, and networking between them all work. If this command returns an empty list or an authorization error instead, skip ahead to troubleshooting items 1 and 2 below.
Step 7: Add or Adjust a Managed Node Group
The cluster config above already created one managed node group, but it’s worth understanding what’s happening, because you’ll add more node groups as workloads diversify. AWS’s node group documentation is explicit that managed node groups can only be created once the cluster itself reaches ACTIVE status, which is why eksctl sequences the control plane first and the node group second.
To add a second node group for a different workload type, for example, memory-heavy pods that need R-family instances instead of general-purpose M-family ones, run:
eksctl create nodegroup \
--cluster ti-demo-cluster \
--region us-east-1 \
--name ng-memory \
--node-type r5.large \
--nodes 2 \
--nodes-min 1 \
--nodes-max 4 \
--node-private-networking
AWS supports two strategies when updating an existing managed node group: a rolling update, which respects PodDisruptionBudgets and drains nodes gradually, and a force update, which does not. Default to rolling updates unless you have a specific reason not to. Also worth knowing: if a node group runs a custom AMI instead of an AWS-managed one, you lose the automatic notification that tells you when a new AMI release is available, so custom AMIs mean tracking updates yourself.
Step 8: Set Up IRSA and EKS Pod Identity for Secure AWS Access
This is the step most new EKS clusters get wrong, and it’s worth slowing down for. The lazy path is attaching a broad IAM policy directly to the node’s IAM role, which means every pod scheduled on that node inherits every permission the node has. That’s a real security problem the moment you run more than one workload per node.
IRSA (IAM Roles for Service Accounts) fixes this using OIDC federation: a Kubernetes service account can assume a specific IAM role, scoped to exactly what that workload needs, without touching the node’s role at all. Since your cluster config already set withOIDC: true, the OIDC provider association is already done. If you created your cluster another way, run this first:
eksctl utils associate-iam-oidc-provider \
--cluster ti-demo-cluster \
--region us-east-1 \
--approve
Then create a service account bound to an IAM policy:
eksctl create iamserviceaccount \
--name s3-read-only \
--namespace default \
--cluster ti-demo-cluster \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
--approve
EKS Pod Identity is the newer alternative, and AWS now generally recommends it for new clusters. It does the same job as IRSA, associating IAM roles with service accounts, but skips most of the OIDC trust-policy configuration by using a dedicated Pod Identity Agent add-on instead. IRSA still works fine and plenty of existing clusters run on it, so this isn’t a rip-and-replace decision, just a “which one for new work” one.
Step 9: Install and Verify Core Add-Ons
EKS ships several components as managed add-ons rather than baking them into the control plane, which means you install and version them independently. A handful cover most clusters.
| Add-On | Purpose | Usually Needed For |
|---|---|---|
| VPC CNI | Assigns VPC IP addresses directly to pods | Every cluster (installed by default) |
| CoreDNS | In-cluster DNS resolution for service discovery | Every cluster (installed by default) |
| kube-proxy | Maintains network rules for Service routing | Every cluster (installed by default) |
| EBS CSI driver | Lets pods mount EBS volumes as persistent storage | Stateful workloads, databases, queues |
| EFS CSI driver | Lets pods mount shared EFS file systems | Workloads needing shared read/write storage |
| Pod Identity Agent | Powers EKS Pod Identity for IAM access | Clusters using Pod Identity instead of IRSA |
VPC CNI, CoreDNS, and kube-proxy come with every new cluster automatically. The storage and identity add-ons don’t, and you install them explicitly through the AWS CLI or console.
aws eks create-addon --cluster-name ti-demo-cluster --addon-name aws-ebs-csi-driver --region us-east-1
aws eks create-addon --cluster-name ti-demo-cluster --addon-name eks-pod-identity-agent --region us-east-1
Give each add-on a minute to reach ACTIVE, then confirm with aws eks describe-addon --cluster-name ti-demo-cluster --addon-name aws-ebs-csi-driver --region us-east-1. Add-ons that stay in DEGRADED almost always trace back to a missing IAM role, the EBS CSI driver needs its own IRSA or Pod Identity binding to create and attach volumes.
Step 10: Add Karpenter for Autoscaling
A fixed minSize/maxSize node group scales, but slowly and coarsely. Karpenter watches for unschedulable pods and provisions right-sized EC2 capacity directly, without waiting on Auto Scaling Group cooldowns. It’s become the default autoscaler recommendation for EKS since reaching general availability, and if you’re deciding between it and the older Cluster Autoscaler or KEDA, our benchmark comparison covers scale-up speed and throughput differences across all three in detail.
Install it with Helm. Check the Karpenter releases page for the current tag before running this, since Karpenter ships frequent point releases within its 1.x line.
helm install karpenter oci://public.ecr.aws/karpenter/karpenter \
--version "1.8.x" \
--namespace kube-system \
--create-namespace \
--set settings.clusterName=ti-demo-cluster \
--set settings.interruptionQueue=ti-demo-cluster \
--wait
Karpenter needs two custom resources to know what it’s allowed to provision: a NodePool describing scheduling constraints, and an EC2NodeClass describing the AWS-specific details like AMI family and IAM role.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
limits:
cpu: 100
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2023
role: KarpenterNodeRole-ti-demo-cluster
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: ti-demo-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: ti-demo-cluster
Once applied, Karpenter takes over provisioning for any pod it can schedule under these constraints. Pair it with a cost visibility tool so you can actually see the savings, our Kubecost setup walkthrough covers getting per-namespace cost data flowing within about an hour.
Step 11: Deploy and Expose a Complete Working Application
With the cluster, node group, IAM wiring, and add-ons in place, deploy something real. This manifest defines three replicas behind a LoadBalancer Service, a complete, working setup you can adapt for an actual application by swapping the image.
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-eks
labels:
app: hello-eks
spec:
replicas: 3
selector:
matchLabels:
app: hello-eks
template:
metadata:
labels:
app: hello-eks
spec:
containers:
- name: hello-eks
image: public.ecr.aws/nginx/nginx:latest
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "250m"
memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
name: hello-eks
spec:
type: LoadBalancer
selector:
app: hello-eks
ports:
- port: 80
targetPort: 80
Those resources requests aren’t decoration. Karpenter and the default scheduler both rely on them to bin-pack nodes efficiently, and skipping them is pitfall number five below. Apply the manifest and check status.
kubectl apply -f hello-eks.yaml
kubectl get pods -l app=hello-eks
kubectl get svc hello-eks
deployment.apps/hello-eks created
service/hello-eks created
NAME READY STATUS RESTARTS AGE
hello-eks-6d8f9c7b5-2xk9p 1/1 Running 0 38s
hello-eks-6d8f9c7b5-7mvqz 1/1 Running 0 38s
hello-eks-6d8f9c7b5-9plkd 1/1 Running 0 38s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-eks LoadBalancer 10.100.201.44 a1b2c3d4e5f6g7h8-123456789.us-east-1.elb.amazonaws.com 80:31942/TCP 2m
The load balancer’s DNS name can take two to three minutes to become reachable even after it appears in kubectl get svc. If EXTERNAL-IP stays on <pending> for longer than five minutes, that’s troubleshooting item 4 below, almost always a subnet tagging or IAM permissions issue.
Step 12: Tear Down Cleanly to Avoid Orphaned Costs
Delete the Service before the cluster, not after. eksctl delete cluster tears down the CloudFormation stacks it created, but it doesn’t know about the load balancer AWS provisioned in response to your Service, that resource lives outside eksctl’s stack. Delete it explicitly first.
kubectl delete svc hello-eks
eksctl delete cluster --name ti-demo-cluster --region us-east-1
After the delete command finishes, double check the EC2 and VPC consoles for anything left behind, orphaned EBS volumes and unattached load balancer security groups are the two most common leftovers. They’re small individually, but they add up if you’re spinning test clusters up and down repeatedly.
What Amazon EKS Actually Costs: Control Plane, Nodes, and Extended Support
The control plane fee is flat and predictable. What catches teams off guard is the Kubernetes version lifecycle, and how fast node and networking costs stack on top of that $0.10 baseline.
| Phase | Duration | Control Plane Price |
|---|---|---|
| Standard support | 14 months from the version’s EKS release date | $0.10 per cluster per hour |
| Extended support | Up to 12 additional months | $0.60 per cluster per hour |
| Total available lifecycle | Up to 26 months | 6x price jump at month 14 if you don’t upgrade |
EKS commits to supporting at least four production-ready Kubernetes versions at any given time, and releases roughly three new minor versions a year. As of mid-2026, that active window spans Kubernetes 1.31 through 1.36, with 1.33 scheduled to reach the end of standard support on July 29, 2026. Miss that date on a cluster still running 1.33 and the control plane bill jumps sixfold automatically, no warning banner, just a bigger invoice. The full pricing breakdown, including extended support, is on AWS’s official EKS pricing page.
Node and networking costs dwarf the control plane fee for any real workload. Here’s a realistic monthly estimate, built from current EC2 on-demand pricing and EBS pricing, for the exact setup this tutorial built: one cluster, three m5.large managed nodes, one NAT gateway, and modest EBS storage.
| Line Item | Rate | Estimated Monthly Cost |
|---|---|---|
| EKS control plane (standard support) | $0.10/hour | ~$73 |
| 3x m5.large on-demand nodes | $0.096/hour each | ~$210 |
| NAT gateway (1x) | $0.045/hour + data processing | ~$33 plus data fees |
| EBS gp3 storage (3x 20GB root volumes) | $0.08/GB-month | ~$5 |
| Estimated baseline total | ~$321/month |
That baseline excludes data transfer, load balancer hours, and anything Karpenter provisions on top of the fixed node group, so treat it as a floor, not a ceiling. Cloud waste across AI-heavy infrastructure teams reportedly hit 29% of spend in 2026, according to our FinOps coverage, and idle EKS node groups sized for peak load year-round are a common contributor. Right-sizing with Karpenter and watching the version clock both pay for themselves quickly at this scale.
5 Common Pitfalls That Break New EKS Clusters
- Untagged subnets. Every LoadBalancer Service depends on the
kubernetes.io/role/elbandinternal-elbtags. Bring your own VPC without them, and every Service silently hangs on<pending>. - Single-AZ node groups. Building a node group in one availability zone “to save time” removes the entire point of managed Kubernetes resilience. One AZ event takes the whole application down.
- Broad IAM policies on node roles. Skipping IRSA or Pod Identity and attaching permissions straight to the node role means every pod on that node inherits access it doesn’t need.
- Ignoring the version clock. Standard support runs 14 months. Clusters that drift into extended support jump from $0.10 to $0.60 per cluster-hour with no alert, just a larger bill.
- Skipping resource requests and limits. Without them, neither the default scheduler nor Karpenter can bin-pack nodes efficiently, and you end up paying for capacity you’re not using.
- Deleting the cluster before the Services.
eksctl delete clusterdoesn’t clean up load balancers created by Kubernetes Services, leaving orphaned ELBs billing quietly in the background. - Using the default VPC. Default subnet CIDR blocks run out of IP addresses fast once pod density climbs past a handful of nodes.
Troubleshooting Guide: 8 Issues Engineers Hit Most Often
Most EKS problems fall into a handful of repeating categories. Here’s what to check, in the order that resolves issues fastest.
- Nodes never show up in kubectl get nodes. The node IAM role usually isn’t mapped correctly. Check
kubectl -n kube-system get configmap aws-auth -o yaml, or on newer clusters, runaws eks list-access-entriesand confirm the node role ARN is present. - “Unauthorized” errors from kubectl. The IAM identity in your local AWS CLI profile has no EKS access entry. Run
aws sts get-caller-identityand confirm it matches an identity with cluster access, either the creator or one added viaaws eks create-access-entry. - Pods stuck in Pending. Usually no node has free capacity, or the node group already hit
maxSize. Runkubectl describe podand check the Events section, then raise the node group’s max size or confirm Karpenter is running. - LoadBalancer stuck on EXTERNAL-IP pending. Nearly always missing subnet tags or missing IAM permissions to create a load balancer. Confirm subnet tagging first, then check the AWS Load Balancer Controller’s IAM role if you’re running one.
- CoreDNS pods crash-looping. Usually a security group blocking DNS traffic on port 53 between nodes, common on custom VPCs with restrictive rules. Check
kubectl -n kube-system logs deploy/corednsfor the specific error. - eksctl create cluster fails with a capacity or subnet error. Either the subnet CIDR is too small or the chosen AZ has no capacity for your instance type. Use a /19 or larger CIDR for node subnets, or try a different AZ or instance type.
- IAM permission errors inside a pod despite IRSA. Check
kubectl get sa [service-account-name] -o yamlfor theeks.amazonaws.com/role-arnannotation, and confirm the pod spec actually references that service account by name. - Managed node group stuck in CREATE_FAILED. Check the CloudFormation stack events for the node group first. The usual causes are a node IAM role missing one of the three required policies (AmazonEKSWorkerNodePolicy, AmazonEKS_CNI_Policy, AmazonEC2ContainerRegistryReadOnly) or a subnet with no route to a NAT or internet gateway.
Advanced Tips: EKS Auto Mode, Fargate, and Multi-AZ Resilience
Once the basic cluster works end to end, a few operating modes are worth knowing about even if you don’t need them for this tutorial’s demo app.
EKS Auto Mode vs. Managed Node Groups
EKS Auto Mode extends the “AWS manages more of this” idea past the control plane into the data plane itself. It can be enabled on any cluster running Kubernetes 1.29 or later, and AWS has continued expanding it, recent additions include support for On-Demand Capacity Reservations and Capacity Blocks for ML workloads, along with IPv4 egress support for IPv6 clusters. Billing differs from standard managed node groups because Auto Mode adds a charge for the managed compute layer itself, not just the underlying EC2 instances. It’s worth testing for teams that want Karpenter-style automatic provisioning without configuring Karpenter themselves.
Fargate Profiles for Bursty or Isolated Workloads
Fargate removes node management from the equation entirely by running pods directly, billed per vCPU and memory requested, metered per second. It fits workloads that need strong isolation between pods or spike unpredictably, since there’s no node group sitting idle waiting for traffic. The tradeoff is less control over the underlying instance and a per-pod cost premium compared to a well-utilized EC2 node group. Most production clusters mix both: managed node groups (or Karpenter) for steady-state workloads, Fargate profiles carved out for specific namespaces that need isolation.
Whatever mix you land on, spread node groups across at least two, ideally three, availability zones, and consider pod topology spread constraints so the scheduler actively avoids stacking every replica of a deployment onto one AZ. Applications with any state, or any dependency on a managed database, should also look at how that piece fits in separately. Amazon RDS is the common pairing for teams that don’t want to run stateful workloads inside the cluster itself, and if your services need to talk to each other asynchronously instead of over HTTP, it’s worth comparing SQS against Kafka before you standardize on one. Teams adding a dedicated networking layer between services often end up asking whether they need a service mesh at all, and the honest answer for most clusters this size is not yet, see our breakdown of what a service mesh actually buys you before adding the operational overhead.
Monitoring and Logging Your Cluster
A cluster that passes kubectl get nodes and serves traffic through a LoadBalancer still isn’t production-ready without visibility into what’s happening inside it. EKS doesn’t turn on deep observability by default. You add it deliberately, the same way you added the EBS CSI driver and Karpenter earlier.
The fastest path is CloudWatch Container Insights, available as a managed add-on through the same aws eks create-addon command used in Step 9. It collects CPU, memory, disk, and network metrics at the cluster, node, pod, and container level, and surfaces them in CloudWatch dashboards without deploying anything into the cluster beyond the add-on itself.
aws eks create-addon --cluster-name ti-demo-cluster --addon-name amazon-cloudwatch-observability --region us-east-1
Teams already running Prometheus and Grafana elsewhere usually prefer keeping that stack consistent rather than splitting observability across two systems. The kube-prometheus-stack Helm chart installs both onto EKS in one pass and scrapes metrics from kubelet, node-exporter, and any application exposing a /metrics endpoint, giving you the same dashboards you’d run on any other Kubernetes cluster regardless of cloud provider.
Logging is a separate decision from metrics. Container Insights captures logs too, but security and compliance teams often centralize Kubernetes logs into a dedicated SIEM instead, both for longer retention and for correlation against other infrastructure. If that’s the direction your team is headed, our Graylog setup walkthrough and Wazuh SIEM Docker guide both cover getting a self-hosted log pipeline running in under an hour, and either one can ingest EKS control plane and application logs through Fluent Bit.
Whichever stack you pick, turn on EKS control plane logging separately. It’s off by default and covers API server, audit, authenticator, controller manager, and scheduler logs. None of that reaches CloudWatch automatically until you enable it per log type on the cluster itself.
Where to Go From Here: GitOps and Multi-Cluster Patterns
Everything in this tutorial used kubectl apply run by hand, which is fine for learning and for a demo app, but it doesn’t scale to a team shipping changes daily. The next step most EKS users take is adopting a GitOps controller. ArgoCD and Flux are the two most common choices, and both continuously reconcile the cluster’s actual state against manifests stored in a Git repository instead of whatever the last person happened to run locally.
Both tools install the same way anything else in this tutorial did, as a Helm chart or a set of manifests applied once, and both then watch a Git repository and apply changes automatically when it updates. The practical benefit shows up the first time someone asks what’s actually running in production. The answer becomes “whatever’s in the main branch,” rather than a guess based on deployment history scattered across a few people’s terminal sessions.
Multi-cluster setups tend to follow naturally once a single cluster runs smoothly, usually split by environment (staging and production on separate clusters) rather than by team, since sharing IAM boundaries and blast radius between environments defeats much of the isolation EKS is supposed to provide in the first place. If cost visibility across multiple clusters becomes a problem once you’re past one, that’s exactly the gap tools like Kubecost are built to close.
None of this needs deciding today. Get comfortable with the single-cluster workflow in this tutorial first. GitOps and multi-cluster patterns solve problems you’ll recognize once you hit them, not problems worth front-loading before your first cluster is even stable. For the broader cost and infrastructure trends shaping these decisions across the industry, our cloud computing coverage tracks what’s changing month to month.
Frequently Asked Questions
How much does a basic EKS cluster cost per month?
Budget around $321 a month for the setup in this tutorial: one cluster on standard support, three m5.large nodes, one NAT gateway, and light EBS storage. That excludes data transfer and load balancer hours, so treat it as a floor rather than a fixed number.
Do I have to use eksctl, or can I create a cluster with Terraform?
eksctl is the fastest path and the one AWS documents most heavily, but it’s not the only one. Terraform’s aws_eks_cluster resource and the AWS Console both call the same underlying EKS API, so the end result is identical. Terraform tends to win out once a cluster becomes part of a larger infrastructure-as-code setup.
What’s the real difference between managed node groups and Fargate?
Managed node groups run EC2 instances that AWS provisions and can drain and replace for you, but you still pick instance types and scaling ranges. Fargate skips EC2 management entirely and runs pods directly, billed per vCPU and memory requested per second. Fargate costs more per unit of compute but removes node-level operations altogether.
Should new clusters use IRSA or EKS Pod Identity?
EKS Pod Identity is the newer mechanism and AWS generally recommends it for new setups, since it skips most of the OIDC trust-policy configuration IRSA requires. IRSA still works and remains fully supported, so there’s no urgency to migrate an existing cluster off it.
How long does creating an EKS cluster actually take?
Plan on 15 to 20 minutes for eksctl create cluster to finish, covering both the control plane and the first managed node group. Additional node groups added afterward typically take a few minutes each.
Can I run EKS inside the AWS Free Tier?
No. The $0.10 per cluster-hour control plane charge applies from the moment your cluster reaches ACTIVE, and it isn’t covered by AWS Free Tier. Free Tier EC2 hours can offset some node costs if you use Free Tier-eligible instance types, but the m5.large instances used in this tutorial don’t qualify.
What happens if I don’t upgrade before standard support ends?
Nothing breaks immediately. The cluster automatically enters extended support, billed at $0.60 per cluster-hour, a sixfold jump from standard support’s $0.10 rate, for up to 12 more months. After extended support runs out, AWS moves toward forcing an upgrade.
Is Karpenter required, or does the Cluster Autoscaler still work?
Karpenter isn’t required. The Cluster Autoscaler still works and is simpler to reason about on small clusters. Karpenter provisions faster and bin-packs more efficiently at scale, which is why most new EKS deployments default to it, but a five-node cluster won’t notice much difference either way.
Why did my LoadBalancer Service never get an external IP?
This is almost always subnet tagging. Public subnets need kubernetes.io/role/elb set to 1. eksctl handles this automatically when it builds your VPC, but a bring-your-own-VPC setup needs the tags applied manually before any LoadBalancer Service will provision successfully.


