← Back to posts

GCP ACE: Pod Health, Rolling Updates, and Resource Requests/Limits

The containers article earlier in this GKE module used a readinessProbe in a YAML example without explaining it. This article covers what that was doing, the two other probe types, how a Deployment actually rolls out an update, and what requests/limits do underneath all of it.

Probes: liveness, readiness, startup

Three different questions, three different consequences on failure:

ProbeQuestion it answersOn failure
livenessProbeIs the container alive, or deadlocked?kubelet kills the container; it restarts per the Pod’s restartPolicy; restartCount increments
readinessProbeCan it accept traffic right now?Pod’s Ready condition becomes False; it’s removed from Service endpoints; the container is not restarted; re-added after enough consecutive successes
startupProbeHas the app finished starting up?Liveness and readiness are disabled until it succeeds; if it keeps failing, the container is killed and restarted; once it succeeds, it never runs again for that container

Default field values, same across all three probe types:

FieldDefaultMeaning
initialDelaySeconds0Wait before the first probe
periodSeconds10How often to probe
timeoutSeconds1Time before a single probe attempt counts as failed
successThreshold1Consecutive successes to count as passing (must be 1 for liveness/startup)
failureThreshold3Consecutive failures to count as failing
# Up to 30 × 5s = 150s to finish starting. Liveness/readiness paused until this passes.
startupProbe:
  httpGet:
    path: /healthz
    port: 8080
  periodSeconds: 5
  failureThreshold: 30

# Restarts the container after 3 consecutive failures (~30s at the default periodSeconds).
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  periodSeconds: 10
  failureThreshold: 3

# Pulls the Pod out of Service rotation after 3 failures; re-adds after 1 success.
readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  periodSeconds: 5
  failureThreshold: 3

The rule the exam likes to test: never make a liveness probe depend on a downstream dependency (a database, a remote API). If that dependency goes down, every Pod’s liveness probe fails together, and Kubernetes restarts all of them in a loop — restarting the Pod doesn’t fix a problem that isn’t inside the Pod. A readiness probe checking the same dependency is fine — it just stops sending traffic there until it recovers.

Rolling updates: maxSurge and maxUnavailable

When you change a Deployment’s Pod template (new image, for instance), the controller creates a new ReplicaSet and scales it up while scaling the old one down, in steps governed by two fields:

FieldMeaningDefault
maxSurgeMax Pods above the desired replicas count allowed during the rollout25%
maxUnavailableMax Pods allowed to be unavailable during the rollout25%

Percentages resolve against replicas — maxSurge rounds up, maxUnavailable rounds down. With replicas: 4 and the defaults, that’s maxSurge=1 and maxUnavailable=1: the total Pod count can briefly reach 5, and at least 3 must stay available throughout. Both values can’t be 0 at once — that would freeze the rollout entirely.

strategy:
  type: RollingUpdate       # the alternative is Recreate (kill all, then start all new)
  rollingUpdate:
    maxSurge: 25%
    maxUnavailable: 25%

Operational commands:

kubectl set image deployment/nginx-deployment nginx=nginx:1.28
kubectl rollout status deployment/nginx-deployment
kubectl rollout pause deployment/nginx-deployment
kubectl rollout resume deployment/nginx-deployment
kubectl rollout history deployment/nginx-deployment
kubectl rollout undo deployment/nginx-deployment                  # roll back one revision
kubectl rollout undo deployment/nginx-deployment --to-revision=2  # roll back to a specific one

revisionHistoryLimit (default 10) controls how many old, scaled-to-zero ReplicaSets are kept around to make rollback possible.

Resource requests and limits

resources:
  requests:
    cpu: "250m"       # scheduling decision + guaranteed minimum
    memory: "256Mi"
  limits:
    cpu: "500m"       # ceiling, enforced by CFS throttling
    memory: "512Mi"   # ceiling, enforced by OOM kill

CPU is compressible — exceeding the limit just throttles the container (the kernel’s CFS bandwidth controller pauses it until the next period). It keeps running, only slower. No restart, no crash — it shows up as latency.

Memory is not compressible — a process can’t be “paused” to free memory, so exceeding the memory limit triggers the Linux OOM killer, which terminates the container. It exits with code 137, reason OOMKilled, and the kubelet restarts it per restartPolicy.

Quality of Service (QoS) classes, derived automatically from requests/limits:

ClassCondition
GuaranteedEvery container sets CPU and memory requests equal to limits (both non-zero)
BurstableNot Guaranteed, but at least one request or limit is set
BestEffortNo requests or limits set at all

Under node resource pressure, eviction order is BestEffort first, then Burstable Pods exceeding their requests, and Guaranteed Pods last. This is also what sets the kernel’s OOM score — Guaranteed Pods are the least likely to be killed when the node itself runs low on memory.

Exam Takeaways

Sources

↗ LinkedIn