Managed K8s on EKS
Everything so far runs on any cluster. This lesson goes to Amazon EKS — managed Kubernetes on AWS, and the bridge to the AWS AI Automation track. You'll stand up a cluster with eksctl, choose node groups (managed EC2 vs Fargate), give Pods AWS permissions the right way with IRSA (IAM Roles for Service Accounts), push images to ECR, expose apps through the AWS Load Balancer Controller, and face the production concerns EKS makes real: logging, cost, and upgrades.
eksctl delete cluster). Commands and IAM/annotation details drift; verify every one against the current EKS and eksctl docs. This ties directly into the AWS track (W1–W14).Learning objectives
- Explain what EKS manages for you (the control plane) versus what you still own (nodes, add-ons).
- Create a cluster and node group with eksctl, and choose managed node groups vs Fargate.
- Grant Pods least-privilege AWS access with IRSA instead of node-wide credentials.
- Push a container image to ECR and pull it into the cluster.
- Expose a service via the AWS Load Balancer Controller, and reason about logging and cost.
1 · What EKS manages — and what you still own
On EKS, AWS runs the control plane — api-server and etcd, replicated across Availability Zones, patched and backed up for you. That removes the scariest operational burden (running etcd). But EKS is not fully managed: you still own the worker nodes, the add-ons (CNI, CoreDNS, controllers), upgrades of your workloads, and cost.
2 · Create a cluster with eksctl
eksctl is the quickest path to a working cluster: one config file describes the cluster and its node groups, and eksctl provisions the VPC, control plane, and nodes via CloudFormation.
cluster.yamlapiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: ai-platform
region: us-east-1
version: "1.30" # pick a currently-supported version
managedNodeGroups:
- name: general
instanceType: m6i.large
minSize: 2
maxSize: 6
desiredCapacity: 3
volumeSize: 40
iam:
withOIDC: true # REQUIRED to use IRSA (section 3)
create.sheksctl create cluster -f cluster.yaml # ~15-20 min; provisions everything
aws eks update-kubeconfig --name ai-platform --region us-east-1
kubectl get nodes # your managed nodes, Ready
# when finished, to STOP being billed:
eksctl delete cluster --name ai-platform
NAME STATUS ROLES AGE VERSION
ip-10-0-1-23.ec2.internal Ready <none> 3m v1.30.x
ip-10-0-2-41.ec2.internal Ready <none> 3m v1.30.x
ip-10-0-3-88.ec2.internal Ready <none> 3m v1.30.x
3 · Node groups: managed EC2 vs Fargate
Where do your Pods actually run? Two models, and you can mix them:
| Managed node group (EC2) | Fargate | |
|---|---|---|
| You manage | Instance type, scaling, AMI patching (assisted) | Nothing — AWS runs each Pod |
| Granularity | Per node (pack many Pods) | Per Pod (one micro-VM each) |
| Cost shape | Pay per node, even partly idle | Pay per Pod's requested vCPU/memory |
| Good for | Steady workloads, GPUs, DaemonSets | Bursty, spiky, or isolation-sensitive workloads |
| GPUs | Yes (GPU instance types) | No GPU support |
4 · IRSA — give Pods AWS permissions the right way
Your app needs to call AWS (read S3, invoke Bedrock, write to DynamoDB). The wrong way is to attach broad permissions to the node's instance role — then every Pod on that node inherits them. The right way is IRSA: map a Kubernetes ServiceAccount to a specific IAM role via OIDC, so only Pods using that ServiceAccount get exactly those permissions.
irsa.sh# eksctl creates the IAM role, the trust policy, and the annotated ServiceAccount:
eksctl create iamserviceaccount \
--cluster ai-platform \
--namespace ai \
--name bedrock-caller \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonBedrockReadOnly \
--approve
# the resulting ServiceAccount carries an annotation like:
# eks.amazonaws.com/role-arn: arn:aws:iam::<acct>:role/<generated-role>
# then a Pod uses it:
# spec.serviceAccountName: bedrock-caller
AmazonS3FullAccess to the node instance role, every Pod on that node — including a compromised one — can use it. IRSA scopes credentials to a single ServiceAccount so each workload gets least privilege. This is the single most important EKS security practice. (Verify whether EKS Pod Identity — a newer alternative — better fits your case against the current docs.)5 · ECR, the AWS Load Balancer Controller, and prod concerns
Two more AWS-native pieces, then the operational realities.
ecr.shaws ecr create-repository --repository-name web
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin <acct>.dkr.ecr.us-east-1.amazonaws.com
docker build -t <acct>.dkr.ecr.us-east-1.amazonaws.com/web:1.0.0 .
docker push <acct>.dkr.ecr.us-east-1.amazonaws.com/web:1.0.0
# then in your Deployment: image: <acct>.dkr.ecr.us-east-1.amazonaws.com/web:1.0.0
The AWS Load Balancer Controller lets an Ingress (K4) provision a real AWS Application Load Balancer, and a LoadBalancer Service provision an NLB. Install it (via Helm, K6) and it fulfills Ingress objects natively on AWS.
alb-ingress.yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: web, port: { number: 80 } }
| Prod concern | On EKS you handle it with |
|---|---|
| Logging | Ship container logs to CloudWatch (Fluent Bit / the CloudWatch add-on) |
| Metrics | Container Insights, or Prometheus + Grafana |
| Cost | Right-size nodes, use Spot for tolerant workloads, Cluster Autoscaler/Karpenter, watch idle nodes |
| Upgrades | Bump the control-plane version, then node groups; respect PDBs (K3) during rolls |
| Security | IRSA for Pods, private API endpoint, least-privilege RBAC, image scanning in ECR |
✓ Checkpoint — you can move on when you can…
- State what EKS manages (control plane) and what you still own (nodes, add-ons, cost, upgrades).
- Create a cluster with an eksctl config and point
kubectlat it. - Choose managed node groups vs Fargate for a given workload, including the GPU constraint.
- Explain IRSA and why node-role permissions are the wrong approach.
- Push an image to ECR and expose a service via the AWS Load Balancer Controller.
A teammate grants their Pod access to S3 by attaching AmazonS3FullAccess to the EKS node group's instance role. It works. Why is this a serious mistake, and what's the correct approach on EKS?
Show answer
serviceAccountName on only the Pods that need it. Now credentials are least-privilege and per-workload. (EKS Pod Identity is a newer alternative — check current docs.)You need to run GPU-based LLM inference (K7) and also have a bursty webhook handler. Which node model fits each on EKS, and why?
Show answer
🪜 Practice ladder beginner → industry
Six graded exercises, easy to real-world. Try each before opening its solution.
Context: Getting from zero to a reachable managed cluster is the first EKS milestone — and knowing how to tear it down is the second.
Your task: Write the eksctl config for a small cluster and the commands to create it, point kubectl at it, and delete it when done.
Requirements:
- A managed node group with 2–4 nodes
- Enable OIDC (needed for IRSA later)
- Show update-kubeconfig and a get nodes check
- Show the delete command and say why it matters
💡 Hint: Enabling withOIDC now saves you re-provisioning later for IRSA.
Show solution
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata: { name: demo, region: us-east-1, version: "1.30" }
iam: { withOIDC: true }
managedNodeGroups:
- { name: general, instanceType: m6i.large, minSize: 2, maxSize: 4, desiredCapacity: 2 }eksctl create cluster -f cluster.yaml
aws eks update-kubeconfig --name demo --region us-east-1
kubectl get nodes
eksctl delete cluster --name demo # stops the billingWhy delete matters: the control plane bills hourly and nodes bill continuously, so a forgotten demo cluster quietly runs up a bill. Enabling withOIDC up front means the cluster is ready for IRSA without re-provisioning. Verify the supported version against current EKS docs.
Context: Your registry is where deploys pull from; wiring ECR correctly is a daily EKS task.
Your task: Give the commands to create an ECR repo, authenticate, build and push an image, then the Deployment snippet that references it.
Requirements:
- Create the repo and log docker in to ECR
- Build and push a version-tagged image (never :latest)
- Reference the full ECR image URI in a Deployment
💡 Hint: The image URI is <acct>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>.
Show solution
aws ecr create-repository --repository-name web
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin .dkr.ecr.us-east-1.amazonaws.com
docker build -t .dkr.ecr.us-east-1.amazonaws.com/web:1.0.0 .
docker push .dkr.ecr.us-east-1.amazonaws.com/web:1.0.0 Reference the pushed image by its full URI, pinned to the version tag:
containers:
- name: app
image: .dkr.ecr.us-east-1.amazonaws.com/web:1.0.0 EKS nodes can pull from ECR in the same account by default via their instance role; cross-account or private setups need an image-pull configuration — verify against current ECR/EKS docs.
Context: IRSA is the correct, least-privilege way to give a Pod AWS access; setting it up is a core EKS competency.
Your task: Give the steps to let only a specific Pod read from Bedrock via IRSA, and explain why this beats node-role permissions.
Requirements:
- Ensure the cluster has OIDC enabled
- Create an IAM-backed ServiceAccount scoped to the needed policy
- Attach it to the Pod and explain the credential path
- Contrast with node-role permissions
💡 Hint: eksctl can create the role, trust policy, and annotated ServiceAccount in one command.
Show solution
eksctl create iamserviceaccount \
--cluster ai-platform --namespace ai --name bedrock-caller \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonBedrockReadOnly --approveAttach it to the Pod:
spec:
serviceAccountName: bedrock-callerHow it works: the ServiceAccount is annotated with an IAM role ARN; via the cluster's OIDC provider, Pods using it receive short-lived, automatically-rotated credentials for exactly that role — the AWS SDK picks them up with no keys in the image or env.
Why it beats node-role permissions: node-role grants apply to every Pod on the node, violating least privilege; IRSA scopes credentials to a single ServiceAccount, so only the intended workload can call Bedrock and a neighbor Pod can't. Verify whether EKS Pod Identity is a better fit against current docs.
Context: On AWS, Ingress is fulfilled by the AWS Load Balancer Controller provisioning a real ALB; using it correctly is how EKS apps face the internet.
Your task: Describe how to expose a web Service through an internet-facing ALB via an Ingress, and name the prerequisite and the annotation caveat.
Requirements:
- State the controller prerequisite
- Write an ALB Ingress with the key annotations
- Explain target-type ip vs instance briefly
- Note that annotations are version-specific
💡 Hint: The controller must be installed (Helm) and have IRSA permissions to manage ELBs.
Show solution
Prerequisite: install the AWS Load Balancer Controller (via Helm, K6) and grant it IRSA permissions to create/manage load balancers. Without it, the Ingress is inert.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
rules:
- http:
paths:
- { path: /, pathType: Prefix, backend: { service: { name: web, port: { number: 80 } } } }target-type: ip routes the ALB straight to Pod IPs (works with Fargate and is the common modern choice); instance routes to node ports. Caveat: these alb.ingress.* annotations are controller-version-specific — verify each against the controller's current docs rather than copying blindly.
Context: On EKS, node choices and observability decisions directly drive the bill and your ability to debug; a professional plans both up front.
Your task: Design the node-group topology, logging, and cost controls for a platform with steady services, bursty jobs, and one GPU inference workload.
Requirements:
- Assign each workload class to managed nodes or Fargate (with the GPU constraint)
- State the logging/metrics pipeline
- Name at least three concrete cost controls
- Note the upgrade approach and its interaction with PDBs
💡 Hint: GPUs force EC2; bursty stateless favors Fargate; steady baseline favors a small managed group.
Show solution
Node topology: a small managed node group for steady baseline services and system add-ons; Fargate for bursty jobs (pay per Pod, no idle cost, isolation); a GPU managed node group for the inference workload, since Fargate has no GPUs (K7). Autoscale nodes with the Cluster Autoscaler or Karpenter.
Logging/metrics: Fluent Bit (or the CloudWatch add-on) ships container logs to CloudWatch; Container Insights or Prometheus+Grafana for metrics — so you can actually debug what the cluster does.
Cost controls: (1) right-size requests so the scheduler packs nodes tightly; (2) use Spot instances for interruption-tolerant workloads; (3) autoscale to reclaim idle nodes; (4) alarm on idle/forgotten clusters and cap max node counts. Idle nodes and forgotten load balancers are the biggest silent costs.
Upgrades: bump the control-plane version first, then roll node groups; respect PodDisruptionBudgets (K3) so draining nodes for the roll never breaches availability. Verify current pricing, autoscaler, and upgrade steps against AWS docs.
Context: Representative scenario: an app runs on kind locally and must go to EKS for production, with proper AWS integration, exposure, and cost discipline.
Your task: Write the migration plan: what changes from the local manifests, how AWS access and image pulls are handled, how it's exposed, and how it's kept observable and affordable.
Requirements:
- State what's identical and what changes moving local → EKS
- Handle images (ECR) and AWS permissions (IRSA)
- Expose it via the AWS Load Balancer Controller
- Include logging, cost controls, and teardown discipline
💡 Hint: The workload YAML barely changes; what changes is registry, IAM, ingress, and cost.
Show solution
What stays the same: the Deployment, Service, ConfigMap/Secret, probes, resources, HPA, and PDB manifests (K2–K4) are portable — that's the payoff of standard Kubernetes. The workload definition barely changes.
What changes for EKS:
- Cluster: create with eksctl (OIDC enabled), point kubectl at it.
- Images: push to ECR and update image URIs to the ECR path (versioned tags).
- AWS access: replace any local fake credentials with IRSA — a scoped ServiceAccount per workload that needs AWS (S3/Bedrock/etc.), never node-role permissions.
- Exposure: swap the local NodePort/port-forward for an ALB Ingress via the AWS Load Balancer Controller, with TLS (ACM/cert-manager).
- Secrets: move real values into AWS Secrets Manager (via a CSI driver) instead of plain manifests; keep only placeholders in git.
Operate it: ship logs to CloudWatch, add Container Insights/Prometheus metrics, right-size requests, use Spot for tolerant workloads, autoscale nodes, and respect PDBs on upgrades. Put a cost alarm on the cluster and tear down anything non-prod when idle. Verify every AWS-specific command and annotation against the current EKS docs — this is the layer that drifts most.