An application is up and running in a container, but “the process is running” and “the service is available” are two different things. The process may be stuck in an infinite loop, it may be unable to reach the database, or it may have been starting up for a long time without ever finishing loading. The health check mechanism in K8s exists precisely to tell these states apart. Combined with a rolling update strategy, it lets us do zero-downtime releases that users never notice — and this article, the finale of the cloud native deployment series, ties together everything we have learned and brings it down to production practice.
1. A Live Process ≠ a Healthy Service
By default, K8s decides whether a container is healthy by checking whether the container’s main process is alive — if the process is running, K8s assumes the container is fine.
But that judgment is too coarse. Look at a few common scenarios:
Scenario one: a deadlock. The process is still running, but all its threads are waiting on one another and it answers no requests from the outside. K8s cannot tell; the process is still there.
Scenario two: a slow start. A Java application needs 60 seconds to load its context. Before startup has finished, the Service has already routed traffic to it, and requests come back 502.
Scenario three: an unavailable dependency. The application is up, but it cannot connect to the database. The process is running, but the business function is not available.
All of these scenarios call for a health check more precise than “is the process running” — which is where the Probe comes in.
2. Three Kinds of Probes: Liveness, Readiness, Startup
K8s provides three kinds of probes, each with a different purpose:
| Probe | Question | When the check fails | Typical use |
|---|---|---|---|
| Liveness | Is the container “dead”? | Restart the container | Detecting deadlocks and out-of-memory |
| Readiness | Is the container ready to receive traffic? | Remove it from the Service’s backends | Checking whether dependencies are ready and startup is complete |
| Startup | Has startup finished? | Restart the container | Protecting slow-starting applications |
Liveness Probe — “Is the Heart Still Beating?”
Liveness checks whether the container is alive. If the check fails, K8s kills the container and starts it again.
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30 # wait 30 seconds after the container starts before checking
periodSeconds: 10 # check every 10 seconds
timeoutSeconds: 5 # timeout of 5 seconds
failureThreshold: 3 # 3 consecutive failures before it is judged unhealthy
What this configuration means: 30 seconds after the container starts, an HTTP GET request is sent to /health every 10 seconds. If three requests in a row return a non-200 status or time out, K8s considers the container “dead” and kills it for a restart.
Note: do not configure Liveness too aggressively. If your application is occasionally slow (a GC pause, say), a small initialDelaySeconds and a small failureThreshold can get the container killed by mistake.
Readiness Probe — “Ready to Take Customers?”
Readiness checks whether the container can receive traffic. If the check fails, K8s removes the Pod from the Service’s backend list and stops sending traffic to it. Once it succeeds again, the Pod is added back.
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
The core difference between Readiness and Liveness:
| Liveness | Readiness | |
|---|---|---|
| Consequence of failure | The container is killed and restarted | The Pod is removed from the Service |
| How it recovers | Only by restarting the container | Recovers automatically once the probe succeeds again |
| Scope of impact | Only the current Pod | The distribution of traffic from the Service |
| What it is for | Solving a “process that only looks alive” | Solving “taking traffic before it is ready” |
The key insight: if an application takes 60 seconds to start and the Readiness probe first passes at second 30, then for the first 30 seconds this Pod receives no external requests — the Service does not send traffic to it. This solves the “slow startup causes 502” problem perfectly.
Startup Probe — Protecting Applications That Hate Waking Up
If your application starts very slowly (two minutes, say), initialDelaySeconds can only set a fixed delay, and it is hard to get the number right. Set it too small and Liveness declares failure and restarts the container before startup has finished; set it too large and failure detection gets slower.
The Startup Probe was introduced precisely to solve this pain point. Stable since K8s 1.18, it has become standard in production environments, and it is strongly recommended for applications with slow startup (Java/Spring Boot, for example).
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30 # up to 30 failures allowed
periodSeconds: 10 # check every 10 seconds
What this configuration means: the container has as long as 300 seconds (30 × 10 seconds) to finish starting up. During that period:
- If the Startup Probe has not yet succeeded, Liveness and Readiness checks are not triggered.
- Once the Startup Probe has succeeded a single time, it no longer runs. Liveness and Readiness take over after that.
# A complete example: the three probes used together
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
failureThreshold: 3
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 10
How Probes Are Implemented
All three kinds of probes support the following check methods:
| Method | Usage | Where it fits |
|---|---|---|
httpGet |
An HTTP GET request | Web services, REST APIs |
tcpSocket |
A TCP port probe | Databases, caches |
exec |
Run a command in the container | Custom check logic |
An example of exec:
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
The application signals that it is healthy by writing a marker file, /tmp/healthy. If the file does not exist, something is wrong and K8s restarts the container.
Common Pitfalls
Pitfall 1: Liveness configured too aggressively. initialDelaySeconds: 5 plus failureThreshold: 1 — if the application has a single GC pause longer than 5 seconds, it gets killed. In production, set failureThreshold to at least 3.
Pitfall 2: the Readiness probe checks an external dependency. Say the Readiness probe goes and checks the database connection — when the database goes down, every Pod’s Readiness turns unhealthy and all of them are removed. But once they are removed there is nothing left, and the service is completely unavailable. Readiness should only check the state of its own container; problems with external dependencies belong to circuit breaking and degradation mechanisms, not to pulling Pods out of the rotation.
Pitfall 3: no Startup Probe configured. For a slow-starting application, Liveness times out during startup, the container is restarted over and over, and it never comes up. If you see CrashLoopBackOff, check whether this is the cause.
3. Rolling Updates — the Key to Zero Downtime
Health checks solve the question of whether a single Pod is available; rolling updates solve the question of how to update without interrupting the service.
How Rolling Updates Work
When you update the image version of a Deployment, K8s does not wipe out all the old Pods at once and then create new ones (that would interrupt the service). It replaces them gradually:
Step 0: [Old] [Old] [Old] 3 个旧 Pod 在运行
Step 1: [Old] [Old] [Old] [New] 创建一个新 Pod
Step 2: [Old] [Old] [New] 新 Pod Ready 后,删一个旧 Pod
Step 3: [Old] [New] [New] 再创建一个新 Pod
Step 4: [Old] [New] [New] [New] 新 Pod Ready 后,再删一个旧 Pod
Step 5: [New] [New] [New] 全部替换完成
Traffic never stops during the process — the old Pod still finishes the requests it is handling before it is deleted, and the new Pod only starts receiving requests after it is Ready.
Controlling the Speed of a Rolling Update
Deployment.spec.strategy has two key parameters:
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # how many Pods may be unavailable at most
maxSurge: 1 # how many Pods beyond the desired replica count are allowed
Take a replica count of 3 as an example:
| maxUnavailable | maxSurge | Minimum available | Maximum running | Effect |
|---|---|---|---|---|
| 1 | 1 | 2 | 4 | Shut down one old Pod and start one new one at each step (recommended) |
| 0 | 1 | 3 | 4 | Start the new one first, then shut down the old one. More conservative, zero risk |
| 1 | 0 | 2 | 3 | Shut down the old one first, then start the new one. Slightly more aggressive |
If your application must never be one Pod short (because the load is running right at capacity, say), choose maxUnavailable: 0. When you have resources and time to spare, this is usually the safest choice.
Rolling Updates Working Together with Readiness
Rolling updates depend on the Readiness probe. The flow is:
- A new Pod is created, in state
Running(the process has started). - The Readiness probe checks the
/readyendpoint. - Once the Readiness probe succeeds, the Pod is added to the Service’s backends.
- The old Pod begins a graceful shutdown (it receives SIGTERM).
- The old Pod exits after finishing the requests already in flight.
- Repeat until every Pod has been updated.
If Readiness is not configured, K8s considers the container Ready the moment it starts and routes traffic over at once, while the application may still be loading configuration or connecting to the database — and the requests that arrive then produce errors such as 502s and connection refused.
Verifying Zero Downtime
You can watch and control a rolling update with the kubectl rollout commands:
# Trigger the update
kubectl set image deployment/myapp myapp=harbor.yourcompany.com/backend/myapp:v2.0
# Watch the update process
kubectl rollout status deployment/myapp
# Watch the Pods change
kubectl get pods -w
# Pause the update (when you spot a problem)
kubectl rollout pause deployment/myapp
# Resume the update
kubectl rollout resume deployment/myapp
# Roll back
kubectl rollout undo deployment/myapp
At the same time you can keep requesting the service from another terminal with while true; do curl -s http://service/health; sleep 1; done to verify whether any requests are dropped during the whole update.
4. A Complete Production-Grade Deployment Example
Pulling together everything we have learned in this series, here is a complete Deployment configuration that is ready for production:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0 # never allow a shortage of replicas
maxSurge: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
terminationGracePeriodSeconds: 30 # time allowed for a graceful exit
containers:
- name: myapp
image: harbor.yourcompany.com/backend/myapp:v2.0
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "production"
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
startupProbe:
httpGet:
path: /actuator/health
port: 8080
failureThreshold: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
periodSeconds: 5
failureThreshold: 3
5. Series Recap
With this, all 10 posts of the cloud native deployment series are complete. Let us look back at the road we have travelled:
| Post | Topic | Key takeaway |
|---|---|---|
| 1 | The history of deployment evolution | Knowing where cloud native came from and what problem it solved |
| 2 | Docker fundamentals | Understanding how images are layered and how containers run |
| 3 | Dockerfile best practices | Writing images that are both small and secure |
| 4 | The Harbor image registry | Building an enterprise-grade private registry, managing the image life cycle |
| 5 | K8s architecture core | Understanding what each Master/Node/etcd component is responsible for |
| 6 | Pod/Deployment/Service/ConfigMap/Secret | Mastering the core API objects you use every day in K8s |
| 7 | K8s networking | Getting CNI, kube-proxy and DNS service discovery straight |
| 8 | K8s storage | Persisting data with PV/PVC/StorageClass |
| 9 | Helm package management | Templated deployment, one Chart for many environments |
| 10 | Health checks and zero downtime | Configuring Probes and rolling updates, shipping safely |
From one application on a single physical machine to zero-downtime rolling updates on a K8s cluster — we have walked that road step by step. Cloud native deployment is not something you get right in one go; these 10 posts cover the core path, and I hope they are of help to you.
Summary
Health checks are K8s’s “immune system” and rolling updates are its “painless scalpel”. Liveness detects deadlocks, Readiness controls traffic, Startup protects slow starts — use the three probes together with maxUnavailable and maxSurge and your deployments will truly be invisible to users.
A small step forward every day is a new height!