Hedronite · Ops Lesson · 01-Earth-DevOps / Kubernetes · Fri 2026-08-28

Kubernetes Rolling Updates on EKS — the surge that is not a second cluster

The new ReplicaSet is extra pods on this cluster. It is not another control plane.

Lesson Class: Ops (DevOps + Kubernetes + AWS + EKS + Rolling Update)
Cloud Referent: EKS Deployment rollingUpdate on one cluster. K8sUR3 Ch.9 p.159: maxSurge 100% is in-cluster blue/green. Rebalances after AKS 08-25 and GKE 08-22.
CKA Domain: Workloads App Lifecycle half: maxSurge / maxUnavailable. 08-16 closed the scheduler half.
Paired Dev: Python rollout census: list Deployments and owned ReplicaSets, do not patch
Paired Cert: CKA App Lifecycle + Q18 template patch that must stay available
Grounding: K8sUR3 Ch.9 pp.143-159 · Poulton Ch.6 pp.59-76 · CKA Q18
Surge
maxSurge is extra Pods. CURRENT above DESIRED is the walk.
Floor
maxUnavailable is the hole you may spend. Recreate spends all of it.
Not a cluster
maxSurge 100% is in-cluster blue/green. A second EKS API is a later year.
The new ReplicaSet is extra pods on this cluster. It is not another control plane.

<!-- hal:authoritative:yaml -->

The new ReplicaSet is extra pods on this cluster. It is not another control plane.

§I — Frame

Tuesday 08-25 named the leftover API. Saturday 08-22 named the leftover volume. Wednesday 08-19 named the leftover Secret. Today the leftover is a second cluster someone spun because a rollout looked like a migration.

k8s_day_counter reads 12. Even is CKA-emphasis. 08-16 closed Workloads and Scheduling on the scheduler half: requests, limits, taints, PriorityClass, the Pod the scheduler refused. The domain is 15%. The other half is App Lifecycle. Rolling update is that half. Do not open the system-pool taint file again.

The last two K8s Ops visits sat on AKS (08-25) and GKE (08-22). EKS is the rebalance. 08-19 already used EKS, and it used EKS as a KMS envelope around etcd. Today EKS is a Deployment. Do not open the encryptionConfig file again. 08-10 already used EKS as an EBS CSI binder. Leave the volume there.

Poulton puts the whole move in one picture (Ch. 6, Rolling updates with Deployments, printed pp. 63-64). You change the image in the same YAML. Kubernetes creates a second ReplicaSet. It raises the new set and lowers the old set. Two ReplicaSets. One Deployment. One cluster.

K8sUR3 writes the sentence this overlay lives on (Ch. 9, p. 159): "Setting maxSurge to 100% is equivalent to a blue/green Deployment." The new version scales to the old count. The old version scales to zero. That is still one Deployment object. That is still one EKS cluster. The second cluster is a different product.

Coin it: the surge that is not a second cluster.

A public EKS Service with an ALB in front already has a target group. A surge pod registers as another target. A second EKS cluster registers as another API. If the task is a new image, you want the first. If the task is a new account, you want the second. Most rollouts are the first wearing the second's name.

§II — Foundations: two ReplicaSets, one desired count

Poulton's stack is the right order (Ch. 6, printed pp. 60-61). Containers package. Pods co-schedule. ReplicaSets heal and scale. Deployments roll out and roll back. Skip a layer and you will try to roll a Pod by hand.

The spec is desired state. The Deployment controller is the loop that keeps observed state next to it (Ch. 6, printed pp. 59-62). Desired is 10 replicas on image 2.0. Observed is 10 replicas on image 1.0. Reconciliation is not a meeting. It is a second ReplicaSet.

The Pod is immutable. "When you update a Pod, you are actually terminating it and replacing it with a brand new one" (Ch. 6, printed p. 71). There is no in-place image write. kubectl set image looks like a mutation. The API records a new Pod template. The old Pods die. If you needed the old process ID, you needed a different object.

Two ReplicaSets is the mechanism. Poulton's vulnerability walk (Ch. 6, printed p. 64): post the same Deployment with a new tag. Kubernetes creates a ReplicaSet for 2.0. It keeps the ReplicaSet for 1.0. It raises one and lowers the other. Net result is a smooth increment. The old ReplicaSet stays at 0 ready so rollback has a handle (Ch. 6, printed pp. 75-76).

Count the objects before you count the clusters. kubectl get deploy is one row. kubectl get rs is two rows while the walk is live, then one row at desired and one row at zero. aws eks list-clusters is a different list. If that list grew because an image tag changed, the surge was misread as a migration. If that list grew because a new account needed its own API, you are no longer in this lesson.

Recreate is the other strategy. K8sUR3 Ch. 9, p. 155: Recreate updates the ReplicaSet and terminates every Pod. The set notices zero and builds the new image. Fast. Simple. Downtime. Use it when a service gap is acceptable. Do not use it on the public ALB and then blame EKS.

RollingUpdate is the default. A few Pods at a time until the new image owns the count (Ch. 9, p. 155). For a while both versions answer. That is not a defect. That is the contract. K8sUR3 spends a page on why (pp. 155-156): a browser that fetched v1 JavaScript will call a v2 Pod. Loose APIs and forward compatibility are not slogans. They are why maxSurge does not page you.

Three knobs sit on the spec.

  1. maxUnavailable. How far below desired you may fall. Number or percent (Ch. 9, p. 158). Poulton's example is 1 on a desired of 10, so you never sit below 9 (Ch. 6, printed pp. 65-66, 72).
  2. maxSurge. How far above desired you may rise. Number or percent (Ch. 9, p. 159). Poulton's example is 1, so you never sit above 11. The delta between 9 and 11 is two Pods in flight (Ch. 6, printed p. 72).
  3. progressDeadlineSeconds and minReadySeconds. How long each new replica may take, and how long it must stay ready before the next replacement (Ch. 6, printed pp. 66, 72). Five minutes and ten seconds in the book. Production wants a number that catches a bad image before the tenth Pod.

revisionHistoryLimit: 5 keeps five old ReplicaSets (Ch. 6, printed p. 66). That is rollback inventory. It is not a second cluster.

K8sUR3's 50% walk is the speed lesson (Ch. 9, p. 158). Four replicas, maxUnavailable: 50%. Old set drops to two. New set rises to two. Old set drops to zero. New set rises to four. Capacity dips. The 0% / 20% walk is the other lesson (p. 159). Ten replicas. New set rises by two first. Capacity never dips. Extra nodes may appear. Extra clusters must not.

§III — Mechanism: EKS, because the second cluster is the expensive synonym

The Bootcamp cheatsheet names EKS as "Managed Kubernetes" and stops. It names ALB as layer 7 and NLB as layer 4 and stops. The surge knobs are not in that file. Poulton's two-ReplicaSet sentence is the tome that licenses the move. K8sUR3's maxSurge-100% note is the tome that names the counterfeit. Both are true on the same Friday.

EKS hides the control plane the way Poulton already warned a hosted cluster would (that sentence lived in 08-25; do not rebuild it). You still own the Deployment. You still own the node group or the Karpenter NodePool. You still own the Service that points at the Pods.

Three EKS fields do the work. None of them is a second cluster.

The Deployment on this API. apps/v1 is the same group Poulton named (Ch. 6, printed p. 59). kubectl apply of a new image on eks-prod starts two ReplicaSets in eks-prod. aws eks update-kubeconfig does not mint a peer cluster. If your runbook says "stand up blue, shift the DNS, retire green," you have left the Deployment. That runbook has a place. This fire is not it.

The Service and the load balancer. A Service of type LoadBalancer on EKS still lands on an NLB or, with the AWS Load Balancer Controller, an ALB Ingress or a target-type: ip Service. New surge Pods register as extra targets on the existing target group. Old Pods deregister when they leave Ready. The readinessProbe is the join ticket. A Pod that is Running and not Ready is not in the surge that users can see. If you needed a second hostname, you needed a second Service, not a second cluster.

The node that appears for the extra Pod. maxSurge: 20% on a packed node group can trip Cluster Autoscaler or Karpenter. A new EC2 instance in the same cluster is still the surge that is not a second cluster. An EKS managed node group has its own updateConfig.maxUnavailable. That field rotates nodes. It is not spec.strategy.rollingUpdate.maxUnavailable. Name it so you do not edit the wrong object. Then leave it. Node rotation is a later Friday.

kubectl get deploy is the read. Ask for the four columns that name the surge.

kubectl get deploy storefront -n prod \
  -o custom-columns=NAME:.metadata.name,DESIRED:.spec.replicas,CURRENT:.status.replicas,UPDATED:.status.updatedReplicas,AVAILABLE:.status.availableReplicas

Read the output the way 08-22 read kubectl get sc. Four columns. CURRENT above DESIRED is maxSurge working. UPDATED below DESIRED is the rollout still walking. AVAILABLE below DESIRED is maxUnavailable being spent, or a probe failing. CURRENT equal to DESIRED and UPDATED equal to DESIRED and a second EKS cluster in the same account is the leftover someone did not need.

Watch the two sets while it walks.

kubectl get rs -n prod -l app=storefront -o wide
kubectl rollout status deploy/storefront -n prod

Poulton's mid-rollout table shows DESIRED 10, CURRENT 11, UP-TO-DATE 5, AVAILABLE 11 (Ch. 6, printed p. 73). That is maxSurge 1 in public. If AVAILABLE drops under the floor you set, the image is the incident, not the cluster count.

§IV — Worked example: raise the new set, do not clone the API

Start from an EKS cluster that already serves storefront behind an ALB. Desired is 10. Image is registry.example.com/storefront:1.4.2. Strategy is the book default in spirit: maxUnavailable: 1, maxSurge: 1. The ALB target group has 10 healthy IPs.

Change one line in the same manifest.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: storefront
  namespace: prod
spec:
  replicas: 10
  revisionHistoryLimit: 5
  progressDeadlineSeconds: 300
  minReadySeconds: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  selector:
    matchLabels:
      app: storefront
  template:
    metadata:
      labels:
        app: storefront
    spec:
      containers:
      - name: storefront
        image: registry.example.com/storefront:1.4.3
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          periodSeconds: 5

Apply it. Do not create eks-prod-blue. Do not copy the cluster Terraform. Do not open a second aws eks update-kubeconfig --name as the release step.

kubectl apply -f storefront.yaml
kubectl rollout status deploy/storefront -n prod

Expected mid-walk: 11 current, 9 or 10 available, a new ReplicaSet climbing, an old ReplicaSet falling. The ALB shows 11 targets, then 10. If the new image fails /ready, minReadySeconds never elapses, progressDeadlineSeconds trips, and the Deployment condition Progressing goes False for ProgressDeadlineExceeded. That is a stalled surge. The fix is rollback on this object.

kubectl rollout undo deploy/storefront -n prod --to-revision=1

Poulton is blunt that undo is an update in reverse (Ch. 6, printed p. 76). Same maxUnavailable. Same maxSurge. Same cluster. Update the YAML after the undo so the next apply does not re-fight you.

Zero-downtime on a packed group wants the K8sUR3 0% / N% shape (Ch. 9, p. 159).

    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 20%

Ten desired. Two extra Pods first. If the nodes are full, Karpenter (or the cluster autoscaler) adds a node in eks-prod. Wait for that node. Do not wait for a second API endpoint.

maxSurge 100% with maxUnavailable 0 is the in-cluster blue/green K8sUR3 named. Twenty pods for a moment. One Service. One ALB. One cluster. If your platform team already reserved a second EKS cluster for that shape, ask whether the reservation is for a new image or for a new account. An image does not need an account.

Q18 is the exam form of the same walk. The stem patches CPU and memory on a Deployment and says the Deployment must remain available at 2 replicas. The patch writes the Pod template. The template change starts a RollingUpdate. Availability is maxUnavailable, not a cloned control plane. Cert drills the patch. Ops names the surge the patch starts.

§V — Pairing and what this fire will not open

08-16 used AKS as a scheduler: system-pool taint, user-pool capacity, the Pending that is not storage. A Pod that never binds will not join a surge. Scheduling success is not a rollout. Leave PriorityClass in that file.

08-22 leftover was a claimRef on a Released PV. Today's leftover is a second EKS cluster next to a Deployment that already had maxSurge. Same discipline: read, name, do not "fix" by creating the expensive object.

08-19 reserved encryption-at-rest on this same cloud. The Secret in etcd is not an image tag. Today never opens KMS.

08-25 closed three AKS doors and named a leftover ClusterRoleBinding. A surge does not change who the API answers. Do not hunt system:anonymous in this file.

08-10 Ops was EBS CSI on EKS. A rolling update of a stateless front-end does not rebind a volume. If the workload is stateful, you are in a different controller. StatefulSet update strategy is not today's spine.

08-13 and 08-01 spent NetworkPolicy. A default-deny does not stop a new ReplicaSet. It can stop the new Pod from answering the probe. If /ready hangs, look at the policy after you look at the image. Do not rewrite the policy as the release.

Q5 is the leftover from 08-16. HPA changes spec.replicas. RollingUpdate changes the template at a given replica count. If both fire at once you will watch CURRENT climb and not know which loop did it. Freeze the HPA or read both objects. Do not treat an HPA max of 4 as maxSurge.

§VI — Connection to today's Dev and Cert

Dev is the Python census. AppsV1Api.list_deployment_for_all_namespaces. Print desired, current, updated, available, the two ReplicaSets, and the Progressing condition. Do not patch the image. 08-25 already taught that a leftover checker that deletes ClusterRoleBindings deletes the evidence. A leftover checker that calls patch_namespaced_deployment is a rollout. Inventory is a timer that prints.

Cert is App Lifecycle by hand. Change the image. Watch two ReplicaSets. Set maxUnavailable and maxSurge. kubectl rollout status. kubectl rollout undo. Q18's "remain available (2 replicas)" is the floor. Q3's sidecar add is a template change and therefore a surge. Q5 is named so you do not confuse scale with replace. The exam is kubeadm. The fleet is EKS. Learn both. Grade the exam on the ReplicaSets.

§VII — Close

Two ReplicaSets. One desired count. maxSurge is extra Pods. maxUnavailable is the floor. maxSurge 100% is blue/green inside the same Deployment. A second EKS cluster is a different decision.

Read kubectl get deploy first. Then kubectl get rs. Then decide whether you are on the exam or on the fleet. The exam wants the strategy block. The fleet wants the same block plus the ALB target count. Neither wants a cloned API for a new tag.

Examine well. The surge is extra Pods. The second cluster is a later year.

Related