Amazon Elastic Kubernetes Service, or Amazon EKS, runs the Kubernetes control plane for you, keeps it highly available and exposes the cluster API. The rest of the platform still depends on compute, networking, identity, DNS and storage working together.

An EKS cluster showing ACTIVE only confirms that the control plane exists. It does not prove that nodes have joined, Pods can obtain addresses, add-ons are healthy or your kubectl identity is authorised. This article separates those systems in a traditional EC2-backed EKS cluster using managed node groups, rather than EKS Auto Mode or Fargate.


01 — CONTROL PLANE

Control Plane

A Kubernetes cluster has a control plane, which records and reconciles desired state, and a data plane, where application workloads run. In EKS, AWS operates the API servers, etcd, scheduler and controllers across multiple Availability Zones; you do not receive or patch their underlying hosts.

These are the standard Kubernetes control-plane components, but AWS manages their availability and infrastructure.

The managed boundary stops there. AWS does not automatically provide healthy worker capacity, working Pod networking or application permissions merely because the API is available.

Control Plane vs Data Plane diagram for Amazon EKS
Figure 1. AWS manages the EKS control plane; you manage nodes and workloads in your AWS account.

The API server

The Kubernetes API server is the cluster's main interface. Commands such as these send requests to it:

terminal
kubectl get pods

kubectl apply -f deployment.yaml

The API server authenticates and authorises the caller, validates the resource and runs admission controls before recording the desired state.

Schedulers, controllers and kubelets also communicate through this API rather than modifying cluster state directly. That makes the API server the consistent control point for both users and internal Kubernetes components.

You can retrieve the API endpoint for an EKS cluster using the AWS CLI:

terminal
aws eks describe-cluster \
  --name my-cluster \
  --query 'cluster.endpoint' \
  --output text

The returned address is Kubernetes' management endpoint, not an application endpoint.

etcd

etcd is the distributed key-value database holding Deployments, Pods, Services, Secrets, ConfigMaps and node registrations. Kubernetes stores declarations such as replicas: 3 as desired state; controllers then compare that state with reality.

AWS operates and protects etcd in EKS, including its underlying hosts, storage and replication.

Scheduler

The scheduler chooses a worker node for each Pod using available CPU and memory, selectors, affinity, topology constraints, taints and tolerations. It records placement; the node's kubelet starts the workload.

This distinction matters during troubleshooting: a Pending Pod may have failed placement even though every existing container runtime is healthy.

Controller manager

Controllers continuously reconcile desired and actual state. If a Deployment requests three replicas but only two exist, its controllers create a replacement Pod. This reconciliation is why Kubernetes recovers automatically from many workload failures.

Controllers do not normally repair a particular failed container. They restore the declared state, often by creating a new Pod and allowing the scheduler to place it again.

AWS manages these control-plane decisions; your data plane executes them.


02 — NETWORKING

Networking

Worker nodes live inside your VPC, while the control plane runs in AWS-managed infrastructure. EKS therefore needs secure network paths between both sides of the cluster.

Amazon EKS network paths between a user's machine, the EKS API endpoint, the AWS-managed control plane and worker nodes in private subnets
Figure 2. EKS networking requires API access, node-to-control-plane connectivity and a return path from the control plane to the kubelet API.

The subnet IDs supplied during cluster creation do not host the control-plane servers. EKS instead creates two to four requester-managed Elastic Network Interfaces (ENIs) in those subnets, giving the managed control plane private VPC connectivity and security-group associations.

The subnets must span at least two Availability Zones and each needs at least six available IP addresses; AWS recommends sixteen. Their finite address space is also consumed by nodes, Pods, load balancers and other networking components.

API endpoint access

The Kubernetes API endpoint can be configured for:

  • Public access
  • Private access
  • Public and private access

Public access lets approved external networks reach the API, while private access keeps node and in-VPC traffic on the VPC path. A private-only endpoint requires administrators and automation to have network connectivity into the VPC.

You can inspect the current configuration using:

terminal
aws eks describe-cluster \
  --name my-cluster \
  --query 'cluster.resourcesVpcConfig.{
    Public:endpointPublicAccess,
    Private:endpointPrivateAccess,
    PublicCIDRs:publicAccessCidrs
  }'

A development cluster might enable both modes while limiting public access to known CIDRs. Authentication is still required, but allowing 0.0.0.0/0 unnecessarily exposes the API endpoint to the entire internet.

Node-to-control-plane communication

Each node's kubelet must reach the API endpoint over HTTPS during bootstrap and normal operation. If that path or authentication fails, a healthy EC2 instance may never register as a Kubernetes node.

That is why these two commands answer very different questions:

terminal
aws ec2 describe-instances

kubectl get nodes

The first tells you whether EC2 exists and is running. The second tells you whether Kubernetes has successfully registered and accepted the node.

This path depends on subnet routes, DNS resolution, security groups, endpoint configuration and the node IAM role. Testing only EC2 health therefore misses several requirements for cluster membership.

Control-plane-to-node communication

Traffic also flows back from the control plane to nodes. Operations such as kubectl exec and kubectl logs can involve the kubelet API on TCP 10250, so overly restrictive security-group rules can break Kubernetes even when ordinary application traffic works.

Useful investigation commands include:

terminal
kubectl get nodes -o wide

kubectl describe node <node-name>

For lower-level network inspection, you can also examine the cluster security groups:

terminal
aws eks describe-cluster \
  --name my-cluster \
  --query 'cluster.resourcesVpcConfig.clusterSecurityGroupId'

Kubernetes often reports the symptom first, while the cause is a route table, security group, subnet, DNS setting or endpoint configuration underneath it.


03 — WORKER NODES

Worker Nodes

Application workloads need compute in the data plane. In an EC2-backed cluster, each worker node is an EC2 instance containing an operating system, Kubernetes networking, a kubelet and a container runtime such as containerd.

The kubelet registers the node, watches for assigned Pods, instructs the runtime to start containers and reports health to the API server.

During bootstrap, the instance needs the correct cluster name, API endpoint and certificate authority data, plus an IAM identity EKS accepts as a node. A mistake in user data, DNS, routing or permissions can leave the instance running but absent from Kubernetes.

You can see registered nodes using:

terminal
kubectl get nodes

Look for STATUS: Ready. EC2 running only means the virtual machine exists; Kubernetes Ready means the cluster can communicate with it and considers it able to accept workloads.

For more detail:

terminal
kubectl describe node ip-10-0-11-25.eu-west-2

This exposes capacity, conditions, taints, Pod addressing, resource allocation and recent events.

Managed node groups

EKS managed node groups provide an AWS-managed lifecycle around EC2 worker nodes. The instances still run in your AWS account and normally belong to an Auto Scaling Group, but EKS assists with operations such as provisioning, updates and draining nodes during replacement.

You still choose instance types, subnets, scaling limits and much of the operating-system configuration. Managed does not mean AWS decides the workload architecture for you.

List and inspect node groups with:

terminal
aws eks list-nodegroups \
  --cluster-name my-cluster

aws eks describe-nodegroup \
  --cluster-name my-cluster \
  --nodegroup-name system-nodes

The minimum, desired and maximum values control EC2 capacity. Kubernetes scheduling remains separate: adding instances supplies machines but does not decide Pod placement.

Why bootstrap nodes matter

Karpenter can provision EC2 capacity for unschedulable workloads, but Karpenter itself runs as a Pod. With zero nodes, it—and system workloads such as CoreDNS or storage controllers—has nowhere to start.

A common design is therefore:

flow
Small managed node group
        |
        v
Core cluster services start
        |
        v
Karpenter starts
        |
        v
Application workloads appear
        |
        v
Karpenter provisions additional capacity

A small managed node group therefore acts as the fixed bootstrap layer while Karpenter supplies most application capacity.


04 — IAM & RBAC

IAM & RBAC

EKS joins two security systems: AWS IAM establishes the AWS identity, while Kubernetes RBAC determines what that identity can do inside the cluster.

Amazon EKS identity flow diagram showing IAM, access entries and Kubernetes RBAC
Figure 3. AWS IAM establishes identity; EKS maps it into cluster access; Kubernetes RBAC controls what the identity can do.

EKS maps the IAM principal into cluster access before Kubernetes authorisation evaluates the requested action.

These stages are separate: IAM authentication can succeed while Kubernetes returns Forbidden, because the authenticated principal lacks the required RBAC permission.

Checking your AWS identity

Before troubleshooting Kubernetes access, check which AWS identity your terminal is actually using:

terminal
aws sts get-caller-identity

With multiple AWS profiles, verify the intended profile explicitly:

terminal
AWS_PROFILE=nabil aws sts get-caller-identity

The wrong AWS identity causes Kubernetes authentication to fail even when kubeconfig is correct.

kubeconfig

To configure kubectl for an EKS cluster:

terminal
aws eks update-kubeconfig \
  --region eu-west-2 \
  --name my-cluster

This writes the API and certificate configuration to ~/.kube/config. Inspect the active and available contexts with:

terminal
kubectl config current-context
kubectl config get-contexts

For EKS authentication, the AWS CLI normally generates a short-lived token from your current AWS credentials.

Consequently, switching AWS profiles can change the identity presented to the same kubeconfig context without changing the cluster endpoint itself.

EKS access entries

Modern EKS clusters can use access entries to map IAM principals into cluster permissions.

List them with:

terminal
aws eks list-access-entries \
  --cluster-name my-cluster

You might see an identity such as arn:aws:iam::123456789012:role/platform-engineer.

An entry can use EKS access policies or map the principal to Kubernetes groups. For supported configurations, this is preferable to relying entirely on the older aws-auth ConfigMap.

The access entry answers who may enter the cluster; EKS access policies or RBAC answer which Kubernetes actions that principal may perform.

Kubernetes RBAC

Test Kubernetes permissions directly:

terminal
kubectl auth can-i get pods
kubectl auth can-i delete deployments -n production

The first may return yes and the second no. Broad AWS permissions do not automatically grant Kubernetes access, and cluster administration does not grant wider AWS permissions.

Cluster role, node role and workload identity

The cluster IAM role lets EKS perform required AWS operations, while the node IAM role supports node-level actions such as pulling from ECR. Applications should use EKS Pod Identity or IAM Roles for Service Accounts instead of inheriting broad node permissions. Keeping these identities separate limits the impact of a compromised workload.


05 — CLUSTER ADD-ONS

Cluster Add-ons

A healthy API server and Ready nodes are not enough. Workloads also depend on components that provide Pod networking, DNS, Service routing and storage integration; EKS can manage several of them as add-ons.

Amazon EKS workload dependencies diagram showing VPC CNI, CoreDNS, kube-proxy and EBS CSI Driver
Figure 4. Core add-ons provide Pod networking, DNS, Service networking and persistent storage integration.

List add-ons and inspect their Kubernetes workloads with:

terminal
aws eks list-addons \
  --cluster-name my-cluster

kubectl get pods -n kube-system

Common results include vpc-cni, coredns, kube-proxy and aws-ebs-csi-driver.


Amazon VPC CNI

The Amazon VPC CNI configures container networking and normally assigns Pods addresses from the VPC rather than a separate overlay.

This allows Pods to participate directly in VPC routing and security designs, but it also ties Pod density to available subnet addresses and the networking limits of each EC2 instance type.

You can see Pod IP addresses using:

terminal
kubectl get pods -o wide

Because those addresses consume real VPC capacity, subnet IP exhaustion can stop new Pods even when nodes still have CPU and memory available.

Useful commands include:

terminal
kubectl get daemonset aws-node -n kube-system

kubectl logs -n kube-system \
  -l k8s-app=aws-node \
  --tail=100

Design subnet CIDR ranges for expected Pod density, not only EC2 instance count.


CoreDNS

Pods are ephemeral, so applications use stable Kubernetes Service names such as postgres.default.svc.cluster.local. CoreDNS resolves those names; if it fails, otherwise healthy applications cannot find databases, APIs or other internal services.

CoreDNS itself runs as Pods, so it needs schedulable worker capacity and working CNI networking before cluster DNS can become healthy.

Test DNS from a Pod and inspect CoreDNS with:

terminal
kubectl get deployment coredns -n kube-system

kubectl logs \
  -n kube-system \
  -l k8s-app=kube-dns

nslookup kubernetes.default.svc.cluster.local

kube-proxy

kube-proxy implements Service networking on each node. Clients use a stable Service address while kube-proxy programs rules that direct traffic to the selected Pods.

A Service with the correct ClusterIP can still fail if its selector matches no ready Pods, because Kubernetes then has no backend endpoints to program.

Inspect Services and their backends with:

terminal
kubectl get services
kubectl get endpoints
kubectl get endpointslices

If a Service exists but has no healthy backend endpoints, requests have nowhere to go.


EBS CSI Driver

Stateful workloads often need storage that survives container recreation. The EBS CSI Driver implements the Container Storage Interface between Kubernetes claims and Amazon EBS volumes.

Inspect storage resources and a failing claim with:

terminal
kubectl get pvc

kubectl get pv
kubectl describe pvc <pvc-name>

A claim stuck in Pending can indicate a StorageClass, CSI, IAM, capacity or Availability Zone problem. Because an EBS volume belongs to one AZ, Kubernetes must schedule its Pod where the volume can attach.

The controller component makes AWS API calls to provision or attach storage, while a node component performs the mount. Both the relevant IAM permissions and the Kubernetes components must be healthy.


06 — FROM KUBECTL TO POD

From kubectl to Pod

The easiest way to understand how all of these systems interact is to follow a deployment from the developer's terminal to a running application.

Amazon EKS deployment lifecycle from kubectl apply to a running Pod
Figure 5. From kubectl apply to a running Pod, multiple independent Kubernetes and AWS components participate.

Consider this Deployment:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:latest
          ports:
            - containerPort: 80

You submit it using:

terminal
kubectl apply -f deployment.yaml

kubectl does not SSH into a node and start NGINX. It declares the desired state and several components act on it.

Step 1: kubectl reads the cluster configuration

kubectl reads kubeconfig to find the API endpoint, then uses your AWS identity to obtain EKS authentication.


Step 2: The API server receives the manifest

The API server authenticates and authorises the request, validates the manifest and stores the Deployment as desired state. Inspect the accepted resource with:

terminal
kubectl get deployment web

Acceptance does not mean the application is running yet.

The remaining work is asynchronous: controllers and nodes watch for the new state and react independently.


Step 3: Controllers create lower-level resources

The Deployment controller creates a ReplicaSet, which maintains the requested Pods: Deployment → ReplicaSet → Pods. Deleting one managed Pod does not remove the application because the ReplicaSet creates a replacement.

This separation lets a Deployment manage rollout strategy while the ReplicaSet concentrates on maintaining the required replica count.


Step 4: The scheduler chooses nodes

The scheduler evaluates capacity and constraints before assigning each new Pod to a node.

Common reasons a Pod cannot be scheduled include:

  • Insufficient CPU or memory
  • Node taints or affinity rules
  • Topology constraints
  • No suitable persistent-volume location

If no node qualifies, the Pod remains Pending. Inspect the Pod and recent events with:

terminal
kubectl describe pod <pod-name>

kubectl get events \
  --sort-by=.lastTimestamp

The events usually identify whether the blocker is capacity, a taint, an affinity rule or storage topology.


Step 5: kubelet receives the assignment

The selected node's kubelet asks the container runtime to create the containers. Images in ECR require working permissions and connectivity; wrong names, tags or credentials commonly produce ErrImagePull followed by ImagePullBackOff.

After pulling the image, the runtime creates the Pod sandbox and containers according to the specification. The kubelet continually reports their status back through the API.


Step 6: Pod networking is configured

The Amazon VPC CNI assigns the Pod an address and configures its network path.

The Pod cannot communicate normally until that setup succeeds, even if the image has already been downloaded to the node.

Watch this process using:

terminal
kubectl get pods -o wide -w

A Pod can move from ContainerCreating with no address to Running with a VPC IP once networking succeeds.


Step 7: Storage is attached if required

If the Pod uses an EBS-backed PersistentVolumeClaim, Kubernetes and the CSI driver create or locate the volume, attach it to the selected node and mount it. Failure can leave a valid image stuck in ContainerCreating.

Volume topology can also influence scheduling because the selected node must be in an Availability Zone compatible with the EBS volume.


Step 8: Health checks determine readiness

A running container is not automatically ready for traffic. Liveness probes decide when to restart it, readiness probes control whether it receives traffic, and startup probes protect slow initialisation.

Example:

yaml
readinessProbe:
  httpGet:
    path: /health
    port: 80
  initialDelaySeconds: 5
  periodSeconds: 10

If the readiness probe fails, the Pod may be Running while still showing READY 0/1.

The process exists, but Kubernetes does not yet consider it a healthy backend.

Services normally remove unready Pods from their usable endpoints, preventing traffic from reaching an application before it can respond safely.


Step 9: Services expose the workload

If you create a Service:

yaml
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

The Service selects Pods matching app=web. Inspect both sides with:

terminal
kubectl get pods -l app=web
kubectl get service web

Traffic can then travel from a load balancer to the Service, selected Pod and container—a path requiring AWS and Kubernetes networking to cooperate.

For internet-facing applications, an Ingress controller or AWS Load Balancer Controller can create the AWS load balancer and connect it to the Kubernetes routing model.


07 — PITFALLS

Pitfalls

EKS sits between AWS infrastructure and Kubernetes, so the visible symptom may be several layers away from its cause.

ACTIVE does not mean the cluster is ready

ACTIVE confirms only the control plane. Operational readiness still requires Ready nodes, healthy system add-ons, scheduled workloads and successful application health checks.


EC2 running does not mean Kubernetes node ready

An EC2 instance can be running while kubectl get nodes shows nothing. That points toward the bootstrap path, identity or connectivity rather than EC2 availability.

Check identity and the managed node group:

terminal
aws sts get-caller-identity

aws eks describe-nodegroup \
  --cluster-name my-cluster \
  --nodegroup-name <nodegroup-name>

Then inspect node-group or Auto Scaling events. Common causes include:

  • The node IAM role
  • Security groups or route tables
  • DNS or API endpoint access
  • Bootstrap configuration, including an incorrect cluster name or broken user data

IAM access does not equal Kubernetes access

An AWS administrator may still receive Error from server (Forbidden). Test the AWS identity, its EKS mapping and Kubernetes authorisation separately:

terminal
aws sts get-caller-identity

aws eks list-access-entries \
  --cluster-name my-cluster

kubectl auth can-i get pods

Subnet exhaustion can look like a scheduling problem

VPC addresses are cluster capacity. A node can have spare compute while new Pods fail because the subnet has no usable addresses.

Check Pod events and CNI health:

terminal
kubectl describe pod <pod-name>

kubectl get pods \
  -n kube-system \
  -l k8s-app=aws-node

Plan for nodes, Pods per node, load balancers, EKS ENIs, other resources and future growth—not only current EC2 instances.


Deployment order matters

Terraform dependencies should follow the real order: control plane, bootstrap nodes, core add-ons, Karpenter capacity and then applications. Creating everything concurrently can expose the bootstrap problem described earlier.


Pending means something different from CrashLoopBackOff

Pod status quickly narrows the investigation:

  • Pending: scheduling, capacity, volumes, selectors, taints or networking setup.
  • ImagePullBackOff: image name, registry, ECR permissions or network access.
  • CrashLoopBackOff: the container starts and repeatedly exits.
  • ContainerCreating: Kubernetes is still preparing networking, volumes, runtime or configuration.

Check the current logs and, for a repeatedly restarting container, the previous instance:

terminal
kubectl logs <pod-name>

kubectl logs <pod-name> --previous

A practical troubleshooting sequence

Investigate from infrastructure upward: control plane, nodes, system components, workload and application output.

terminal
aws eks describe-cluster \
  --name my-cluster \
  --query 'cluster.status'

kubectl get nodes
kubectl get pods -n kube-system
kubectl get pods -o wide
kubectl describe pod <pod-name>
kubectl get events \
  --sort-by=.lastTimestamp
kubectl logs <pod-name>

# If necessary, verify Kubernetes permissions and AWS identity
kubectl auth can-i get pods
aws sts get-caller-identity

Each layer depends on the one beneath it, so a lower-level failure can create misleading symptoms above it.


08 — CONCLUSION

Conclusion

Amazon EKS removes the work of operating Kubernetes' control plane, but nodes, network paths, IAM mappings, Pod addresses, DNS, Service routing and storage must still function together before an application is usable.

Once those responsibilities are separated, failures become easier to locate: scheduling, node bootstrap, identity, subnet capacity, image pulls, DNS, storage or the application itself. That understanding turns EKS from a black box into a platform you can design and troubleshoot deliberately.

The practical habit is to work upward from the control plane and network, through nodes and add-ons, to the workload and its traffic path. Each check removes an entire class of possible causes.