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:
| Probe | Question it answers | On failure |
|---|---|---|
| livenessProbe | Is the container alive, or deadlocked? | kubelet kills the container; it restarts per the Pod’s restartPolicy; restartCount increments |
| readinessProbe | Can 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 |
| startupProbe | Has 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:
| Field | Default | Meaning |
|---|---|---|
initialDelaySeconds | 0 | Wait before the first probe |
periodSeconds | 10 | How often to probe |
timeoutSeconds | 1 | Time before a single probe attempt counts as failed |
successThreshold | 1 | Consecutive successes to count as passing (must be 1 for liveness/startup) |
failureThreshold | 3 | Consecutive 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:
| Field | Meaning | Default |
|---|---|---|
maxSurge | Max Pods above the desired replicas count allowed during the rollout | 25% |
maxUnavailable | Max Pods allowed to be unavailable during the rollout | 25% |
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
- Request: what the scheduler uses to decide which node has room for the Pod, and what the kubelet reserves specifically for it.
- Limit: the hard ceiling the kubelet enforces via cgroups.
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:
| Class | Condition |
|---|---|
| Guaranteed | Every container sets CPU and memory requests equal to limits (both non-zero) |
| Burstable | Not Guaranteed, but at least one request or limit is set |
| BestEffort | No 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
- Liveness failure → container restarted. Readiness failure → Pod pulled from Service endpoints, not restarted. Startup failure (persistent) → container killed and restarted; while pending, liveness/readiness are paused.
- Never base a liveness probe on an external dependency — it causes a restart loop that doesn’t fix anything.
- Default
maxSurge/maxUnavailableare both 25%;maxSurgerounds up,maxUnavailablerounds down; both can’t be 0. kubectl rollout undorolls back;revisionHistoryLimit(default 10) is how many old ReplicaSets stay available to roll back to.- CPU over limit → throttled (compressible resource, no restart). Memory over limit → OOMKilled, exit code 137 (incompressible resource).
- QoS class (Guaranteed/Burstable/BestEffort) is derived from requests vs. limits and decides eviction order under node pressure.
- HPA CPU-utilization scaling (see the GKE scaling article) is measured against the request, not the limit — another reason requests always need to be set deliberately, not left as an afterthought.