Kubernetes In Anger

Lobsters Hottest News

Summary

A comprehensive guide to debugging and managing Amazon EKS clusters in production, focusing on common failure modes, incident response, and safe upgrades. Covers key differences between EKS and standard Kubernetes.

<p>I have been operating Kubernetes for more than 7 years now- had gathered notes over those years. With the help of AI organised all of it into this. Thanks to <a href="https://erlang-in-anger.com/" rel="ugc">Erlang in Anger</a> for the inspiring this.</p> <p><a href="https://lobste.rs/s/iggblv/kubernetes_anger">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 05/21/26, 12:14 PM

# Kubernetes In Anger Source: [https://samof76.space/kubernetes-in-anger.html](https://samof76.space/kubernetes-in-anger.html) 2026\-05\-21 > NOTE: Any discussions can be had on[Lobsters](https://lobste.rs/s/iggblv/kubernetes_anger) ## 0\. Quick start \(emergency edition\) ### Is this the right guide? **YES, if:** - You’re debugging a live EKS production issue - You need to upgrade/change EKS safely - You want to prevent common EKS outages - You’re oncall for EKS workloads **NO, if:** - You’re learning Kubernetes basics \(try the official tutorials first\) - You need EKS setup instructions \(use AWS documentation\) - You want comprehensive Kubernetes reference \(use kubernetes\.io\) ### Emergency shortcuts **Cluster is on fire right now?**→ Jump to[Section 2\.10 Tier\-0 Incident Playbook](https://samof76.space/kubernetes-in-anger.html#210-what-to-do-when-a-tier-0-component-is-unhealthy-eks-incident-playbook) **Need to upgrade safely?**→ Jump to[Section 8 Upgrades and maintenance](https://samof76.space/kubernetes-in-anger.html#8-upgrades-and-maintenance) **Investigating an incident?**→ Start with[Section 1\.2 Quick Cluster Health Snapshot](https://samof76.space/kubernetes-in-anger.html#12-quick-cluster-health-snapshot-30-seconds) ### Prerequisites This guide assumes you know: - Basic kubectl commands \(`get`,`describe`,`logs`\) - AWS CLI basics - What pods, services, and deployments are - How to read YAML manifests ### What makes EKS different EKS is not “just Kubernetes”\. Key differences that affect reliability: - Pods get real VPC IPs \(AWS VPC CNI\) - AWS services become dependencies \(NAT, NLB, EBS\) - Node limits are AWS EC2 limits - Networking failures look like application failures - Upgrades affect multiple AWS components --- ## Introduction ### On running infrastructure There’s a common way of thinking about Kubernetes that goes something like this: you declare what you want, the system converges toward it, and your job is mostly done\. Write the YAML, apply it, the scheduler places your pods, the controllers reconcile state, and everything just works\. This is roughly true until it isn’t\. The thing about Kubernetes — and EKS specifically — is that it doesn’t fail like a monolith fails\. A monolith crashes and you know it\. EKS degrades\. DNS gets slow\. A node hits a network limit you didn’t know existed\. Pods keep running but their connections reset every 6 minutes\. The dashboard is green\. Customers are complaining\. You’re staring at healthy pods wondering what’s wrong with your application, when the real problem is three layers down in a conntrack table or a subnet that ran out of IPs\. Most other platforms fail at the boundary between your code and the infrastructure\. EKS fails*inside*the infrastructure, in ways that look like your code is broken\. This is the fundamental debugging challenge: the symptom is always “the app is slow” or “requests are failing”, and the cause is somewhere in a stack of networking, scheduling, storage, and AWS service interactions that your application has no visibility into\. This matters because the instinct — “my app is returning 5xx, let me look at my app” — is wrong most of the time in EKS\. The 5xx is real\. But the fix is often in a probe configuration, a security group limit, a DNS resolver being overwhelmed, or a node that silently filled its conntrack table\. ### The two jobs If you run EKS in production, you have two jobs: The first is building workloads that survive the platform misbehaving\. Probes that don’t cascade\. Graceful shutdowns that actually drain\. Pod distributions that tolerate losing a node or an AZ without paging anyone\. This is the preventive work — the engineering equivalent of washing your hands\. The second is diagnosing live systems when things go wrong anyway\. Connecting to a cluster that’s on fire, figuring out what’s actually broken vs what’s just symptomatic, collecting evidence before it disappears, and fixing the right thing without making the incident worse\. This is the equivalent of surgery — you’re operating on a patient that’s still awake and serving traffic\. Both matter\. Most guides only cover the first one\. This guide is about both\. It’s a collection of patterns, failure modes, and diagnostic workflows that came from running EKS in production — the things that caused real incidents, the things that made debugging take hours instead of minutes, and the guardrails that prevented repeat occurrences\. ### Who is this for? This guide is not for beginners\. There’s a gap between knowing Kubernetes concepts \(pods, deployments, services, kubectl\) and actually being able to keep an EKS cluster healthy in production\. There’s a fumbling phase where you’ve read the docs, passed the certification maybe, deployed some workloads — and then something breaks at 2am and you realize you don’t know where to look or what’s safe to touch\. This assumes you know the basics\. It does not assume you know how to debug a cluster that’s misbehaving, how EKS\-specific failure modes differ from generic Kubernetes ones, or what the safe sequence of actions is when you’re staring at a production incident\. What you won’t find here: how to set up EKS, what a pod is, or how to write a Deployment manifest\. What you will find: what to do when pods are Pending and you don’t know why, how to tell if DNS is the problem or just a symptom, why your NLB keeps resetting connections, and how to collect evidence before the cluster auto\-heals and destroys your ability to do an RCA\. ### How to read this guide The guide is organized by domain — networking, storage, security, observability, scaling, upgrades, and so on\. Each section mixes both jobs: how to build it right, and how to debug it when it breaks\. You’ll find design patterns and diagnostic runbooks side by side, because in practice you need both at the same time\. You can read it front\-to\-back if you’re setting up a new cluster or onboarding to an existing one\. Or you can jump to the relevant section when something breaks — each one is self\-contained enough to be useful on its own\. If the cluster is on fire right now, start at Section 1\. It gives you a triage sequence to identify the failure domain in under 2 minutes\. --- ## 1\. How to dive into an EKS cluster When production is broken the first job is to mitigate — stop the bleeding, restore service, reduce blast radius\. But you can’t mitigate effectively if you don’t know*what’s broken*\. Rollback the wrong thing and you’ve wasted 10 minutes\. Upsize the wrong component and nothing changes\. So the actual first job is: figure out where the problem is, fast enough that you can pick the right mitigation within 2 minutes\. Not root cause — just failure domain: - One pod? - One deployment / namespace? - One node group / AZ? - The entire cluster? - AWS integration \(CNI / LB / EBS\)? - An upstream dependency \(RDS, Redis, external APIs\)? Once you know the failure domain, you mitigate \(rollback, drain, upsize, block\)\. Root cause comes after the incident is contained\. What follows is a reliable entry sequence to get that signal fast, without guessing\. --- ### 1\.1 Establish Context \(don’t debug the wrong cluster\) Before anything else, confirm you’re looking at the right cluster and identity\. **Commands** ``` kubectl config current-context kubectl cluster-info aws sts get-caller-identity ``` **What you’re checking** - You’re in the correct kubecontext \(prod vs staging mistakes happen\) - You have valid AWS credentials - The API server is reachable at all If`kubectl cluster\-info`is slow or timing out, that’s already a strong signal: - API server under load - auth problems - network path issues from your machine \(VPN / corp DNS / proxy\) --- ### 1\.2 Quick Cluster Health Snapshot \(30 seconds\) This is the fastest “is the cluster sick?” view\. **Commands** ``` kubectl get nodes -o wide kubectl get pods -A --field-selector=status.phase!=Running kubectl get events -A --sort-by=.lastTimestamp | tail -n 50 ``` **What you’re checking** - Any nodes`NotReady` - Any pods stuck in`Pending`,`CrashLoopBackOff`,`ImagePullBackOff` - Events that scream the root cause:- `FailedScheduling` - `FailedMount` - `Unhealthy`\(probe failures\) - `Back\-off restarting failed container` - `FailedCreatePodSandBox`\(CNI problems\) This is where you decide: - **workload issue**vs**node issue**vs**cluster\-wide issue** --- ### 1\.3 Confirm EKS System Components \(the “platform basics”\) If system components are down, application debugging is mostly pointless\. **Commands** ``` kubectl get pods -n kube-system -o wide kubectl get ds -n kube-system kubectl get deploy -n kube-system ``` **Focus on these in EKS** - `coredns`\(cluster DNS\) - `aws\-node`\(AWS VPC CNI\) - `kube\-proxy`\(unless you’re on eBPF dataplane\) - `ebs\-csi\-node`/`ebs\-csi\-controller`\(if you use EBS CSI\) - `metrics\-server`\(if HPA depends on it\) **Red flags** - CoreDNS pods not ready → widespread service discovery failures - `aws\-node`not ready → pods can’t get IPs / networking breaks - EBS CSI issues → StatefulSets fail to mount volumes --- ### 1\.4 Decide: Is this a “Scheduling” Problem? If pods are Pending, do this immediately\. **Commands** ``` kubectl get pods -A | grep -E "Pending|ContainerCreating" kubectl describe pod -n <ns> <pod> ``` **What you’re looking for in`describe`** - `FailedScheduling`- insufficient CPU/memory - taints not tolerated - affinity rules too strict - topology spread constraints blocking placement - `Insufficient pods`/`Too many pods`- node has hit max pod density \(ENI/IP limits\) - `node\(s\) had volume node affinity conflict`- common with EBS \+ AZ mismatch If scheduling fails, do**not**restart the deployment blindly\. It won’t help\. --- ### 1\.5 Decide: Is this a “Node” Problem? If nodes are NotReady or workloads are failing on specific nodes, zoom in\. **Commands** ``` kubectl describe node <node-name> kubectl top nodes kubectl get pods -A -o wide | grep <node-name> ``` **What to check on the node** - `Conditions`: MemoryPressure / DiskPressure / PIDPressure - `Allocatable`vs`Allocated resources` - Events mentioning:- kubelet issues - container runtime issues - frequent reboots - network plugin failures In EKS, node failures often correlate with: - EBS issues - CNI/IP exhaustion - disk full \(especially on small root volumes\) - aggressive DaemonSets consuming resources --- ### 1\.6 Decide: Is this a “Network / CNI” Problem? EKS networking failures are often AWS VPC CNI related\. **Symptoms** - Pods stuck at`ContainerCreating` - `FailedCreatePodSandBox` - random cross\-service timeouts - sudden increase in Pending pods \(no IPs\) **Commands** ``` kubectl -n kube-system logs -l k8s-app=aws-node --tail=100 kubectl -n kube-system describe ds aws-node ``` **Also check pod density** ``` kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.allocatable.pods}{"\n"}{end}' ``` If you’re hitting pod density/IP limits: - scaling nodes may help - but the real fix is often**prefix delegation**/ node type sizing / ENI planning --- ### 1\.7 Decide: Is this a “DNS / CoreDNS” Problem? DNS issues can look like “application is broken”\. **Symptoms** - timeouts to internal services - failures resolving service names - sudden spike in retries / connection errors **Commands** ``` kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide kubectl -n kube-system logs -l k8s-app=kube-dns --tail=200 ``` If CoreDNS is unhealthy: - don’t waste time debugging app\-level service discovery logic - fix CoreDNS capacity / upstream resolver / node networking first --- ### 1\.8 Decide: Is this a “Storage / EBS CSI” Problem? Stateful workloads fail differently\. **Symptoms** - pods stuck in`ContainerCreating` - `FailedMount` - volumes not attaching **Commands** ``` kubectl describe pod -n <ns> <pod> kubectl get pvc -A kubectl -n kube-system get pods | grep ebs kubectl -n kube-system logs deploy/ebs-csi-controller --tail=200 ``` **What you’re looking for** - volume attachment timeouts - AZ mismatch - stuck PV/PVC lifecycle - CSI controller/node not healthy --- ### 1\.9 Find the “Blast Radius” \(what is actually impacted\) Before you attempt changes, quantify impact\. **Commands** ``` kubectl get pods -A | wc -l kubectl get pods -A --field-selector=status.phase!=Running | head -n 50 kubectl get nodes | grep -v Ready ``` **Interpretation** - If only one namespace is impacted → likely app or namespace\-level dependency - If one node group/AZ is impacted → capacity, subnet, EBS/AZ, or rollout targeting issue - If kube\-system is unhealthy → platform issue, stop chasing app symptoms --- ### 1\.10 Evidence Collection \(before you change anything\) Kubernetes evidence disappears fast \(pods restart, nodes recycle, events roll over\)\. Capture minimal evidence first\. **Commands** ``` kubectl get events -A --sort-by=.lastTimestamp | tail -n 200 > events.txt kubectl get nodes -o wide > nodes.txt kubectl get pods -A -o wide > pods.txt ``` If it’s a single workload incident: ``` kubectl describe pod -n <ns> <pod> > pod.describe.txt kubectl logs -n <ns> <pod> --previous > pod.prev.log.txt ``` This makes the RCA possible later, without relying on memory and guesswork\. --- ### 1\.11 Node\-Specific Failures \(Conntrack, Sysctls, and Kubelet Settings\) Some of the nastiest EKS incidents are**node\-local**\. The cluster looks “fine”, pods are “Running”, but traffic becomes unreliable, latency spikes, or connections fail randomly\. These issues often come from: - **conntrack exhaustion** - **ephemeral port exhaustion** - **kernel / sysctl defaults not sized for your traffic** - **kubelet behaviour under pressure** - **bad QoS due to incorrect requests/limits** #### 1\.11\.1 Conntrack Exhaustion \(classic “random networking failures”\) **What it looks like** - intermittent timeouts to upstream services - random 5xx at ingress / Envoy / Nginx - “connection reset”, “i/o timeout”, “no route to host” type errors - affects specific nodes more than others - spikes during traffic bursts or connection\-heavy workloads **Why it happens**Linux conntrack tracks NAT and connection state\. On busy nodes \(especially with L7 proxies, service meshes, high churn, short\-lived connections\), conntrack tables fill up and the kernel starts dropping new connections\. **Quick checks \(from Kubernetes side\)** 1. Identify if failures correlate to a node: ``` kubectl get pods -A -o wide | grep <node-name> kubectl describe node <node-name> ``` 1. Check node\-level kernel counters \(best effort\) If you have node access \(SSM/SSH\): ``` sudo sysctl net.netfilter.nf_conntrack_count sudo sysctl net.netfilter.nf_conntrack_max dmesg | egrep -i "conntrack|nf_conntrack" ``` **Strong indicators** - `nf\_conntrack\_count`close to`nf\_conntrack\_max` - kernel logs mentioning conntrack table full / dropped packets **Fix patterns** - Increase conntrack max \(sysctl\) - Reduce connection churn \(keep\-alives, pooling\) - Spread load \(more nodes / better pod distribution\) - Ensure nodes aren’t overloaded with too many L7\-heavy pods --- #### 1\.11\.2 Ephemeral Port Exhaustion \(the sneaky cousin\) **What it looks like** - outbound calls failing from a node under burst load - retries make it worse - symptoms disappear when traffic drops **Quick checks**On node: ``` cat /proc/sys/net/ipv4/ip_local_port_range ss -s ss -ant state time-wait | wc -l ``` **Fix patterns** - widen ephemeral port range - reduce TIME\_WAIT pressure \(carefully\) - enable connection reuse/keepalive at clients and proxies --- ### 1\.12 Evidence Collection Automation When production is broken, evidence disappears fast\. Capture it first, debug second\. **Quick evidence collection script** ``` #!/bin/bash # Save as: collect-evidence.sh TIMESTAMP=$(date +%Y%m%d-%H%M%S) EVIDENCE_DIR="evidence-${TIMESTAMP}" mkdir -p "$EVIDENCE_DIR" echo "Collecting evidence to $EVIDENCE_DIR..." # Cluster overview kubectl get nodes -o wide > "$EVIDENCE_DIR/nodes.txt" kubectl get pods -A -o wide > "$EVIDENCE_DIR/pods-all.txt" kubectl get events -A --sort-by=.lastTimestamp > "$EVIDENCE_DIR/events-all.txt" # Tier-0 components kubectl -n kube-system get pods -o wide > "$EVIDENCE_DIR/kube-system-pods.txt" kubectl -n kube-system describe pods > "$EVIDENCE_DIR/kube-system-describe.txt" # Recent events (last 200) kubectl get events -A --sort-by=.lastTimestamp | tail -n 200 > "$EVIDENCE_DIR/events-recent.txt" # Unhealthy pods kubectl get pods -A --field-selector=status.phase!=Running > "$EVIDENCE_DIR/unhealthy-pods.txt" # Node conditions kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.conditions[?(@.type=="Ready")].status}{"\t"}{.status.conditions[?(@.type=="MemoryPressure")].status}{"\t"}{.status.conditions[?(@.type=="DiskPressure")].status}{"\n"}{end}' > "$EVIDENCE_DIR/node-conditions.txt" echo "Evidence collected in $EVIDENCE_DIR" echo "Attach this directory to your incident ticket" ``` **What to capture for specific failure types** **DNS/CoreDNS issues** ``` kubectl -n kube-system logs -l k8s-app=kube-dns --tail=500 > coredns-logs.txt kubectl -n kube-system get pods -l k8s-app=kube-dns -o yaml > coredns-pods.yaml ``` **CNI/Networking issues** ``` kubectl -n kube-system logs -l k8s-app=aws-node --tail=500 > aws-node-logs.txt kubectl get pods -A -o wide | grep -E "ContainerCreating|Pending" > stuck-pods.txt ``` **Node\-specific issues** ``` # Replace NODE_NAME with actual node kubectl describe node NODE_NAME > node-describe.txt kubectl get pods -A -o wide | grep NODE_NAME > node-pods.txt ``` --- ### 1\.13 Common False Positives These look like EKS issues but usually aren’t: #### “Kubernetes is slow” \(but it’s not\) **Symptoms:** - kubectl commands are slow - Deployments take forever - “Everything is sluggish” **Usually actually:** - Your laptop’s VPN/network to cluster - AWS API throttling \(too many concurrent kubectl users\) - Your kubeconfig pointing to wrong cluster/region **Quick check:** ``` time kubectl get nodes # Should complete in <2 seconds for healthy cluster ``` #### “Pods keep restarting” \(but EKS is fine\) **Symptoms:** - High restart count - CrashLoopBackOff - “Kubernetes keeps killing my app” **Usually actually:** - Application bugs \(not Kubernetes bugs\) - Incorrect liveness probe configuration - Resource limits too low \(OOMKilled\) - Missing dependencies \(DB, Redis, etc\.\) **Quick check:** ``` kubectl describe pod POD_NAME # Look at "Last State" and "Reason" # OOMKilled = memory limit too low # Error = application crash ``` #### “Service discovery is broken” \(but DNS is fine\) **Symptoms:** - Services can’t reach each other - “Connection refused” errors - “Name resolution failures” **Usually actually:** - Wrong service name/namespace in application config - Application listening on wrong port - Readiness probe failing \(pod not ready to receive traffic\) - Network policies blocking traffic **Quick check:** ``` # Test DNS resolution from inside a pod kubectl exec -it POD_NAME -- nslookup SERVICE_NAME.NAMESPACE.svc.cluster.local # Test if service endpoints exist kubectl get endpoints SERVICE_NAME ``` --- ### What you should know by now After running through the above, you should be able to answer these with evidence: 1. **What is broken, and what is not?**- One pod vs one workload vs one namespace vs one node group vs entire cluster 2. **Where is the failure domain?**- **Workload\-level**\(bad rollout, probe failures, config/secret issues\) - **Cluster\-level**\(kube\-system degradation, DNS, CNI, storage controller issues\) - **AWS integration layer**\(VPC CNI / ENI/IP limits, ALB/NLB behaviour, EBS attach/detach\) - **Node\-level**\(resource pressure, kubelet instability, kernel/network issues\) 3. **What category does this incident fall into?**- Scheduling/capacity \(`FailedScheduling`, Pending pods\) - Networking/CNI \(pod sandbox failures, IP exhaustion, random timeouts\) - DNS/CoreDNS \(service discovery failures\) - Storage/EBS CSI \(FailedMount, volume attach issues\) - Control plane/API issues \(timeouts, throttling, admission webhook failures\) 4. **Is this node\-specific “kernel pain” or cluster\-wide?**- Conntrack exhaustion / ephemeral port pressure - Mis\-sized sysctls - Kubelet eviction behaviour under pressure - Incorrect QoS class due to bad requests/limits 5. **What is the blast radius and what’s the next safe action?**- Can you isolate by draining/cordoning nodes? - Should you pause a rollout? - Should you scale out node groups / reduce pressure? - Or do you need to stop and fix platform components first? 6. **What evidence did you capture before making changes?**- Events, node state, pod distribution, and logs that will make the RCA real \(and not guesswork\) If you can’t answer these after Section 1, don’t start “random fixes”\. You need more signal \(metrics, kube\-system logs, AWS\-side telemetry\) before touching production\. --- ## 2\. EKS Tier\-0 components \(what must stay healthy\) EKS gives you a managed control plane, but your workloads still depend on a small set of platform\-critical components\. If any of these degrade, the cluster looks partially alive while production is effectively down\. Below: what they do, how they fail, what you’ll see, and what to check first\. --- ### 2\.1 EKS Control Plane \(Managed, but not magic\) **What it includes** - Kubernetes API server - etcd \(managed\) - controller\-manager \(managed\) - scheduler \(managed\) **Common symptoms when control plane is unhealthy** - `kubectl`commands are slow / timing out - deployments take forever to apply - controllers lag \(HPA doesn’t scale, pods don’t reschedule\) - random “context deadline exceeded” errors **First checks** ``` kubectl get --raw='/readyz?verbose' kubectl get --raw='/livez?verbose' kubectl get events -A --sort-by=.lastTimestamp | tail -n 50 ``` **Pragmatic note**Even if the control plane is “managed”, you still need: - sane API usage \(avoid thundering herds from controllers/tools\) - sane webhook behaviour \(one broken webhook can block deployments\) --- ### 2\.2 CoreDNS \(Cluster DNS\) **Why it’s Tier\-0**If DNS breaks, your apps don’t “partially degrade”\. They fail in confusing ways: - timeouts - connection errors - retries that amplify load **Symptoms** - services can’t resolve \(`\*\.svc\.cluster\.local`\) - random failures between pods - sudden spike in upstream request errors **First checks** ``` kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide kubectl -n kube-system logs -l k8s-app=kube-dns --tail=200 kubectl -n kube-system describe deploy coredns ``` **Common root causes** - CoreDNS under\-provisioned \(CPU/mem\) - upstream resolver issues - network problems to kube\-dns service IP - node\-local DNS/cache behaviour \(if enabled\) --- ### 2\.3 AWS VPC CNI \(`aws\-node`\) — Networking Foundation **Why it’s Tier\-0**This is what gives pods IPs and makes pod networking real\. If this is unhealthy, pods won’t start or won’t communicate reliably\. **Symptoms** - pods stuck in`ContainerCreating` - `FailedCreatePodSandBox` - sudden surge of Pending pods due to no IPs - node\-specific network failures **First checks** ``` kubectl -n kube-system get pods -l k8s-app=aws-node -o wide kubectl -n kube-system logs -l k8s-app=aws-node --tail=200 kubectl -n kube-system describe ds aws-node ``` **Typical EKS causes** - subnet IP exhaustion - ENI limits / pod density limits - prefix delegation mis\-sizing - conntrack pressure \(often shows up as “network flaky”\) --- ### 2\.4 kube\-proxy \(Service Routing\) **Why it’s Tier\-0**Even if pods are healthy, service routing can break: - ClusterIP routing issues - weird partial connectivity problems **First checks** ``` kubectl -n kube-system get pods -l k8s-app=kube-proxy -o wide kubectl -n kube-system logs -l k8s-app=kube-proxy --tail=200 ``` **Pragmatic note**If you’re using an eBPF dataplane instead of kube\-proxy, document it explicitly\. Debug steps change\. --- ### 2\.5 AWS Load Balancer Controller \(ALB/NLB Integration\) **Why it’s Tier\-0**This is the bridge between Kubernetes ingress/service objects and AWS load balancers\. If it breaks, external traffic fails even if the cluster is healthy\. **Symptoms** - Ingress doesn’t provision - Target groups empty / unhealthy - external traffic 4xx/5xx - “it works inside the cluster but not from the internet” **First checks** ``` kubectl -n kube-system get deploy | grep -i load-balancer kubectl -n kube-system logs deploy/aws-load-balancer-controller --tail=200 kubectl get ingress -A kubectl describe ingress -n <ns> <name> ``` **Common causes** - IAM permissions issues - security group rules wrong - subnet tagging wrong - controller version mismatch during upgrades #### 2\.5\.1 NLB Idle Timeout \+ Keep\-Alive \(Silent Connection Kill\) When you use an**AWS Network Load Balancer \(NLB\)**, remember this: - NLB is**L4**, not L7\. - It does**not**create a separate application\-layer connection like an ALB does\. - It tracks TCP/UDP flows internally so it can route packets correctly\. - If a connection is**idle for ~350 seconds**, NLB will forget it\. - After that, if the client or server tries to send more data on that “old” connection, the NLB can respond with a**TCP RST**\. **What it looks like** - random`connection reset by peer` - intermittent failures for long\-lived but mostly\-idle connections - higher failure rates on low\-traffic tenants or long polling style traffic - hard\-to\-reproduce “only happens sometimes” reports **Why it happens**Your application \(or client\) assumes the TCP connection is still valid because it was never explicitly closed\. But the NLB has expired the idle flow state, so the next packet hits a dead path and gets reset\. **Fix**Enable**TCP keep\-alives**so the connection never goes idle long enough to be forgotten\. You generally need: - keep\-alives enabled on the**server listener socket** - keep\-alives enabled on the**client side**too \(if you control it\) **Pragmatic guidance** - If you’re running connection\-heavy services behind NLB \(gRPC, streaming, long\-lived HTTP/1\.1 keep\-alive, custom TCP protocols\), treat keep\-alive tuning as a**production requirement**, not an optimization\. - If you can’t control the client, you may need to:- reduce server\-side idle timeouts - implement app\-level heartbeats - or prefer ALB where L7 behaviour is needed #### 2\.5\.2 NAT Idle Timeout \+ Keep\-Alive \(Egress Connection Resets\) When workloads in EKS talk to services on the public internet, traffic often goes through a**NAT device**\(commonly AWS NAT Gateway\)\. NAT devices track connection state, and they will**forget idle connections**after a timeout\. For example,**AWS NAT Gateway has an idle timeout of ~350 seconds**\. After that, the NAT forgets the flow\. If the client or server tries to send traffic on that old connection, it can result in a**TCP RST**\. **What it looks like** - random outbound`connection reset by peer` - flaky third\-party API calls \(only under low traffic / idle periods\) - long\-lived connections that “randomly die” - retries sometimes help, sometimes amplify load **Why it happens**Your application thinks it still has a valid TCP connection\. The NAT has expired the mapping due to idleness\. The next packet is treated as invalid state and gets reset\. **Fix: enable TCP keep\-alives** The simplest fix is to ensure connections don’t remain idle long enough to be forgotten\. **Options** 1. **Enable TCP keep\-alives in the application / proxy**- If your proxy supports keepalive tuning, configure it\. - Example: Envoy has TCP keepalive configuration support\. 2. **If the application/proxy doesn’t support it**- Consider enabling it transparently using`LD\_PRELOAD` - Tools like`libsetsockopt`can be used to apply`setsockopt\(\)`defaults without changing application code **Pragmatic guidance** - Prefer fixing this at the proxy layer \(Envoy / Nginx / HAProxy\) where possible\. - If you use`LD\_PRELOAD`, treat it as an engineering workaround:- document it - test it under load - make it part of your base image / runtime standard - expect debugging complexity later **Monitor it \(otherwise you’ll rediscover it during incidents\)** If you use AWS NAT Gateway, monitor the metric: - **IdleTimeoutCount** A rising IdleTimeoutCount is a strong indicator of: - too many idle\-but\-long\-lived connections - missing keepalive settings - workload patterns that need pooling / reuse / heartbeat --- ### 2\.6 EBS CSI Driver \(Stateful Workloads Depend on It\) **Why it’s Tier\-0**If you run StatefulSets with EBS, this is not optional\. **Symptoms** - pods stuck in`ContainerCreating` - volume attach/detach timeouts - `FailedMount`events - reschedules fail across AZs **First checks** ``` kubectl -n kube-system get pods | grep ebs kubectl -n kube-system logs deploy/ebs-csi-controller --tail=200 kubectl get pvc -A kubectl describe pod -n <ns> <pod> ``` --- ### 2\.7 Metrics Server \(Scaling \+ Visibility\) **Why it matters**Not Tier\-0 for*serving traffic*, but Tier\-0 for*operating sanely*\. Without it: - HPA breaks - `kubectl top`is useless - you lose quick visibility into node/pod pressure **First checks** ``` kubectl -n kube-system get deploy metrics-server kubectl -n kube-system logs deploy/metrics-server --tail=200 kubectl top nodes kubectl top pods -A | head ``` --- ### 2\.8 EKS Addon Compatibility \(Silent Failure Generator\) EKS upgrades are rarely just “upgrade Kubernetes”\. You’re upgrading an ecosystem: - CoreDNS - kube\-proxy - VPC CNI - CSI drivers - LB controller **Rule**If you don’t track addon versions and compatibility, you will eventually debug a failure caused by version skew\. --- ### 2\.9 AWS VPC / EC2 Network Limits \(The Invisible Ceiling\) In AWS, network performance is not “infinite until it breaks”\. It is governed by a set of**hard limits**at the VPC/EC2 layer\. Some of these limits are documented, many are not\. When you hit them, AWS usually does not fail loudly — it fails as**latency, timeouts, dropped packets, and random connection resets**\. In EKS, this becomes easier to trigger because Kubernetes**binpacks many workloads onto one EC2 instance**, which means: - one noisy pod can consume a node\-level network limit - every other pod on the node suffers - symptoms look like “the app is slow” even though the app is fine This section exists so we stop blaming applications for AWS network ceilings\. #### 2\.9\.1 Why this matters more in EKS \(binpacking amplifies limits\) In a VM\-per\-service world, a single service hitting a network limit impacts itself\. In Kubernetes, a single node can run: - dozens/hundreds of pods - shared proxies \(Envoy/Nginx\) - CoreDNS - DaemonSets Everything shares the same node\-level network limits\. The result is predictable: > A single workload can push the node over a limit and make unrelated services time out\. #### 2\.9\.2 Real\-world failure mode: DNS lookups clustering onto CoreDNS nodes By default, pods resolve DNS through CoreDNS: **Traffic path** ``` application pod -> kube-dns service (CoreDNS pods) -> EC2 link-local resolver ``` Only a small number of CoreDNS pods typically run in a cluster, which means: - DNS traffic concentrates onto a few nodes \(where CoreDNS pods are scheduled\) - those EC2 instances can hit**link\-local limits** - the cluster sees “DNS is flaky” even though most nodes are fine This is one of the easiest ways to create a cluster\-wide incident with no obvious “broken component”\. #### 2\.9\.3 Link\-local traffic limits \(169\.254\.x\.x\) — easy to hit, hard to debug Each EC2 instance exposes local services via link\-local addresses \(example:`169\.254\.169\.254`\)\. These are used for: - instance metadata - temporary IAM credentials - time sync and other local services - DNS resolution via the VPC resolver path These endpoints have limits\. If you breach them, traffic gets rate\-limited or dropped, and the failures are messy: - timeouts - slow DNS - slow credential refresh - sporadic errors that look unrelated **Pragmatic rule**Treat link\-local as a**shared, rate\-limited dependency**\. #### 2\.9\.4 DNS query limits \(VPC resolver\) — the 1024 packets/sec trap Each EC2 instance has a hard cap on DNS traffic to the VPC resolver\. As of this writing, it’s effectively capped at: - **1024 packets per second***\(packets, not queries\)* This distinction matters: - A “DNS query” is not always one packet\. - With UDP, you usually pay at least:- 1 packet request \+ 1 packet response →**2 packets per query** - That means you might only get**~512 queries/sec**in the simplest case\. - With larger responses, retries, TCP fallback, or DNSSEC, it gets worse\. In EKS, hundreds of pods on a node share this limit\. It’s trivial to breach it\. **What it looks like** - intermittent DNS resolution timeouts - cascading app failures \(everything depends on DNS\) - retries amplify the packet rate and make it worse **Mitigation** - cache DNS aggressively \(application\-level where possible\) - consider node\-local DNS caching \(NodeLocal DNSCache / dnsmasq style\) - keep CoreDNS well distributed across nodes/AZs #### 2\.9\.5 Security Group connection tracking limits \(stateful firewall limits\) Security Groups are stateful\. That means connection tracking happens, and there is a finite limit to how many concurrent connections an instance can sustain\. Important details: - limits vary by instance type - some limits are not clearly documented - when you hit them, symptoms look like:- new TCP connections fail - connection establishment stalls - timeouts and latency spikes **Where this hurts in EKS** - reverse proxies handling lots of traffic - long\-lived connections \(websocket, streaming, gRPC\) - high churn connection patterns \(bad client behaviour, retries, load tests\) **Pragmatic guidance** - long\-lived connections are fine, but you must design for them:- keepalive tuning - connection pooling - horizontal scaling - avoid concentrating all traffic on a single node #### 2\.9\.6 What to monitor \(and alert on\) CloudWatch is not enough for this class of failures\. To monitor these limits properly, you need**node\-level network driver metrics**\. AWS ENA exposes useful counters on each EC2 instance that are: - not always available in CloudWatch by default - best collected by scraping from the node and shipping to your monitoring system **Action item**Run a node\-level metrics collector \(DaemonSet or host agent\) that scrapes ENA\-related counters and publishes them to Prometheus / your metrics pipeline\. --- ### 2\.10 What to do when a Tier\-0 component is unhealthy \(EKS Incident Playbook\) Tier\-0 failures are not limited to`kube\-system`\. In EKS, Tier\-0 also includes AWS networking primitives that your cluster depends on: - **NLB / ALB**\(ingress and L4/L7 connectivity\) - **NAT Gateway**\(egress to internet / third\-party dependencies\) - **Security Group connection tracking**\(stateful connection ceilings\) When any of these degrade, application symptoms become misleading\. Your job is to stabilize the platform and the AWS networking layer first\. #### 2\.10\.1 Step 0 — Stop making the incident worse **Do NOT** - restart random deployments - roll out unrelated changes - run scripts that spam the Kubernetes API - scale CoreDNS / proxies blindly without checking node headroom **Do** - pause ongoing rollouts if they’re increasing churn - capture evidence before it disappears **Capture evidence \(minimum\)** ``` kubectl get nodes -o wide > nodes.txt kubectl get pods -A -o wide > pods.txt kubectl get events -A --sort-by=.lastTimestamp | tail -n 200 > events.txt ``` #### 2\.10\.2 Step 1 — Confirm blast radius \(cluster vs node\-group vs AZ vs edge\) Tier\-0 failures often appear “global” but aren’t\. Quickly classify: - **Cluster\-wide**: kube\-system pods unhealthy across nodes - **Node\-group/AZ\-specific**: only one pool or AZ shows issues - **Edge\-only**: internal works, external traffic fails \(NLB/ALB\) - **Egress\-only**: internal works, outbound calls fail \(NAT\) **Quick checks** ``` kubectl get nodes kubectl get pods -n kube-system -o wide kubectl get pods -A -o wide | head -n 50 ``` #### 2\.10\.3 Step 2 — Identify the failing Tier\-0 dependency \(K8s \+ AWS\) **A\) CoreDNS unhealthy → DNS failures everywhere** **Checks** ``` kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide kubectl -n kube-system logs -l k8s-app=kube-dns --tail=200 ``` **Safe actions** - scale CoreDNS only if nodes have headroom - spread CoreDNS pods \(avoid hotspot nodes\) - reduce DNS pressure \(cache / fix runaway clients\) **B\) VPC CNI \(`aws\-node`\) unhealthy → pod networking breaks** **Checks** ``` kubectl -n kube-system get pods -l k8s-app=aws-node -o wide kubectl -n kube-system logs -l k8s-app=aws-node --tail=200 ``` **Safe actions** - cordon impacted nodes - scale out node groups if out of IPs / pod density - verify subnet IP capacity / prefix delegation **C\) NAT idle timeout / keepalive mismatch → random outbound resets \(egress path\)** Same 350s idle timeout as NLB, but on the egress path\. See[Section 2\.5\.2](https://samof76.space/kubernetes-in-anger.html#252-nat-idle-timeout-keep-alive-egress-connection-resets)for full detail\. **Safe actions** - enable TCP keep\-alives in app/proxy - monitor NAT`IdleTimeoutCount` #### 2\.10\.4 Step 3 — Contain blast radius \(prevent spread\) Once you know the failing Tier\-0 dependency: **Containment options** - cordon nodes to stop new scheduling: - drain nodes only when safe: ``` kubectl drain <node> --ignore-daemonsets --delete-emptydir-data ``` - scale out node groups if the failure is capacity\-related \(IP/CPU/mem/conntrack\) **Key principle**Containment \> churn\. Avoid actions that increase retries and reconnections\. #### 2\.10\.5 Step 4 — Restore Tier\-0 health \(stabilize, then recover\) Stabilize in this order: 1. Control plane responsiveness \(kubectl/API sanity\) 2. kube\-system essentials \(CoreDNS, aws\-node, kube\-proxy\) 3. Storage controllers \(EBS CSI if stateful workloads exist\) 4. Ingress/egress path \(NLB/NAT behaviours, keepalives, target health\) 5. Workloads Only after Tier\-0 is stable should you restart/roll workloads\. #### 2\.10\.6 Step 5 — Validate recovery \(don’t declare victory early\) Recovery means: - DNS stable - new pods schedule and start - ingress traffic healthy - egress stable \(no NAT idle reset spikes\) - error rate \+ tail latency back to baseline **Quick checks** ``` kubectl get nodes kubectl -n kube-system get pods -o wide kubectl get pods -A --field-selector=status.phase!=Running | head ``` #### 2\.10\.7 Post\-incident hardening \(mandatory follow\-up\) Every Tier\-0 incident must produce: - alerts \(CoreDNS, aws\-node, EBS CSI, LB controller\) - AWS networking alerts \(NAT`IdleTimeoutCount`, node`conntrack\_allowance\_exceeded`\) - guardrails:- CoreDNS spread constraints - DNS caching strategy - keepalive defaults for ingress/egress - instance type sizing for connection\-heavy services --- ## 3\. Networking This is where most “mysterious” production failures actually live\. Unlike compute or storage failures that fail loudly, networking degrades gradually and inconsistently\. A connection works 95% of the time\. DNS resolves “most of the time”\. Latency spikes “only under load”\. Below: the EKS networking stack and how to debug it systematically when things go sideways\. --- ### 3\.1 EKS Networking Stack \(What Can Break and Where\) Understanding the layers helps you debug faster: ``` [Pod A] ↓ (veth pair) [Node's root netns] ↓ (AWS VPC CNI / ENI) [AWS VPC] ↓ (routing, security groups, NACLs) [Target: Pod B / Service / Internet] ``` **Each layer can fail differently:** - **Pod network namespace**: wrong routes, missing interfaces - **Node networking**: CNI plugin issues, IP exhaustion, conntrack - **VPC layer**: security groups, routing tables, subnet capacity - **AWS services**: NLB/ALB behavior, NAT timeouts, DNS resolver limits --- ### 3\.2 AWS VPC CNI Deep Dive \(The Foundation\) The AWS VPC CNI is what makes “pod gets a real VPC IP” work\. When it breaks, symptoms are confusing because pods might start but not communicate, or communication works sometimes but not others\. #### 3\.2\.1 How AWS VPC CNI Works \(Simplified\) 1. **ENI allocation**: Each node gets multiple ENIs \(network interfaces\) 2. **IP allocation**: Each ENI gets either:- Multiple secondary IPs \(legacy mode\) - IP prefixes \(/28 blocks\) when prefix delegation is enabled 3. **Pod assignment**: Each pod gets one IP from the available pool 4. **Routing**: Node routes traffic between pod netns and ENI Pod density isn’t just CPU/memory limited — it’s bounded by ENI limits and either IP\-per\-ENI limits \(legacy\) or prefix allocation limits \(with prefix delegation\)\. **Prefix delegation benefits:** - Dramatically increases pod density \(from ~10\-250 pods per node to ~110\-750\+ pods\) - Reduces ENI pressure on larger instance types - More efficient IP utilization #### 3\.2\.2 Common CNI Failure Modes **A\) IP Exhaustion \(Pods stuck in Pending\)** **Symptoms:** ``` kubectl get pods -A | grep Pending kubectl describe pod <pod> | grep -i "failed to allocate" ``` **Root causes:** - Subnet out of IPs - Node hit max pods per instance type - Prefix delegation misconfigured or not enabled - ENI limits reached without prefix delegation **Quick diagnosis:** ``` # Check available IPs in subnet aws ec2 describe-subnets --subnet-ids <subnet-id> # Check pod density limits kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.allocatable.pods}{"\n"}{end}' # Check CNI logs kubectl -n kube-system logs -l k8s-app=aws-node --tail=100 ``` **B\) ENI Attachment Failures** **Symptoms:** - New nodes can’t schedule pods - `FailedCreatePodSandBox`errors - CNI timeouts during pod creation **Quick diagnosis:** ``` kubectl -n kube-system logs -l k8s-app=aws-node | grep -i "eni\|attach\|interface" ``` **C\) Cross\-AZ Communication Issues** **Symptoms:** - Pods in different AZs can’t reach each other - Intermittent timeouts between services - Works within AZ, fails across AZ **Root causes:** - Route table misconfigurations - Security group rules - NACLs blocking cross\-AZ traffic #### 3\.2\.3 CNI Configuration Tuning **Key parameters to understand:** ``` # Check current CNI configuration kubectl -n kube-system describe daemonset aws-node ``` **Important environment variables:** - `ENABLE\_PREFIX\_DELEGATION`: Increases pod density - `WARM\_ENI\_TARGET`: Pre\-allocates ENIs for faster pod startup - `WARM\_IP\_TARGET`: Pre\-allocates IPs for faster pod startup - `MAX\_ENI`: Limits ENI usage per node **Production tuning example:** ``` env: - name: ENABLE_PREFIX_DELEGATION value: "true" - name: WARM_PREFIX_TARGET value: "1" # Keep 1 prefix warm (16 IPs) - name: WARM_IP_TARGET value: "3" # Keep 3 individual IPs warm - name: MAX_ENI value: "10" # Limit ENI usage if needed - name: AWS_VPC_K8S_CNI_EXTERNALSNAT value: "true" # Preserve pod IPs for cross-VPC communication ``` **Understanding prefix vs IP mode:** - **Legacy \(IP mode\)**: Each ENI gets ~15\-50 secondary IPs depending on instance type - **Prefix mode**: Each ENI gets /28 prefixes \(16 IPs each\), dramatically increasing density - **Mixed mode**: Can use both prefixes and individual IPs on same ENI **External SNAT configuration:** - **Default \(false\)**: Pod traffic to external destinations gets SNATed to node IP - **External SNAT \(true\)**: Pod retains its VPC IP when talking to external destinations - **Critical for**: Cross\-VPC communication, VPC peering, Transit Gateway scenarios - **Why it matters**: Allows destination to see actual pod IP instead of node IP for logging, security groups, etc\. --- ### 3\.3 DNS and Service Discovery \(CoreDNS Operational Reality\) DNS failures in Kubernetes don’t just break service discovery—they cascade into timeouts, retries, and connection pool exhaustion that can take down entire applications\. #### 3\.3\.1 CoreDNS Under Load \(When DNS Becomes the Bottleneck\) **Common failure pattern:** 1. Application makes many DNS queries \(poor caching\) 2. CoreDNS pods hit CPU/memory limits 3. DNS queries start timing out 4. Applications retry aggressively 5. DNS load increases, making timeouts worse 6. Cascade failure across services **Symptoms:** ``` # DNS timeouts in application logs kubectl logs <app-pod> | grep -i "dns\|resolve\|timeout" # CoreDNS resource pressure kubectl -n kube-system top pods -l k8s-app=kube-dns # DNS query patterns kubectl -n kube-system logs -l k8s-app=kube-dns | grep -E "NXDOMAIN|timeout|error" ``` #### 3\.3\.2 DNS Query Patterns That Kill Performance **Bad patterns:** - No DNS caching in applications - Querying external domains from every pod - Short TTL on frequently accessed services - DNS queries in tight loops **Example of problematic application behavior:** ``` # BAD: DNS lookup on every request def make_request(): host = socket.gethostbyname("api.external.com") # DNS lookup every time return requests.get(f"http://{host}/api") # GOOD: Cache DNS resolution dns_cache = {} def make_request(): if "api.external.com" not in dns_cache: dns_cache["api.external.com"] = socket.gethostbyname("api.external.com") host = dns_cache["api.external.com"] return requests.get(f"http://{host}/api") ``` #### 3\.3\.3 CoreDNS Scaling and Distribution **Horizontal scaling:** ``` kubectl -n kube-system scale deployment coredns --replicas=5 ``` **Anti\-affinity to spread CoreDNS pods:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: coredns namespace: kube-system spec: template: spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: k8s-app operator: In values: ["kube-dns"] topologyKey: kubernetes.io/hostname ``` #### 3\.3\.4 NodeLocal DNSCache \(Advanced DNS Optimization\) For clusters with heavy DNS load, NodeLocal DNSCache runs a DNS cache on each node: **Benefits:** - Reduces load on CoreDNS - Improves DNS response times - Reduces DNS\-related network traffic **Trade\-offs:** - Additional complexity - More moving parts to debug - Cache invalidation edge cases **When to consider:** - High DNS query volume \(\>1000 QPS cluster\-wide\) - DNS\-related performance issues - Applications that can’t implement proper DNS caching #### 3\.3\.5 DNS Search Domain Optimization \(ndots Configuration\) **The ndots problem:**By default, Kubernetes sets`ndots:5`in`/etc/resolv\.conf`, causing excessive DNS queries for external domains\. **Default behavior analysis:** ``` # Inside a pod, resolving "google.com" triggers these queries: # 1. google.com.default.svc.cluster.local # 2. google.com.svc.cluster.local # 3. google.com.cluster.local # 4. google.com.us-west-2.compute.internal # 5. google.com.compute.internal # 6. google.com (finally!) ``` **Impact on AWS linklocal limits:** ``` # Each failed query hits 169.254.169.254 (AWS DNS resolver) # With ndots:5, external domains generate 6x DNS traffic # AWS limit: 1024 PPS per instance - easily exceeded in dense clusters ``` **Optimized ndots configuration:** ``` apiVersion: v1 kind: Pod metadata: name: optimized-dns-app spec: dnsPolicy: ClusterFirst dnsConfig: options: - name: ndots value: "1" # Reduce from default 5 to 1 - name: edns0 # Enable DNS extensions containers: - name: app image: nginx ``` **Application\-specific DNS optimization:** ``` # For apps that primarily call external services apiVersion: apps/v1 kind: Deployment metadata: name: external-api-client spec: template: spec: dnsConfig: options: - name: ndots value: "1" containers: - name: app env: - name: EXTERNAL_API_URL value: "https://api.example.com." # Trailing dot = absolute FQDN ``` ### 3\.3\.6 Listen backlog and connection handling These are not DNS topics, but connection\-level tuning is commonly needed alongside DNS optimization when debugging service latency\. **Listen Backlog Configuration for High\-Traffic Services** **Problem:**Default listen backlog \(128\) causes connection drops under bursty load\. **Root cause:**When services receive more concurrent connection attempts than the listen backlog can queue, connections are dropped at the kernel level\. **Solution \- Configure via sysctls:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: high-traffic-service spec: template: spec: securityContext: sysctls: - name: net.core.somaxconn value: "32000" # Increase from default 128 - name: net.ipv4.ip_local_port_range value: "1024 64000" # Expand ephemeral port range containers: - name: app # Application configuration ``` **Monitor listen backlog with sidecar pattern:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: monitored-service spec: template: spec: containers: - name: app image: my-app:latest # Main application container - name: node-exporter image: prom/node-exporter:latest args: - --web.listen-address=0.0.0.0:9100 - --collector.disable-defaults - --web.disable-exporter-metrics - --collector.conntrack - --collector.filefd - --collector.netstat - --collector.sockstat ports: - containerPort: 9100 name: metrics securityContext: readOnlyRootFilesystem: true runAsNonRoot: true allowPrivilegeEscalation: false capabilities: drop: ["all"] ``` **Prometheus alerts for listen backlog issues:** ``` groups: - name: listen-backlog rules: - alert: ListenDrops expr: sum by (k8s_cluster_name, pod) (rate(node_netstat_TcpExt_ListenDrops[5m]) > 0) > 5 for: 2m annotations: summary: "Listen drops detected on " description: "Pod is dropping connections due to listen backlog overflow" - alert: ListenOverflows expr: sum by (k8s_cluster_name, pod) (rate(node_netstat_TcpExt_ListenOverflows[5m]) > 0) > 5 for: 2m annotations: summary: "Listen overflows detected on " description: "Pod has listen queue overflows - increase somaxconn" ``` **Protection Against Slow Clients** **Problem:**Slow clients can exhaust thread/process pools in request\-per\-thread models\. **Attack vector simulation:** ``` # Simulate slow client sending 10KB slowly (1 byte per second) (echo -e -n 'POST /api HTTP/1.1\r\nHost: example.com\r\nContent-Length: 10000\r\n\r\n'; i=0; while [ $i -lt 10000 ]; do echo -n "a"; sleep 1; i=$((i+1)); done) \ | socat -t 10 - TCP4:service.example.com:80 ``` **Solution \- Reverse proxy with buffering:** ``` apiVersion: v1 kind: ConfigMap metadata: name: nginx-config data: nginx.conf: | events { worker_connections 1024; } http { # Buffer entire request before forwarding to backend proxy_request_buffering on; proxy_buffering on; # Timeout configurations client_header_timeout 10s; # Max time to receive headers client_body_timeout 30s; # Max time to receive body send_timeout 30s; # Max time to send response keepalive_timeout 65s; # Connection idle timeout upstream backend { server app-service:8080; } server { listen 80; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } } --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-proxy spec: template: spec: containers: - name: nginx image: nginx:alpine volumeMounts: - name: config mountPath: /etc/nginx/nginx.conf subPath: nginx.conf volumes: - name: config configMap: name: nginx-config ``` **Envoy configuration for slow client protection:** ``` apiVersion: v1 kind: ConfigMap metadata: name: envoy-config data: envoy.yaml: | static_resources: listeners: - name: listener_0 address: socket_address: address: 0.0.0.0 port_value: 8080 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager request_timeout: 30s stream_idle_timeout: 300s request_headers_timeout: 10s http_filters: - name: envoy.filters.http.buffer typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer max_request_bytes: 1048576 # 1MB buffer - name: envoy.filters.http.router route_config: name: local_route virtual_hosts: - name: backend domains: ["*"] routes: - match: prefix: "/" route: cluster: backend_cluster clusters: - name: backend_cluster connect_timeout: 5s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: backend_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: app-service port_value: 8080 ``` --- ### 3\.4 Service Mesh Networking \(When L7 Proxy Becomes Critical Path\) Service meshes add another networking layer that can fail in EKS\-specific ways\. #### 3\.4\.1 Envoy Sidecar Resource Limits **Common failure:**Envoy sidecar hits CPU/memory limits under load, causing: - Request timeouts - Connection pool exhaustion - Circuit breaker activation **Diagnosis:** ``` # Check sidecar resource usage kubectl top pods --containers | grep envoy # Check Envoy admin interface kubectl exec <pod> -c istio-proxy -- curl localhost:15000/stats | grep -E "cx_|rq_|upstream" ``` **Tuning:** ``` metadata: annotations: sidecar.istio.io/proxyCPU: "100m" sidecar.istio.io/proxyMemory: "128Mi" sidecar.istio.io/proxyCPULimit: "200m" sidecar.istio.io/proxyMemoryLimit: "256Mi" ``` #### 3\.4\.2 mTLS Certificate Rotation Issues **Symptoms:** - Intermittent 503 errors between services - TLS handshake failures - Services work sometimes, fail other times **Diagnosis:** ``` # Check certificate expiration kubectl exec <pod> -c istio-proxy -- openssl s_client -connect <service>:443 -servername <service> < /dev/null 2>/dev/null | openssl x509 -noout -dates # Check Envoy TLS stats kubectl exec <pod> -c istio-proxy -- curl localhost:15000/stats | grep ssl ``` --- ### 3\.5 Load Balancer Integration \(ALB/NLB Operational Patterns\) #### 3\.5\.1 ALB Target Group Health Issues **Common failure pattern:** 1. Pod starts and becomes “Ready” 2. ALB target group shows “unhealthy” 3. Traffic doesn’t reach the pod 4. Application appears to be “not working” **Root causes:** - Health check path misconfigured - Security group rules blocking ALB health checks - Pod readiness probe vs ALB health check mismatch **Diagnosis:** ``` # Check ALB target group health aws elbv2 describe-target-health --target-group-arn <arn> # Check ingress configuration kubectl describe ingress <ingress-name> # Check ALB controller logs kubectl -n kube-system logs deployment/aws-load-balancer-controller ``` **Fix patterns:** ``` apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: alb.ingress.kubernetes.io/healthcheck-path: /health alb.ingress.kubernetes.io/healthcheck-interval-seconds: '30' alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '5' alb.ingress.kubernetes.io/healthy-threshold-count: '2' alb.ingress.kubernetes.io/unhealthy-threshold-count: '3' ``` #### 3\.5\.2 NLB Connection Tracking and Keep\-Alive The NLB idle timeout problem and TCP keepalive fix are covered in detail in[Section 2\.5\.1](https://samof76.space/kubernetes-in-anger.html#251-nlb-idle-timeout-keep-alive-silent-connection-kill)\. This section adds the language\-specific code examples\. **For HTTP clients \(Python\):** ``` import requests from requests.adapters import HTTPAdapter session = requests.Session() adapter = HTTPAdapter( socket_options=[ (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 300), (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 5), (socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5), ] ) session.mount("http://", adapter) session.mount("https://", adapter) ``` **For gRPC \(Python\):** ``` import grpc options = [ ('grpc.keepalive_time_ms', 300000), ('grpc.keepalive_timeout_ms', 5000), ('grpc.keepalive_permit_without_calls', True), ('grpc.http2.max_pings_without_data', 0), ] channel = grpc.insecure_channel('service:50051', options=options) ``` --- ### 3\.6 Network Policies \(Micro\-segmentation That Actually Works\) Network policies in EKS require a CNI that supports them \(like Calico\)\. When they’re misconfigured, they create “works sometimes” failures that are hard to debug\. #### 3\.6\.1 Common Network Policy Mistakes **Mistake 1: Blocking DNS** ``` # BAD: This blocks DNS resolution apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-all spec: podSelector: {} policyTypes: - Ingress - Egress # No egress rules = no DNS ``` **Fix: Always allow DNS** ``` apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-dns spec: podSelector: {} policyTypes: - Egress egress: - to: [] ports: - protocol: UDP port: 53 - protocol: TCP port: 53 ``` **Mistake 2: Forgetting about health checks** ``` # Need to allow kubelet health checks apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-health-checks spec: podSelector: {} policyTypes: - Ingress ingress: - from: [] ports: - protocol: TCP port: 8080 # Your health check port ``` #### 3\.6\.2 Debugging Network Policy Issues **Test connectivity between pods:** ``` # From source pod to target pod kubectl exec -it <source-pod> -- nc -zv <target-pod-ip> <port> # Test DNS resolution kubectl exec -it <pod> -- nslookup kubernetes.default.svc.cluster.local # Check if network policies are applied kubectl get networkpolicy -A kubectl describe networkpolicy <policy-name> ``` **Calico\-specific debugging:** ``` # Check Calico policy status kubectl exec -n kube-system <calico-node-pod> -- calicoctl get policy -o wide # Check Calico logs kubectl -n kube-system logs -l k8s-app=calico-node ``` --- ### 3\.7 Cross\-AZ Networking \(Latency and Cost Optimization\) #### 3\.7\.1 Understanding Cross\-AZ Traffic Patterns **Network latency between AZs:** - Intra\-AZ: ~0\.1\-0\.5ms - Inter\-AZ: ~1\-2ms - Cross\-region: 20\-100ms\+ **Cost implications:** - Intra\-AZ traffic: Free - Inter\-AZ traffic: $0\.01/GB \(as of 2024\) - Cross\-region: $0\.02/GB\+ #### 3\.7\.2 Topology Spread Constraints for Network Optimization **Spread pods across AZs for availability:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: web-app spec: replicas: 6 template: spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: web-app ``` **Keep related services in same AZ:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: cache-service spec: template: spec: affinity: podAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: ["web-app"] topologyKey: topology.kubernetes.io/zone ``` --- ### 3\.8 Debugging Network Issues \(Systematic Approach\) #### 3\.8\.1 Layer\-by\-Layer Debugging **Step 1: Pod\-to\-Pod IP connectivity** ``` # Get pod IPs kubectl get pods -o wide # Test basic IP connectivity kubectl exec -it <source-pod> -- ping <target-pod-ip> # Test specific port kubectl exec -it <source-pod> -- nc -zv <target-pod-ip> <port> ``` **Step 2: Service discovery** ``` # Test DNS resolution kubectl exec -it <pod> -- nslookup <service-name>.<namespace>.svc.cluster.local # Test service connectivity kubectl exec -it <pod> -- curl <service-name>.<namespace>.svc.cluster.local:<port> ``` **Step 3: Ingress/Load balancer** ``` # Check ingress status kubectl get ingress kubectl describe ingress <ingress-name> # Test from outside cluster curl -v http://<load-balancer-dns>/health ``` #### 3\.8\.2 Network Debugging Tools **Essential tools to have in debug pods:** ``` apiVersion: v1 kind: Pod metadata: name: network-debug spec: containers: - name: debug image: nicolaka/netshoot command: ["/bin/bash"] args: ["-c", "while true; do sleep 30; done;"] securityContext: capabilities: add: ["NET_ADMIN"] ``` **Useful commands in debug pod:** ``` # Network interface info ip addr show ip route show # DNS debugging dig @8.8.8.8 google.com nslookup kubernetes.default.svc.cluster.local # Port scanning nmap -p 80,443,8080 <target-ip> # Packet capture tcpdump -i any -w /tmp/capture.pcap host <target-ip> # Connection testing nc -zv <host> <port> telnet <host> <port> ``` #### 3\.8\.3 Performance Testing and Monitoring **Network performance testing:** ``` # Bandwidth testing between pods kubectl exec -it <pod1> -- iperf3 -s & kubectl exec -it <pod2> -- iperf3 -c <pod1-ip> # Latency testing kubectl exec -it <pod1> -- ping -c 100 <pod2-ip> ``` **Key metrics to monitor:** - DNS query latency and error rate - Service\-to\-service latency \(P50, P95, P99\) - Network throughput and packet loss - Connection pool utilization - Cross\-AZ traffic volume and cost --- ### 3\.9 Network Security \(Defense in Depth\) #### 3\.9\.1 Security Groups vs Network Policies **Security Groups \(AWS level\):** - Applied at ENI level - Stateful firewall rules - Good for node\-to\-node and external access control **Network Policies \(Kubernetes level\):** - Applied at pod level - More granular control - Good for micro\-segmentation within cluster **Best practice: Use both layers** ``` # Network policy for pod-to-pod apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: web-app-policy spec: podSelector: matchLabels: app: web-app policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: frontend ports: - protocol: TCP port: 8080 egress: - to: - podSelector: matchLabels: app: database ports: - protocol: TCP port: 5432 ``` #### 3\.9\.2 Pod Security Groups \(EKS\-specific\) For fine\-grained security group control at pod level: ``` apiVersion: v1 kind: Pod metadata: name: secure-pod annotations: eks.amazonaws.com/security-groups: sg-12345678,sg-87654321 spec: containers: - name: app image: myapp:latest ``` **When to use pod security groups:** - Need different security rules per workload - Compliance requirements for network isolation - Integration with AWS security tools **Trade\-offs:** - Additional complexity - Potential performance impact - Limited to specific instance types and CNI versions --- ### 3\.10 Network Troubleshooting Runbook #### 3\.10\.1 “Service is unreachable” Runbook **Symptoms:**Application can’t reach another service **Step 1: Verify service exists and has endpoints** ``` kubectl get svc <service-name> kubectl get endpoints <service-name> ``` **Step 2: Test DNS resolution** ``` kubectl exec -it <pod> -- nslookup <service-name>.<namespace>.svc.cluster.local ``` **Step 3: Test direct IP connectivity** ``` kubectl exec -it <pod> -- nc -zv <endpoint-ip> <port> ``` **Step 4: Check network policies** ``` kubectl get networkpolicy -n <namespace> kubectl describe networkpolicy <policy-name> ``` **Step 5: Check security groups \(if using pod security groups\)** ``` aws ec2 describe-security-groups --group-ids <sg-id> ``` #### 3\.10\.2 “DNS is slow/failing” Runbook **Symptoms:**DNS timeouts, slow service discovery **Step 1: Check CoreDNS health** ``` kubectl -n kube-system get pods -l k8s-app=kube-dns kubectl -n kube-system logs -l k8s-app=kube-dns --tail=100 ``` **Step 2: Test DNS from multiple pods** ``` kubectl exec -it <pod1> -- time nslookup kubernetes.default.svc.cluster.local kubectl exec -it <pod2> -- time nslookup kubernetes.default.svc.cluster.local ``` **Step 3: Check DNS query patterns** ``` kubectl -n kube-system logs -l k8s-app=kube-dns | grep -E "NXDOMAIN|timeout" | tail -20 ``` **Step 4: Monitor CoreDNS resource usage** ``` kubectl -n kube-system top pods -l k8s-app=kube-dns ``` **Step 5: Scale CoreDNS if needed** ``` kubectl -n kube-system scale deployment coredns --replicas=<new-count> ``` #### 3\.10\.3 “Load balancer not working” Runbook **Symptoms:**External traffic can’t reach services **Step 1: Check ingress/service status** ``` kubectl get ingress kubectl describe ingress <ingress-name> kubectl get svc <service-name> ``` **Step 2: Check AWS Load Balancer Controller** ``` kubectl -n kube-system logs deployment/aws-load-balancer-controller ``` **Step 3: Verify target group health** ``` aws elbv2 describe-target-health --target-group-arn <arn> ``` **Step 4: Test internal connectivity** ``` kubectl exec -it <debug-pod> -- curl <service-name>:<port>/health ``` **Step 5: Check security group rules** ``` aws ec2 describe-security-groups --group-ids <alb-sg-id> ``` --- ## 4\. Workload identity and security EKS security failures often look like “the application is broken” when the real issue is auth, authorization, or secrets\. Misconfigured IRSA, missing encryption, bad RBAC — these create confusing incidents that send you chasing application bugs that don’t exist\. --- ### 4\.1 IAM Roles for Service Accounts \(IRSA\) \- The Foundation IRSA is how pods get AWS permissions without embedding long\-lived credentials\. When it breaks, applications fail to access AWS services with cryptic permission errors\. #### 4\.1\.1 How IRSA Works \(What Can Break\) ``` [Pod with ServiceAccount] ↓ (projected token volume) [OIDC JWT Token] ↓ (AWS STS AssumeRoleWithWebIdentity) [Temporary AWS Credentials] ↓ (AWS API calls) [AWS Services: S3, RDS, etc.] ``` **Each step can fail:** - ServiceAccount annotation missing/wrong - OIDC provider not configured - IAM role trust policy incorrect - IAM role permissions insufficient - Token projection/mounting issues #### 4\.1\.2 Common IRSA Failure Patterns **A\) “Access Denied” but IAM role looks correct** **Symptoms:** ``` AccessDenied: User: arn:aws:sts::123456789012:assumed-role/eksctl-my-cluster-nodegroup-NodeInstanceRole-XXXXX/i-1234567890abcdef0 is not authorized to perform: s3:GetObject ``` **Root cause:**Pod is using node IAM role instead of IRSA role **Diagnosis:** ``` # Check if ServiceAccount has IRSA annotation kubectl describe sa <service-account-name> # Check if pod is using the ServiceAccount kubectl describe pod <pod-name> | grep "Service Account" # Check if OIDC provider exists aws eks describe-cluster --name <cluster-name> --query "cluster.identity.oidc.issuer" aws iam list-open-id-connect-providers ``` **B\) Token projection failures** **Symptoms:** - Pod starts but AWS calls fail with authentication errors - Missing`/var/run/secrets/eks\.amazonaws\.com/serviceaccount/token` **Diagnosis:** ``` # Check if token is mounted kubectl exec <pod-name> -- ls -la /var/run/secrets/eks.amazonaws.com/serviceaccount/ # Check token content (should be JWT) kubectl exec <pod-name> -- cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token | cut -d. -f2 | base64 -d ``` #### 4\.1\.3 IRSA Setup and Troubleshooting **Correct IRSA setup:** 1. **Create OIDC provider \(one\-time per cluster\):**``` eksctl utils associate-iam-oidc-provider --cluster <cluster-name> --approve ``` 2. **Create IAM role with trust policy:**``` { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::ACCOUNT-ID:oidc-provider/oidc.eks.REGION.amazonaws.com/id/OIDC-ID" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "oidc.eks.REGION.amazonaws.com/id/OIDC-ID:sub": "system:serviceaccount:NAMESPACE:SERVICE-ACCOUNT-NAME", "oidc.eks.REGION.amazonaws.com/id/OIDC-ID:aud": "sts.amazonaws.com" } } } ] } ``` 3. **Annotate ServiceAccount:**``` apiVersion: v1 kind: ServiceAccount metadata: name: my-service-account namespace: my-namespace annotations: eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT-ID:role/my-irsa-role ``` 4. **Use ServiceAccount in pod:**``` apiVersion: v1 kind: Pod metadata: name: my-pod spec: serviceAccountName: my-service-account containers: - name: my-container image: my-app:latest ``` **Validation script:** ``` #!/bin/bash # Test IRSA setup NAMESPACE="my-namespace" SA_NAME="my-service-account" POD_NAME="test-pod" echo "1. Checking ServiceAccount annotation..." kubectl get sa $SA_NAME -n $NAMESPACE -o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}' echo -e "\n2. Checking pod ServiceAccount..." kubectl get pod $POD_NAME -n $NAMESPACE -o jsonpath='{.spec.serviceAccountName}' echo -e "\n3. Checking token mount..." kubectl exec $POD_NAME -n $NAMESPACE -- ls -la /var/run/secrets/eks.amazonaws.com/serviceaccount/ echo -e "\n4. Testing AWS credentials..." kubectl exec $POD_NAME -n $NAMESPACE -- aws sts get-caller-identity ``` --- ### 4\.2 Pod Security Standards \(Replacing PSPs\) Pod Security Policies \(PSPs\) are deprecated\. Pod Security Standards are the replacement, but they work differently and can create new failure modes\. #### 4\.2\.1 Pod Security Standards Levels **Privileged:**No restrictions \(dangerous for production\)**Baseline:**Minimal restrictions, prevents known privilege escalations**Restricted:**Heavily restricted, follows pod hardening best practices #### 4\.2\.2 Common Pod Security Failures **A\) Pods rejected by admission controller** **Symptoms:** ``` Error creating: pods "my-pod" is forbidden: violates PodSecurity "restricted:latest": allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true ``` **Fix patterns:** ``` apiVersion: v1 kind: Pod metadata: name: secure-pod spec: securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 2000 containers: - name: app image: myapp:latest securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1000 capabilities: drop: - ALL ``` **B\) Applications fail due to security restrictions** **Common issues:** - App tries to write to read\-only filesystem - App needs specific capabilities - App runs as root by default **Debugging approach:** ``` # Check pod security context kubectl describe pod <pod-name> | grep -A 20 "Security Context" # Check container security context kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].securityContext}' # Test file system permissions kubectl exec <pod-name> -- touch /tmp/test-write kubectl exec <pod-name> -- id ``` #### 4\.2\.3 Namespace\-Level Pod Security Configuration ``` apiVersion: v1 kind: Namespace metadata: name: production labels: pod-security.kubernetes.io/enforce: restricted pod-security.kubernetes.io/audit: restricted pod-security.kubernetes.io/warn: restricted ``` **Gradual rollout strategy:** 1. Start with`warn`mode to identify violations 2. Add`audit`mode to log violations 3. Finally enable`enforce`mode to block violations --- ### 4\.3 Secrets Management \(Beyond Kubernetes Secrets\) Kubernetes Secrets are base64 encoded, not encrypted at rest by default, and visible to anyone with cluster access\. For production workloads, you need better secrets management\. #### 4\.3\.1 AWS Secrets Manager Integration **Using AWS Load Balancer Controller with Secrets Manager:** ``` apiVersion: v1 kind: Secret metadata: name: db-credentials annotations: aws-load-balancer-controller.k8s.aws/secret-manager: "arn:aws:secretsmanager:region:account:secret:prod/db/credentials" type: Opaque ``` **Using External Secrets Operator:** ``` apiVersion: external-secrets.io/v1beta1 kind: SecretStore metadata: name: aws-secrets-manager spec: provider: aws: service: SecretsManager region: us-west-2 auth: secretRef: accessKeyID: name: awssm-secret key: access-key secretAccessKey: name: awssm-secret key: secret-access-key --- apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: app-secret spec: refreshInterval: 15s secretStoreRef: name: aws-secrets-manager kind: SecretStore target: name: app-secret creationPolicy: Owner data: - secretKey: password remoteRef: key: prod/db/credentials property: password ``` #### 4\.3\.2 Secrets CSI Driver **Mount secrets as volumes:** ``` apiVersion: v1 kind: Pod metadata: name: app-pod spec: serviceAccountName: app-service-account containers: - name: app image: myapp:latest volumeMounts: - name: secrets-store mountPath: "/mnt/secrets" readOnly: true volumes: - name: secrets-store csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: secretProviderClass: "app-secrets" --- apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: app-secrets spec: provider: aws parameters: objects: | - objectName: "prod/db/credentials" objectType: "secretsmanager" jmesPath: - path: "password" objectAlias: "db-password" ``` #### 4\.3\.3 Secrets Rotation and Lifecycle **Automatic rotation with External Secrets:** ``` apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: rotating-secret spec: refreshInterval: 1h # Check for updates every hour secretStoreRef: name: aws-secrets-manager kind: SecretStore target: name: app-secret creationPolicy: Owner template: metadata: annotations: reloader.stakater.com/match: "true" # Trigger pod restart on change ``` **Monitoring secrets rotation:** ``` # Check External Secrets status kubectl get externalsecrets kubectl describe externalsecret <name> # Check secret age kubectl get secrets -o custom-columns=NAME:.metadata.name,AGE:.metadata.creationTimestamp ``` --- ### 4\.4 Network Security \(Security Groups and Network Policies\) #### 4\.4\.1 Security Groups for Pods EKS allows assigning security groups directly to pods for fine\-grained network control: ``` apiVersion: v1 kind: Pod metadata: name: secure-pod annotations: eks.amazonaws.com/security-groups: sg-12345678 spec: containers: - name: app image: myapp:latest ``` **When to use pod security groups:** - Need different network rules per workload - Compliance requirements for network isolation - Integration with AWS security services **Limitations:** - Only works with supported instance types - Requires specific CNI configuration - Can impact performance #### 4\.4\.2 Network Policies for Micro\-segmentation **Default deny all traffic:** ``` apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-all spec: podSelector: {} policyTypes: - Ingress - Egress ``` **Allow specific service communication:** ``` apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: web-to-api spec: podSelector: matchLabels: app: api policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: web ports: - protocol: TCP port: 8080 ``` **Always allow DNS and health checks:** ``` apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-dns-and-health spec: podSelector: {} policyTypes: - Egress - Ingress egress: # Allow DNS - to: [] ports: - protocol: UDP port: 53 - protocol: TCP port: 53 ingress: # Allow health checks from kubelet - from: [] ports: - protocol: TCP port: 8080 # Your health check port ``` --- ### 4\.5 Image Security and Supply Chain #### 4\.5\.1 Image Scanning and Vulnerability Management **ECR image scanning:** ``` # Enable scan on push aws ecr put-image-scanning-configuration --repository-name myapp --image-scanning-configuration scanOnPush=true # Manual scan aws ecr start-image-scan --repository-name myapp --image-id imageTag=latest # Get scan results aws ecr describe-image-scan-findings --repository-name myapp --image-id imageTag=latest ``` **Admission controller for image scanning:** ``` apiVersion: v1 kind: ValidatingAdmissionWebhook metadata: name: image-security-webhook webhooks: - name: image-scan-check clientConfig: service: name: image-security-service namespace: security-system path: "/validate" rules: - operations: ["CREATE", "UPDATE"] apiGroups: [""] apiVersions: ["v1"] resources: ["pods"] ``` #### 4\.5\.2 Image Signing and Verification **Using Cosign for image signing:** ``` # Sign image cosign sign --key cosign.key myregistry/myapp:v1.0.0 # Verify signature cosign verify --key cosign.pub myregistry/myapp:v1.0.0 ``` **Policy enforcement with Gatekeeper:** ``` apiVersion: templates.gatekeeper.sh/v1beta1 kind: ConstraintTemplate metadata: name: requiresignedimages spec: crd: spec: names: kind: RequireSignedImages validation: properties: trustedKeys: type: array items: type: string targets: - target: admission.k8s.gatekeeper.sh rego: | package requiresignedimages violation[{"msg": msg}] { container := input.review.object.spec.containers[_] not is_signed(container.image) msg := sprintf("Image %v is not signed", [container.image]) } is_signed(image) { # Implementation depends on your signing verification logic } ``` --- ### 4\.6 Audit Logging and Compliance #### 4\.6\.1 EKS Audit Logging Configuration **Enable audit logging:** ``` aws eks update-cluster-config \ --name my-cluster \ --logging '{"enable":["api","audit","authenticator","controllerManager","scheduler"]}' ``` **Audit policy for security events:** ``` apiVersion: audit.k8s.io/v1 kind: Policy rules: # Log secret access - level: Metadata resources: - group: "" resources: ["secrets"] # Log RBAC changes - level: RequestResponse resources: - group: "rbac.authorization.k8s.io" resources: ["*"] # Log security context changes - level: Request resources: - group: "" resources: ["pods"] namespaces: ["production"] omitStages: - RequestReceived ``` #### 4\.6\.2 Security Monitoring and Alerting **Key security metrics to monitor:** - Failed authentication attempts - Privilege escalation attempts - Unauthorized secret access - Network policy violations - Image pull failures from untrusted registries **Example Prometheus alerts:** ``` groups: - name: kubernetes-security rules: - alert: UnauthorizedSecretAccess expr: increase(apiserver_audit_total{verb="get",objectRef_resource="secrets",user_username!~"system:.*"}[5m]) > 0 labels: severity: warning annotations: summary: "Unauthorized access to secrets detected" - alert: PrivilegedPodCreated expr: increase(apiserver_audit_total{verb="create",objectRef_resource="pods",requestObject_spec_securityContext_privileged="true"}[5m]) > 0 labels: severity: critical annotations: summary: "Privileged pod created" ``` --- ### 4\.7 Security Incident Response Runbook #### 4\.7\.1 “Pod can’t access AWS services” Runbook **Symptoms:**AWS API calls failing with permission errors **Step 1: Verify IRSA setup** ``` kubectl describe sa <service-account> | grep eks.amazonaws.com/role-arn kubectl describe pod <pod> | grep "Service Account" ``` **Step 2: Check token projection** ``` kubectl exec <pod> -- ls -la /var/run/secrets/eks.amazonaws.com/serviceaccount/ kubectl exec <pod> -- aws sts get-caller-identity ``` **Step 3: Verify IAM role and policies** ``` aws iam get-role --role-name <irsa-role-name> aws iam list-attached-role-policies --role-name <irsa-role-name> ``` **Step 4: Test permissions** ``` kubectl exec <pod> -- aws s3 ls # Or whatever AWS service you're trying to access ``` #### 4\.7\.2 “Pods being rejected by security policies” Runbook **Symptoms:**Pod creation fails with security policy violations **Step 1: Check namespace security labels** ``` kubectl get namespace <namespace> -o yaml | grep pod-security ``` **Step 2: Identify specific violations** ``` kubectl describe pod <pod> | grep -A 10 "violates PodSecurity" ``` **Step 3: Fix security context** ``` # Check current security context kubectl get pod <pod> -o jsonpath='{.spec.securityContext}' kubectl get pod <pod> -o jsonpath='{.spec.containers[*].securityContext}' ``` **Step 4: Apply fixes and redeploy** #### 4\.7\.3 “Secrets not updating” Runbook **Symptoms:**Application using old secret values **Step 1: Check External Secrets status** ``` kubectl get externalsecrets kubectl describe externalsecret <name> ``` **Step 2: Verify secret store connectivity** ``` kubectl get secretstore kubectl describe secretstore <name> ``` **Step 3: Check AWS Secrets Manager** ``` aws secretsmanager describe-secret --secret-id <secret-name> aws secretsmanager get-secret-value --secret-id <secret-name> ``` **Step 4: Force refresh** ``` kubectl annotate externalsecret <name> force-sync=$(date +%s) ``` --- ### 4\.8 Health probes \(critical for reliable services\) > Note: health probes aren’t a security topic, but they’re here because probe misconfiguration is one of the most common causes of cascading failures during deployments\. Misplaced in this section, but important enough to keep rather than move\. #### 4\.8\.1 Readiness Probe \(Traffic Routing Control\) **Purpose:**“Is it a good idea to send traffic to this Pod right now?” **Common misconception:**Since Kubernetes manages pods, graceful draining isn’t needed\. **Reality:**Without proper readiness probes: - Traffic sent to pods before they’re ready - Traffic continues to terminating pods - Rolling updates cause 5xx errors **Best practices:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: web-service spec: template: spec: containers: - name: app image: my-app:latest readinessProbe: httpGet: path: /health/ready port: 8080 initialDelaySeconds: 10 # Wait for app to start periodSeconds: 5 # Check every 5s timeoutSeconds: 3 # 3s timeout per check successThreshold: 1 # 1 success = ready failureThreshold: 3 # 3 failures = not ready lifecycle: preStop: exec: command: - /bin/sh - -c - | # Fail readiness probe immediately touch /tmp/shutdown # Wait for load balancer to update sleep 15 ``` **Readiness probe endpoint implementation:** ``` // Go example func readinessHandler(w http.ResponseWriter, r *http.Request) { // Check if shutdown initiated if _, err := os.Stat("/tmp/shutdown"); err == nil { http.Error(w, "Shutting down", http.StatusServiceUnavailable) return } // Check application readiness (NOT dependencies) if !app.IsReady() { http.Error(w, "Not ready", http.StatusServiceUnavailable) return } w.WriteHeader(http.StatusOK) w.Write([]byte("OK")) } ``` **Critical rule:**Never depend on downstream services in readiness probes\. If a database restarts, removing healthy pods from load balancers makes the outage worse\. #### 4\.8\.2 Liveness Probe \(Container Health Check\) **Purpose:**“Is the container healthy, or should we restart it?” **When to use:**Only when your application can deadlock and needs restart to recover\. **When NOT to use:**If you don’t know why you need it, don’t configure it\. **Best practices:** ``` containers: - name: app image: my-app:latest livenessProbe: httpGet: path: /health/live # Different from readiness! port: 8080 initialDelaySeconds: 60 # Give app time to start periodSeconds: 30 # Check every 30s (less frequent than readiness) timeoutSeconds: 5 failureThreshold: 3 # 3 failures before restart ``` **Liveness probe implementation:** ``` func livenessHandler(w http.ResponseWriter, r *http.Request) { // Only check internal application health // Never check dependencies (databases, external APIs) if app.IsDeadlocked() { http.Error(w, "Deadlocked", http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write([]byte("OK")) } ``` **Critical rules:** - Never use the same endpoint for liveness and readiness - Never check external dependencies in liveness probes - Use conservative timeouts to avoid false positives under load #### 4\.8\.3 Startup Probe \(Slow\-Starting Applications\) **Purpose:**“Should we start running the liveness probe now?” **Use case:**Applications that take longer to start than liveness probe allows\. ``` containers: - name: slow-app image: java-app:latest startupProbe: httpGet: path: /health/startup port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 30 # Allow 5 minutes for startup (30 * 10s) livenessProbe: httpGet: path: /health/live port: 8080 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3 ``` #### 4\.8\.4 Probe Failure Troubleshooting **Common probe failures:** 1. **Readiness probe failing during load:**\`\`\`bash## Check probe configuration kubectl describe pod ## Check application logs kubectl logs\-\-previous ## Test probe endpoint manually kubectl exec\-\- curl \-f http://localhost:8080/health/ready ``` 2. **Liveness probe causing restart loops:** ```bash # Check restart count kubectl get pods -o wide # Check events kubectl describe pod <pod-name> # Increase probe timeouts temporarily kubectl patch deployment <deployment> -p '{ "spec": { "template": { "spec": { "containers": [{ "name": "<container>", "livenessProbe": { "timeoutSeconds": 10, "failureThreshold": 5 } }] } } } }' ``` 1. **Startup probe preventing application start:**\`\`\`bash## Check startup probe status kubectl get pods \-o jsonpath=’\{\.items\[\*\]\.status\.conditions\[?\(@\.type==”Ready”\)\]\.message\}’ ## Extend startup probe timeout kubectl patch deployment\-p '\{ "spec": \{ "template": \{ "spec": \{ "containers": \[\{ "name": "", "startupProbe": \{ "failureThreshold": 60 \} \}\] \} \} \} \}' ``` ## 5. Storage and persistent volumes Storage failures are different from everything else in this guide: they can mean data loss, not just downtime. What follows is the operational reality of running stateful workloads on EKS — the failure modes that catch teams off-guard. --- ### 5.1 EBS CSI Driver (The Critical Path for Stateful Workloads) The EBS CSI driver is what makes persistent volumes work in EKS. When it fails, StatefulSets can't start, volumes can't attach, and data becomes inaccessible. #### 5.1.1 EBS CSI Architecture and Failure Points ``` \[Pod with PVC\] ↓ \(volume mount request\) \[Kubelet\] ↓ \(CSI calls\) \[EBS CSI Node Plugin\] ↓ \(AWS API calls\) \[EBS Volume Attach/Mount\] ``` **Each layer can fail:** * **Pod level**: Wrong PVC references, security context issues * **Kubelet level**: Mount failures, device path issues * **CSI level**: Controller crashes, node plugin issues, IAM permissions * **AWS level**: EBS limits, AZ constraints, volume states #### 5.1.2 Common EBS CSI Failure Modes **A) Pods stuck in ContainerCreating** **Symptoms:** ```bash kubectl get pods | grep ContainerCreating kubectl describe pod <pod-name> # Shows: FailedMount, timeout waiting for volume to be attached ``` **Root causes:** - Volume already attached to another node - AZ mismatch between pod and volume - EBS CSI controller/node plugin unhealthy - IAM permissions missing **Diagnosis:** ``` # Check CSI components kubectl -n kube-system get pods | grep ebs-csi kubectl -n kube-system logs deployment/ebs-csi-controller kubectl -n kube-system logs daemonset/ebs-csi-node # Check volume attachment status kubectl get volumeattachment kubectl describe volumeattachment <va-name> # Check AWS side aws ec2 describe-volumes --volume-ids <volume-id> ``` **B\) Volume attachment timeouts** **Symptoms:** - Pods fail to start after node replacement - “Multi\-Attach error for volume” messages - Long delays in pod scheduling **Common scenario:** 1. Node fails/terminates unexpectedly 2. EBS volume remains “attached” to dead node 3. New pod can’t attach volume until detached 4. Detachment can take 6\+ minutes **Force detachment \(emergency\):** ``` # Find the volume kubectl get pv <pv-name> -o jsonpath='{.spec.csi.volumeHandle}' # Force detach from AWS side aws ec2 detach-volume --volume-id <volume-id> --force # Delete stale VolumeAttachment kubectl delete volumeattachment <va-name> ``` #### 5\.1\.3 EBS CSI Configuration and Tuning **Essential CSI controller configuration:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: ebs-csi-controller namespace: kube-system spec: template: spec: containers: - name: ebs-plugin args: - controller - --endpoint=$(CSI_ENDPOINT) - --logtostderr - --v=2 - --timeout=60s # Increase for slow EBS operations env: - name: AWS_REGION value: us-west-2 resources: requests: cpu: 10m memory: 40Mi limits: cpu: 100m memory: 256Mi ``` **Node plugin tuning for high\-density workloads:** ``` apiVersion: apps/v1 kind: DaemonSet metadata: name: ebs-csi-node namespace: kube-system spec: template: spec: containers: - name: ebs-plugin args: - node - --endpoint=$(CSI_ENDPOINT) - --logtostderr - --v=2 resources: requests: cpu: 10m memory: 40Mi limits: cpu: 100m memory: 256Mi securityContext: privileged: true volumeMounts: - name: kubelet-dir mountPath: /var/lib/kubelet mountPropagation: "Bidirectional" ``` --- ### 5\.2 Storage Classes and Dynamic Provisioning #### 5\.2\.1 Production Storage Class Configuration **GP3 with proper defaults:** ``` apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: gp3-retain annotations: storageclass.kubernetes.io/is-default-class: "true" provisioner: ebs.csi.aws.com parameters: type: gp3 iops: "3000" # Baseline IOPS throughput: "125" # MB/s encrypted: "true" kmsKeyId: "alias/ebs-encryption-key" reclaimPolicy: Retain # Prevent accidental data loss allowVolumeExpansion: true volumeBindingMode: WaitForFirstConsumer # Critical for AZ placement ``` **High\-performance storage for databases:** ``` apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: io2-high-perf provisioner: ebs.csi.aws.com parameters: type: io2 iops: "10000" encrypted: "true" reclaimPolicy: Retain allowVolumeExpansion: true volumeBindingMode: WaitForFirstConsumer ``` #### 5\.2\.2 Volume Binding Mode Implications **Immediate vs WaitForFirstConsumer:** **Immediate \(default\):** - Volume created immediately when PVC is created - Can cause AZ mismatch if pod scheduled to different AZ - Good for pre\-provisioning scenarios **WaitForFirstConsumer \(recommended\):** - Volume created only when pod is scheduled - Ensures volume and pod are in same AZ - Required for multi\-AZ clusters **AZ mismatch failure example:** ``` # PVC created with Immediate binding in us-west-2a kubectl get pv <pv-name> -o jsonpath='{.metadata.labels.topology\.ebs\.csi\.aws\.com/zone}' # Output: us-west-2a # Pod scheduled to us-west-2b kubectl get pod <pod-name> -o wide # Shows node in us-west-2b # Result: FailedMount due to AZ mismatch ``` --- ### 5\.3 StatefulSets and Persistent Volume Lifecycle #### 5\.3\.1 StatefulSet Volume Management **Proper StatefulSet with volume claims:** ``` apiVersion: apps/v1 kind: StatefulSet metadata: name: database spec: serviceName: database replicas: 3 template: spec: containers: - name: db image: postgres:13 volumeMounts: - name: data mountPath: /var/lib/postgresql/data env: - name: POSTGRES_DB value: myapp - name: PGDATA value: /var/lib/postgresql/data/pgdata volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] storageClassName: gp3-retain resources: requests: storage: 100Gi ``` #### 5\.3\.2 StatefulSet Scaling and Volume Orphaning **The orphaned PVC problem:**When you scale down a StatefulSet, PVCs are NOT automatically deleted: ``` # Scale down from 5 to 3 replicas kubectl scale statefulset database --replicas=3 # PVCs for database-3 and database-4 remain kubectl get pvc | grep database # database-data-0 Bound # database-data-1 Bound # database-data-2 Bound # database-data-3 Bound # Orphaned! # database-data-4 Bound # Orphaned! ``` **Manual cleanup required:** ``` # Delete orphaned PVCs (DANGEROUS - data loss!) kubectl delete pvc database-data-3 database-data-4 # Or retain for potential scale-up # PVCs will be reused if you scale back up ``` #### 5\.3\.3 StatefulSet Rolling Updates and Volume Safety **Safe rolling update configuration:** ``` apiVersion: apps/v1 kind: StatefulSet metadata: name: database spec: updateStrategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 # Never update more than 1 pod at a time podManagementPolicy: OrderedReady # Wait for each pod to be ready ``` **Volume expansion during updates:** ``` # Expand PVC (requires allowVolumeExpansion: true) kubectl patch pvc database-data-0 -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}' # Check expansion status kubectl describe pvc database-data-0 # Look for: FileSystemResizePending or FileSystemResizeSuccessful # May require pod restart to complete filesystem resize kubectl delete pod database-0 # StatefulSet will recreate it ``` --- ### 5\.4 Backup and Disaster Recovery #### 5\.4\.1 EBS Snapshot\-Based Backups **Volume Snapshot Class:** ``` apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshotClass metadata: name: ebs-snapshot-class driver: ebs.csi.aws.com deletionPolicy: Retain # Keep snapshots even if VolumeSnapshot is deleted parameters: tagSpecification_1: "Name=CreatedBy,Value=EKS-CSI" tagSpecification_2: "Environment=Production" ``` **Creating snapshots:** ``` apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshot metadata: name: database-backup-20240126 spec: volumeSnapshotClassName: ebs-snapshot-class source: persistentVolumeClaimName: database-data-0 ``` **Restoring from snapshot:** ``` apiVersion: v1 kind: PersistentVolumeClaim metadata: name: database-restored spec: accessModes: - ReadWriteOnce storageClassName: gp3-retain resources: requests: storage: 100Gi dataSource: name: database-backup-20240126 kind: VolumeSnapshot apiGroup: snapshot.storage.k8s.io ``` #### 5\.4\.2 Application\-Consistent Backups **Pre/post hooks for database consistency:** ``` apiVersion: v1 kind: Pod metadata: name: postgres-with-backup-hooks annotations: pre.hook.backup.velero.io/command: '["/bin/bash", "-c", "PGPASSWORD=$POSTGRES_PASSWORD pg_dump -h localhost -U $POSTGRES_USER $POSTGRES_DB > /backup/dump.sql"]' post.hook.backup.velero.io/command: '["/bin/bash", "-c", "rm -f /backup/dump.sql"]' spec: containers: - name: postgres image: postgres:13 volumeMounts: - name: data mountPath: /var/lib/postgresql/data - name: backup mountPath: /backup ``` #### 5\.4\.3 Cross\-Region Backup Strategy **Automated cross\-region snapshot copying:** ``` #!/bin/bash # Copy EBS snapshots to DR region SOURCE_REGION="us-west-2" DR_REGION="us-east-1" # Get recent snapshots SNAPSHOTS=$(aws ec2 describe-snapshots \ --region $SOURCE_REGION \ --owner-ids self \ --filters "Name=tag:Environment,Values=Production" \ --query 'Snapshots[?StartTime>=`2024-01-25`].SnapshotId' \ --output text) for snapshot in $SNAPSHOTS; do echo "Copying $snapshot to $DR_REGION" aws ec2 copy-snapshot \ --region $DR_REGION \ --source-region $SOURCE_REGION \ --source-snapshot-id $snapshot \ --description "DR copy of $snapshot" done ``` --- ### 5\.5 Performance and Monitoring #### 5\.5\.1 EBS Performance Characteristics **IOPS and throughput limits by volume type:** Volume TypeMax IOPSMax ThroughputUse Casegp316,0001,000 MB/sGeneral purposeio164,0001,000 MB/sHigh IOPSio264,0001,000 MB/sMission criticalio2 Block Express256,0004,000 MB/sExtreme performance**Instance\-level limits also apply:** ``` # Check instance storage performance limits aws ec2 describe-instance-types \ --instance-types m5.large \ --query 'InstanceTypes[0].EbsInfo' ``` #### 5\.5\.2 Storage Performance Monitoring **Key metrics to monitor:** ``` # Prometheus recording rules for storage groups: - name: storage-performance rules: - record: ebs:iops_utilization expr: rate(node_disk_reads_completed_total[5m]) + rate(node_disk_writes_completed_total[5m]) - record: ebs:throughput_utilization expr: rate(node_disk_read_bytes_total[5m]) + rate(node_disk_written_bytes_total[5m]) - record: ebs:latency_p99 expr: histogram_quantile(0.99, rate(node_disk_io_time_seconds_total[5m])) ``` **Storage alerts:** ``` groups: - name: storage-alerts rules: - alert: HighDiskLatency expr: ebs:latency_p99 > 0.1 # 100ms for: 5m labels: severity: warning annotations: summary: "High disk latency detected" - alert: EBSVolumeStuck expr: increase(kubelet_volume_stats_available_bytes[10m]) == 0 for: 10m labels: severity: critical annotations: summary: "EBS volume appears stuck" ``` #### 5\.5\.3 Storage Capacity Management **Automatic PVC expansion:** ``` apiVersion: v1 kind: ConfigMap metadata: name: pvc-autoresizer-config data: config.yaml: | intervals: - name: "5min" interval: 5m rules: - name: "expand-when-80-percent-full" selector: matchLabels: app: database thresholds: - threshold: 80 increase: "20%" - threshold: 90 increase: "50%" ``` --- ### 5\.6 Multi\-AZ and Cross\-AZ Storage Patterns #### 5\.6\.1 EBS Volume AZ Constraints **The fundamental constraint:**EBS volumes are AZ\-specific and cannot be attached to instances in different AZs\. **Impact on StatefulSets:** ``` # This will fail if pods get scheduled across AZs apiVersion: apps/v1 kind: StatefulSet metadata: name: database spec: replicas: 3 template: spec: # No AZ constraints = pods can land anywhere # But PVCs are bound to specific AZs containers: - name: db image: postgres:13 ``` **Solution \- AZ\-aware scheduling:** ``` apiVersion: apps/v1 kind: StatefulSet metadata: name: database spec: replicas: 3 template: spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: database containers: - name: db image: postgres:13 ``` #### 5\.6\.2 Cross\-AZ Data Replication Patterns **For databases requiring cross\-AZ replication:** ``` # Primary in us-west-2a apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres-primary spec: replicas: 1 template: spec: nodeSelector: topology.kubernetes.io/zone: us-west-2a containers: - name: postgres image: postgres:13 env: - name: POSTGRES_REPLICATION_MODE value: master --- # Replica in us-west-2b apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres-replica spec: replicas: 1 template: spec: nodeSelector: topology.kubernetes.io/zone: us-west-2b containers: - name: postgres image: postgres:13 env: - name: POSTGRES_REPLICATION_MODE value: slave - name: POSTGRES_MASTER_SERVICE value: postgres-primary ``` --- ### 5\.7 Storage Troubleshooting Runbooks #### 5\.7\.1 “Pod stuck in ContainerCreating” Runbook **Symptoms:**Pod won’t start, stuck in ContainerCreating state **Step 1: Check pod events** ``` kubectl describe pod <pod-name> # Look for: FailedMount, timeout, volume attachment errors ``` **Step 2: Check PVC status** ``` kubectl get pvc <pvc-name> kubectl describe pvc <pvc-name> # Status should be "Bound" ``` **Step 3: Check VolumeAttachment** ``` kubectl get volumeattachment kubectl describe volumeattachment <va-name> # Look for attachment errors ``` **Step 4: Check CSI components** ``` kubectl -n kube-system get pods | grep ebs-csi kubectl -n kube-system logs deployment/ebs-csi-controller kubectl -n kube-system logs daemonset/ebs-csi-node -c ebs-plugin ``` **Step 5: Check AWS EBS volume** ``` # Get volume ID from PV kubectl get pv <pv-name> -o jsonpath='{.spec.csi.volumeHandle}' # Check volume status aws ec2 describe-volumes --volume-ids <volume-id> # State should be "available" or "in-use" ``` #### 5\.7\.2 “Volume attachment timeout” Runbook **Symptoms:**Long delays in pod startup, attachment timeout errors **Step 1: Identify stuck attachment** ``` kubectl get volumeattachment -o wide # Look for old attachments with "Attaching" status ``` **Step 2: Check if volume is stuck on dead node** ``` aws ec2 describe-volumes --volume-ids <volume-id> \ --query 'Volumes[0].Attachments' # Check if attached to non-existent instance ``` **Step 3: Force detachment \(if safe\)** ``` # Verify the instance is really dead aws ec2 describe-instances --instance-ids <instance-id> # Force detach aws ec2 detach-volume --volume-id <volume-id> --force # Clean up VolumeAttachment kubectl delete volumeattachment <va-name> ``` **Step 4: Verify pod can start** ``` kubectl get pod <pod-name> # Should transition to Running ``` #### 5\.7\.3 “PVC expansion stuck” Runbook **Symptoms:**PVC shows larger size but pod still sees old size **Step 1: Check PVC conditions** ``` kubectl describe pvc <pvc-name> # Look for: FileSystemResizePending, VolumeResizeSuccessful ``` **Step 2: Check if pod restart is needed** ``` # Some filesystems require pod restart to complete resize kubectl get pod <pod-name> -o jsonpath='{.metadata.creationTimestamp}' kubectl get pvc <pvc-name> -o jsonpath='{.status.conditions[?(@.type=="FileSystemResizePending")].lastTransitionTime}' ``` **Step 3: Restart pod if needed** ``` kubectl delete pod <pod-name> # StatefulSet/Deployment will recreate it ``` **Step 4: Verify expansion completed** ``` kubectl exec <pod-name> -- df -h /data # Should show new size ``` --- ## 6\. Observability and monitoring When monitoring is broken you can’t tell the difference between “the app is slow” and “the cluster is degraded”\. This is about building observability that actually helps during incidents, not dashboards that look good in screenshots\. --- ### 6\.1 The EKS Observability Stack \(What You Actually Need\) #### 6\.1\.1 Metrics Collection Architecture ``` [Application Metrics] → [Prometheus] → [Long-term Storage] → [Alerting/Dashboards] [System Metrics] → [Node Exporter] ↗ [Kubernetes Metrics] → [kube-state-metrics] ↗ [AWS Metrics] → [CloudWatch] → [Prometheus via adapter] ↗ ``` You need metrics at multiple layers because EKS failures can happen at any level: - Application layer \(your code\) - Kubernetes layer \(pods, services, ingress\) - Node layer \(CPU, memory, disk, network\) - AWS layer \(EBS, ENI, load balancers\) #### 6\.1\.2 Essential Metrics Components **Core Prometheus stack:** ``` # Prometheus server configuration apiVersion: v1 kind: ConfigMap metadata: name: prometheus-config data: prometheus.yml: | global: scrape_interval: 15s evaluation_interval: 15s rule_files: - "/etc/prometheus/rules/*.yml" scrape_configs: # Kubernetes API server - job_name: 'kubernetes-apiservers' kubernetes_sd_configs: - role: endpoints scheme: https tls_config: ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token relabel_configs: - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name] action: keep regex: default;kubernetes;https # Node metrics - job_name: 'kubernetes-nodes' kubernetes_sd_configs: - role: node relabel_configs: - action: labelmap regex: __meta_kubernetes_node_label_(.+) # Pod metrics - job_name: 'kubernetes-pods' kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] action: keep regex: true - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] action: replace target_label: __metrics_path__ regex: (.+) ``` **Node exporter for system metrics:** ``` apiVersion: apps/v1 kind: DaemonSet metadata: name: node-exporter spec: selector: matchLabels: app: node-exporter template: metadata: labels: app: node-exporter spec: hostNetwork: true hostPID: true containers: - name: node-exporter image: prom/node-exporter:v1.6.1 args: - --path.procfs=/host/proc - --path.sysfs=/host/sys - --path.rootfs=/host/root - --collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/) - --collector.netdev.device-exclude=^(veth.*|docker.*|br-.*|lo)$$ - --collector.conntrack # Critical for connection tracking issues - --collector.ethtool # AWS ENA metrics - --collector.ethtool.metrics-include=^(ena_.*|.*_exceeded)$$ ports: - containerPort: 9100 hostPort: 9100 volumeMounts: - name: proc mountPath: /host/proc readOnly: true - name: sys mountPath: /host/sys readOnly: true - name: root mountPath: /host/root readOnly: true volumes: - name: proc hostPath: path: /proc - name: sys hostPath: path: /sys - name: root hostPath: path: / ``` --- ### 6\.2 EKS\-Specific Monitoring \(The Metrics That Matter\) #### 6\.2\.1 Control Plane Monitoring **API Server health:** ``` # Critical API server alerts groups: - name: kubernetes-apiserver rules: - alert: KubernetesApiServerDown expr: up{job="kubernetes-apiservers"} == 0 for: 5m labels: severity: critical annotations: summary: "Kubernetes API server is down" - alert: KubernetesApiServerLatency expr: histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket[5m])) by (le, verb)) > 1 for: 10m labels: severity: warning annotations: summary: "Kubernetes API server high latency" - alert: KubernetesApiServerErrors expr: sum(rate(apiserver_request_total{code=~"5.."}[5m])) / sum(rate(apiserver_request_total[5m])) > 0.05 for: 5m labels: severity: critical annotations: summary: "Kubernetes API server error rate > 5%" ``` #### 6\.2\.2 Node\-Level Monitoring **AWS\-specific node metrics:** ``` # AWS ENA network limits groups: - name: aws-node-limits rules: - alert: AWSNetworkLimitExceeded expr: rate(node_ethtool_conntrack_allowance_exceeded[5m]) > 0 for: 1m labels: severity: critical annotations: summary: "AWS connection tracking limit exceeded on " - alert: AWSLinkLocalLimitExceeded expr: rate(node_ethtool_linklocal_allowance_exceeded[5m]) > 5 for: 1m labels: severity: warning annotations: summary: "AWS link-local rate limit exceeded on " - alert: AWSBandwidthLimitExceeded expr: rate(node_ethtool_bw_in_allowance_exceeded[5m]) > 0 or rate(node_ethtool_bw_out_allowance_exceeded[5m]) > 0 for: 1m labels: severity: warning annotations: summary: "AWS bandwidth limit exceeded on " ``` **Connection tracking monitoring:** ``` # Conntrack exhaustion alerts - alert: ConntrackTableFull expr: (node_nf_conntrack_entries / node_nf_conntrack_entries_limit) > 0.8 for: 5m labels: severity: warning annotations: summary: "Conntrack table 80% full on " - alert: ConntrackTableNearlyFull expr: (node_nf_conntrack_entries / node_nf_conntrack_entries_limit) > 0.95 for: 1m labels: severity: critical annotations: summary: "Conntrack table 95% full on " ``` #### 6\.2\.3 Pod and Container Monitoring **Container resource monitoring:** ``` # Container resource alerts groups: - name: container-resources rules: - alert: ContainerHighCPUUsage expr: (sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (namespace, pod, container) / sum(container_spec_cpu_quota{container!=""}/container_spec_cpu_period{container!=""}) by (namespace, pod, container)) > 0.9 for: 5m labels: severity: warning annotations: summary: "Container // high CPU usage" - alert: ContainerHighMemoryUsage expr: (container_memory_working_set_bytes{container!=""} / container_spec_memory_limit_bytes{container!=""}) > 0.9 for: 5m labels: severity: warning annotations: summary: "Container // high memory usage" - alert: ContainerOOMKilled expr: increase(kube_pod_container_status_restarts_total[5m]) > 0 and on(namespace, pod, container) kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1 for: 0m labels: severity: warning annotations: summary: "Container // was OOM killed" ``` --- ### 6\.3 DNS and Service Discovery Monitoring #### 6\.3\.1 CoreDNS Performance Monitoring **CoreDNS metrics collection:** ``` # CoreDNS monitoring - job_name: 'coredns' kubernetes_sd_configs: - role: endpoints relabel_configs: - source_labels: [__meta_kubernetes_service_name] action: keep regex: kube-dns - source_labels: [__meta_kubernetes_endpoint_port_name] action: keep regex: metrics ``` **CoreDNS alerts:** ``` groups: - name: coredns rules: - alert: CoreDNSDown expr: up{job="coredns"} == 0 for: 1m labels: severity: critical annotations: summary: "CoreDNS is down" - alert: CoreDNSHighLatency expr: histogram_quantile(0.99, sum(rate(coredns_dns_request_duration_seconds_bucket[5m])) by (le)) > 0.1 for: 5m labels: severity: warning annotations: summary: "CoreDNS high latency (99th percentile > 100ms)" - alert: CoreDNSHighErrorRate expr: sum(rate(coredns_dns_responses_total{rcode!="NOERROR"}[5m])) / sum(rate(coredns_dns_responses_total[5m])) > 0.05 for: 5m labels: severity: warning annotations: summary: "CoreDNS error rate > 5%" ``` #### 6\.3\.2 Service Discovery Health Checks **Synthetic DNS monitoring:** ``` apiVersion: v1 kind: Pod metadata: name: dns-monitor labels: app: dns-monitor spec: containers: - name: monitor image: busybox command: - /bin/sh - -c - | while true; do # Test internal DNS if nslookup kubernetes.default.svc.cluster.local; then echo "internal_dns_success 1" | nc -u -w1 prometheus-pushgateway 9091 else echo "internal_dns_success 0" | nc -u -w1 prometheus-pushgateway 9091 fi # Test external DNS if nslookup google.com; then echo "external_dns_success 1" | nc -u -w1 prometheus-pushgateway 9091 else echo "external_dns_success 0" | nc -u -w1 prometheus-pushgateway 9091 fi sleep 30 done ``` --- ### 6\.4 AWS Integration Monitoring #### 6\.4\.1 Load Balancer Monitoring **ALB/NLB CloudWatch metrics:** ``` # CloudWatch exporter configuration for ALB metrics apiVersion: v1 kind: ConfigMap metadata: name: cloudwatch-exporter-config data: config.yml: | region: us-west-2 metrics: # ALB metrics - aws_namespace: AWS/ApplicationELB aws_metric_name: TargetResponseTime aws_dimensions: [LoadBalancer] aws_statistics: [Average] - aws_namespace: AWS/ApplicationELB aws_metric_name: HTTPCode_Target_5XX_Count aws_dimensions: [LoadBalancer] aws_statistics: [Sum] # NLB metrics - aws_namespace: AWS/NetworkELB aws_metric_name: TCP_ELB_Reset_Count aws_dimensions: [LoadBalancer] aws_statistics: [Sum] # NAT Gateway metrics - aws_namespace: AWS/NatGateway aws_metric_name: IdleTimeoutCount aws_dimensions: [NatGatewayId] aws_statistics: [Sum] ``` **Load balancer alerts:** ``` groups: - name: aws-loadbalancer rules: - alert: ALBHighLatency expr: aws_applicationelb_target_response_time_average > 1 for: 5m labels: severity: warning annotations: summary: "ALB high response time" - alert: ALBHighErrorRate expr: rate(aws_applicationelb_httpcode_target_5_xx_count_sum[5m]) > 10 for: 5m labels: severity: critical annotations: summary: "ALB high 5xx error rate" - alert: NLBConnectionResets expr: rate(aws_networkelb_tcp_elb_reset_count_sum[5m]) > 5 for: 5m labels: severity: warning annotations: summary: "NLB connection resets detected" ``` #### 6\.4\.2 EBS and Storage Monitoring **EBS performance metrics:** ``` # EBS CloudWatch metrics - aws_namespace: AWS/EBS aws_metric_name: VolumeReadOps aws_dimensions: [VolumeId] aws_statistics: [Sum] - aws_namespace: AWS/EBS aws_metric_name: VolumeWriteOps aws_dimensions: [VolumeId] aws_statistics: [Sum] - aws_namespace: AWS/EBS aws_metric_name: VolumeTotalReadTime aws_dimensions: [VolumeId] aws_statistics: [Sum] - aws_namespace: AWS/EBS aws_metric_name: BurstBalance aws_dimensions: [VolumeId] aws_statistics: [Average] ``` **Storage alerts:** ``` groups: - name: ebs-storage rules: - alert: EBSBurstBalanceLow expr: aws_ebs_burst_balance_average < 20 for: 10m labels: severity: warning annotations: summary: "EBS volume burst balance low" - alert: EBSHighLatency expr: (aws_ebs_volume_total_read_time_sum + aws_ebs_volume_total_write_time_sum) / (aws_ebs_volume_read_ops_sum + aws_ebs_volume_write_ops_sum) > 0.1 for: 5m labels: severity: warning annotations: summary: "EBS volume high latency" ``` --- ### 6\.5 Application Performance Monitoring #### 6\.5\.1 Golden Signals for Kubernetes Workloads **The four golden signals adapted for Kubernetes:** 1. **Latency**\- Request duration 2. **Traffic**\- Request rate 3. **Errors**\- Error rate 4. **Saturation**\- Resource utilization **Application metrics instrumentation:** ``` # Example application with Prometheus metrics apiVersion: apps/v1 kind: Deployment metadata: name: web-app spec: template: metadata: annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" prometheus.io/path: "/metrics" spec: containers: - name: app image: myapp:latest ports: - containerPort: 8080 env: - name: METRICS_ENABLED value: "true" ``` **Golden signals alerts:** ``` groups: - name: golden-signals rules: # Latency - alert: HighRequestLatency expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)) > 0.5 for: 5m labels: severity: warning annotations: summary: "High request latency for " # Traffic - alert: LowTrafficVolume expr: sum(rate(http_requests_total[5m])) by (service) < 1 for: 10m labels: severity: warning annotations: summary: "Low traffic volume for " # Errors - alert: HighErrorRate expr: sum(rate(http_requests_total{status=~"5.."}[5m])) by (service) / sum(rate(http_requests_total[5m])) by (service) > 0.05 for: 5m labels: severity: critical annotations: summary: "High error rate for " # Saturation - alert: HighCPUSaturation expr: avg(rate(container_cpu_usage_seconds_total[5m])) by (namespace, pod) > 0.8 for: 5m labels: severity: warning annotations: summary: "High CPU saturation for /" ``` #### 6\.5\.2 Distributed Tracing Integration **Jaeger deployment for EKS:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: jaeger spec: template: spec: containers: - name: jaeger image: jaegertracing/all-in-one:latest env: - name: COLLECTOR_ZIPKIN_HTTP_PORT value: "9411" - name: SPAN_STORAGE_TYPE value: "elasticsearch" - name: ES_SERVER_URLS value: "http://elasticsearch:9200" ports: - containerPort: 16686 # UI - containerPort: 14268 # HTTP collector - containerPort: 6831 # UDP agent ``` --- ### 6\.6 Log Aggregation and Analysis #### 6\.6\.1 Centralized Logging Architecture **Fluent Bit for log collection:** ``` apiVersion: apps/v1 kind: DaemonSet metadata: name: fluent-bit spec: template: spec: containers: - name: fluent-bit image: fluent/fluent-bit:2.1.10 volumeMounts: - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true - name: fluent-bit-config mountPath: /fluent-bit/etc/ env: - name: FLUENT_ELASTICSEARCH_HOST value: "elasticsearch" - name: FLUENT_ELASTICSEARCH_PORT value: "9200" volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers - name: fluent-bit-config configMap: name: fluent-bit-config ``` **Fluent Bit configuration:** ``` apiVersion: v1 kind: ConfigMap metadata: name: fluent-bit-config data: fluent-bit.conf: | [SERVICE] Flush 1 Log_Level info Daemon off Parsers_File parsers.conf HTTP_Server On HTTP_Listen 0.0.0.0 HTTP_Port 2020 [INPUT] Name tail Path /var/log/containers/*.log Parser cri Tag kube.* Refresh_Interval 5 Mem_Buf_Limit 50MB Skip_Long_Lines On [FILTER] Name kubernetes Match kube.* Kube_URL https://kubernetes.default.svc:443 Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token Merge_Log On K8S-Logging.Parser On K8S-Logging.Exclude Off [OUTPUT] Name es Match * Host ${FLUENT_ELASTICSEARCH_HOST} Port ${FLUENT_ELASTICSEARCH_PORT} Index fluent-bit Type _doc ``` #### 6\.6\.2 Log\-Based Alerting **Critical log patterns to monitor:** ``` # Log-based alerts using Loki/Promtail groups: - name: log-alerts rules: - alert: PodCrashLooping expr: sum(rate({namespace="production"} |= "CrashLoopBackOff"[5m])) > 0 for: 2m labels: severity: warning annotations: summary: "Pod crash looping detected in production" - alert: OutOfMemoryKills expr: sum(rate({namespace="production"} |= "OOMKilled"[5m])) > 0 for: 1m labels: severity: critical annotations: summary: "OOM kills detected in production" - alert: ImagePullErrors expr: sum(rate({namespace="production"} |= "ImagePullBackOff"[5m])) > 0 for: 5m labels: severity: warning annotations: summary: "Image pull errors in production" ``` --- ### 6\.7 Incident Response Dashboards #### 6\.7\.1 EKS Incident Response Dashboard **Critical metrics for incident response:** ``` { "dashboard": { "title": "EKS Incident Response", "panels": [ { "title": "Cluster Health Overview", "targets": [ { "expr": "up{job=\"kubernetes-apiservers\"}", "legendFormat": "API Server" }, { "expr": "up{job=\"coredns\"}", "legendFormat": "CoreDNS" }, { "expr": "kube_node_status_condition{condition=\"Ready\",status=\"true\"}", "legendFormat": "Ready Nodes" } ] }, { "title": "Pod Status Distribution", "targets": [ { "expr": "sum by (phase) (kube_pod_status_phase)", "legendFormat": "" } ] }, { "title": "Resource Utilization", "targets": [ { "expr": "100 - (avg(irate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)", "legendFormat": "CPU Usage %" }, { "expr": "(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100", "legendFormat": "Memory Usage %" } ] }, { "title": "Network Issues", "targets": [ { "expr": "rate(node_ethtool_conntrack_allowance_exceeded[5m])", "legendFormat": "Conntrack Exceeded - " }, { "expr": "rate(node_ethtool_linklocal_allowance_exceeded[5m])", "legendFormat": "Link-local Exceeded - " } ] } ] } } ``` #### 6\.7\.2 Application Health Dashboard **Service\-level indicators:** ``` { "dashboard": { "title": "Application Health", "panels": [ { "title": "Request Rate", "targets": [ { "expr": "sum(rate(http_requests_total[5m])) by (service)", "legendFormat": "" } ] }, { "title": "Error Rate", "targets": [ { "expr": "sum(rate(http_requests_total{status=~\"5..\"}[5m])) by (service) / sum(rate(http_requests_total[5m])) by (service)", "legendFormat": "" } ] }, { "title": "Response Time", "targets": [ { "expr": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service))", "legendFormat": "95th percentile - " }, { "expr": "histogram_quantile(0.50, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service))", "legendFormat": "50th percentile - " } ] } ] } } ``` --- ### 6\.8 Monitoring Troubleshooting Runbooks #### 6\.8\.1 “Metrics missing” Runbook **Symptoms:**Dashboards show no data, alerts not firing **Step 1: Check Prometheus targets** ``` # Access Prometheus UI kubectl port-forward svc/prometheus 9090:9090 # Check targets status at http://localhost:9090/targets # Look for targets in "DOWN" state ``` **Step 2: Verify service discovery** ``` # Check if services have correct annotations kubectl get svc -o yaml | grep -A 5 -B 5 prometheus.io # Check if pods are exposing metrics kubectl exec <pod-name> -- curl localhost:8080/metrics ``` **Step 3: Check network connectivity** ``` # Test connectivity from Prometheus pod kubectl exec prometheus-pod -- nc -zv <target-service> <port> ``` #### 6\.8\.2 “Alerts not firing” Runbook **Symptoms:**Known issues not triggering alerts **Step 1: Check alert rules** ``` # Access Prometheus rules page # http://localhost:9090/rules # Verify rule syntax promtool check rules /path/to/rules.yml ``` **Step 2: Check Alertmanager** ``` kubectl logs deployment/alertmanager # Check Alertmanager config kubectl get configmap alertmanager-config -o yaml ``` **Step 3: Test alert conditions** ``` # Query the alert condition directly in Prometheus # Example: up{job="kubernetes-apiservers"} == 0 ``` --- This observability section provides the foundation for effective incident response in EKS\. The focus is on metrics and alerts that actually help during outages, not just operational dashboards\. The key is building observability that distinguishes between application issues and platform issues quickly\. ## 7\. Scaling and performance Scaling failures show up as “the app is slow” when the real issue is resource contention, autoscaler misconfiguration, or hitting AWS service limits\. What follows: scaling patterns that actually work under load, and the ways they break during traffic spikes\. --- ### 7\.1 Horizontal Pod Autoscaler \(HPA\) Operational Reality #### 7\.1\.1 HPA Architecture and Dependencies ``` [Metrics Server] → [HPA Controller] → [Deployment/ReplicaSet] → [Pods] ↑ ↑ [kubelet cAdvisor] [Custom Metrics API] ``` **Critical dependencies:** - **Metrics Server**\- Must be healthy for CPU/memory\-based scaling - **Resource requests**\- HPA cannot function without them - **Custom metrics**\- For advanced scaling \(queue depth, response time\) - **Node capacity**\- Scaling is useless if nodes can’t accommodate new pods #### 7\.1\.2 Common HPA Failure Modes **A\) HPA shows “unknown” metrics** **Symptoms:** ``` kubectl get hpa # NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS # web-app Deployment/web-app <unknown>/80% 2 10 2 ``` **Root causes:** - Metrics server down or unhealthy - Pods missing resource requests - Metrics server can’t reach kubelet **Diagnosis:** ``` # Check metrics server kubectl -n kube-system get pods -l k8s-app=metrics-server kubectl -n kube-system logs -l k8s-app=metrics-server # Check if metrics are available kubectl top pods kubectl top nodes # Check pod resource requests kubectl describe deployment web-app | grep -A 10 "Requests:" ``` **B\) HPA scaling thrashing \(rapid scale up/down\)** **Symptoms:** - Replica count oscillates rapidly - Pods constantly being created and destroyed - Performance degrades due to churn **Root causes:** - Scaling thresholds too sensitive - Resource requests don’t match actual usage - Missing stabilization windows **Fix with stabilization:** ``` apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web-app-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web-app minReplicas: 2 maxReplicas: 50 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 behavior: scaleUp: stabilizationWindowSeconds: 60 # Wait 60s before scaling up again policies: - type: Percent value: 100 # Max 100% increase per step periodSeconds: 60 scaleDown: stabilizationWindowSeconds: 300 # Wait 5min before scaling down policies: - type: Percent value: 10 # Max 10% decrease per step periodSeconds: 60 ``` #### 7\.1\.3 Custom Metrics Scaling **Scaling based on queue depth:** ``` apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: worker-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: worker minReplicas: 1 maxReplicas: 20 metrics: - type: External external: metric: name: sqs_queue_depth selector: matchLabels: queue_name: "work-queue" target: type: AverageValue averageValue: "5" # Scale up when queue depth > 5 per pod ``` **Prometheus adapter configuration:** ``` apiVersion: v1 kind: ConfigMap metadata: name: adapter-config data: config.yaml: | rules: - seriesQuery: 'sqs_queue_depth{queue_name!=""}' resources: overrides: queue_name: {resource: "queue"} name: matches: "^sqs_queue_depth" as: "sqs_queue_depth" metricsQuery: 'avg(sqs_queue_depth{queue_name="<<.LabelMatchers>>"})' ``` --- ### 7\.2 Cluster Autoscaler \(CA\) Operational Patterns #### 7\.2\.1 Cluster Autoscaler Configuration **Production CA configuration:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: cluster-autoscaler namespace: kube-system spec: template: spec: containers: - image: k8s.gcr.io/autoscaling/cluster-autoscaler:v1.27.3 name: cluster-autoscaler command: - ./cluster-autoscaler - --v=4 - --stderrthreshold=info - --cloud-provider=aws - --skip-nodes-with-local-storage=false - --expander=least-waste - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/eks-cluster-name - --balance-similar-node-groups - --scale-down-enabled=true - --scale-down-delay-after-add=10m - --scale-down-unneeded-time=10m - --scale-down-utilization-threshold=0.5 - --skip-nodes-with-system-pods=false env: - name: AWS_REGION value: us-west-2 resources: limits: cpu: 100m memory: 300Mi requests: cpu: 100m memory: 300Mi ``` #### 7\.2\.2 Common CA Failure Modes **A\) Nodes not scaling up despite pending pods** **Symptoms:** ``` kubectl get pods -A | grep Pending kubectl describe pod <pending-pod> # Shows: 0/X nodes are available: insufficient cpu/memory ``` **Root causes:** - Node group max size reached - AWS service limits \(EC2, EIP, etc\.\) - Pod resource requests too large for any instance type - Taints/tolerations preventing scheduling **Diagnosis:** ``` # Check CA logs kubectl -n kube-system logs deployment/cluster-autoscaler # Check node group limits aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names <asg-name> # Check AWS service limits aws service-quotas get-service-quota --service-code ec2 --quota-code L-1216C47A # Running On-Demand instances ``` **B\) Nodes scaling down too aggressively** **Symptoms:** - Pods getting evicted during low traffic - Services become unavailable during scale\-down - Frequent node churn **Tuning scale\-down behavior:** ``` # Cluster autoscaler configuration - --scale-down-delay-after-add=10m # Wait 10min after scale-up before considering scale-down - --scale-down-unneeded-time=10m # Node must be unneeded for 10min before removal - --scale-down-utilization-threshold=0.5 # Only remove nodes <50% utilized - --max-node-provision-time=15m # Give up on node provisioning after 15min ``` #### 7\.2\.3 Node Group Strategy **Multiple node groups for different workload types:** ``` # General purpose workloads apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: production-cluster nodeGroups: - name: general-purpose instanceTypes: ["m5.large", "m5.xlarge", "m5.2xlarge"] minSize: 2 maxSize: 20 desiredCapacity: 5 labels: workload-type: general tags: k8s.io/cluster-autoscaler/enabled: "true" k8s.io/cluster-autoscaler/production-cluster: "owned" # Compute-intensive workloads - name: compute-optimized instanceTypes: ["c5.2xlarge", "c5.4xlarge"] minSize: 0 maxSize: 10 desiredCapacity: 0 labels: workload-type: compute taints: - key: workload-type value: compute effect: NoSchedule tags: k8s.io/cluster-autoscaler/enabled: "true" k8s.io/cluster-autoscaler/production-cluster: "owned" # Memory-intensive workloads - name: memory-optimized instanceTypes: ["r5.xlarge", "r5.2xlarge"] minSize: 0 maxSize: 5 desiredCapacity: 0 labels: workload-type: memory taints: - key: workload-type value: memory effect: NoSchedule ``` --- ### 7\.3 Vertical Pod Autoscaler \(VPA\) and Right\-Sizing #### 7\.3\.1 VPA for Resource Discovery **VPA in recommendation mode:** ``` apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: web-app-vpa spec: targetRef: apiVersion: apps/v1 kind: Deployment name: web-app updatePolicy: updateMode: "Off" # Only generate recommendations, don't auto-update resourcePolicy: containerPolicies: - containerName: web-app maxAllowed: cpu: 2 memory: 4Gi minAllowed: cpu: 100m memory: 128Mi ``` **Getting VPA recommendations:** ``` # Get current recommendations kubectl describe vpa web-app-vpa # Example output: # Recommendation: # Container Recommendations: # Container Name: web-app # Lower Bound: # Cpu: 100m # Memory: 128Mi # Target: # Cpu: 250m # Memory: 512Mi # Uncapped Target: # Cpu: 250m # Memory: 512Mi # Upper Bound: # Cpu: 500m # Memory: 1Gi ``` #### 7\.3\.2 Resource Request Right\-Sizing **Common resource request mistakes:** ``` # BAD: Overprovisioned resources: requests: cpu: 2000m # App only uses 200m memory: 4Gi # App only uses 512Mi limits: cpu: 4000m memory: 8Gi # GOOD: Right-sized based on actual usage resources: requests: cpu: 250m # Based on VPA recommendation + buffer memory: 512Mi # Based on actual usage patterns limits: cpu: 500m # 2x requests for burst capacity memory: 1Gi # Hard limit to prevent OOM ``` **Resource monitoring for right\-sizing:** ``` # Monitor actual resource usage kubectl top pods --containers # Get detailed resource usage over time kubectl exec prometheus-pod -- promtool query instant \ 'avg_over_time(container_cpu_usage_seconds_total{container="web-app"}[24h])' ``` --- ### 7\.4 Performance Under Load #### 7\.4\.1 Load Testing EKS Workloads **Gradual load testing approach:** ``` # Load test job apiVersion: batch/v1 kind: Job metadata: name: load-test spec: parallelism: 10 template: spec: containers: - name: load-test image: loadimpact/k6:latest command: - k6 - run - --vus=50 - --duration=10m - --rps=100 - /scripts/load-test.js volumeMounts: - name: test-scripts mountPath: /scripts volumes: - name: test-scripts configMap: name: load-test-scripts restartPolicy: Never ``` **Load test script example:** ``` // load-test.js import http from 'k6/http'; import { check, sleep } from 'k6'; export let options = { stages: [ { duration: '2m', target: 100 }, // Ramp up { duration: '5m', target: 100 }, // Stay at 100 users { duration: '2m', target: 200 }, // Ramp up to 200 { duration: '5m', target: 200 }, // Stay at 200 { duration: '2m', target: 0 }, // Ramp down ], thresholds: { http_req_duration: ['p(95)<500'], // 95% of requests under 500ms http_req_failed: ['rate<0.1'], // Error rate under 10% }, }; export default function() { let response = http.get('http://web-app.default.svc.cluster.local/api/health'); check(response, { 'status is 200': (r) => r.status === 200, 'response time < 500ms': (r) => r.timings.duration < 500, }); sleep(1); } ``` #### 7\.4\.2 Performance Bottleneck Identification **Common EKS performance bottlenecks:** 1. **CPU throttling due to limits** 2. **Memory pressure causing OOM kills** 3. **Network bandwidth limits \(AWS instance\-level\)** 4. **Storage IOPS limits \(EBS\)** 5. **DNS resolution delays \(CoreDNS overload\)** 6. **Connection tracking limits \(conntrack\)** **Performance monitoring queries:** ``` # CPU throttling detection sum(rate(container_cpu_cfs_throttled_seconds_total[5m])) by (namespace, pod, container) > 0 # Memory pressure detection (container_memory_working_set_bytes / container_spec_memory_limit_bytes) > 0.8 # Network bandwidth utilization rate(node_network_receive_bytes_total[5m]) + rate(node_network_transmit_bytes_total[5m]) # Storage IOPS utilization rate(node_disk_reads_completed_total[5m]) + rate(node_disk_writes_completed_total[5m]) # DNS latency histogram_quantile(0.95, sum(rate(coredns_dns_request_duration_seconds_bucket[5m])) by (le)) ``` --- ### 7\.5 AWS Service Limits and Quotas **EC2 limits that affect scaling:** ``` # Check current EC2 limits aws service-quotas get-service-quota --service-code ec2 --quota-code L-1216C47A # On-Demand instances aws service-quotas get-service-quota --service-code ec2 --quota-code L-34B43A08 # All Standard Spot Instance Requests aws service-quotas get-service-quota --service-code ec2 --quota-code L-0263D0A3 # Security Groups per VPC aws service-quotas get-service-quota --service-code ec2 --quota-code L-FE5A380F # Network Interfaces per VPC # Check current usage aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" --query 'Reservations[*].Instances[*].InstanceType' | jq -r '.[][] | select(. != null)' | sort | uniq -c ``` **EKS\-specific limits:** ``` # EKS cluster limits aws service-quotas get-service-quota --service-code eks --quota-code L-1194D53C # Clusters per region aws service-quotas get-service-quota --service-code eks --quota-code L-6D54EA21 # Managed node groups per cluster aws service-quotas get-service-quota --service-code eks --quota-code L-CD136C55 # Nodes per managed node group ``` #### 7\.5\.2 Proactive Limit Monitoring **Service limit monitoring:** ``` # CloudWatch custom metrics for service limits apiVersion: batch/v1 kind: CronJob metadata: name: service-limit-monitor spec: schedule: "0 */6 * * *" # Every 6 hours jobTemplate: spec: template: spec: containers: - name: limit-monitor image: amazon/aws-cli:latest command: - /bin/bash - -c - | # Get current EC2 usage RUNNING_INSTANCES=$(aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" --query 'Reservations[*].Instances[*].InstanceId' --output text | wc -w) # Get EC2 limit EC2_LIMIT=$(aws service-quotas get-service-quota --service-code ec2 --quota-code L-1216C47A --query 'Quota.Value' --output text) # Calculate utilization percentage UTILIZATION=$(echo "scale=2; $RUNNING_INSTANCES / $EC2_LIMIT * 100" | bc) # Send to CloudWatch aws cloudwatch put-metric-data \ --namespace "AWS/ServiceLimits" \ --metric-data MetricName=EC2InstanceUtilization,Value=$UTILIZATION,Unit=Percent echo "EC2 utilization: $UTILIZATION%" restartPolicy: OnFailure ``` --- ### 7\.6 Scaling Troubleshooting Runbooks #### 7\.6\.1 “HPA not scaling” Runbook **Symptoms:**Pods under load but HPA not creating more replicas **Step 1: Check HPA status** ``` kubectl get hpa kubectl describe hpa <hpa-name> # Look for: current metrics, scaling events ``` **Step 2: Verify metrics availability** ``` # Check if metrics server is working kubectl top pods kubectl top nodes # Check specific pod metrics kubectl get --raw "/apis/metrics.k8s.io/v1beta1/namespaces/<namespace>/pods/<pod-name>" ``` **Step 3: Check resource requests** ``` kubectl describe deployment <deployment-name> | grep -A 5 "Requests:" # HPA requires CPU/memory requests to be set ``` **Step 4: Check node capacity** ``` kubectl describe nodes | grep -A 5 "Allocated resources" # Verify nodes have capacity for new pods ``` #### 7\.6\.2 “Cluster Autoscaler not adding nodes” Runbook **Symptoms:**Pods pending but no new nodes being created **Step 1: Check CA logs** ``` kubectl -n kube-system logs deployment/cluster-autoscaler | tail -50 # Look for: scale-up events, errors, AWS API issues ``` **Step 2: Check pending pods** ``` kubectl get pods -A --field-selector=status.phase=Pending kubectl describe pod <pending-pod> # Look for: resource requirements, node selector constraints ``` **Step 3: Check node group limits** ``` aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names <asg-name> # Check: min/max size, desired capacity, current instances ``` **Step 4: Check AWS service limits** ``` aws service-quotas get-service-quota --service-code ec2 --quota-code L-1216C47A # Verify you haven't hit EC2 instance limits ``` #### 7\.6\.3 “Performance degraded under load” Runbook **Symptoms:**Application slow during traffic spikes **Step 1: Check resource utilization** ``` kubectl top pods --sort-by=cpu kubectl top pods --sort-by=memory kubectl top nodes ``` **Step 2: Check for CPU throttling** ``` # Look for throttling in Prometheus # Query: sum(rate(container_cpu_cfs_throttled_seconds_total[5m])) by (namespace, pod) ``` **Step 3: Check network limits** ``` # Check AWS ENA metrics for network limits kubectl exec node-exporter-pod -- cat /sys/class/net/eth0/statistics/rx_dropped kubectl exec node-exporter-pod -- cat /sys/class/net/eth0/statistics/tx_dropped ``` **Step 4: Check DNS performance** ``` # Test DNS resolution speed kubectl exec test-pod -- time nslookup kubernetes.default.svc.cluster.local # Check CoreDNS metrics kubectl -n kube-system logs -l k8s-app=kube-dns | grep -i error ``` --- ## 8\. Upgrades and maintenance EKS upgrades are where “everything was working fine” becomes “production is down”\. Unlike app deploys, cluster upgrades touch every layer simultaneously and can fail in ways that are hard to predict and harder to roll back\. Below: strategies that minimize risk and how to recover when upgrades go sideways\. --- ### 8\.1 EKS Upgrade Strategy \(The Reality of Breaking Changes\) #### 8\.1\.1 EKS Upgrade Components **What actually gets upgraded:** ``` [EKS Control Plane] → [Managed by AWS] [EKS Add-ons] → [CoreDNS, kube-proxy, VPC CNI, EBS CSI] [Node Groups] → [AMI, Kubernetes version, instance types] [Third-party Components] → [Ingress controllers, service mesh, monitoring] ``` Each component can break independently, and version skew between components creates new failure modes\. #### 8\.1\.2 Pre\-Upgrade Validation Checklist **Compatibility matrix validation:** ``` #!/bin/bash # EKS upgrade compatibility checker CLUSTER_NAME="production-cluster" CURRENT_VERSION=$(aws eks describe-cluster --name $CLUSTER_NAME --query 'cluster.version' --output text) TARGET_VERSION="1.28" echo "Current EKS version: $CURRENT_VERSION" echo "Target EKS version: $TARGET_VERSION" # Check addon compatibility echo "Checking addon versions..." aws eks describe-addon-versions --kubernetes-version $TARGET_VERSION --addon-name vpc-cni aws eks describe-addon-versions --kubernetes-version $TARGET_VERSION --addon-name coredns aws eks describe-addon-versions --kubernetes-version $TARGET_VERSION --addon-name kube-proxy # Check deprecated APIs echo "Checking for deprecated APIs..." kubectl get --raw /api/v1 | jq '.resources[] | select(.name == "componentstatuses")' kubectl get --raw /apis/extensions/v1beta1 2>/dev/null || echo "extensions/v1beta1 not available (good)" # Check node group versions echo "Current node group versions:" aws eks describe-nodegroup --cluster-name $CLUSTER_NAME --nodegroup-name primary --query 'nodegroup.version' ``` **Workload compatibility testing:** ``` # Test job to validate workloads on new version apiVersion: batch/v1 kind: Job metadata: name: upgrade-compatibility-test spec: template: spec: containers: - name: test image: kubectl:latest command: - /bin/bash - -c - | # Test basic functionality kubectl get nodes kubectl get pods -A # Test service discovery nslookup kubernetes.default.svc.cluster.local # Test storage kubectl get pvc -A kubectl get pv # Test networking kubectl get svc -A kubectl get ingress -A echo "Compatibility test completed" restartPolicy: Never ``` #### 8\.1\.3 Staged Upgrade Approach **Phase 1: Control plane upgrade** ``` # Upgrade control plane first (managed by AWS) aws eks update-cluster-version --name production-cluster --version 1.28 # Monitor upgrade progress aws eks describe-update --name production-cluster --update-id <update-id> # Validate control plane health kubectl get nodes kubectl get pods -n kube-system ``` **Phase 2: Add\-on upgrades** ``` # Upgrade VPC CNI first (networking critical) aws eks update-addon --cluster-name production-cluster --addon-name vpc-cni --addon-version v1.15.1-eksbuild.1 # Upgrade CoreDNS aws eks update-addon --cluster-name production-cluster --addon-name coredns --addon-version v1.10.1-eksbuild.4 # Upgrade kube-proxy aws eks update-addon --cluster-name production-cluster --addon-name kube-proxy --addon-version v1.28.2-eksbuild.2 ``` **Phase 3: Node group upgrades \(most risky\)** ``` # Create new node group with new version aws eks create-nodegroup \ --cluster-name production-cluster \ --nodegroup-name primary-v128 \ --kubernetes-version 1.28 \ --node-role arn:aws:iam::123456789012:role/NodeInstanceRole \ --subnets subnet-12345 subnet-67890 \ --instance-types m5.large \ --ami-type AL2_x86_64 \ --capacity-type ON_DEMAND \ --scaling-config minSize=2,maxSize=10,desiredSize=3 # Gradually migrate workloads kubectl cordon <old-node> kubectl drain <old-node> --ignore-daemonsets --delete-emptydir-data --force --grace-period=300 # Delete old node group after validation aws eks delete-nodegroup --cluster-name production-cluster --nodegroup-name primary-old ``` --- ### 8\.2 Node Group Replacement Strategies #### 8\.2\.1 Blue\-Green Node Group Strategy **Advantages:** - Zero downtime for stateless workloads - Easy rollback if issues occur - Full validation before switching traffic **Implementation:** ``` # Blue node group (current) apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: production-cluster nodeGroups: - name: blue-nodes instanceTypes: ["m5.large"] minSize: 3 maxSize: 10 desiredCapacity: 5 labels: deployment-group: blue tags: Environment: production DeploymentGroup: blue # Green node group (new version) - name: green-nodes instanceTypes: ["m5.large"] minSize: 3 maxSize: 10 desiredCapacity: 5 labels: deployment-group: green tags: Environment: production DeploymentGroup: green ``` **Migration process:** ``` # 1. Create green node group eksctl create nodegroup --config-file=cluster-config.yaml --include="green-nodes" # 2. Validate green nodes kubectl get nodes -l deployment-group=green kubectl describe nodes -l deployment-group=green # 3. Migrate workloads gradually for deployment in $(kubectl get deployments -o name); do echo "Migrating $deployment" kubectl patch $deployment -p '{"spec":{"template":{"spec":{"nodeSelector":{"deployment-group":"green"}}}}}' kubectl rollout status $deployment sleep 30 done # 4. Validate applications on green nodes ./run-smoke-tests.sh # 5. Remove blue nodes kubectl cordon -l deployment-group=blue kubectl drain -l deployment-group=blue --ignore-daemonsets --delete-emptydir-data eksctl delete nodegroup --cluster=production-cluster --name=blue-nodes ``` #### 8\.2\.2 Rolling Node Group Updates **For stateful workloads that can’t move easily:** ``` # Update node group in place with rolling replacement aws eks update-nodegroup-version \ --cluster-name production-cluster \ --nodegroup-name primary \ --kubernetes-version 1.28 \ --launch-template-version 2 # Monitor rolling update progress aws eks describe-nodegroup \ --cluster-name production-cluster \ --nodegroup-name primary \ --query 'nodegroup.updateConfig' ``` **Custom rolling update script:** ``` #!/bin/bash # Custom node rolling update with validation CLUSTER_NAME="production-cluster" NODEGROUP_NAME="primary" # Get list of nodes in node group NODES=$(kubectl get nodes -l eks.amazonaws.com/nodegroup=$NODEGROUP_NAME -o jsonpath='{.items[*].metadata.name}') for node in $NODES; do echo "Updating node: $node" # Cordon node kubectl cordon $node # Wait for new pods to be scheduled elsewhere sleep 60 # Drain node kubectl drain $node --ignore-daemonsets --delete-emptydir-data --force --grace-period=300 # Terminate instance (ASG will replace it) INSTANCE_ID=$(kubectl get node $node -o jsonpath='{.spec.providerID}' | cut -d'/' -f5) aws ec2 terminate-instances --instance-ids $INSTANCE_ID # Wait for replacement node to be ready echo "Waiting for replacement node..." while true; do READY_NODES=$(kubectl get nodes -l eks.amazonaws.com/nodegroup=$NODEGROUP_NAME --no-headers | grep " Ready " | wc -l) if [ $READY_NODES -ge $(echo $NODES | wc -w) ]; then break fi sleep 30 done echo "Node $node replaced successfully" done ``` --- ### 8\.3 Application Compatibility and API Deprecations #### 8\.3\.1 Deprecated API Detection **Automated API deprecation scanning:** ``` #!/bin/bash # Scan for deprecated APIs in cluster echo "Scanning for deprecated APIs..." # Check for deprecated APIs in running resources kubectl get --raw /api/v1 | jq -r '.resources[] | select(.name | contains("componentstatuses")) | .name' # Check extensions/v1beta1 usage (deprecated in 1.22+) kubectl get deployments.extensions -A 2>/dev/null && echo "WARNING: Found extensions/v1beta1 Deployments" kubectl get ingresses.extensions -A 2>/dev/null && echo "WARNING: Found extensions/v1beta1 Ingresses" # Check networking.k8s.io/v1beta1 usage (deprecated in 1.22+) kubectl get ingresses.networking.k8s.io/v1beta1 -A 2>/dev/null && echo "WARNING: Found networking.k8s.io/v1beta1 Ingresses" # Check policy/v1beta1 usage (deprecated in 1.25+) kubectl get podsecuritypolicies 2>/dev/null && echo "WARNING: Found PodSecurityPolicies (deprecated)" # Check autoscaling/v2beta1 usage (deprecated in 1.23+) kubectl get hpa.autoscaling/v2beta1 -A 2>/dev/null && echo "WARNING: Found autoscaling/v2beta1 HPAs" echo "Deprecated API scan completed" ``` **Pluto for comprehensive deprecation checking:** ``` # Install pluto curl -L https://github.com/FairwindsOps/pluto/releases/download/v5.18.4/pluto_5.18.4_linux_amd64.tar.gz | tar xz sudo mv pluto /usr/local/bin/ # Scan cluster for deprecated APIs pluto detect-all-in-cluster --target-versions k8s=v1.28.0 # Scan Helm releases pluto detect-helm --target-versions k8s=v1.28.0 # Scan files pluto detect-files -d ./k8s-manifests --target-versions k8s=v1.28.0 ``` #### 8\.3\.2 API Migration Strategies **Ingress API migration \(extensions/v1beta1 → networking\.k8s\.io/v1\):** ``` # OLD (deprecated in 1.22+) apiVersion: extensions/v1beta1 kind: Ingress metadata: name: web-app spec: rules: - host: app.example.com http: paths: - path: / backend: serviceName: web-app servicePort: 80 # NEW (required in 1.22+) apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: web-app spec: rules: - host: app.example.com http: paths: - path: / pathType: Prefix # Required field backend: service: # Changed structure name: web-app port: number: 80 ``` **HPA API migration \(autoscaling/v2beta1 → autoscaling/v2\):** ``` # OLD (deprecated in 1.23+) apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata: name: web-app-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web-app minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu targetAverageUtilization: 70 # NEW (required in 1.23+) apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web-app-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web-app minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 # Changed structure ``` --- ### 8\.4 Rollback Strategies #### 8\.4\.1 Control Plane Rollback Limitations **Critical understanding:**EKS control plane upgrades**cannot be rolled back**\(as of early 2026\)\. Once upgraded, you can only move forward\. > **Note:**AWS is developing an EKS control plane rollback feature for inline upgrades, but it’s not yet released\. Until available, the limitations below apply\. **Current mitigation strategies:** 1. **Thorough testing**in staging environment 2. **Blue\-green cluster**strategy for critical workloads 3. **Backup and restore**procedures for etcd data #### 8\.4\.2 Node Group Rollback **Quick node group rollback:** ``` # If new node group has issues, switch back to old one kubectl patch deployment web-app -p '{"spec":{"template":{"spec":{"nodeSelector":{"deployment-group":"blue"}}}}}' # Scale up old node group aws eks update-nodegroup-config \ --cluster-name production-cluster \ --nodegroup-name blue-nodes \ --scaling-config minSize=3,maxSize=10,desiredSize=5 # Delete problematic new node group aws eks delete-nodegroup \ --cluster-name production-cluster \ --nodegroup-name green-nodes ``` #### 8\.4\.3 Add\-on Rollback **Rolling back EKS add\-ons:** ``` # Check available versions aws eks describe-addon-versions --addon-name vpc-cni --kubernetes-version 1.27 # Rollback to previous version aws eks update-addon \ --cluster-name production-cluster \ --addon-name vpc-cni \ --addon-version v1.14.1-eksbuild.1 \ --resolve-conflicts OVERWRITE ``` --- ### 8\.5 Maintenance Windows and Disruption Management #### 8\.5\.1 Planned Maintenance Strategy **Maintenance window planning:** ``` # Maintenance mode deployment apiVersion: apps/v1 kind: Deployment metadata: name: maintenance-page spec: replicas: 2 selector: matchLabels: app: maintenance-page template: metadata: labels: app: maintenance-page spec: containers: - name: nginx image: nginx:alpine ports: - containerPort: 80 volumeMounts: - name: maintenance-content mountPath: /usr/share/nginx/html volumes: - name: maintenance-content configMap: name: maintenance-content --- apiVersion: v1 kind: ConfigMap metadata: name: maintenance-content data: index.html: | <!DOCTYPE html> <html> <head><title>Maintenance</title></head> <body> <h1>System Maintenance</h1> <p>We're performing scheduled maintenance. Please try again in 30 minutes.</p> </body> </html> ``` **Traffic switching for maintenance:** ``` # Switch ingress to maintenance page kubectl patch ingress web-app -p '{"spec":{"rules":[{"host":"app.example.com","http":{"paths":[{"path":"/","pathType":"Prefix","backend":{"service":{"name":"maintenance-page","port":{"number":80}}}}]}}]}}' # Perform maintenance operations ./upgrade-cluster.sh # Switch back to application kubectl patch ingress web-app -p '{"spec":{"rules":[{"host":"app.example.com","http":{"paths":[{"path":"/","pathType":"Prefix","backend":{"service":{"name":"web-app","port":{"number":80}}}}]}}]}}' ``` #### 8\.5\.2 Pod Disruption Budget Management **Maintenance\-aware PDB configuration:** ``` apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: web-app-pdb spec: minAvailable: 2 selector: matchLabels: app: web-app # Allow more disruption during maintenance windows unhealthyPodEvictionPolicy: AlwaysAllow ``` **Temporary PDB adjustment for maintenance:** ``` # Relax PDB for maintenance kubectl patch pdb web-app-pdb -p '{"spec":{"minAvailable":1}}' # Perform node drains kubectl drain node1 --ignore-daemonsets --delete-emptydir-data # Restore strict PDB after maintenance kubectl patch pdb web-app-pdb -p '{"spec":{"minAvailable":2}}' ``` --- ### 8\.6 Upgrade Troubleshooting Runbooks #### 8\.6\.1 “Control plane upgrade stuck” Runbook **Symptoms:**EKS upgrade shows “InProgress” for hours **Step 1: Check upgrade status** ``` aws eks describe-update --name production-cluster --update-id <update-id> # Look for: status, errors, created/modified timestamps ``` **Step 2: Check control plane health** ``` kubectl get nodes kubectl get pods -n kube-system kubectl get --raw='/readyz?verbose' ``` **Step 3: Check for blocking resources** ``` # Check for stuck finalizers kubectl get all -A | grep Terminating # Check for webhook issues kubectl get validatingwebhookconfigurations kubectl get mutatingwebhookconfigurations ``` **Step 4: Contact AWS Support** ``` # If upgrade is truly stuck (>4 hours), open AWS support case # Include: cluster name, update ID, timeline of events ``` #### 8\.6\.2 “Pods failing after node upgrade” Runbook **Symptoms:**Applications not working after node group upgrade **Step 1: Check pod status** ``` kubectl get pods -A | grep -v Running kubectl describe pod <failing-pod> # Look for: scheduling issues, image pull problems, volume mount failures ``` **Step 2: Check node conditions** ``` kubectl get nodes kubectl describe node <new-node> # Look for: Ready condition, resource availability, taints ``` **Step 3: Check networking** ``` # Test pod-to-pod connectivity kubectl exec test-pod -- ping <other-pod-ip> # Test DNS resolution kubectl exec test-pod -- nslookup kubernetes.default.svc.cluster.local # Check CNI health kubectl -n kube-system logs -l k8s-app=aws-node ``` **Step 4: Check storage** ``` # Check PVC status kubectl get pvc -A # Check volume attachments kubectl get volumeattachment # Check CSI driver health kubectl -n kube-system logs -l app=ebs-csi-controller ``` #### 8\.6\.3 “Add\-on upgrade failed” Runbook **Symptoms:**EKS add\-on shows “DEGRADED” status **Step 1: Check add\-on status** ``` aws eks describe-addon --cluster-name production-cluster --addon-name vpc-cni # Look for: status, health issues, configuration conflicts ``` **Step 2: Check add\-on pods** ``` kubectl -n kube-system get pods -l k8s-app=aws-node kubectl -n kube-system logs -l k8s-app=aws-node ``` **Step 3: Resolve conflicts** ``` # If configuration conflicts exist, resolve with OVERWRITE aws eks update-addon \ --cluster-name production-cluster \ --addon-name vpc-cni \ --resolve-conflicts OVERWRITE ``` **Step 4: Rollback if necessary** ``` # Check available versions aws eks describe-addon-versions --addon-name vpc-cni --kubernetes-version 1.27 # Rollback to previous version aws eks update-addon \ --cluster-name production-cluster \ --addon-name vpc-cni \ --addon-version <previous-version> ``` --- ### 8\.7 Graceful Deployments and Pod Termination **The deployment problem:**During rolling updates, pods can receive traffic while terminating or before they’re ready, causing 5xx errors\. **Pod termination sequence:** ``` 1. Pod marked for termination (status: Terminating) 2. Pod removed from Service endpoints (async) 3. SIGTERM sent to containers (async) 4. preStop hooks executed (if configured) 5. Grace period countdown starts (default: 30s) 6. SIGKILL sent if still running after grace period ``` **Race condition:**Steps 2 and 3 happen in parallel, so pods can receive traffic after SIGTERM\. #### 8\.7\.1 Graceful Shutdown Configuration **Application\-level graceful shutdown:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: graceful-app spec: template: spec: terminationGracePeriodSeconds: 60 # Allow time for cleanup containers: - name: app image: my-app:latest lifecycle: preStop: exec: command: - /bin/sh - -c - | # Signal app to stop accepting new requests kill -TERM 1 # Wait for load balancer to update (AWS NLB ~10s, ALB ~15s) sleep 15 # Allow existing requests to complete sleep 10 ports: - containerPort: 8080 readinessProbe: httpGet: path: /health/ready port: 8080 initialDelaySeconds: 5 periodSeconds: 5 livenessProbe: httpGet: path: /health/live port: 8080 initialDelaySeconds: 30 periodSeconds: 10 ``` **Application code example \(Go\):** ``` package main import ( "context" "net/http" "os" "os/signal" "syscall" "time" ) func main() { server := &http.Server{Addr: ":8080"} // Graceful shutdown handling go func() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) <-sigChan // Stop accepting new requests ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() server.Shutdown(ctx) }() server.ListenAndServe() } ``` #### 8\.7\.2 Load Balancer Integration **AWS NLB connection draining:** ``` apiVersion: v1 kind: Service metadata: name: app-service annotations: service.beta.kubernetes.io/aws-load-balancer-type: "nlb" service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "350" # Enable connection draining service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled: "true" service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout: "60" spec: type: LoadBalancer ports: - port: 80 targetPort: 8080 selector: app: graceful-app ``` **Envoy proxy graceful shutdown:** ``` apiVersion: v1 kind: ConfigMap metadata: name: envoy-config data: envoy.yaml: | static_resources: listeners: - name: listener_0 address: socket_address: address: 0.0.0.0 port_value: 8080 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager # Graceful shutdown settings drain_timeout: 30s delayed_close_timeout: 10s http_filters: - name: envoy.filters.http.router route_config: name: local_route virtual_hosts: - name: backend domains: ["*"] routes: - match: prefix: "/" route: cluster: backend_cluster clusters: - name: backend_cluster connect_timeout: 5s type: STRICT_DNS lb_policy: ROUND_ROBIN # Connection recycling for NLB compatibility max_requests_per_connection: 1000 max_connection_duration: 300s load_assignment: cluster_name: backend_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: app-backend port_value: 8080 ``` #### 8\.7\.3 Rolling Update Strategy **Deployment strategy for zero\-downtime updates:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: zero-downtime-app spec: replicas: 6 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 # Only terminate 1 pod at a time maxSurge: 2 # Allow 2 extra pods during update template: spec: terminationGracePeriodSeconds: 45 containers: - name: app image: my-app:v2 readinessProbe: httpGet: path: /health/ready port: 8080 initialDelaySeconds: 10 periodSeconds: 5 failureThreshold: 3 lifecycle: preStop: httpGet: path: /shutdown port: 8080 ``` **PodDisruptionBudget for controlled disruptions:** ``` apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: app-pdb spec: minAvailable: 4 # Always keep 4 pods running selector: matchLabels: app: zero-downtime-app ``` ## 9\. Disaster recovery When a cluster fails catastrophically, the pressure to restore service leads to rushed decisions that make recovery slower\. Below: DR strategies that work under pressure — what can be recovered, what must be rebuilt, and how to not make it worse\. --- ### 9\.1 EKS Disaster Scenarios \(What Actually Breaks\) #### 9\.1\.1 Control Plane Failures **AWS\-managed control plane issues:** - Regional AWS service outages - EKS API server degradation - etcd corruption \(rare but catastrophic\) - Certificate rotation failures **Reality check:**You cannot directly access or repair the EKS control plane\. Recovery depends entirely on AWS support and your backup strategies\. #### 9\.1\.2 Complete Cluster Loss **Common causes:** - Accidental cluster deletion - VPC/networking misconfiguration making cluster unreachable - All node groups terminated simultaneously - Region\-wide AWS outages **Recovery time expectations:** - New cluster provisioning: 10\-15 minutes - Add\-on installation: 5\-10 minutes - Application restoration: Depends on backup strategy - **Total RTO: 30 minutes to several hours** #### 9\.1\.3 Data Layer Failures **EBS volume failures:** - Zone\-wide EBS outages - Volume corruption - Snapshot restoration issues **Application data loss:** - StatefulSet data corruption - Database failures - Persistent volume claim issues --- ### 9\.2 Backup Strategies \(What to Backup and How\) #### 9\.2\.1 Cluster Configuration Backup **Essential cluster state to backup:** ``` #!/bin/bash # Cluster backup script CLUSTER_NAME="production-cluster" BACKUP_DIR="./cluster-backup-$(date +%Y%m%d-%H%M%S)" mkdir -p $BACKUP_DIR # Backup cluster configuration aws eks describe-cluster --name $CLUSTER_NAME > $BACKUP_DIR/cluster-config.json # Backup node groups aws eks list-nodegroups --cluster-name $CLUSTER_NAME --query 'nodegroups[]' --output text | \ while read nodegroup; do aws eks describe-nodegroup --cluster-name $CLUSTER_NAME --nodegroup-name $nodegroup > $BACKUP_DIR/nodegroup-$nodegroup.json done # Backup EKS add-ons aws eks list-addons --cluster-name $CLUSTER_NAME --query 'addons[]' --output text | \ while read addon; do aws eks describe-addon --cluster-name $CLUSTER_NAME --addon-name $addon > $BACKUP_DIR/addon-$addon.json done # Backup VPC configuration VPC_ID=$(aws eks describe-cluster --name $CLUSTER_NAME --query 'cluster.resourcesVpcConfig.vpcId' --output text) aws ec2 describe-vpcs --vpc-ids $VPC_ID > $BACKUP_DIR/vpc-config.json aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" > $BACKUP_DIR/subnets.json aws ec2 describe-security-groups --filters "Name=vpc-id,Values=$VPC_ID" > $BACKUP_DIR/security-groups.json echo "Cluster configuration backed up to $BACKUP_DIR" ``` #### 9\.2\.2 Application State Backup with Velero **Velero installation for EKS:** ``` # Install Velero with AWS plugin velero install \ --provider aws \ --plugins velero/velero-plugin-for-aws:v1.8.0 \ --bucket velero-backups-production \ --backup-location-config region=us-west-2 \ --snapshot-location-config region=us-west-2 \ --secret-file ./credentials-velero ``` **Comprehensive backup schedule:** ``` apiVersion: velero.io/v1 kind: Schedule metadata: name: daily-backup spec: schedule: "0 2 * * *" # Daily at 2 AM template: includedNamespaces: - production - staging excludedResources: - events - events.events.k8s.io storageLocation: default volumeSnapshotLocations: - default ttl: 720h # 30 days retention ``` **Critical workload backup:** ``` apiVersion: velero.io/v1 kind: Backup metadata: name: critical-workloads spec: includedNamespaces: - production labelSelector: matchLabels: backup: critical snapshotVolumes: true includeClusterResources: true hooks: resources: - name: database-backup-hook includedNamespaces: - production labelSelector: matchLabels: app: database pre: - exec: container: database command: - /bin/bash - -c - "pg_dump -h localhost -U postgres mydb > /tmp/backup.sql" timeout: 300s ``` #### 9\.2\.3 etcd Backup Strategy **Automated etcd backup \(for self\-managed clusters\):** ``` #!/bin/bash # etcd backup script (not applicable to EKS managed control plane) # This is for reference if you have self-managed etcd ETCDCTL_API=3 etcdctl snapshot save backup.db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \ --key=/etc/kubernetes/pki/etcd/healthcheck-client.key # Upload to S3 aws s3 cp backup.db s3://etcd-backups/backup-$(date +%Y%m%d-%H%M%S).db ``` **Note:**EKS manages etcd backups automatically\. You cannot directly backup EKS etcd\. --- ### 9\.3 Cross\-Region Disaster Recovery #### 9\.3\.1 Multi\-Region EKS Architecture **Active\-passive setup:** ``` # Primary region cluster apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: production-primary region: us-west-2 nodeGroups: - name: primary-nodes instanceTypes: ["m5.large"] minSize: 3 maxSize: 10 desiredCapacity: 5 availabilityZones: ["us-west-2a", "us-west-2b", "us-west-2c"] --- # DR region cluster (smaller, can be scaled up) apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: production-dr region: us-east-1 nodeGroups: - name: dr-nodes instanceTypes: ["m5.large"] minSize: 1 maxSize: 10 desiredCapacity: 2 availabilityZones: ["us-east-1a", "us-east-1b", "us-east-1c"] ``` #### 9\.3\.2 Cross\-Region Replication Strategy **Database replication:** ``` # RDS cross-region read replica apiVersion: v1 kind: ConfigMap metadata: name: database-config data: primary-endpoint: "prod-db.us-west-2.rds.amazonaws.com" dr-endpoint: "prod-db-replica.us-east-1.rds.amazonaws.com" failover-script: | #!/bin/bash # Promote read replica to primary aws rds promote-read-replica \ --db-instance-identifier prod-db-replica \ --region us-east-1 ``` **Application data replication:** ``` # Cross-region S3 replication for application assets aws s3api put-bucket-replication \ --bucket production-assets \ --replication-configuration file://replication-config.json ``` #### 9\.3\.3 DNS Failover Configuration **Route 53 health checks and failover:** ``` { "Type": "A", "Name": "api.example.com", "SetIdentifier": "primary", "Failover": "PRIMARY", "AliasTarget": { "DNSName": "k8s-elb-primary.us-west-2.elb.amazonaws.com", "EvaluateTargetHealth": true }, "HealthCheckId": "primary-health-check" } ``` --- ### 9\.4 Recovery Procedures #### 9\.4\.1 Complete Cluster Recreation **Cluster recreation runbook:** ``` #!/bin/bash # Complete cluster recovery procedure set -e CLUSTER_NAME="production-cluster" REGION="us-west-2" BACKUP_DIR="./latest-backup" echo "Starting cluster recovery for $CLUSTER_NAME" # Step 1: Recreate cluster eksctl create cluster --config-file=$BACKUP_DIR/cluster-config.yaml # Step 2: Wait for cluster to be ready aws eks wait cluster-active --name $CLUSTER_NAME --region $REGION # Step 3: Install essential add-ons aws eks create-addon --cluster-name $CLUSTER_NAME --addon-name vpc-cni aws eks create-addon --cluster-name $CLUSTER_NAME --addon-name coredns aws eks create-addon --cluster-name $CLUSTER_NAME --addon-name kube-proxy # Step 4: Install AWS Load Balancer Controller helm repo add eks https://aws.github.io/eks-charts helm install aws-load-balancer-controller eks/aws-load-balancer-controller \ -n kube-system \ --set clusterName=$CLUSTER_NAME # Step 5: Install Velero velero install --provider aws --plugins velero/velero-plugin-for-aws:v1.8.0 \ --bucket velero-backups-production \ --backup-location-config region=$REGION \ --snapshot-location-config region=$REGION \ --secret-file ./credentials-velero # Step 6: Restore from backup LATEST_BACKUP=$(velero backup get --output json | jq -r '.items[0].metadata.name') velero restore create --from-backup $LATEST_BACKUP echo "Cluster recovery initiated. Monitor with: kubectl get pods -A" ``` #### 9\.4\.2 Partial Recovery Scenarios **Node group replacement:** ``` # If only node groups are affected aws eks create-nodegroup \ --cluster-name production-cluster \ --nodegroup-name recovery-nodes \ --kubernetes-version 1.28 \ --node-role arn:aws:iam::123456789012:role/NodeInstanceRole \ --subnets subnet-12345 subnet-67890 \ --instance-types m5.large \ --scaling-config minSize=3,maxSize=10,desiredSize=5 # Migrate workloads to new nodes kubectl cordon -l eks.amazonaws.com/nodegroup=old-nodes kubectl drain -l eks.amazonaws.com/nodegroup=old-nodes --ignore-daemonsets --delete-emptydir-data ``` **Application\-only recovery:** ``` # If cluster is healthy but applications are corrupted velero restore create app-recovery \ --from-backup latest-backup \ --include-namespaces production \ --restore-volumes=true ``` #### 9\.4\.3 Data Recovery Procedures **EBS volume recovery:** ``` # Restore from EBS snapshot SNAPSHOT_ID="snap-1234567890abcdef0" VOLUME_ID=$(aws ec2 create-volume \ --snapshot-id $SNAPSHOT_ID \ --availability-zone us-west-2a \ --volume-type gp3 \ --query 'VolumeId' --output text) # Update PV to use new volume kubectl patch pv pvc-12345 -p '{"spec":{"awsElasticBlockStore":{"volumeID":"'$VOLUME_ID'"}}}' ``` **Database recovery:** ``` # RDS point-in-time recovery aws rds restore-db-instance-to-point-in-time \ --source-db-instance-identifier prod-db \ --target-db-instance-identifier prod-db-recovered \ --restore-time 2024-01-15T10:00:00.000Z ``` --- ### 9\.5 Recovery Testing and Validation #### 9\.5\.1 Disaster Recovery Testing Schedule **Monthly DR drill:** ``` #!/bin/bash # DR drill script - run in non-production environment echo "Starting DR drill $(date)" # Test 1: Backup restoration velero restore create dr-test-$(date +%Y%m%d) \ --from-backup latest-production-backup \ --namespace-mappings production:dr-test # Test 2: Application functionality kubectl -n dr-test run test-pod --image=curlimages/curl --rm -it -- \ curl http://web-app.dr-test.svc.cluster.local/health # Test 3: Database connectivity kubectl -n dr-test exec deployment/app -- \ pg_isready -h database.dr-test.svc.cluster.local # Test 4: External dependencies kubectl -n dr-test exec deployment/app -- \ curl -f https://api.external-service.com/health echo "DR drill completed. Check results manually." ``` #### 9\.5\.2 Recovery Time Objective \(RTO\) Validation **RTO measurement script:** ``` #!/bin/bash # Measure actual recovery times START_TIME=$(date +%s) # Simulate cluster failure kubectl delete deployment --all -n production # Start recovery velero restore create rto-test --from-backup latest-backup # Wait for recovery completion while true; do READY_PODS=$(kubectl get pods -n production --no-headers | grep Running | wc -l) TOTAL_PODS=$(kubectl get pods -n production --no-headers | wc -l) if [ $READY_PODS -eq $TOTAL_PODS ] && [ $TOTAL_PODS -gt 0 ]; then break fi sleep 10 done END_TIME=$(date +%s) RTO=$((END_TIME - START_TIME)) echo "Recovery completed in $RTO seconds" echo "RTO target: 1800 seconds (30 minutes)" if [ $RTO -lt 1800 ]; then echo "✅ RTO target met" else echo "❌ RTO target exceeded" fi ``` --- ### 9\.6 Disaster Recovery Runbooks #### 9\.6\.1 “Complete cluster loss” Runbook **Symptoms:**Cannot connect to cluster, AWS console shows cluster deleted/unavailable **Step 1: Assess scope** ``` # Check if cluster exists aws eks describe-cluster --name production-cluster # Check if it's a regional AWS issue curl -s https://status.aws.amazon.com/ | grep -i "service issues" ``` **Step 2: Activate DR procedures** ``` # Switch DNS to DR region (if available) aws route53 change-resource-record-sets \ --hosted-zone-id Z123456789 \ --change-batch file://failover-to-dr.json # Scale up DR cluster aws eks update-nodegroup-config \ --cluster-name production-dr \ --nodegroup-name dr-nodes \ --scaling-config minSize=3,maxSize=20,desiredSize=10 ``` **Step 3: Recreate primary cluster** ``` # Use backup configuration eksctl create cluster --config-file=./backups/cluster-config.yaml # Restore applications velero restore create disaster-recovery \ --from-backup $(velero backup get -o json | jq -r '.items[0].metadata.name') ``` #### 9\.6\.2 “Data corruption” Runbook **Symptoms:**Applications running but data is corrupted/missing **Step 1: Stop writes immediately** ``` # Scale down applications to prevent further corruption kubectl scale deployment --replicas=0 -n production -l tier=application # Cordon nodes to prevent new pods kubectl cordon --all ``` **Step 2: Assess data integrity** ``` # Check database consistency kubectl exec -it database-pod -- pg_dump --schema-only mydb > schema-backup.sql # Check persistent volume data kubectl exec -it app-pod -- find /data -name "*.log" -mtime -1 | head -10 ``` **Step 3: Restore from backup** ``` # Restore database from point-in-time backup aws rds restore-db-instance-to-point-in-time \ --source-db-instance-identifier prod-db \ --target-db-instance-identifier prod-db-restored \ --restore-time $(date -d '1 hour ago' -Iseconds) # Restore application data from Velero velero restore create data-recovery \ --from-backup latest-backup \ --include-resources persistentvolumeclaims,persistentvolumes ``` #### 9\.6\.3 “Region\-wide outage” Runbook **Symptoms:**All AWS services in primary region unavailable **Step 1: Immediate failover** ``` # Activate DR region immediately aws route53 change-resource-record-sets \ --hosted-zone-id Z123456789 \ --change-batch file://emergency-failover.json # Scale DR cluster to handle production load kubectl scale deployment --replicas=5 -n production -l tier=web kubectl scale deployment --replicas=3 -n production -l tier=api ``` **Step 2: Promote read replicas** ``` # Promote RDS read replica to primary aws rds promote-read-replica \ --db-instance-identifier prod-db-replica \ --region us-east-1 # Update application configuration kubectl patch configmap app-config -p '{"data":{"database_url":"prod-db-replica.us-east-1.rds.amazonaws.com"}}' ``` **Step 3: Monitor and adjust** ``` # Monitor application health in DR region kubectl get pods -A | grep -v Running kubectl top nodes kubectl top pods -A --sort-by=cpu ``` --- ## 10\. Cost optimization Every engineering decision here directly hits the budget\. Below: the cost levers that actually matter in production EKS and how to pull them without breaking reliability\. --- ### 10\.1 EKS Cost Structure \(Where Your Money Goes\) #### 10\.1\.1 EKS Cost Components **Control plane costs:** - EKS cluster: $0\.10/hour per cluster \($73/month\) - Fargate: $0\.04048/vCPU/hour \+ $0\.004445/GB/hour - Data transfer costs \(often overlooked\) **Compute costs \(largest component\):** - EC2 instances for node groups - EBS volumes for node storage - Data transfer between AZs - NAT Gateway costs for private subnets **Hidden costs:** - Load balancer costs \(ALB/NLB\) - EBS snapshots and backups - CloudWatch logs and metrics - Cross\-AZ data transfer #### 10\.1\.2 Cost Visibility and Tracking **Essential cost tracking:** ``` # Get EKS cluster costs aws ce get-cost-and-usage \ --time-period Start=2024-01-01,End=2024-01-31 \ --granularity MONTHLY \ --metrics BlendedCost \ --group-by Type=DIMENSION,Key=SERVICE \ --filter file://eks-cost-filter.json # EKS cost filter cat > eks-cost-filter.json << EOF { "Dimensions": { "Key": "SERVICE", "Values": ["Amazon Elastic Kubernetes Service", "Amazon Elastic Compute Cloud"] } } EOF ``` **Resource tagging for cost allocation:** ``` apiVersion: v1 kind: Node metadata: labels: cost-center: "engineering" environment: "production" team: "platform" project: "web-app" ``` --- ### 10\.2 Right\-Sizing Workloads #### 10\.2\.1 Resource Request Optimization **The over\-provisioning problem:** ``` # Find over-provisioned pods kubectl top pods -A --sort-by=cpu | head -20 kubectl top pods -A --sort-by=memory | head -20 # Compare requests vs actual usage kubectl get pods -A -o custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace,CPU_REQ:.spec.containers[*].resources.requests.cpu,MEM_REQ:.spec.containers[*].resources.requests.memory ``` **VPA for right\-sizing recommendations:** ``` apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: web-app-vpa spec: targetRef: apiVersion: apps/v1 kind: Deployment name: web-app updatePolicy: updateMode: "Off" # Recommendation only resourcePolicy: containerPolicies: - containerName: web-app maxAllowed: cpu: 2 memory: 4Gi minAllowed: cpu: 100m memory: 128Mi ``` **Automated right\-sizing script:** ``` #!/bin/bash # Generate right-sizing recommendations echo "Analyzing resource usage for right-sizing..." for namespace in $(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}'); do echo "Namespace: $namespace" kubectl top pods -n $namespace --no-headers | while read pod cpu memory; do # Get resource requests requests=$(kubectl get pod $pod -n $namespace -o jsonpath='{.spec.containers[*].resources.requests}') echo "Pod: $pod" echo " Current usage: CPU=$cpu, Memory=$memory" echo " Requests: $requests" echo " Recommendation: Review if requests match usage" echo "" done done ``` #### 10\.2\.2 Node Right\-Sizing **Instance type cost analysis:** ``` # Compare instance costs per vCPU and per GB RAM aws ec2 describe-instance-types \ --instance-types m5.large m5.xlarge m5.2xlarge c5.large c5.xlarge \ --query 'InstanceTypes[*].[InstanceType,VCpuInfo.DefaultVCpus,MemoryInfo.SizeInMiB]' \ --output table # Get current pricing (requires AWS Pricing API) aws pricing get-products \ --service-code AmazonEC2 \ --filters Type=TERM_MATCH,Field=instanceType,Value=m5.large \ --filters Type=TERM_MATCH,Field=location,Value="US West (Oregon)" ``` **Node utilization analysis:** ``` # Check node resource utilization kubectl top nodes # Detailed node analysis kubectl describe nodes | grep -A 5 "Allocated resources" # Find underutilized nodes kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU_CAPACITY:.status.capacity.cpu,MEMORY_CAPACITY:.status.capacity.memory ``` --- ### 10\.3 Spot Instances and Mixed Instance Types #### 10\.3\.1 Spot Instance Strategy **Spot\-optimized node group:** ``` apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: cost-optimized-cluster nodeGroups: - name: spot-nodes instanceTypes: - m5.large - m5.xlarge - c5.large - c5.xlarge spot: true minSize: 2 maxSize: 20 desiredCapacity: 5 labels: node-type: spot taints: - key: spot-instance value: "true" effect: NoSchedule ``` **Spot\-tolerant workload configuration:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: batch-processor spec: replicas: 5 template: spec: tolerations: - key: spot-instance operator: Equal value: "true" effect: NoSchedule nodeSelector: node-type: spot containers: - name: processor image: batch-processor:latest resources: requests: cpu: 500m memory: 1Gi ``` #### 10\.3\.2 Mixed Instance Type Strategy **Diversified node groups:** ``` # On-demand for critical workloads - name: on-demand-critical instanceTypes: ["m5.large"] minSize: 2 maxSize: 5 desiredCapacity: 2 labels: node-type: on-demand workload-type: critical # Spot for batch/stateless workloads - name: spot-batch instanceTypes: - m5.large - m5.xlarge - c5.large - c5.xlarge spot: true minSize: 0 maxSize: 50 desiredCapacity: 5 labels: node-type: spot workload-type: batch ``` **Workload placement strategy:** ``` # Critical workloads on on-demand apiVersion: apps/v1 kind: Deployment metadata: name: payment-service spec: template: spec: nodeSelector: node-type: on-demand workload-type: critical containers: - name: payment image: payment-service:latest --- # Batch workloads on spot apiVersion: batch/v1 kind: Job metadata: name: data-processing spec: template: spec: tolerations: - key: spot-instance operator: Equal value: "true" effect: NoSchedule nodeSelector: node-type: spot workload-type: batch ``` --- ### 10\.4 Storage Cost Optimization #### 10\.4\.1 EBS Volume Optimization **Storage class cost comparison:** ``` # gp3 (newer, more cost-effective) apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: gp3-optimized provisioner: ebs.csi.aws.com parameters: type: gp3 iops: "3000" # Baseline IOPS throughput: "125" # Baseline throughput allowVolumeExpansion: true reclaimPolicy: Delete # gp2 (legacy, more expensive for same performance) apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: gp2-legacy provisioner: ebs.csi.aws.com parameters: type: gp2 allowVolumeExpansion: true reclaimPolicy: Delete ``` **Volume cleanup automation:** ``` #!/bin/bash # Clean up unused EBS volumes echo "Finding unused EBS volumes..." # Get all EBS volumes aws ec2 describe-volumes \ --filters Name=status,Values=available \ --query 'Volumes[*].[VolumeId,CreateTime,Size,VolumeType]' \ --output table # Find volumes older than 30 days with no attachments aws ec2 describe-volumes \ --filters Name=status,Values=available \ --query "Volumes[?CreateTime<='$(date -d '30 days ago' -Iseconds)'].[VolumeId,CreateTime,Size]" \ --output table echo "Review these volumes for deletion to reduce costs" ``` #### 10\.4\.2 Persistent Volume Reclaim Policies **Cost\-conscious reclaim policies:** ``` # For development environments - Delete to avoid orphaned volumes apiVersion: v1 kind: PersistentVolume metadata: name: dev-pv spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Delete # Automatically delete when PVC is deleted storageClassName: gp3-optimized # For production - Retain for safety, but monitor for cleanup apiVersion: v1 kind: PersistentVolume metadata: name: prod-pv spec: capacity: storage: 100Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain # Manual cleanup required storageClassName: gp3-optimized ``` --- ### 10\.5 Network Cost Optimization #### 10\.5\.1 Cross\-AZ Data Transfer Reduction **Single\-AZ node groups for specific workloads:** ``` # For high-throughput, low-latency workloads apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig nodeGroups: - name: single-az-compute instanceTypes: ["c5n.xlarge"] availabilityZones: ["us-west-2a"] # Single AZ to avoid cross-AZ charges minSize: 2 maxSize: 10 labels: topology: single-az ``` **Pod anti\-affinity for AZ awareness:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: data-processor spec: replicas: 3 template: spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: ["data-processor"] topologyKey: topology.kubernetes.io/zone ``` #### 10\.5\.2 NAT Gateway Cost Optimization **NAT Gateway alternatives:** ``` # Option 1: NAT instances (cheaper for high traffic) # Create NAT instance instead of NAT Gateway for cost savings # Option 2: VPC endpoints for AWS services aws ec2 create-vpc-endpoint \ --vpc-id vpc-12345678 \ --service-name com.amazonaws.us-west-2.s3 \ --route-table-ids rtb-12345678 # Option 3: Public subnets for non-sensitive workloads # Move some workloads to public subnets to avoid NAT costs ``` --- ### 10\.6 Cluster Consolidation and Multi\-Tenancy #### 10\.6\.1 Cluster Consolidation Strategy **When to consolidate clusters:** - Multiple small clusters with low utilization - Similar security requirements - Shared operational overhead **Namespace\-based multi\-tenancy:** ``` # Resource quotas per team apiVersion: v1 kind: ResourceQuota metadata: name: team-a-quota namespace: team-a spec: hard: requests.cpu: "10" requests.memory: 20Gi limits.cpu: "20" limits.memory: 40Gi persistentvolumeclaims: "10" services.loadbalancers: "2" --- # Network policies for isolation apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: team-a-isolation namespace: team-a spec: podSelector: {} policyTypes: - Ingress - Egress ingress: - from: - namespaceSelector: matchLabels: name: team-a egress: - to: - namespaceSelector: matchLabels: name: team-a - to: [] # Allow egress to internet ports: - protocol: TCP port: 443 - protocol: TCP port: 80 ``` #### 10\.6\.2 Shared Services Strategy **Centralized monitoring and logging:** ``` # Shared monitoring namespace apiVersion: v1 kind: Namespace metadata: name: monitoring labels: shared-service: "true" --- # Prometheus for all teams apiVersion: apps/v1 kind: Deployment metadata: name: prometheus namespace: monitoring spec: template: spec: containers: - name: prometheus image: prom/prometheus:latest resources: requests: cpu: 2 memory: 4Gi limits: cpu: 4 memory: 8Gi ``` --- ### 10\.7 Cost Monitoring and Alerting #### 10\.7\.1 Cost Anomaly Detection **CloudWatch cost alerts:** ``` # Create cost budget with alerts aws budgets create-budget \ --account-id 123456789012 \ --budget file://eks-cost-budget.json # Budget configuration cat > eks-cost-budget.json << EOF { "BudgetName": "EKS-Monthly-Budget", "BudgetLimit": { "Amount": "5000", "Unit": "USD" }, "TimeUnit": "MONTHLY", "BudgetType": "COST", "CostFilters": { "Service": ["Amazon Elastic Kubernetes Service", "Amazon Elastic Compute Cloud"] } } EOF ``` #### 10\.7\.2 Resource Utilization Monitoring **Cluster cost efficiency metrics:** ``` #!/bin/bash # Calculate cluster cost efficiency # Get total cluster capacity TOTAL_CPU=$(kubectl get nodes -o jsonpath='{.items[*].status.capacity.cpu}' | tr ' ' '+' | bc) TOTAL_MEMORY=$(kubectl get nodes -o jsonpath='{.items[*].status.capacity.memory}' | sed 's/Ki//g' | tr ' ' '+' | bc) # Get allocated resources ALLOCATED_CPU=$(kubectl describe nodes | grep -A 5 "Allocated resources" | grep "cpu" | awk '{print $2}' | sed 's/[^0-9]//g' | tr '\n' '+' | sed 's/+$//' | bc) ALLOCATED_MEMORY=$(kubectl describe nodes | grep -A 5 "Allocated resources" | grep "memory" | awk '{print $2}' | sed 's/[^0-9]//g' | tr '\n' '+' | sed 's/+$//' | bc) # Calculate utilization CPU_UTILIZATION=$(echo "scale=2; $ALLOCATED_CPU / $TOTAL_CPU * 100" | bc) MEMORY_UTILIZATION=$(echo "scale=2; $ALLOCATED_MEMORY / $TOTAL_MEMORY * 100" | bc) echo "Cluster Resource Utilization:" echo "CPU: ${CPU_UTILIZATION}%" echo "Memory: ${MEMORY_UTILIZATION}%" # Alert if utilization is too low (waste) or too high (risk) if (( $(echo "$CPU_UTILIZATION < 30" | bc -l) )); then echo "⚠️ Low CPU utilization - consider downsizing" elif (( $(echo "$CPU_UTILIZATION > 80" | bc -l) )); then echo "⚠️ High CPU utilization - consider scaling up" fi ``` --- ### 10\.8 Cost Optimization Runbooks #### 10\.8\.1 “Monthly cost spike” Investigation **Step 1: Identify cost drivers** ``` # Get cost breakdown by service aws ce get-cost-and-usage \ --time-period Start=$(date -d 'last month' +%Y-%m-01),End=$(date +%Y-%m-01) \ --granularity MONTHLY \ --metrics BlendedCost \ --group-by Type=DIMENSION,Key=SERVICE # Get cost by resource tags aws ce get-cost-and-usage \ --time-period Start=$(date -d 'last month' +%Y-%m-01),End=$(date +%Y-%m-01) \ --granularity MONTHLY \ --metrics BlendedCost \ --group-by Type=TAG,Key=Environment ``` **Step 2: Analyze resource usage** ``` # Check for resource over-provisioning kubectl top nodes kubectl top pods -A --sort-by=cpu | head -20 # Look for unused resources kubectl get pvc -A | grep -v Bound aws ec2 describe-volumes --filters Name=status,Values=available ``` **Step 3: Implement immediate cost reductions** ``` # Scale down non-production environments kubectl scale deployment --replicas=0 -n staging --all kubectl scale deployment --replicas=1 -n development --all # Clean up unused resources kubectl delete pvc -A --field-selector=status.phase=Pending ``` #### 10\.8\.2 “Right\-sizing recommendations” Runbook **Step 1: Collect usage data** ``` # Install VPA recommender kubectl apply -f https://github.com/kubernetes/autoscaler/releases/download/vertical-pod-autoscaler-0.13.0/vpa-release.yaml # Create VPA for all deployments for deployment in $(kubectl get deployments -A -o jsonpath='{.items[*].metadata.name}'); do kubectl create vpa ${deployment}-vpa --target-ref=Deployment/${deployment} --update-mode=Off done ``` **Step 2: Analyze recommendations** ``` # Get VPA recommendations kubectl get vpa -A -o custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace,CPU_TARGET:.status.recommendation.containerRecommendations[0].target.cpu,MEMORY_TARGET:.status.recommendation.containerRecommendations[0].target.memory ``` **Step 3: Apply optimizations** ``` # Update deployment with new resource requests kubectl patch deployment web-app -p '{"spec":{"template":{"spec":{"containers":[{"name":"web-app","resources":{"requests":{"cpu":"200m","memory":"256Mi"}}}]}}}}' ``` --- ## 11\. Troubleshooting cookbook Step\-by\-step solutions for the most common EKS production failures\. Symptoms, diagnosis, root cause, and fixes you can run under pressure\. --- ### 11\.1 Pod Scheduling Failures #### 11\.1\.1 “Pods stuck in Pending state” **Symptoms:** - New pods remain in Pending status - `kubectl get pods`shows Pending for extended periods - Applications fail to scale up **Diagnosis:** ``` # Check pod events for scheduling failures kubectl describe pod <pending-pod> # Check node resource availability kubectl describe nodes | grep -A 5 "Allocated resources" # Check for taints blocking scheduling kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints ``` **Common root causes and fixes:** **Insufficient resources:** ``` # Check cluster capacity kubectl top nodes # Scale cluster if needed aws eks update-nodegroup-config \ --cluster-name production-cluster \ --nodegroup-name primary \ --scaling-config minSize=3,maxSize=20,desiredSize=10 ``` **Node selector mismatch:** ``` # Check pod node selector kubectl get pod <pod> -o yaml | grep -A 5 nodeSelector # Check available node labels kubectl get nodes --show-labels # Fix: Update pod spec or add labels to nodes kubectl label node <node-name> environment=production ``` **Taints and tolerations:** ``` # Remove problematic taint kubectl taint node <node-name> key:NoSchedule- # Or add toleration to pod kubectl patch deployment <deployment> -p '{ "spec": { "template": { "spec": { "tolerations": [{ "key": "key", "operator": "Equal", "value": "value", "effect": "NoSchedule" }] } } } }' ``` #### 11\.1\.2 “Cluster Autoscaler thrashing \(rapid scale up/down\)” **Symptoms:** - Nodes constantly being added and removed - Workload instability during scaling events - High AWS costs from node churn **Root cause:**Flaky readiness probes causing pods to appear unschedulable\. **Diagnosis:** ``` # Check Cluster Autoscaler logs kubectl -n kube-system logs -l app=cluster-autoscaler --tail=100 # Look for rapid scale events kubectl -n kube-system logs -l app=cluster-autoscaler | grep -E "(scale-up|scale-down)" # Check pod readiness probe failures kubectl get events --field-selector reason=Unhealthy --sort-by='.lastTimestamp' ``` **Fix:** ``` # Identify problematic deployment kubectl describe pod <failing-pod> | grep -A 10 "Readiness probe failed" # Fix readiness probe configuration kubectl patch deployment <deployment> -p '{ "spec": { "template": { "spec": { "containers": [{ "name": "<container>", "readinessProbe": { "initialDelaySeconds": 30, "periodSeconds": 10, "timeoutSeconds": 5, "failureThreshold": 3, "successThreshold": 1 } }] } } } }' # Tune Cluster Autoscaler to reduce thrashing kubectl -n kube-system patch deployment cluster-autoscaler -p '{ "spec": { "template": { "spec": { "containers": [{ "name": "cluster-autoscaler", "command": [ "./cluster-autoscaler", "--scale-down-delay-after-add=10m", "--scale-down-unneeded-time=10m", "--skip-nodes-with-local-storage=false" ] }] } } } }' ``` #### 11\.1\.3 “DiskPressure causing pod evictions” **Symptoms:** - Pods being evicted with reason “DiskPressure” - Node conditions show DiskPressure=True - Container image pulls failing **Root cause:**Large container images or excessive logging filling node disk\. **Diagnosis:** ``` # Check node disk usage kubectl get nodes -o custom-columns=NAME:.metadata.name,DISK-PRESSURE:.status.conditions[?(@.type==\"DiskPressure\")].status # Check disk usage on specific node kubectl debug node/<node-name> -it --image=busybox -- df -h # Check container image sizes kubectl debug node/<node-name> -it --image=busybox -- crictl images | sort -k2 -h # Check log sizes kubectl debug node/<node-name> -it --image=busybox -- du -sh /var/log/containers/* ``` **Fix:** ``` # Clean up unused images kubectl debug node/<node-name> -it --image=busybox -- crictl rmi --prune # Restart containerd to clear cache kubectl debug node/<node-name> -it --image=busybox -- systemctl restart containerd # For EKS managed nodes, increase disk size aws eks update-nodegroup-config \ --cluster-name production-cluster \ --nodegroup-name primary \ --launch-template name=eks-node-template,version=2 # Configure log rotation kubectl apply -f - <<EOF apiVersion: v1 kind: ConfigMap metadata: name: fluent-bit-config namespace: amazon-cloudwatch data: fluent-bit.conf: | [SERVICE] Flush 1 Log_Level info Daemon off Parsers_File parsers.conf HTTP_Server On HTTP_Listen 0.0.0.0 HTTP_Port 2020 storage.path /var/fluent-bit/state/flb-storage/ storage.sync normal storage.checksum off storage.backlog.mem_limit 5M [INPUT] Name tail Tag application.* Exclude_Path /var/log/containers/cloudwatch-agent*, /var/log/containers/fluent-bit* Path /var/log/containers/*.log Docker_Mode On Docker_Mode_Flush 5 Docker_Mode_Parser container_firstline Parser docker DB /var/fluent-bit/state/flb_container.db Mem_Buf_Limit 50MB Skip_Long_Lines On Refresh_Interval 10 Rotate_Wait 30 storage.type filesystem Read_from_Head Off EOF ``` #### 11\.1\.4 “Zombie pods with stuck finalizers” **Symptoms:** - Pods stuck in Terminating state for extended periods - `kubectl delete pod \-\-force`doesn’t work - Namespace deletion hangs **Root cause:**Custom finalizers not being processed due to controller failures\. **Diagnosis:** ``` # Find pods with finalizers kubectl get pods --all-namespaces -o json | jq -r '.items[] | select(.metadata.finalizers != null) | "\(.metadata.namespace)/\(.metadata.name): \(.metadata.finalizers)"' # Check specific pod finalizers kubectl get pod <pod-name> -o json | jq '.metadata.finalizers' # Check if controller managing finalizer is running kubectl get pods -n <controller-namespace> | grep <controller-name> ``` **Fix:** ``` # Remove finalizers manually (DANGEROUS - only if controller is confirmed dead) kubectl patch pod <pod-name> -p '{"metadata":{"finalizers":[]}}' --type=merge # For namespace stuck in terminating kubectl get namespace <namespace> -o json | jq '.spec.finalizers = []' | kubectl replace --raw "/api/v1/namespaces/<namespace>/finalize" -f - # Restart the controller managing the finalizer kubectl -n <controller-namespace> rollout restart deployment <controller-name> ``` ### 11\.2 API and Internal Networking Failures #### 11\.2\.1 “API Server throttling causing cluster inaccessibility” **Symptoms:** - `kubectl`commands timing out or returning 429 errors - Applications unable to communicate with API server - High API server latency in metrics **Root cause:**Too many concurrent API requests overwhelming the API server\. **Diagnosis:** ``` # Check API server metrics kubectl top pods -n kube-system | grep kube-apiserver # Check for throttling in API server logs (EKS managed - use CloudWatch) aws logs filter-log-events \ --log-group-name /aws/eks/production-cluster/cluster \ --filter-pattern "throttling" # Identify high-volume API clients kubectl get events --sort-by='.lastTimestamp' | head -20 ``` **Fix:** ``` # Identify and throttle problematic controllers kubectl get deployments --all-namespaces -o wide | grep -v "1/1" # Scale down misbehaving controllers temporarily kubectl scale deployment <problematic-controller> --replicas=0 -n <namespace> # For custom controllers, implement exponential backoff # Add rate limiting to controller reconcile loops ``` #### 11\.2\.2 “Pod\-to\-pod communication failures” **Symptoms:** - Services unreachable from other pods - Intermittent connection timeouts - DNS resolution working but connections failing **Root cause:**Network policies, security groups, or CNI issues\. **Diagnosis:** ``` # Test basic connectivity kubectl run debug-pod --image=busybox -it --rm -- sh # Inside pod: nslookup <service-name>.<namespace>.svc.cluster.local # Inside pod: wget -qO- <service-name>.<namespace>.svc.cluster.local:8080 # Check network policies kubectl get networkpolicies --all-namespaces # Check AWS security groups (for pods using security groups) aws ec2 describe-security-groups --group-ids <sg-id> # Check CNI plugin status kubectl -n kube-system get pods -l k8s-app=aws-node kubectl -n kube-system logs -l k8s-app=aws-node ``` **Fix:** ``` # Allow traffic in network policy kubectl apply -f - <<EOF apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-debug-traffic namespace: <target-namespace> spec: podSelector: {} policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: name: <source-namespace> ports: - protocol: TCP port: 8080 EOF # Restart CNI pods if needed kubectl -n kube-system delete pods -l k8s-app=aws-node ``` ### 11\.3 Resource Management Issues #### 11\.3\.1 “CronJobs exhausting cluster resources” **Symptoms:** - Cluster resource exhaustion during scheduled job runs - Multiple CronJobs running simultaneously - Node resource pressure during specific time windows **Root cause:**CronJobs without resource limits running concurrently\. **Diagnosis:** ``` # Check running CronJobs kubectl get cronjobs --all-namespaces # Check job resource usage kubectl top pods --all-namespaces | grep -E "(job|cron)" # Check CronJob schedules for overlap kubectl get cronjobs --all-namespaces -o custom-columns=NAME:.metadata.name,SCHEDULE:.spec.schedule,SUSPEND:.spec.suspend ``` **Fix:** ``` # Add resource limits to CronJob kubectl patch cronjob <cronjob-name> -p '{ "spec": { "jobTemplate": { "spec": { "template": { "spec": { "containers": [{ "name": "<container-name>", "resources": { "requests": { "cpu": "100m", "memory": "256Mi" }, "limits": { "cpu": "500m", "memory": "512Mi" } } }] } } } } } }' # Prevent concurrent executions kubectl patch cronjob <cronjob-name> -p '{ "spec": { "concurrencyPolicy": "Forbid" } }' # Stagger CronJob schedules kubectl patch cronjob <cronjob-name> -p '{ "spec": { "schedule": "5 2 * * *" } }' ``` #### 11\.3\.2 “Excessive logging filling node disk” **Symptoms:** - Node DiskPressure conditions - Pod evictions due to disk space - `/var/log/containers/`consuming excessive space **Root cause:**Applications logging at debug level or without log rotation\. **Diagnosis:** ``` # Check disk usage on nodes kubectl get nodes -o custom-columns=NAME:.metadata.name,DISK-PRESSURE:.status.conditions[?(@.type==\"DiskPressure\")].status # Find largest log files kubectl debug node/<node-name> -it --image=busybox -- du -sh /var/log/containers/* | sort -h | tail -10 # Check specific pod log size kubectl debug node/<node-name> -it --image=busybox -- ls -lah /var/log/containers/<pod-name>* ``` **Fix:** ``` # Reduce log level in application kubectl set env deployment/<deployment-name> LOG_LEVEL=INFO # Configure log rotation via containerd kubectl debug node/<node-name> -it --image=busybox -- sh -c ' cat > /etc/containerd/config.toml << EOF [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true [plugins."io.containerd.grpc.v1.cri"] max_container_log_line_size = 16384 [plugins."io.containerd.grpc.v1.cri".registry] [plugins."io.containerd.grpc.v1.cri".registry.mirrors] [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"] endpoint = ["https://registry-1.docker.io"] EOF systemctl restart containerd ' # Clean up large log files immediately kubectl debug node/<node-name> -it --image=busybox -- sh -c 'truncate -s 0 /var/log/containers/<large-log-file>' ``` ### 11\.4 Security and RBAC Issues #### 11\.4\.1 “aws\-auth ConfigMap corruption causing cluster lockout” **Symptoms:** - Unable to access cluster with existing IAM roles/users - `kubectl`commands return “Unauthorized” errors - Previously working IAM authentication suddenly fails - New nodes unable to join cluster **Root cause:**Malformed YAML in aws\-auth ConfigMap due to indentation errors\. The aws\-auth ConfigMap is the single point of failure for EKS cluster access\. A single space or tab error can lock out all users\. **Diagnosis:** ``` # Check current aws-auth ConfigMap kubectl get configmap aws-auth -n kube-system -o yaml # Validate YAML syntax kubectl get configmap aws-auth -n kube-system -o yaml | yq eval '.' # Check for common issues kubectl get configmap aws-auth -n kube-system -o yaml | grep -E "^\s*-\s*rolearn|^\s*-\s*userarn" | cat -A ``` **Emergency access recovery:** ``` # If locked out, use cluster creator credentials or root user aws sts get-caller-identity # Access via AWS Console EKS service or CloudShell # Or use emergency break-glass role if configured ``` **Fix malformed aws\-auth:** ``` # Backup current ConfigMap first kubectl get configmap aws-auth -n kube-system -o yaml > aws-auth-backup.yaml # Fix common indentation issues kubectl patch configmap aws-auth -n kube-system -p '{ "data": { "mapRoles": "- rolearn: arn:aws:iam::123456789012:role/eksctl-cluster-nodegroup-NodeInstanceRole\n username: system:node:\n groups:\n - system:bootstrappers\n - system:nodes\n- rolearn: arn:aws:iam::123456789012:role/EKSAdminRole\n username: admin\n groups:\n - system:masters", "mapUsers": "- userarn: arn:aws:iam::123456789012:user/developer\n username: developer\n groups:\n - developers" } }' # Validate the fix kubectl auth can-i '*' '*' --as=arn:aws:iam::123456789012:role/EKSAdminRole ``` **Correct aws\-auth format:** ``` apiVersion: v1 kind: ConfigMap metadata: name: aws-auth namespace: kube-system data: mapRoles: | - rolearn: arn:aws:iam::123456789012:role/eksctl-cluster-nodegroup-NodeInstanceRole username: system:node: groups: - system:bootstrappers - system:nodes - rolearn: arn:aws:iam::123456789012:role/EKSAdminRole username: admin groups: - system:masters mapUsers: | - userarn: arn:aws:iam::123456789012:user/developer username: developer groups: - developers ``` **Common aws\-auth mistakes:** ``` # WRONG - Mixed tabs and spaces mapRoles: | - rolearn: arn:aws:iam::123456789012:role/NodeRole username: system:node: # Tab here groups: # Spaces here - system:nodes # WRONG - Incorrect indentation mapRoles: | - rolearn: arn:aws:iam::123456789012:role/NodeRole # Should be indented username: system:node: # WRONG - Missing pipe character mapRoles: # Missing | - rolearn: arn:aws:iam::123456789012:role/NodeRole # WRONG - Extra characters mapRoles: | - rolearn: arn:aws:iam::123456789012:role/NodeRole, # Comma at end username: system:node: ``` **Prevention and monitoring:** ``` # Validate before applying yq eval '.data.mapRoles' aws-auth.yaml yq eval '.data.mapUsers' aws-auth.yaml # Set up monitoring for aws-auth changes kubectl create -f - <<EOF apiVersion: v1 kind: Event metadata: name: aws-auth-monitor namespace: kube-system EOF # Use eksctl for safer aws-auth management eksctl create iamidentitymapping \ --cluster production-cluster \ --region us-west-2 \ --arn arn:aws:iam::123456789012:role/EKSAdminRole \ --group system:masters \ --username admin # Always backup before changes kubectl get configmap aws-auth -n kube-system -o yaml > "aws-auth-backup-$(date +%Y%m%d-%H%M%S).yaml" ``` #### 11\.4\.2 “Pod Security Policy not enforcing restrictions” **Symptoms:** - Privileged containers running despite PSP configuration - Security policies being bypassed - Containers running as root when they shouldn’t **Root cause:**Missing admission controller or misconfigured PSP\. **Diagnosis:** ``` # Check if PSP admission controller is enabled (EKS doesn't enable by default) kubectl get pods -n kube-system kube-apiserver-* -o yaml | grep -A 5 admission-control # Check existing PSPs kubectl get psp # Check pod security context kubectl get pod <pod-name> -o yaml | grep -A 10 securityContext # Check if pod is using PSP kubectl describe pod <pod-name> | grep -i "psp\|security" ``` **Fix:** ``` # For EKS, use Pod Security Standards instead of PSP kubectl label namespace <namespace> pod-security.kubernetes.io/enforce=restricted kubectl label namespace <namespace> pod-security.kubernetes.io/audit=restricted kubectl label namespace <namespace> pod-security.kubernetes.io/warn=restricted # Create restrictive security context in deployment kubectl patch deployment <deployment> -p '{ "spec": { "template": { "spec": { "securityContext": { "runAsNonRoot": true, "runAsUser": 1000, "fsGroup": 2000 }, "containers": [{ "name": "<container>", "securityContext": { "allowPrivilegeEscalation": false, "readOnlyRootFilesystem": true, "capabilities": { "drop": ["ALL"] } } }] } } } }' ``` #### 11\.4\.3 “RBAC permissions too broad or too restrictive” **Symptoms:** - Users can access resources they shouldn’t - Service accounts failing with permission errors - Applications unable to perform required operations **Diagnosis:** ``` # Check current user permissions kubectl auth can-i --list # Check service account permissions kubectl auth can-i --list --as=system:serviceaccount:<namespace>:<service-account> # Check role bindings for user/service account kubectl get rolebindings,clusterrolebindings --all-namespaces -o wide | grep <user-or-sa> # Test specific permission kubectl auth can-i create pods --as=system:serviceaccount:<namespace>:<service-account> ``` **Fix:** ``` # Create minimal role for service account kubectl apply -f - <<EOF apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: <namespace> name: <app>-role rules: - apiGroups: [""] resources: ["pods", "configmaps"] verbs: ["get", "list", "watch"] - apiGroups: ["apps"] resources: ["deployments"] verbs: ["get", "list", "patch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: <app>-binding namespace: <namespace> subjects: - kind: ServiceAccount name: <service-account> namespace: <namespace> roleRef: kind: Role name: <app>-role apiGroup: rbac.authorization.k8s.io EOF # Remove overly broad cluster role binding kubectl delete clusterrolebinding <overly-broad-binding> ``` #### 11\.4\.4 “Secrets exposed in environment variables or logs” **Symptoms:** - Sensitive data visible in pod environment - Secrets appearing in application logs - Configuration containing plaintext credentials **Diagnosis:** ``` # Check environment variables in running pod kubectl exec <pod-name> -- env | grep -i -E "(password|secret|key|token)" # Check if secrets are mounted as files vs env vars kubectl describe pod <pod-name> | grep -A 10 -B 5 -i secret # Check recent logs for exposed secrets kubectl logs <pod-name> | grep -i -E "(password|secret|key|token)" | head -5 ``` **Fix:** ``` # Mount secrets as files instead of env vars kubectl patch deployment <deployment> -p '{ "spec": { "template": { "spec": { "containers": [{ "name": "<container>", "volumeMounts": [{ "name": "secret-volume", "mountPath": "/etc/secrets", "readOnly": true }] }], "volumes": [{ "name": "secret-volume", "secret": { "secretName": "<secret-name>", "defaultMode": 256 } }] } } } }' # Remove secret from environment variables kubectl patch deployment <deployment> -p '{ "spec": { "template": { "spec": { "containers": [{ "name": "<container>", "env": null }] } } } }' # Use External Secrets Operator for better secret management kubectl apply -f - <<EOF apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: <app>-secret namespace: <namespace> spec: refreshInterval: 1h secretStoreRef: name: aws-secrets-manager kind: SecretStore target: name: <app>-secret creationPolicy: Owner data: - secretKey: password remoteRef: key: <secret-arn> property: password EOF ``` #### 11\.4\.5 “PodDisruptionBudget blocking evictions” **Symptoms:** - Node drain operations hang - Cluster autoscaler cannot scale down - Rolling updates stuck **Diagnosis:** ``` # Check PDB status kubectl get pdb -A # Check which pods are blocking eviction kubectl describe pdb <pdb-name> ``` **Fix:** ``` # Temporarily relax PDB kubectl patch pdb <pdb-name> -p '{"spec":{"minAvailable":1}}' # Or scale up replicas to meet PDB requirements kubectl scale deployment <deployment> --replicas=5 # Complete maintenance, then restore PDB kubectl patch pdb <pdb-name> -p '{"spec":{"minAvailable":3}}' ``` --- ### 11\.5 Egress and Service Discovery Failures #### 11\.5\.1 “Pods can’t reach external services” **Symptoms:** - Timeouts connecting to external APIs - DNS resolution works but connections fail - Intermittent connectivity issues **Diagnosis:** ``` # Test connectivity from pod kubectl exec -it <pod> -- curl -v https://api.external.com # Check NAT Gateway health aws ec2 describe-nat-gateways --nat-gateway-ids <nat-gw-id> # Check security group rules aws ec2 describe-security-groups --group-ids <sg-id> ``` **Common fixes:** **NAT Gateway issues:** ``` # Check NAT Gateway metrics aws cloudwatch get-metric-statistics \ --namespace AWS/NATGateway \ --metric-name PacketsDropCount \ --dimensions Name=NatGatewayId,Value=<nat-gw-id> \ --start-time $(date -d '1 hour ago' -Iseconds) \ --end-time $(date -Iseconds) \ --period 300 \ --statistics Sum ``` **Security group blocking traffic:** ``` # Add egress rule for HTTPS aws ec2 authorize-security-group-egress \ --group-id <sg-id> \ --protocol tcp \ --port 443 \ --cidr 0.0.0.0/0 ``` #### 11\.5\.2 “Service discovery not working” **Symptoms:** - Pods can’t reach other services by name - `nslookup`fails for service names - Intermittent DNS failures **Diagnosis:** ``` # Test DNS resolution kubectl exec -it <pod> -- nslookup kubernetes.default.svc.cluster.local # Check CoreDNS health kubectl -n kube-system get pods -l k8s-app=kube-dns kubectl -n kube-system logs -l k8s-app=kube-dns --tail=100 # Check service endpoints kubectl get endpoints <service-name> ``` **Fixes:** **CoreDNS not ready:** ``` # Scale up CoreDNS kubectl -n kube-system scale deployment coredns --replicas=3 # Check CoreDNS configuration kubectl -n kube-system get configmap coredns -o yaml ``` **Service has no endpoints:** ``` # Check if pods are ready kubectl get pods -l app=<service-selector> # Check service selector kubectl describe service <service-name> # Fix selector mismatch kubectl patch service <service-name> -p '{"spec":{"selector":{"app":"correct-label"}}}' ``` --- ### 11\.6 Storage Issues #### 11\.6\.1 “Pods stuck in ContainerCreating due to volume mount failures” **Symptoms:** - Pods stuck in ContainerCreating state - Events show volume mount errors - StatefulSet pods fail to start **Diagnosis:** ``` # Check pod events kubectl describe pod <pod> # Check PVC status kubectl get pvc # Check volume attachment kubectl get volumeattachment ``` **Common fixes:** **EBS volume in wrong AZ:** ``` # Check pod and volume zones kubectl get pod <pod> -o wide kubectl describe pv <pv-name> | grep zone # Delete pod to reschedule in correct AZ kubectl delete pod <pod> ``` **CSI driver issues:** ``` # Check CSI driver health kubectl -n kube-system get pods -l app=ebs-csi-controller kubectl -n kube-system logs -l app=ebs-csi-controller # Restart CSI driver if needed kubectl -n kube-system rollout restart deployment ebs-csi-controller ``` #### 11\.6\.2 “PVC stuck in Pending state” **Symptoms:** - PVC remains in Pending status - No PV created for dynamic provisioning - Storage class issues **Diagnosis:** ``` # Check PVC events kubectl describe pvc <pvc-name> # Check storage class kubectl describe storageclass <storage-class> # Check CSI provisioner logs kubectl -n kube-system logs -l app=ebs-csi-controller ``` **Fixes:** **Storage class misconfiguration:** ``` # Check available storage classes kubectl get storageclass # Create correct storage class kubectl apply -f - <<EOF apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: gp3 provisioner: ebs.csi.aws.com parameters: type: gp3 iops: "3000" throughput: "125" allowVolumeExpansion: true reclaimPolicy: Delete EOF ``` --- ### 11\.7 Application Failures #### 11\.7\.1 “Pods crashing with OOMKilled” **Symptoms:** - Pods restart frequently - Exit code 137 \(OOMKilled\) - Application performance degradation **Diagnosis:** ``` # Check pod resource usage kubectl top pod <pod> # Check pod events for OOM kubectl describe pod <pod> | grep -i oom # Check memory limits kubectl get pod <pod> -o yaml | grep -A 5 resources ``` **Fixes:** **Increase memory limits:** ``` # Update deployment with higher memory limits kubectl patch deployment <deployment> -p '{"spec":{"template":{"spec":{"containers":[{"name":"<container>","resources":{"limits":{"memory":"2Gi"},"requests":{"memory":"1Gi"}}}]}}}}' ``` **Optimize application memory usage:** ``` # Check for memory leaks kubectl exec -it <pod> -- ps aux --sort=-%mem | head # Enable memory profiling (application-specific) kubectl set env deployment/<deployment> GOMAXPROCS=2 GOMEMLIMIT=1GiB ``` #### 11\.7\.2 “Readiness probe failures causing traffic issues” **Symptoms:** - Pods not receiving traffic - Service endpoints empty - Load balancer health checks failing **Diagnosis:** ``` # Check pod readiness kubectl get pods -o wide # Check readiness probe configuration kubectl describe pod <pod> | grep -A 10 "Readiness" # Test probe endpoint manually kubectl exec -it <pod> -- curl localhost:8080/health ``` **Fixes:** **Adjust probe timing:** ``` # Update probe configuration kubectl patch deployment <deployment> -p '{"spec":{"template":{"spec":{"containers":[{"name":"<container>","readinessProbe":{"initialDelaySeconds":30,"periodSeconds":10,"timeoutSeconds":5,"failureThreshold":3}}]}}}}' ``` **Fix probe endpoint:** ``` # Check if health endpoint is correct kubectl exec -it <pod> -- netstat -tlnp | grep :8080 # Update probe path if needed kubectl patch deployment <deployment> -p '{"spec":{"template":{"spec":{"containers":[{"name":"<container>","readinessProbe":{"httpGet":{"path":"/healthz","port":8080}}}]}}}}' ``` --- ### 11\.8 Cluster\-Level Issues #### 11\.8\.1 “API server timeouts and high latency” **Symptoms:** - `kubectl`commands timeout - High API server response times - Cluster operations slow **Diagnosis:** ``` # Check API server metrics kubectl get --raw /metrics | grep apiserver_request_duration # Check etcd health kubectl get --raw /healthz/etcd # Check for resource pressure kubectl top nodes ``` **Fixes:** **Reduce API server load:** ``` # Find clients making excessive requests kubectl get events --sort-by='.lastTimestamp' | tail -20 # Scale down chatty controllers kubectl scale deployment <noisy-controller> --replicas=0 # Increase API server resources (managed by AWS for EKS) # Contact AWS support if persistent ``` #### 11\.8\.2 “Cluster autoscaler not scaling” **Symptoms:** - Pending pods but no new nodes - Cluster autoscaler logs show errors - Node groups not scaling up **Diagnosis:** ``` # Check cluster autoscaler logs kubectl -n kube-system logs -l app=cluster-autoscaler # Check node group configuration aws eks describe-nodegroup --cluster-name <cluster> --nodegroup-name <nodegroup> # Check IAM permissions aws sts get-caller-identity ``` **Fixes:** **IAM permission issues:** ``` # Check autoscaler service account kubectl -n kube-system describe sa cluster-autoscaler # Verify IAM role has required permissions aws iam get-role-policy --role-name <autoscaler-role> --policy-name <policy-name> ``` **Node group limits:** ``` # Increase node group max size aws eks update-nodegroup-config \ --cluster-name <cluster> \ --nodegroup-name <nodegroup> \ --scaling-config minSize=2,maxSize=20,desiredSize=5 ``` --- ### 11\.9 Performance Issues #### 11\.9\.1 “High CPU throttling affecting performance” **Symptoms:** - Application response times high - CPU usage appears low but performance poor - Intermittent slowdowns **Diagnosis:** ``` # Check CPU throttling kubectl exec -it <pod> -- cat /sys/fs/cgroup/cpu/cpu.stat | grep throttled # Check CPU limits vs requests kubectl describe pod <pod> | grep -A 10 Limits # Monitor CPU usage patterns kubectl top pod <pod> --containers ``` **Fixes:** **Adjust CPU limits:** ``` # Remove CPU limits for CPU-intensive workloads kubectl patch deployment <deployment> -p '{"spec":{"template":{"spec":{"containers":[{"name":"<container>","resources":{"limits":{"cpu":null}}}]}}}}' # Or increase CPU limits kubectl patch deployment <deployment> -p '{"spec":{"template":{"spec":{"containers":[{"name":"<container>","resources":{"limits":{"cpu":"2000m"}}}]}}}}' ``` #### 11\.9\.2 “Disk I/O bottlenecks” **Symptoms:** - High disk wait times - Application timeouts during disk operations - EBS volume performance issues **Diagnosis:** ``` # Check disk I/O from pod kubectl exec -it <pod> -- iostat -x 1 5 # Check EBS volume metrics aws cloudwatch get-metric-statistics \ --namespace AWS/EBS \ --metric-name VolumeReadOps \ --dimensions Name=VolumeId,Value=<volume-id> \ --start-time $(date -d '1 hour ago' -Iseconds) \ --end-time $(date -Iseconds) \ --period 300 \ --statistics Average ``` **Fixes:** **Upgrade to higher IOPS volume:** ``` # Modify EBS volume type aws ec2 modify-volume \ --volume-id <volume-id> \ --volume-type gp3 \ --iops 10000 ``` --- ### 11\.10 Emergency Procedures #### 11\.10\.1 “Cluster completely unresponsive” **Immediate actions:** ``` # 1. Check if it's a regional AWS issue curl -s https://status.aws.amazon.com/ # 2. Try different kubectl context/region kubectl config use-context <backup-context> # 3. Check EKS cluster status aws eks describe-cluster --name <cluster> --region <region> # 4. If control plane is down, activate DR procedures # Switch DNS to DR region aws route53 change-resource-record-sets \ --hosted-zone-id <zone-id> \ --change-batch file://failover.json ``` #### 11\.10\.2 “Mass pod failures across cluster” **Immediate actions:** ``` # 1. Stop any ongoing deployments kubectl rollout pause deployment/<deployment> # 2. Check for cluster-wide issues kubectl get nodes kubectl -n kube-system get pods # 3. Check recent changes kubectl get events --sort-by='.lastTimestamp' | tail -50 # 4. Rollback recent changes if identified kubectl rollout undo deployment/<deployment> ``` --- ### 11\.11 Quick Reference Commands #### 11\.11\.1 Essential Debugging Commands ``` # Pod debugging kubectl describe pod <pod> kubectl logs <pod> --previous kubectl exec -it <pod> -- /bin/bash # Service debugging kubectl describe service <service> kubectl get endpoints <service> kubectl port-forward service/<service> 8080:80 # Node debugging kubectl describe node <node> kubectl top node <node> kubectl get pods -A -o wide --field-selector spec.nodeName=<node> # Cluster debugging kubectl cluster-info kubectl get events --sort-by='.lastTimestamp' kubectl get all -A | grep -v Running ``` #### 11\.11\.2 Emergency Recovery Commands ``` # Force delete stuck resources kubectl delete pod <pod> --force --grace-period=0 kubectl patch pvc <pvc> -p '{"metadata":{"finalizers":null}}' # Emergency scaling kubectl scale deployment <deployment> --replicas=0 kubectl scale deployment <deployment> --replicas=3 # Quick rollback kubectl rollout undo deployment/<deployment> kubectl rollout status deployment/<deployment> ``` --- This troubleshooting cookbook provides step\-by\-step solutions for the most common EKS production failures\. Each scenario is designed to be used under pressure, with clear symptoms, diagnosis steps, and proven fixes\. The key is systematic diagnosis before attempting fixes, and having emergency procedures ready for critical situations\. ## 12\. EKS at scale Scale introduces failure modes that simply don’t exist in smaller clusters\. Hundreds of nodes, thousands of pods, multiple clusters — different operational patterns, different failure scenarios\. --- ### 12\.1 Multi\-Cluster Patterns #### 12\.1\.1 When to Use Multiple Clusters **Cluster boundaries that make sense:** - **Environment isolation**\(prod/staging/dev\) - **Team isolation**\(different blast radius requirements\) - **Compliance boundaries**\(PCI/SOX/HIPAA workloads\) - **Geographic distribution**\(latency/data residency\) - **Scale limits**\(approaching EKS/EC2 quotas\) **Anti\-patterns to avoid:** - One cluster per microservice \(operational overhead\) - Clusters for cost allocation \(use namespaces \+ tagging\) - Clusters for different Kubernetes versions \(use node groups\) #### 12\.1\.2 Multi\-Cluster Networking **Cross\-cluster service communication:** ``` # External DNS for cross-cluster service discovery apiVersion: v1 kind: Service metadata: name: user-service annotations: external-dns.alpha.kubernetes.io/hostname: user-service.prod.internal spec: type: LoadBalancer selector: app: user-service ports: - port: 80 targetPort: 8080 ``` **VPC peering for cluster connectivity:** ``` # Create VPC peering between clusters aws ec2 create-vpc-peering-connection \ --vpc-id vpc-cluster1 \ --peer-vpc-id vpc-cluster2 \ --peer-region us-west-2 # Update route tables for cross-cluster communication aws ec2 create-route \ --route-table-id rtb-cluster1 \ --destination-cidr-block 10.1.0.0/16 \ --vpc-peering-connection-id pcx-12345 ``` #### 12\.1\.3 Multi\-Cluster Management **Centralized cluster management with ArgoCD:** ``` apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: production-workloads namespace: argocd spec: project: default source: repoURL: https://github.com/company/k8s-manifests targetRevision: HEAD path: production destination: server: https://prod-cluster.us-west-2.eks.amazonaws.com namespace: production syncPolicy: automated: prune: true selfHeal: true ``` **Cluster inventory management:** ``` #!/bin/bash # Multi-cluster inventory script CLUSTERS=( "prod-us-west-2" "prod-us-east-1" "staging-us-west-2" "dev-us-west-2" ) for cluster in "${CLUSTERS[@]}"; do echo "=== Cluster: $cluster ===" # Update kubeconfig aws eks update-kubeconfig --name $cluster --region ${cluster##*-} # Get cluster info echo "Nodes: $(kubectl get nodes --no-headers | wc -l)" echo "Pods: $(kubectl get pods -A --no-headers | wc -l)" echo "Version: $(kubectl version --short --client=false | grep Server)" # Check critical components kubectl get pods -n kube-system | grep -E "(coredns|aws-node|kube-proxy)" | grep -v Running && echo "⚠️ System pods not ready" echo "" done ``` --- ### 12\.2 Large Node Pool Management #### 12\.2\.1 Node Pool Strategies at Scale **Diversified instance types for resilience:** ``` apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: large-scale-cluster nodeGroups: - name: general-purpose instanceTypes: - m5.large - m5.xlarge - m5a.large - m5a.xlarge - m4.large - m4.xlarge minSize: 10 maxSize: 500 desiredCapacity: 50 spot: true labels: node-class: general-purpose ``` **Dedicated node pools for specific workloads:** ``` # High-memory workloads - name: memory-optimized instanceTypes: ["r5.xlarge", "r5.2xlarge"] minSize: 2 maxSize: 50 labels: node-class: memory-optimized taints: - key: workload-type value: memory-intensive effect: NoSchedule # GPU workloads - name: gpu-nodes instanceTypes: ["p3.2xlarge", "p3.8xlarge"] minSize: 0 maxSize: 20 labels: node-class: gpu taints: - key: nvidia.com/gpu value: "true" effect: NoSchedule ``` #### 12\.2\.2 Node Lifecycle Management at Scale **Automated node replacement:** ``` #!/bin/bash # Automated node replacement for large clusters # Find nodes older than 30 days OLD_NODES=$(kubectl get nodes -o json | jq -r '.items[] | select(.metadata.creationTimestamp | fromdateiso8601 < (now - 30*24*3600)) | .metadata.name') for node in $OLD_NODES; do echo "Replacing old node: $node" # Cordon node kubectl cordon $node # Drain node with timeout timeout 600 kubectl drain $node --ignore-daemonsets --delete-emptydir-data --force # Get instance ID and terminate INSTANCE_ID=$(kubectl get node $node -o jsonpath='{.spec.providerID}' | cut -d'/' -f5) aws ec2 terminate-instances --instance-ids $INSTANCE_ID # Wait for replacement sleep 300 done ``` **Node health monitoring:** ``` apiVersion: apps/v1 kind: DaemonSet metadata: name: node-health-monitor spec: selector: matchLabels: app: node-health-monitor template: spec: hostNetwork: true hostPID: true containers: - name: monitor image: node-health-monitor:latest securityContext: privileged: true volumeMounts: - name: proc mountPath: /host/proc readOnly: true - name: sys mountPath: /host/sys readOnly: true env: - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName command: - /bin/bash - -c - | while true; do # Check disk space DISK_USAGE=$(df /host/proc/1/root | tail -1 | awk '{print $5}' | sed 's/%//') if [ $DISK_USAGE -gt 85 ]; then kubectl annotate node $NODE_NAME node.kubernetes.io/disk-pressure=true --overwrite fi # Check memory pressure MEM_AVAILABLE=$(cat /host/proc/meminfo | grep MemAvailable | awk '{print $2}') MEM_TOTAL=$(cat /host/proc/meminfo | grep MemTotal | awk '{print $2}') MEM_USAGE=$(echo "scale=2; (1 - $MEM_AVAILABLE/$MEM_TOTAL) * 100" | bc) if (( $(echo "$MEM_USAGE > 90" | bc -l) )); then kubectl annotate node $NODE_NAME node.kubernetes.io/memory-pressure=true --overwrite fi sleep 60 done volumes: - name: proc hostPath: path: /proc - name: sys hostPath: path: /sys ``` --- ### 12\.3 High\-Density Pod Scheduling #### 12\.3\.1 Pod Density Optimization **Understanding EKS pod limits:** ``` # Check maximum pods per node type curl -s https://raw.githubusercontent.com/aws/amazon-vpc-cni-k8s/refs/heads/master/misc/eni-max-pods.txt | grep -E "(m5|c5|r5)" # Current pod density kubectl get nodes -o custom-columns=NAME:.metadata.name,PODS:.status.capacity.pods,RUNNING:.status.allocatable.pods ``` **High\-density scheduling configuration:** ``` apiVersion: v1 kind: ConfigMap metadata: name: amazon-vpc-cni namespace: kube-system data: enable-prefix-delegation: "true" # Increases pod density warm-prefix-target: "1" warm-ip-target: "5" minimum-ip-target: "10" ``` #### 12\.3\.2 Resource Fragmentation Prevention **Pod resource standardization:** ``` # Standard resource classes apiVersion: v1 kind: LimitRange metadata: name: standard-resources spec: limits: - type: Container default: cpu: 500m memory: 512Mi defaultRequest: cpu: 100m memory: 128Mi min: cpu: 50m memory: 64Mi max: cpu: 4 memory: 8Gi ``` **Anti\-fragmentation scheduling:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: high-density-app spec: replicas: 100 template: spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: ["high-density-app"] topologyKey: kubernetes.io/hostname containers: - name: app image: app:latest resources: requests: cpu: 100m memory: 128Mi limits: cpu: 200m memory: 256Mi ``` --- ### 12\.4 API Server Performance at Scale #### 12\.4\.1 API Server Load Management **Client\-side rate limiting:** ``` # Configure kubectl rate limiting export KUBECTL_QPS=50 export KUBECTL_BURST=100 # For applications using client-go kubectl patch deployment controller-manager -p '{"spec":{"template":{"spec":{"containers":[{"name":"manager","env":[{"name":"QPS","value":"20"},{"name":"BURST","value":"30"}]}]}}}}' ``` **Watch optimization:** ``` # Efficient controller pattern apiVersion: apps/v1 kind: Deployment metadata: name: efficient-controller spec: template: spec: containers: - name: controller image: controller:latest env: - name: WATCH_NAMESPACE value: "production" # Limit watch scope - name: RESYNC_PERIOD value: "10m" # Reduce resync frequency - name: WORKER_COUNT value: "5" # Limit concurrent workers ``` #### 12\.4\.2 etcd Performance Optimization **etcd monitoring at scale:** ``` # Monitor etcd performance metrics kubectl get --raw /metrics | grep etcd_request_duration_seconds # Check etcd database size kubectl get --raw /metrics | grep etcd_mvcc_db_total_size_in_bytes # Monitor watch streams kubectl get --raw /metrics | grep etcd_network_client_grpc_received_bytes_total ``` --- ### 12\.5 Cross\-Region and Multi\-Region Patterns #### 12\.5\.1 Active\-Active Multi\-Region Setup **Regional cluster configuration:** ``` # US-West-2 cluster apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: prod-us-west-2 region: us-west-2 tags: Environment: production Region: us-west-2 Role: primary # US-East-1 cluster apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: prod-us-east-1 region: us-east-1 tags: Environment: production Region: us-east-1 Role: secondary ``` **Cross\-region service mesh:** ``` # Istio multi-cluster configuration apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: cross-cluster-gateway spec: selector: istio: eastwestgateway servers: - port: number: 15443 name: tls protocol: TLS tls: mode: ISTIO_MUTUAL hosts: - "*.local" ``` #### 12\.5\.2 Global Load Balancing **Route 53 health checks for multi\-region:** ``` # Create health check for each region aws route53 create-health-check \ --caller-reference "eks-us-west-2-$(date +%s)" \ --health-check-config Type=HTTPS,ResourcePath=/health,FullyQualifiedDomainName=api-us-west-2.example.com,Port=443 # Create weighted routing policy aws route53 change-resource-record-sets \ --hosted-zone-id Z123456789 \ --change-batch file://multi-region-routing.json ``` --- ### 12\.6 Scaling Challenges and Solutions #### 12\.6\.1 Cluster Autoscaler at Scale **Multi\-AZ autoscaling configuration:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: cluster-autoscaler namespace: kube-system spec: template: spec: containers: - name: cluster-autoscaler image: k8s.gcr.io/autoscaling/cluster-autoscaler:v1.21.0 command: - ./cluster-autoscaler - --v=4 - --stderrthreshold=info - --cloud-provider=aws - --skip-nodes-with-local-storage=false - --expander=least-waste - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/production-cluster - --balance-similar-node-groups - --skip-nodes-with-system-pods=false - --scale-down-delay-after-add=10m - --scale-down-unneeded-time=10m - --max-node-provision-time=15m ``` #### 12\.6\.2 Karpenter for Large\-Scale Provisioning **Karpenter configuration for scale:** ``` apiVersion: karpenter.sh/v1alpha5 kind: Provisioner metadata: name: large-scale-provisioner spec: limits: resources: cpu: 10000 memory: 10000Gi requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand"] - key: kubernetes.io/arch operator: In values: ["amd64"] - key: node.kubernetes.io/instance-type operator: In values: ["m5.large", "m5.xlarge", "m5.2xlarge", "c5.large", "c5.xlarge", "c5.2xlarge"] providerRef: name: large-scale-nodepool ttlSecondsAfterEmpty: 30 ttlSecondsUntilExpired: 2592000 # 30 days ``` --- ### 12\.7 Operational Patterns at Scale #### 12\.7\.1 Centralized Logging and Monitoring **Fluent Bit configuration for high\-throughput:** ``` apiVersion: v1 kind: ConfigMap metadata: name: fluent-bit-config data: fluent-bit.conf: | [SERVICE] Flush 5 Log_Level info Daemon off Parsers_File parsers.conf HTTP_Server On HTTP_Listen 0.0.0.0 HTTP_Port 2020 storage.path /var/fluent-bit/state/flb-storage/ storage.sync normal storage.checksum off storage.backlog.mem_limit 50M [INPUT] Name tail Path /var/log/containers/*.log multiline.parser docker, cri Tag kube.* Mem_Buf_Limit 50MB Skip_Long_Lines On Skip_Empty_Lines On storage.type filesystem Refresh_Interval 10 ``` #### 12\.7\.2 GitOps at Scale **ArgoCD application\-of\-applications pattern:** ``` apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: production-apps namespace: argocd spec: project: default source: repoURL: https://github.com/company/k8s-apps targetRevision: HEAD path: production/applications destination: server: https://kubernetes.default.svc namespace: argocd syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true ``` --- #### 12\.8\.1 “Too many pods per node” Failure **Symptoms:** - Pods stuck in Pending with “Too many pods” error - Node capacity reached but resources available **Diagnosis:** ``` # Check pod limits per node kubectl describe node <node> | grep -A 10 "Allocatable" # Check current pod count kubectl get pods -A -o wide | grep <node> | wc -l ``` **Solutions:** ``` # Enable prefix delegation for higher pod density kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true # Or use larger instance types aws eks update-nodegroup-config \ --cluster-name production-cluster \ --nodegroup-name primary \ --instance-types m5.xlarge ``` #### 12\.8\.2 “API server overwhelmed” Failure **Symptoms:** - kubectl commands timeout - High API server CPU/memory - etcd performance degradation **Diagnosis:** ``` # Check API server metrics kubectl get --raw /metrics | grep apiserver_request_total # Check for excessive watch connections kubectl get --raw /metrics | grep apiserver_registered_watchers ``` **Solutions:** ``` # Implement client-side rate limiting kubectl patch deployment <controller> -p '{"spec":{"template":{"spec":{"containers":[{"name":"controller","env":[{"name":"QPS","value":"10"},{"name":"BURST","value":"15"}]}]}}}}' # Scale down chatty controllers kubectl scale deployment <noisy-controller> --replicas=1 ``` --- ## Appendices ### Appendix A: Reference Materials and Cheat Sheets #### A\.1 Essential kubectl Commands for EKS Troubleshooting **Pod debugging:** ``` # Get pod details with events kubectl describe pod <pod-name> # Get logs from previous container instance kubectl logs <pod-name> --previous # Get logs from specific container in multi-container pod kubectl logs <pod-name> -c <container-name> # Execute commands in pod kubectl exec -it <pod-name> -- /bin/bash # Port forward for local debugging kubectl port-forward pod/<pod-name> 8080:80 ``` **Service and networking:** ``` # Check service endpoints kubectl get endpoints <service-name> # Debug service connectivity kubectl run debug-pod --image=nicolaka/netshoot --rm -it -- bash # Check DNS resolution kubectl exec -it <pod> -- nslookup kubernetes.default.svc.cluster.local # Test service connectivity kubectl exec -it <pod> -- curl <service-name>.<namespace>.svc.cluster.local ``` **Node and cluster debugging:** ``` # Get node resource usage kubectl top nodes # Describe node conditions and capacity kubectl describe node <node-name> # Get all pods on a specific node kubectl get pods -A -o wide --field-selector spec.nodeName=<node-name> # Check cluster component health kubectl get componentstatuses kubectl cluster-info ``` #### A\.2 AWS CLI Commands for EKS Operations **Cluster management:** ``` # Update kubeconfig for EKS cluster aws eks update-kubeconfig --region <region> --name <cluster-name> # Get cluster information aws eks describe-cluster --name <cluster-name> # List all EKS clusters aws eks list-clusters # Get cluster endpoint and certificate aws eks describe-cluster --name <cluster-name> --query 'cluster.{endpoint:endpoint,ca:certificateAuthority.data}' ``` **Node group operations:** ``` # List node groups aws eks list-nodegroups --cluster-name <cluster-name> # Describe node group aws eks describe-nodegroup --cluster-name <cluster-name> --nodegroup-name <nodegroup-name> # Update node group scaling aws eks update-nodegroup-config --cluster-name <cluster-name> --nodegroup-name <nodegroup-name> --scaling-config minSize=2,maxSize=10,desiredSize=5 ``` **Add\-on management:** ``` # List available add-ons aws eks describe-addon-versions --kubernetes-version 1.28 # Install add-on aws eks create-addon --cluster-name <cluster-name> --addon-name vpc-cni --addon-version <version> # Update add-on aws eks update-addon --cluster-name <cluster-name> --addon-name vpc-cni --addon-version <new-version> ``` #### A\.3 Useful Tools and Utilities **Network debugging tools:** ``` # Install netshoot for comprehensive network debugging kubectl run netshoot --image=nicolaka/netshoot --rm -it -- bash # Inside netshoot pod: nslookup kubernetes.default.svc.cluster.local dig @8.8.8.8 google.com curl -v http://service-name.namespace.svc.cluster.local traceroute 8.8.8.8 ss -tulpn ``` **Resource analysis tools:** ``` # Install kube-capacity for resource analysis kubectl krew install resource-capacity kubectl resource-capacity # Install kubectl-top for enhanced resource monitoring kubectl krew install top kubectl top pod --sort-by=cpu ``` --- ### Appendix B: EKS Networking Deep Dive \- Pod Egress Traffic Flow This appendix provides a detailed technical analysis of how pods in EKS use link\-local addresses for egress traffic, using the`ip`command family to trace the complete network path\. #### B\.1 EKS Pod Networking Architecture **Understanding the network stack:** ``` [Pod Container] ↓ (veth pair) [Pod Network Namespace] ↓ (veth peer) [Node Root Network Namespace] ↓ (AWS VPC CNI routing) [ENI on EC2 Instance] ↓ (VPC routing) [Internet Gateway / NAT Gateway] ↓ [Internet] ``` #### B\.2 Tracing Pod Egress Traffic Step\-by\-Step **Step 1: Examine pod network namespace** ``` # Get pod details and node kubectl get pod <pod-name> -o wide # SSH to the node or use kubectl exec kubectl exec -it <pod-name> -- bash # Inside the pod, examine network configuration ip addr show ip route show ip route show table all ``` **Example output from inside pod:** ``` # ip addr show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN inet 127.0.0.1/8 scope host lo 3: eth0@if123: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP inet 10.0.1.45/32 scope global eth0 # Pod IP from VPC CIDR # ip route show default via 169.254.1.1 dev eth0 # Link-local gateway 169.254.1.1 dev eth0 scope link # Link-local route ``` **Step 2: Examine the veth pair connection** ``` # From the node (not inside pod), find the pod's network namespace docker ps | grep <pod-name> docker inspect <container-id> | grep NetworkMode # Find the veth pair ip link show | grep -A1 -B1 "veth" # Example output: # 123: veth12345@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue master eni-abc123 # link/ether 12:34:56:78:9a:bc brd ff:ff:ff:ff:ff:ff link-netnsid 1 ``` **Step 3: Trace the link\-local gateway \(169\.254\.1\.1\)** ``` # From the node, examine routing for link-local traffic ip route show table all | grep 169.254.1.1 # Check ARP table for link-local gateway ip neigh show | grep 169.254.1.1 # Example output: # 169.254.1.1 dev eni-abc123 lladdr 12:34:56:78:9a:bc REACHABLE ``` **The key insight:**The link\-local address 169\.254\.1\.1 is not a real gateway\. It’s a virtual gateway created by the AWS VPC CNI plugin that maps to the node’s primary ENI\. #### B\.3 AWS VPC CNI Link\-Local Implementation **Step 4: Understanding the CNI’s link\-local magic** ``` # Examine the ENI that serves as the "gateway" ip addr show eni-abc123 # Check routing rules for the ENI ip rule show ip route show table main ip route show table local # Example output: # ip addr show eni-abc123 # 4: eni-abc123: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP # inet 10.0.1.10/24 brd 10.0.1.255 scope global eni-abc123 # Node's primary IP ``` **Step 5: Tracing the actual egress path** ``` # From inside the pod, trace route to external destination kubectl exec -it <pod-name> -- traceroute 8.8.8.8 # From the node, capture traffic to see the actual path tcpdump -i eni-abc123 host 8.8.8.8 -n # Check iptables rules that handle the traffic iptables -t nat -L POSTROUTING -n -v iptables -t filter -L FORWARD -n -v ``` #### B\.4 The Complete Traffic Flow Analysis **Detailed packet flow for pod egress:** 1. **Pod generates traffic:**``` # Inside pod: curl https://api.github.com # Packet: src=10.0.1.45 (pod IP), dst=140.82.112.3 (github.com) # Route lookup: default via 169.254.1.1 dev eth0 ``` 2. **Traffic hits veth pair:**``` # Packet moves from pod's eth0 to node's veth123 # Node receives packet on veth123 interface # Source IP still: 10.0.1.45, Destination: 140.82.112.3 ``` 3. **Node routing decision:**\`\`\`bash## Node routing table lookup ip route get 140\.82\.112\.3 from 10\.0\.1\.45 ## Example output: ## 140\.82\.112\.3 from 10\.0\.1\.45 via 10\.0\.1\.1 dev eni\-abc123 src 10\.0\.1\.10 ## cache ``` 4. **SNAT (Source NAT) transformation:** ```bash # iptables POSTROUTING chain applies SNAT iptables -t nat -L POSTROUTING -n -v | grep -A5 -B5 "10.0.1.45" # Packet transformation: # Before SNAT: src=10.0.1.45 (pod IP), dst=140.82.112.3 # After SNAT: src=10.0.1.10 (node IP), dst=140.82.112.3 ``` 1. **Egress via ENI:**``` # Packet exits via node's primary ENI # AWS VPC routing takes over # If private subnet: packet goes to NAT Gateway # If public subnet: packet goes to Internet Gateway ``` #### B\.5 Advanced Debugging Techniques **Monitoring link\-local traffic:** ``` # Monitor ARP traffic for link-local gateway tcpdump -i any arp and host 169.254.1.1 # Monitor all traffic to/from link-local subnet tcpdump -i any net 169.254.0.0/16 # Check conntrack entries for pod traffic conntrack -L | grep 10.0.1.45 ``` **Understanding AWS VPC CNI’s iptables rules:** ``` # AWS VPC CNI creates specific iptables rules iptables -t nat -L AWS-VPC-CNI-POSTROUTING -n -v iptables -t filter -L AWS-VPC-CNI-FORWARD -n -v # Example rules: # Chain AWS-VPC-CNI-POSTROUTING (1 references) # target prot opt source destination # MASQUERADE all -- 10.0.1.0/24 !10.0.0.0/16 /* AWS VPC CNI */ ``` **Debugging external SNAT mode:** ``` # Check if external SNAT is enabled kubectl -n kube-system get daemonset aws-node -o yaml | grep AWS_VPC_K8S_CNI_EXTERNALSNAT # If external SNAT is disabled (default), node does SNAT # If external SNAT is enabled, NAT Gateway/Instance does SNAT ``` #### B\.6 Common Link\-Local Issues and Debugging **Issue 1: Link\-local gateway unreachable** ``` # Symptoms: Pod can't reach external services # Debug from inside pod: ping 169.254.1.1 # Should succeed ip route get 8.8.8.8 # Should show via 169.254.1.1 # If ping fails, check veth pair: # From node: ip link show | grep veth ethtool veth123 # Check if link is up ``` **Issue 2: ARP resolution failures** ``` # Check ARP table from pod's perspective kubectl exec -it <pod> -- ip neigh show # Should show: # 169.254.1.1 dev eth0 lladdr xx:xx:xx:xx:xx:xx REACHABLE # If FAILED or missing, check CNI plugin health: kubectl -n kube-system logs -l k8s-app=aws-node ``` **Issue 3: SNAT not working** ``` # Check if pod traffic is being SNATed correctly # From node, monitor outgoing traffic: tcpdump -i eni-abc123 src host 10.0.1.45 # Should see no traffic (SNATed) tcpdump -i eni-abc123 src host 10.0.1.10 # Should see SNATed traffic # Check iptables SNAT rules: iptables -t nat -L POSTROUTING -n -v | grep 10.0.1.45 ``` #### B\.7 Performance Implications of Link\-Local Routing **Understanding the overhead:** ``` # Measure latency through the link-local path kubectl exec -it <pod> -- ping -c 10 169.254.1.1 # Compare with direct node communication ping -c 10 10.0.1.10 # From another node # Monitor CPU usage of network processing top -p $(pgrep -f aws-node) ``` **Optimizing for high\-throughput workloads:** ``` # Check network buffer sizes kubectl exec -it <pod> -- cat /proc/sys/net/core/rmem_max kubectl exec -it <pod> -- cat /proc/sys/net/core/wmem_max # Monitor network interface statistics kubectl exec -it <pod> -- cat /proc/net/dev ``` This deep dive shows that the “link\-local gateway” at 169\.254\.1\.1 is actually a clever abstraction by the AWS VPC CNI\. It’s not a real gateway but a virtual endpoint that allows pods to send traffic to the node’s ENI through the veth pair, where iptables rules then handle SNAT and routing to the actual destination\. --- ### Appendix C: Prometheus queries and EKS limits > Tools and utilities are in[Appendix A\.3](https://samof76.space/kubernetes-in-anger.html#a3-useful-tools-and-utilities)to avoid duplication\. #### C\.2 Prometheus Queries for EKS Monitoring **Node health:** ``` # Node CPU usage 100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) # Node memory usage (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 # Node disk usage (1 - (node_filesystem_avail_bytes / node_filesystem_size_bytes)) * 100 ``` **Pod resource usage:** ``` # Pod CPU usage rate(container_cpu_usage_seconds_total[5m]) # Pod memory usage container_memory_working_set_bytes # Pod network I/O rate(container_network_receive_bytes_total[5m]) rate(container_network_transmit_bytes_total[5m]) ``` #### C\.3 Common EKS Limits and Quotas **EKS Service Limits:** - Clusters per region: 100 - Node groups per cluster: 30 - Nodes per node group: 450 - Fargate profiles per cluster: 10 **EC2 Instance Limits \(affects node groups\):** - Default vCPU limit varies by instance family - Spot instance limits separate from On\-Demand - Elastic IP addresses: 5 per region \(affects NAT Gateways\) **VPC Limits \(affects networking\):** - VPCs per region: 5 - Subnets per VPC: 200 - Route tables per VPC: 200 - Security groups per VPC: 2,500 --- This comprehensive appendix provides essential reference materials, deep technical analysis of EKS networking, and practical tools for day\-to\-day EKS operations\. The link\-local address deep dive reveals the sophisticated networking abstraction that makes EKS pod networking appear simple while handling complex VPC integration behind the scenes\.

Similar Articles

How Kubernetes Probes Work

Hacker News Top

This article explains how Kubernetes probes, including startup, readiness, and liveness probes, work to improve application resilience, with interactive demos demonstrating their functionality.

Scaling Kubernetes to 7,500 nodes

OpenAI Blog

OpenAI shares detailed lessons learned from scaling a single Kubernetes cluster to 7,500 nodes to support large machine learning workloads, covering networking, scheduling, and infrastructure challenges. The post builds on their earlier experience scaling to 2,500 nodes and aims to help the broader Kubernetes community.

Scaling Kubernetes to 2,500 nodes

OpenAI Blog

OpenAI shares infrastructure lessons from scaling Kubernetes to 2,500 nodes, detailing optimizations for container image pulls including kubelet configuration changes, Docker overlay2 migration, and preloading strategies to resolve Pending pod issues.