CCloudSuccessTechworld with Randy
Labs / Kubernetes Incident Lab: Production Is Down
Lab · Intermediate · 45–60 min

Kubernetes Incident Lab: Production Is Down

Break a real checkout service with a bad release, then debug it like an on-call SRE: CrashLoopBackOff, OOMKilled (exit 137), a rollback that doesn't fix it, a service selector trap, a capacity crunch and a fix-forward. Plus Kubernetes rapid-fire scenarios and the classic Linux “disk full but du shows nothing” problem.

Real recorded runFrom: SRE Mock Interview
The output on this page is from our actual run, not edited. Your pod names, IPs and timings will differ; the behaviour won't.

What you'll learn

  • Read pod state, events and previous-container logs to find a root cause
  • Why exit code 137 means OOMKilled, and how limits cause it
  • Why rollout undo didn't bring traffic back (service selectors)
  • Requests vs real usage, and how bad requests block scale-ups
  • PodDisruptionBudgets, QoS classes, cluster DNS and ndots
  • Finding disk space held by deleted files with lsof

Before you start

  • Docker Desktop (or any Docker engine)
  • kind v0.27+ and kubectl
  • ~4 GB free RAM

Setup

Create a 3-node cluster

bash
cat > kind.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
  - role: worker
  - role: worker
EOF
kind create cluster --name prod-east --config kind.yaml --wait 180s

Install metrics-server (for kubectl top)

bash
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.7.2/components.yaml
kubectl -n kube-system patch deploy metrics-server --type=json \
  -p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
kubectl -n kube-system rollout status deploy/metrics-server

Deploy the shop (storefront + checkout v1)

bash
kubectl apply -f manifests.yaml
kubectl apply -f checkout-v1.yaml
kubectl -n shop rollout status deploy/checkout
kubectl -n shop wait --for=condition=Ready pod/storefront

Lab files

The lab

01The healthy baseline

Before breaking anything, look at what normal looks like. Three nodes, three checkout pods, and the storefront sending 40 requests that all succeed. Every dot is a successful request.

randy — zsh
randy@macbook ~ % kubectl get nodes
NAME                      STATUS   ROLES           AGE   VERSION
prod-east-control-plane   Ready    control-plane   63s   v1.32.2
prod-east-worker          Ready    <none>          48s   v1.32.2
prod-east-worker2         Ready    <none>          48s   v1.32.2
randy — zsh
randy@macbook ~ % kubectl -n shop get pods -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP           NODE                NOMINATED NODE   READINESS GATES
checkout-5dd967cbb-2pqgt   1/1     Running   0          5s    10.244.1.4   prod-east-worker    <none>           <none>
checkout-5dd967cbb-jk6bl   1/1     Running   0          5s    10.244.1.3   prod-east-worker    <none>           <none>
checkout-5dd967cbb-sj752   1/1     Running   0          5s    10.244.2.4   prod-east-worker2   <none>           <none>
storefront                 1/1     Running   0          5s    10.244.1.2   prod-east-worker    <none>           <none>
randy — zsh
randy@macbook ~ % kubectl -n shop exec storefront -- python /app/hit.py 40
requests: ........................................
200 OK: 40   errors: 0   error rate: 0%

02The bad release

Release 2.0 ships a new checkout image and the pipeline also points the service selector at version=2. This is the change that takes checkout down.

randy — zsh
randy@macbook ~ % kubectl apply -f checkout-v2.yaml
deployment.apps/checkout configured
randy — zsh
randy@macbook ~ % kubectl -n shop patch svc checkout -p '{"spec":{"selector":{"app":"checkout","version":"2"}}}'
service/checkout patched

03Triage: what changed?

Every request now fails. Start with the question that matters most at 3 a.m.: what changed? Then read the evidence in order: pod status, rollout history, the pod's last state, the previous container's logs, and events. Exit code 137 plus OOMKilled tells you the new cache needs more memory than the 64Mi limit.

randy — zsh
randy@macbook ~ % kubectl -n shop exec storefront -- python /app/hit.py 40
requests: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
200 OK: 0   errors: 40   error rate: 100%
randy — zsh
randy@macbook ~ % kubectl -n shop get pods
NAME                       READY   STATUS             RESTARTS      AGE
checkout-5dd967cbb-2pqgt   1/1     Running            0             82s
checkout-5dd967cbb-jk6bl   1/1     Running            0             82s
checkout-5dd967cbb-sj752   1/1     Running            0             82s
checkout-8b854d58f-jpsjt   0/1     CrashLoopBackOff   3 (30s ago)   74s
storefront                 1/1     Running            0             82s
randy — zsh
randy@macbook ~ % kubectl -n shop rollout history deploy/checkout
deployment.apps/checkout 
REVISION  CHANGE-CAUSE
1         release 1.0 - baseline
2         release 2.0 - add product cache warm-up
randy — zsh
randy@macbook ~ % kubectl -n shop rollout status deploy/checkout --timeout=5s
Waiting for deployment "checkout" rollout to finish: 1 out of 3 new replicas have been updated...
error: timed out waiting for the condition
randy — zsh
randy@macbook ~ % kubectl -n shop describe pod checkout-8b854d58f-jpsjt
Name:             checkout-8b854d58f-jpsjt
Namespace:        shop
Priority:         0
Service Account:  default
Node:             prod-east-worker2/172.18.0.2
Start Time:       Mon, 26 Oct 2026 23:44:08 +0000
Labels:           app=checkout
                  pod-template-hash=8b854d58f
                  version=2
Annotations:      <none>
Status:           Running
IP:               10.244.2.5
IPs:
  IP:           10.244.2.5
Controlled By:  ReplicaSet/checkout-8b854d58f
Containers:
  checkout:
    Container ID:  containerd://ed853fdb9dd2025bafb5951d52cf0de12c35df69b27741df2cb4b1e9fb495d79
    Image:         python:3.12-alpine
    Image ID:      sha256:38fd5a425aa93b06d367e2e47e2da6a9bc0cabdaf5788fb1707d143fd1416e97
    Port:          <none>
    Host Port:     <none>
    Command:
      python
      -u
      /app/app.py
    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       OOMKilled
      Exit Code:    137
      Started:      Mon, 26 Oct 2026 23:44:51 +0000
      Finished:     Mon, 26 Oct 2026 23:44:52 +0000
    Ready:          False
    Restart Count:  3
    Limits:
      memory:  64Mi
    Requests:
      cpu:      1500m
      memory:   48Mi
    Readiness:  http-get http://:8080/ delay=0s timeout=1s period=3s #success=1 #failure=3
    Environment:
      VERSION:     2
      WARM_CACHE:  1
    Mounts:
      /app from code (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-kv24w (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True 
  Initialized                 True 
  Ready                       False 
  ContainersReady             False 
  PodScheduled                True 
Volumes:
  code:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      checkout-code
    Optional:  false
  kube-api-access-kv24w:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   Burstable
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  79s                default-scheduler  Successfully assigned shop/checkout-8b854d58f-jpsjt to prod-east-worker2
  Normal   Pulled     36s (x4 over 79s)  kubelet            Container image "python:3.12-alpine" already present on machine
  Normal   Created    36s (x4 over 79s)  kubelet            Created container: checkout
  Normal   Started    36s (x4 over 79s)  kubelet            Started container checkout
  Warning  Unhealthy  35s (x5 over 78s)  kubelet            Readiness probe failed: Get "http://10.244.2.5:8080/": dial tcp 10.244.2.5:8080: connect: connection refused
  Warning  BackOff    10s (x9 over 74s)  kubelet            Back-off restarting failed container checkout in pod checkout-8b854d58f-jpsjt_shop(a85506e3-c395-4a64-a5a5-f8a0025b7b50)
randy — zsh
randy@macbook ~ % kubectl -n shop logs checkout-8b854d58f-jpsjt --previous
checkout v2: warming product cache (80 MB)...
  cache loaded 20 MB
  cache loaded 40 MB
randy — zsh
randy@macbook ~ % kubectl -n shop get events --sort-by=.lastTimestamp | tail -14
87s         Normal    SuccessfulCreate    replicaset/checkout-5dd967cbb   Created pod: checkout-5dd967cbb-2pqgt
87s         Normal    Pulled              pod/storefront                  Container image "python:3.12-alpine" already present on machine
87s         Normal    ScalingReplicaSet   deployment/checkout             Scaled up replica set checkout-5dd967cbb from 0 to 3
86s         Normal    Started             pod/checkout-5dd967cbb-jk6bl    Started container checkout
86s         Warning   Unhealthy           pod/checkout-5dd967cbb-sj752    Readiness probe failed: Get "http://10.244.2.4:8080/": dial tcp 10.244.2.4:8080: connect: connection refused
86s         Normal    Started             pod/checkout-5dd967cbb-2pqgt    Started container checkout
79s         Normal    SuccessfulCreate    replicaset/checkout-8b854d58f   Created pod: checkout-8b854d58f-jpsjt
79s         Normal    ScalingReplicaSet   deployment/checkout             Scaled up replica set checkout-8b854d58f from 0 to 1
79s         Normal    Scheduled           pod/checkout-8b854d58f-jpsjt    Successfully assigned shop/checkout-8b854d58f-jpsjt to prod-east-worker2
36s         Normal    Started             pod/checkout-8b854d58f-jpsjt    Started container checkout
36s         Normal    Created             pod/checkout-8b854d58f-jpsjt    Created container: checkout
36s         Normal    Pulled              pod/checkout-8b854d58f-jpsjt    Container image "python:3.12-alpine" already present on machine
35s         Warning   Unhealthy           pod/checkout-8b854d58f-jpsjt    Readiness probe failed: Get "http://10.244.2.5:8080/": dial tcp 10.244.2.5:8080: connect: connection refused
10s         Warning   BackOff             pod/checkout-8b854d58f-jpsjt    Back-off restarting failed container checkout in pod checkout-8b854d58f-jpsjt_shop(a85506e3-c395-4a64-a5a5-f8a0025b7b50)
randy — zsh
randy@macbook ~ % kubectl -n shop top pods
NAME                       CPU(cores)   MEMORY(bytes)   
checkout-5dd967cbb-2pqgt   1m           11Mi            
checkout-5dd967cbb-jk6bl   1m           11Mi            
checkout-5dd967cbb-sj752   1m           11Mi            
storefront                 4m           11Mi            

04The rollback that didn't fix it

Rolling back the deployment brings v1 pods back, but traffic still fails. The service is still selecting version=2, so it has zero endpoints. Follow the request: service → endpoints → pod labels.

randy — zsh
randy@macbook ~ % kubectl -n shop rollout undo deploy/checkout
deployment.apps/checkout rolled back
randy — zsh
randy@macbook ~ % kubectl -n shop rollout status deploy/checkout --timeout=120s
deployment "checkout" successfully rolled out
randy — zsh
randy@macbook ~ % kubectl -n shop get pods
NAME                       READY   STATUS        RESTARTS   AGE
checkout-5dd967cbb-2pqgt   1/1     Running       0          88s
checkout-5dd967cbb-jk6bl   1/1     Running       0          88s
checkout-5dd967cbb-sj752   1/1     Running       0          88s
checkout-8b854d58f-jpsjt   0/1     Terminating   3          80s
storefront                 1/1     Running       0          88s
randy — zsh
randy@macbook ~ % kubectl -n shop exec storefront -- python /app/hit.py 40
requests: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
200 OK: 0   errors: 40   error rate: 100%
randy — zsh
randy@macbook ~ % kubectl -n shop get endpoints checkout
NAME       ENDPOINTS   AGE
checkout   <none>      2m6s
randy — zsh
randy@macbook ~ % kubectl -n shop get svc checkout -o wide
NAME       TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE    SELECTOR
checkout   ClusterIP   10.96.31.165   <none>        8080/TCP   2m6s   app=checkout,version=2
randy — zsh
randy@macbook ~ % kubectl -n shop get pods --show-labels
NAME                       READY   STATUS    RESTARTS   AGE    LABELS
checkout-5dd967cbb-2pqgt   1/1     Running   0          2m6s   app=checkout,pod-template-hash=5dd967cbb,version=1
checkout-5dd967cbb-jk6bl   1/1     Running   0          2m6s   app=checkout,pod-template-hash=5dd967cbb,version=1
checkout-5dd967cbb-sj752   1/1     Running   0          2m6s   app=checkout,pod-template-hash=5dd967cbb,version=1
storefront                 1/1     Running   0          2m6s   app=storefront

05The real fix

Remove version from the service selector. Endpoints come back and every request succeeds. Action item for the postmortem: never put a version label in a service selector.

randy — zsh
randy@macbook ~ % kubectl -n shop patch svc checkout --type=json -p '[{"op":"remove","path":"/spec/selector/version"}]'
service/checkout patched
randy — zsh
randy@macbook ~ % kubectl -n shop get endpoints checkout
NAME       ENDPOINTS                                         AGE
checkout   10.244.1.3:8080,10.244.1.4:8080,10.244.2.4:8080   2m6s
randy — zsh
randy@macbook ~ % kubectl -n shop exec storefront -- python /app/hit.py 40
requests: ........................................
200 OK: 40   errors: 0   error rate: 0%

06Traffic spike: capacity

Marketing sends a big email and you scale to 8 replicas. Some pods stay Pending with Insufficient cpu, even though real CPU usage is tiny. The scheduler only looks at requests. Right-size the requests from real usage and everything fits.

randy — zsh
randy@macbook ~ % kubectl -n shop scale deploy/checkout --replicas=8
deployment.apps/checkout scaled
randy — zsh
randy@macbook ~ % kubectl -n shop get pods
NAME                       READY   STATUS    RESTARTS   AGE
checkout-5dd967cbb-2pqgt   1/1     Running   0          2m26s
checkout-5dd967cbb-58s52   0/1     Pending   0          15s
checkout-5dd967cbb-hl7bg   1/1     Running   0          15s
checkout-5dd967cbb-jk6bl   1/1     Running   0          2m26s
checkout-5dd967cbb-r5pxc   0/1     Pending   0          15s
checkout-5dd967cbb-sj752   1/1     Running   0          2m26s
checkout-5dd967cbb-ssdhd   0/1     Pending   0          15s
checkout-5dd967cbb-v5v4d   0/1     Pending   0          15s
storefront                 1/1     Running   0          2m26s
randy — zsh
randy@macbook ~ % kubectl -n shop describe pod checkout-5dd967cbb-58s52 | tail -6
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  15s   default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 Insufficient cpu. preemption: 0/3 nodes are available: 1 Preemption is not helpful for scheduling, 2 No preemption victims found for incoming pod.
randy — zsh
randy@macbook ~ % for n in $(kubectl get nodes -o name); do echo "== ${n#node/}"; kubectl describe $n | grep -A5 'Allocated resources' | grep cpu; done
== prod-east-control-plane
  cpu                950m (23%)  100m (2%)
== prod-east-worker
  cpu                3100m (77%)  100m (2%)
== prod-east-worker2
  cpu                3200m (80%)  100m (2%)
randy — zsh
randy@macbook ~ % kubectl -n shop top pods
NAME                       CPU(cores)   MEMORY(bytes)   
checkout-5dd967cbb-2pqgt   1m           11Mi            
checkout-5dd967cbb-jk6bl   1m           11Mi            
checkout-5dd967cbb-sj752   2m           11Mi            
storefront                 1m           12Mi            
randy — zsh
randy@macbook ~ % kubectl -n shop set resources deploy/checkout -c checkout --requests=cpu=100m,memory=48Mi
deployment.apps/checkout resource requirements updated
randy — zsh
randy@macbook ~ % kubectl -n shop rollout status deploy/checkout --timeout=180s
Waiting for deployment "checkout" rollout to finish: 4 out of 8 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 4 out of 8 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 4 out of 8 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 4 out of 8 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 5 out of 8 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 5 out of 8 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 6 out of 8 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 6 out of 8 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 6 out of 8 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 6 out of 8 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 7 out of 8 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 7 out of 8 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 2 old replicas are pending termination...
Waiting for deployment "checkout" rollout to finish: 2 old replicas are pending termination...
Waiting for deployment "checkout" rollout to finish: 2 old replicas are pending termination...
Waiting for deployment "checkout" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "checkout" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "checkout" rollout to finish: 6 of 8 updated replicas are available...
Waiting for deployment "checkout" rollout to finish: 7 of 8 updated replicas are available...
deployment "checkout" successfully rolled out
randy — zsh
randy@macbook ~ % kubectl -n shop get pods -o wide
NAME                        READY   STATUS        RESTARTS   AGE     IP            NODE                NOMINATED NODE   READINESS GATES
checkout-5dd967cbb-2pqgt    1/1     Terminating   0          2m31s   10.244.1.4    prod-east-worker    <none>           <none>
checkout-5dd967cbb-hl7bg    1/1     Terminating   0          20s     10.244.2.6    prod-east-worker2   <none>           <none>
checkout-5dd967cbb-jk6bl    1/1     Terminating   0          2m31s   10.244.1.3    prod-east-worker    <none>           <none>
checkout-5dd967cbb-sj752    1/1     Terminating   0          2m31s   10.244.2.4    prod-east-worker2   <none>           <none>
checkout-7d54675988-22kc9   1/1     Running       0          4s      10.244.2.7    prod-east-worker2   <none>           <none>
checkout-7d54675988-4qh4w   1/1     Running       0          4s      10.244.1.5    prod-east-worker    <none>           <none>
checkout-7d54675988-7ppqv   1/1     Running       0          2s      10.244.2.10   prod-east-worker2   <none>           <none>
checkout-7d54675988-9qp94   1/1     Running       0          4s      10.244.1.6    prod-east-worker    <none>           <none>
checkout-7d54675988-jcz5v   1/1     Running       0          2s      10.244.1.8    prod-east-worker    <none>           <none>
checkout-7d54675988-k9wq6   1/1     Running       0          4s      10.244.2.8    prod-east-worker2   <none>           <none>
checkout-7d54675988-kwzgx   1/1     Running       0          2s      10.244.2.9    prod-east-worker2   <none>           <none>
checkout-7d54675988-vslt2   1/1     Running       0          2s      10.244.1.7    prod-east-worker    <none>           <none>
storefront                  1/1     Running       0          2m31s   10.244.1.2    prod-east-worker    <none>           <none>
randy — zsh
randy@macbook ~ % kubectl -n shop exec storefront -- python /app/hit.py 40
requests: ........................................
200 OK: 40   errors: 0   error rate: 0%

07Fix forward

Ship v2 again with a memory limit that fits its cache and a selector without a version. Watch it roll out cleanly and serve traffic.

randy — zsh
randy@macbook ~ % kubectl apply -f checkout-v2-fixed.yaml
deployment.apps/checkout configured
randy — zsh
randy@macbook ~ % kubectl -n shop rollout status deploy/checkout --timeout=240s
Waiting for deployment "checkout" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "checkout" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "checkout" rollout to finish: 1 old replicas are pending termination...
deployment "checkout" successfully rolled out
randy — zsh
randy@macbook ~ % kubectl -n shop get pods
NAME                        READY   STATUS        RESTARTS   AGE
checkout-5dd967cbb-2pqgt    1/1     Terminating   0          2m43s
checkout-5dd967cbb-hl7bg    1/1     Terminating   0          32s
checkout-5dd967cbb-jk6bl    1/1     Terminating   0          2m43s
checkout-5dd967cbb-sj752    1/1     Terminating   0          2m43s
checkout-7d54675988-22kc9   1/1     Terminating   0          16s
checkout-7d54675988-4qh4w   1/1     Terminating   0          16s
checkout-7d54675988-7ppqv   1/1     Terminating   0          14s
checkout-7d54675988-9qp94   1/1     Terminating   0          16s
checkout-7d54675988-jcz5v   1/1     Terminating   0          14s
checkout-7d54675988-k9wq6   1/1     Terminating   0          16s
checkout-7d54675988-kwzgx   1/1     Terminating   0          14s
checkout-7d54675988-vslt2   1/1     Terminating   0          14s
checkout-bbcf8fbcb-26wb2    1/1     Running       0          4s
checkout-bbcf8fbcb-2p9c2    1/1     Running       0          7s
checkout-bbcf8fbcb-4x9c7    1/1     Running       0          2s
storefront                  1/1     Running       0          2m43s
randy — zsh
randy@macbook ~ % kubectl -n shop top pods
NAME                        CPU(cores)   MEMORY(bytes)   
checkout-7d54675988-22kc9   49m          11Mi            
checkout-7d54675988-4qh4w   62m          11Mi            
checkout-7d54675988-7ppqv   1m           11Mi            
checkout-7d54675988-9qp94   1m           11Mi            
checkout-7d54675988-jcz5v   46m          11Mi            
checkout-7d54675988-k9wq6   1m           11Mi            
checkout-7d54675988-kwzgx   45m          11Mi            
checkout-7d54675988-vslt2   1m           11Mi            
checkout-bbcf8fbcb-2p9c2    41m          92Mi            
storefront                  5m           2Mi             
randy — zsh
randy@macbook ~ % kubectl -n shop rollout history deploy/checkout
deployment.apps/checkout 
REVISION  CHANGE-CAUSE
2         release 2.0 - add product cache warm-up
3         release 1.0 - baseline
4         release 1.0 - baseline
5         release 2.1 - cache warm-up with right-sized memory
randy — zsh
randy@macbook ~ % kubectl -n shop exec storefront -- python /app/hit.py 40
requests: ........................................
200 OK: 40   errors: 0   error rate: 0%
randy — zsh
randy@macbook ~ % kubectl -n shop logs deploy/checkout | head -8
checkout v1 listening on :8080
Found 11 pods, using pod/checkout-7d54675988-22kc9

08Kubernetes rapid fire

QoS classes, how service DNS resolves (and the ndots setting), a PodDisruptionBudget blocking a node drain, and the most common image pull mistake.

randy — zsh
randy@macbook ~ % kubectl -n shop get pods -l app=checkout -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass,NODE:.spec.nodeName | head -5
NAME                       QOS         NODE
checkout-bbcf8fbcb-nnh9g   Burstable   prod-east-worker2
checkout-bbcf8fbcb-vhpxb   Burstable   prod-east-worker
checkout-bbcf8fbcb-w4gdg   Burstable   prod-east-worker2
randy — zsh
randy@macbook ~ % kubectl -n shop exec storefront -- nslookup checkout.shop
Server:		10.96.0.10
Address:	10.96.0.10:53

Non-authoritative answer:
Name:	checkout.shop
Address: 2606:4700:3033::ac43:a79f
Name:	checkout.shop
Address: 2606:4700:3037::6815:4312

Non-authoritative answer:
Name:	checkout.shop
Address: 172.67.167.159
Name:	checkout.shop
Address: 104.21.67.18
randy — zsh
randy@macbook ~ % kubectl -n shop exec storefront -- cat /etc/resolv.conf
search shop.svc.cluster.local svc.cluster.local cluster.local us-east-2.compute.internal
nameserver 10.96.0.10
options ndots:5
randy — zsh
randy@macbook ~ % kubectl apply -f pdb.yaml
poddisruptionbudget.policy/checkout-pdb created
randy — zsh
randy@macbook ~ % kubectl -n shop get pdb
NAME           MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
checkout-pdb   N/A             0                 0                     1s
randy — zsh
randy@macbook ~ % kubectl drain prod-east-worker --ignore-daemonsets --delete-emptydir-data --timeout=25s
node/prod-east-worker cordoned
evicting pod shop/checkout-bbcf8fbcb-vhpxb
evicting pod kube-system/metrics-server-8467fcc7b7-6pmbk
pod/metrics-server-8467fcc7b7-6pmbk evicted
evicting pod shop/checkout-bbcf8fbcb-vhpxb
evicting pod shop/checkout-bbcf8fbcb-vhpxb
evicting pod shop/checkout-bbcf8fbcb-vhpxb
evicting pod shop/checkout-bbcf8fbcb-vhpxb
evicting pod shop/checkout-bbcf8fbcb-vhpxb
Warning: ignoring DaemonSet-managed Pods: kube-system/kindnet-ns8tg, kube-system/kube-proxy-8htc4
error when evicting pods/"checkout-bbcf8fbcb-vhpxb" -n "shop" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
error when evicting pods/"checkout-bbcf8fbcb-vhpxb" -n "shop" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
error when evicting pods/"checkout-bbcf8fbcb-vhpxb" -n "shop" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
error when evicting pods/"checkout-bbcf8fbcb-vhpxb" -n "shop" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
error when evicting pods/"checkout-bbcf8fbcb-vhpxb" -n "shop" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
There are pending pods in node "prod-east-worker" when an error occurred: error when evicting pods/"checkout-bbcf8fbcb-vhpxb" -n "shop": global timeout reached: 25s
pod/checkout-bbcf8fbcb-vhpxb
error: unable to drain node "prod-east-worker" due to error: error when evicting pods/"checkout-bbcf8fbcb-vhpxb" -n "shop": global timeout reached: 25s, continuing command...
There are pending nodes to be drained:
 prod-east-worker
error when evicting pods/"checkout-bbcf8fbcb-vhpxb" -n "shop": global timeout reached: 25s
randy — zsh
randy@macbook ~ % kubectl get nodes
NAME                      STATUS                     ROLES           AGE   VERSION
prod-east-control-plane   Ready                      control-plane   86s   v1.32.2
prod-east-worker          Ready,SchedulingDisabled   <none>          76s   v1.32.2
prod-east-worker2         Ready                      <none>          76s   v1.32.2
randy — zsh
randy@macbook ~ % kubectl uncordon prod-east-worker && kubectl delete pdb checkout-pdb -n shop
node/prod-east-worker uncordoned
poddisruptionbudget.policy "checkout-pdb" deleted
randy — zsh
randy@macbook ~ % kubectl -n shop run typo --image=pyhton:3.12-alpine --restart=Never -- sleep 60
pod/typo created
randy — zsh
randy@macbook ~ % kubectl -n shop get pod typo
NAME   READY   STATUS             RESTARTS   AGE
typo   0/1     ImagePullBackOff   0          25s
randy — zsh
randy@macbook ~ % kubectl -n shop describe pod typo | tail -7
  ----     ------     ----              ----               -------
  Normal   Scheduled  25s               default-scheduler  Successfully assigned shop/typo to prod-east-worker
  Normal   BackOff    24s               kubelet            Back-off pulling image "pyhton:3.12-alpine"
  Warning  Failed     24s               kubelet            Error: ImagePullBackOff
  Normal   Pulling    9s (x2 over 25s)  kubelet            Pulling image "pyhton:3.12-alpine"
  Warning  Failed     9s (x2 over 25s)  kubelet            Failed to pull image "pyhton:3.12-alpine": failed to pull and unpack image "docker.io/library/pyhton:3.12-alpine": failed to resolve reference "docker.io/library/pyhton:3.12-alpine": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
  Warning  Failed     9s (x2 over 25s)  kubelet            Error: ErrImagePull

09Linux on-call: disk full, but du finds nothing

df says the volume is almost full, du says the files are small. A process still holds a deleted log file open. lsof +L1 finds it, and truncating the file through /proc frees the space without restarting the process. (To reproduce this part, run it on any Linux VM: write a large file from a long-running process, then delete it.)

randy — zsh
randy@macbook ~ % df -h /var/log/checkout
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           512M  471M   42M  92% /var/log/checkout
randy — zsh
randy@macbook ~ % du -sh /var/log/checkout/*
4.0K	/var/log/checkout/status.txt
randy — zsh
randy@macbook ~ % ls -la /var/log/checkout
total 20
drwxrwxrwt.  2 root root    60 Oct 26 23:49 .
drwxr-xr-x. 10 root root 16384 Oct 26 23:49 ..
-rw-r--r--.  1 root root    23 Oct 26 23:49 status.txt
randy — zsh
randy@macbook ~ % lsof +L1 2>/dev/null | head -5
COMMAND     PID USER   FD   TYPE DEVICE  SIZE/OFF NLINK NODE NAME
dbus-brok  2058 dbus   12u   REG    0,1   2097152     0    2 /memfd:dbus-broker-log (deleted)
python3   38182 root    3w   REG  0,316 492830720     0    2 /var/log/checkout/app.log (deleted)
randy — zsh
randy@macbook ~ % PID=$(lsof +L1 2>/dev/null | awk '/app.log/{print $2; exit}'); ls -l /proc/$PID/fd | grep deleted
l-wx------. 1 root root 64 Oct 26 23:49 3 -> /var/log/checkout/app.log (deleted)
randy — zsh
randy@macbook ~ % : > /proc/38182/fd/3 && df -h /var/log/checkout
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           512M  4.0K  512M   1% /var/log/checkout
randy — zsh
randy@macbook ~ % uptime
 23:49:28 up 3 min,  0 users,  load average: 0.86, 0.67, 0.28
randy — zsh
randy@macbook ~ % free -m
               total        used        free      shared  buff/cache   available
Mem:           15619        1623        9040          28        4955       13638
Swap:              0           0           0
randy — zsh
randy@macbook ~ % df -i / | head -3
Filesystem       Inodes IUsed    IFree IUse% Mounted on
/dev/nvme0n1p1 15722480 96912 15625568    1% /

Check yourself

8 questions on what happened in this lab. Pick an answer for each, then check.

01The new checkout pod shows Last State: Terminated, Reason: OOMKilled, Exit Code: 137. What does 137 tell you?
02kubectl logs --previous shows the cache loading 20 MB, then 40 MB, then nothing. The deployment's memory limit is 64Mi. What does that mean for release 2.0?
03Right after the bad release, the three v1 pods were still Running and Ready, yet 40 out of 40 requests failed. Why?
04You scale checkout to 8 replicas and four pods stay Pending with "2 Insufficient cpu", even though kubectl top shows each pod using about 1m of CPU. Why?
05What would have happened if you fixed only the service selector and then re-applied the original checkout-v2.yaml (same 64Mi limit)?
06A drain of prod-east-worker keeps printing "Cannot evict pod as it would violate the pod's disruption budget". The PDB shows MAX UNAVAILABLE 0 and ALLOWED DISRUPTIONS 0. What is the best long-term fix?
07nslookup checkout.shop returned public internet IPs, and resolv.conf shows options ndots:5. What is going on?
08df says /var/log/checkout is 92% full, but du finds only a 4 KB file. lsof +L1 shows python3 holding app.log (deleted) at about 470 MB. How do you free the space without restarting the process?
Go deeper

Take this further

The Field Guides cover the whole topic: more labs, a troubleshooting playbook, interview questions and a 30-day plan.

Or get every guide with All-Access.

Get the next lab first

One real lab every week, straight to your inbox.

One email a week, real labs only. Unsubscribe any time.