Kubernetes is available on AWS as a managed service: AWS takes care of the control plane (replacing unhealthy nodes, automated updates), you take care of worker nodes. This saves you a lot of setup and maintenance work. Of course the control plane is highly available as it is deployed across multiple availability zones.
You can use ECR to store container images, EBS volumes for your persistent volume claims, ELBs for your load balancer services and IAM to map users and groups to your cluster.
In this tutorial we’ll use eksctl, a simple command line utility for creating and managing Kubernetes clusters on Amazon EKS. AWS requires creating many resources such as IAM roles, security groups and networks, and by using eksctl, all of this is simplified.
By the end of this guide you will be able to:
- Install eksctl and create an EKS cluster with a single command.
- Describe a production-style cluster declaratively in a
cluster.yamlfile. - Add, scale, drain and delete nodegroups, including Spot nodegroups.
- Give other IAM users and roles access to the cluster with EKS access entries.
- Grant AWS permissions to pods with EKS Pod Identity.
- Manage add-ons, enable control plane logging, upgrade the cluster and tear it down cleanly.
How eksctl works
eksctl is the official CLI for Amazon EKS. Behind the scenes it generates AWS CloudFormation stacks: one for the cluster (control plane, VPC, subnets, security groups, IAM roles) and one per nodegroup. That has two practical consequences:
- If something fails, the CloudFormation console shows exactly which resource failed and why.
- You should manage eksctl-created resources with eksctl, not by editing the stacks by hand, otherwise eksctl’s view of the cluster drifts from reality.
Every command accepts either CLI flags (great for quick experiments) or a ClusterConfig file passed with -f (better for anything you want to keep in Git and reproduce).
Prerequisites
- An AWS account and credentials configured (
aws configure,~/.aws/credentialsor environment variables) for an IAM identity that can manage CloudFormation, EC2, EC2 Auto Scaling, EKS, IAM and Systems Manager. The eksctl docs list the minimum IAM policies. - The AWS CLI v2 — eksctl’s kubeconfig uses
aws eks get-tokento authenticate. kubectl, ideally within one minor version of the cluster’s Kubernetes version.
Confirm your credentials point at the account you expect before creating anything:
aws sts get-caller-identity
Install eksctl
eksctl now lives in the eksctl-io/eksctl GitHub organization (it moved from weaveworks). On Linux, install the latest release with:
# for ARM systems, set ARCH to arm64
ARCH=amd64
PLATFORM=$(uname -s)_$ARCH
curl -sLO "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_$PLATFORM.tar.gz"
# (Optional) verify the checksum
curl -sL "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_checksums.txt" | grep "eksctl_$PLATFORM.tar.gz$" | sha256sum --check
tar -xzf eksctl_$PLATFORM.tar.gz -C /tmp && rm eksctl_$PLATFORM.tar.gz
sudo install -m 0755 /tmp/eksctl /usr/local/bin && rm /tmp/eksctl
eksctl version
On macOS, Homebrew works too (brew install eksctl), though AWS only supports the official GitHub release binaries. For Windows and other options, see the official installation guide.
Create the cluster
The quickest way to get a cluster is a single command:
eksctl create cluster --name dev-citizix --region us-west-2 --managed
With no other flags, eksctl creates a dedicated VPC and one managed nodegroup of two m5.large nodes running Amazon Linux 2023. Managed nodegroups are the default in current eksctl releases, so --managed is optional. Without --version, eksctl uses its default Kubernetes version; pass --version to pin one.
Common flags you will want to set yourself:
eksctl create cluster \
--name dev-citizix \
--region us-west-2 \
--nodegroup-name ng-general \
--node-type t3.large \
--nodes 2 \
--nodes-min 1 \
--nodes-max 4
In
us-east-1you may hitUnsupportedAvailabilityZoneException. Copy the zones suggested in the error and pass them with--zones, e.g.--zones=us-east-1a,us-east-1b,us-east-1d.
Feel free to go grab a coffee – this usually takes 15 minutes. When it’s finished, you should have kubectl context configured to communicate with your newly created cluster.
Let’s verify everything is fine:
kubectl version
Preview the config with –dry-run
Not sure what a set of flags will produce? --dry-run prints the equivalent ClusterConfig without creating anything. It is the easiest way to bootstrap a config file:
eksctl create cluster --name dev-citizix --region us-west-2 --dry-run > cluster.yaml
Create a cluster from a config file
For anything beyond a throwaway cluster, keep the definition in a file. This example creates a cluster with a general-purpose on-demand nodegroup, a Spot nodegroup for interruptible workloads, private worker nodes, control plane audit logs, the core add-ons and an admin access entry:
# cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: dev-citizix
region: us-west-2
# version: "1.xx" # omit to use eksctl's default Kubernetes version
tags:
environment: dev
accessConfig:
authenticationMode: API_AND_CONFIG_MAP
accessEntries:
- principalARN: arn:aws:iam::111122223333:role/admin-role
accessPolicies:
- policyARN: arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy
accessScope:
type: cluster
managedNodeGroups:
- name: ng-general
amiFamily: AmazonLinux2023
instanceType: t3.large
minSize: 1
maxSize: 4
desiredCapacity: 2
volumeSize: 50
privateNetworking: true
labels: { role: general }
- name: ng-spot
amiFamily: AmazonLinux2023
instanceTypes: ["m5.large", "m5a.large", "m6i.large"]
spot: true
minSize: 0
maxSize: 5
desiredCapacity: 1
privateNetworking: true
labels: { role: spot }
addons:
- name: vpc-cni
useDefaultPodIdentityAssociations: true
- name: coredns
- name: kube-proxy
- name: eks-pod-identity-agent
cloudWatch:
clusterLogging:
enableTypes: ["api", "audit", "authenticator"]
Replace 111122223333 and admin-role with your own account ID and IAM role. Then create it:
eksctl create cluster -f cluster.yaml
A few notes on the choices above:
privateNetworking: trueputs nodes in private subnets, so they are not directly reachable from the internet.- Listing several
instanceTypesfor Spot gives EC2 more capacity pools to draw from, which reduces interruptions. amiFamily: AmazonLinux2023is already the default; it is set explicitly so the file documents the choice.Bottlerocketis the other common option.- Cluster and nodegroup names may only contain alphanumeric characters and hyphens, and must start with a letter.
Get clusters
eksctl get clusters --region us-west-2
Add --output yaml or --output json to most get commands for machine-readable output.
Export kubeconfig
eksctl writes the kubeconfig entry for you when it creates the cluster. If you are on a different machine, or the context was not written for you, generate it with either tool:
aws eks --region us-west-2 update-kubeconfig --name dev-citizix
# or
eksctl utils write-kubeconfig --cluster dev-citizix --region us-west-2
Working with several clusters? See how to merge multiple kubeconfig files into one.
Get cluster nodes
kubectl get nodes -o wide
Nodes from the config file above carry the labels you set, so you can check which nodegroup a node belongs to:
kubectl get nodes -L role,eks.amazonaws.com/capacityType
Manage nodegroups
Get nodegroups
eksctl get nodegroup --cluster dev-citizix --region us-west-2
Add a nodegroup
From the CLI:
eksctl create nodegroup \
--cluster dev-citizix \
--region us-west-2 \
--name ng-ci \
--node-type t3.xlarge \
--nodes 2 \
--node-labels "purpose=ci-worker"
Or add the nodegroup to cluster.yaml and create only that one:
eksctl create nodegroup -f cluster.yaml --include=ng-ci
A Spot nodegroup can be added the same way with --spot --instance-types=m5.large,m5a.large,m6i.large.
Scale a nodegroup
eksctl scale nodegroup \
--cluster dev-citizix \
--region us-west-2 \
--name ng-general \
--nodes 3 \
--nodes-min 1 \
--nodes-max 5
Manual scaling is fine for small clusters. For automatic, workload-driven scaling, see setting up autoscaling with Karpenter on Amazon EKS.
Drain and delete a nodegroup
Nodegroups are largely immutable: to change the instance type or AMI, create a new nodegroup, move workloads onto it, then delete the old one. Drain first so pods are evicted gracefully:
eksctl drain nodegroup --cluster dev-citizix --region us-west-2 --name ng-general
eksctl delete nodegroup --cluster dev-citizix --region us-west-2 --name ng-general
eksctl drain nodegroup ... --undo uncordons the nodes if you change your mind.
Give other IAM users access with access entries
Only the IAM identity that created the cluster has access by default. The modern way to grant access to others is EKS access entries, which replace hand-editing the aws-auth ConfigMap. eksctl sets authenticationMode to API_AND_CONFIG_MAP on new clusters, so both work.
To grant a teammate’s role cluster-admin access, declare it under accessConfig.accessEntries in cluster.yaml (as in the example above) with an access policy, then apply:
eksctl create accessentry -f cluster.yaml
Access policies can only be attached from the config file. From the CLI you can create an entry that maps the principal to Kubernetes groups, and grant those groups permissions with your own RBAC:
eksctl create accessentry \
--cluster dev-citizix \
--region us-west-2 \
--principal-arn arn:aws:iam::111122223333:role/developer-role \
--kubernetes-groups developers
List and remove entries:
eksctl get accessentry --cluster dev-citizix --region us-west-2
eksctl delete accessentry --cluster dev-citizix --region us-west-2 \
--principal-arn arn:aws:iam::111122223333:role/admin-role
Besides AmazonEKSClusterAdminPolicy, EKS provides predefined policies such as AmazonEKSAdminPolicy, AmazonEKSEditPolicy and AmazonEKSViewPolicy, which can be scoped to specific namespaces.
Give pods AWS permissions with EKS Pod Identity
Applications that call AWS APIs (S3, SQS, DynamoDB) should not use static access keys. EKS Pod Identity maps a Kubernetes service account to an IAM role. Unlike the older IRSA approach, it needs no OIDC provider and the same role can be reused across clusters.
It requires the eks-pod-identity-agent add-on (already in the config file above). On an existing cluster:
eksctl create addon --cluster dev-citizix --region us-west-2 --name eks-pod-identity-agent
Then associate a service account with an IAM policy. eksctl creates the IAM role and its trust policy for you:
eksctl create podidentityassociation \
--cluster dev-citizix \
--region us-west-2 \
--namespace default \
--service-account-name s3-reader \
--create-service-account \
--permission-policy-arns arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
Any pod that runs with serviceAccountName: s3-reader in the default namespace now gets temporary credentials for that role. Check what is configured with:
eksctl get podidentityassociation --cluster dev-citizix --region us-west-2
Manage add-ons
eksctl installs the default networking add-ons (vpc-cni, coredns, kube-proxy) as EKS managed add-ons, so AWS handles their versions. List what is installed and what versions are available:
eksctl get addons --cluster dev-citizix --region us-west-2
eksctl utils describe-addon-versions --cluster dev-citizix --region us-west-2
Add another add-on, for example the EBS CSI driver for persistent volumes:
eksctl create addon --cluster dev-citizix --region us-west-2 --name aws-ebs-csi-driver
Update add-ons declared in your config file:
eksctl update addon -f cluster.yaml
Enable control plane logging
EKS can ship control plane logs (api, audit, authenticator, controllerManager, scheduler) to CloudWatch Logs. They are off by default. Enable them on an existing cluster with:
eksctl utils update-cluster-logging \
--cluster dev-citizix \
--region us-west-2 \
--enable-types audit,authenticator \
--approve
Use --enable-types all to turn on everything, and --disable-types to turn types off. CloudWatch ingestion is billed, so enable only what you need. For application and node logs, see exporting Amazon EKS logs to CloudWatch using Fluent Bit.
Upgrade cluster
An EKS upgrade has three steps, in this order.
1. Upgrade the control plane. eksctl upgrade cluster only upgrades the control plane, one minor version at a time. Without --approve it just shows the plan:
eksctl upgrade cluster --name dev-citizix --region us-west-2 --approve
2. Upgrade each nodegroup to match the control plane. Nodes may lag the control plane, but must never be newer than it:
eksctl upgrade nodegroup --name ng-general --cluster dev-citizix --region us-west-2
3. Update the default add-ons (vpc-cni, kube-proxy, coredns) as described in the eksctl add-on upgrade docs.
Repeat the cycle for each minor version if you are more than one version behind. Clusters left on a version past its standard support window move to EKS extended support, which costs more, so it pays to upgrade regularly.
Delete cluster
eksctl delete cluster --name dev-citizix --region us-west-2 --wait
If you created the cluster from a config file, eksctl delete cluster -f cluster.yaml --wait works too.
Without --wait, eksctl only starts the CloudFormation stack deletion and returns, so you won’t see a failure. Before deleting, remove LoadBalancer services and anything else that created AWS resources in the cluster’s VPC (load balancers, ENIs, EBS volumes from PersistentVolumes); otherwise VPC deletion can fail.
Troubleshooting
- Stack creation failed: open the CloudFormation console, find the
eksctl-dev-citizix-*stack and look at the events tab for the failing resource. Re-run with--cfn-disable-rollbackto keep the failed resources around for debugging. kubectlreturnsUnauthorized/You must be logged in to the server: your current AWS identity has no access entry on the cluster. Check it withaws sts get-caller-identityand add an access entry for it from an identity that has access.- Delete is stuck or fails: usually leftover load balancers or ENIs in the VPC. Delete them, then delete the remaining stack from the CloudFormation console or re-run
eksctl delete cluster --wait. - Stack deletion blocked by termination protection: eksctl enables termination protection on the stacks it creates and turns it off before deleting, which needs the
cloudformation:UpdateTerminationProtectionpermission.
Cost considerations
- Control plane: EKS charges per cluster per hour, with a higher rate for versions in extended support. See Amazon EKS pricing.
- Worker nodes: standard EC2 and EBS pricing. Spot nodegroups can cut this substantially for interruptible workloads.
- NAT gateway: private nodes reach the internet through a NAT gateway, which has an hourly and per-GB charge.
- Logging: CloudWatch Logs ingestion and storage for control plane logs.
Delete dev clusters you are not using; an idle cluster still pays for the control plane, nodes and NAT gateway.
Conclusion
eksctl turns the dozens of AWS resources behind an EKS cluster into a few commands. Start with eksctl create cluster to experiment, move to a cluster.yaml in Git once the cluster matters, use access entries and Pod Identity for access control, and keep up with upgrades one minor version at a time. From here, a natural next step is installing applications with Helm.
Related content
- Complete guide to setting up autoscaling with Karpenter on Amazon EKS
- How to export Amazon EKS logs to CloudWatch using Fluent Bit
- Complete guide to setting up Robusta Kubernetes monitoring on Amazon EKS
- How to Create a Kubernetes Cluster in AWS Using Kops
- Launch DigitalOcean Kubernetes with Terraform: Part 1
- Terraform AWS VPC with Public and Private subnets with NAT