Pod 挂了没人管?Kubernetes 探针教它自己判断生死
Kubernetes 调度 Pod 的逻辑很积极——容器崩了就拉起来,节点挂了就迁移。但有些情况它判断不了:进程还活着但死锁了,端口还在监听但连不上数据库,启动太慢被误杀又重启又太慢又误杀……探针就是让 Pod 自己告诉 K8s “我什么状态”。

1. 三种探针各管什么
| 探针 | 作用 | 失败后果 | 适合场景 |
|---|---|---|---|
| Liveness | 容器是否活着 | 重启 Pod | 检测死锁、内存泄漏 |
| Readiness | 是否准备好接流量 | 从 Endpoints 摘除 | 等待依赖就绪、预热缓存 |
| Startup | 是否启动完成 | 重启 Pod(但先不跑 Liveness) | 慢启动应用保护 |
三者最容易搞混的是 Liveness 和 Readiness。一个简单的区分方式:Liveness 失败会重启,Readiness 失败只是摘流量不重启。你的服务连不上数据库,应该用 Readiness——服务本身没坏,等数据库恢复就好,重启反而浪费时间。

Startup Probe 是后来加的,专门解决慢启动应用被 Liveness 误杀的问题。Java 应用启动动辄 60 秒,如果 Liveness 设的 initialDelaySeconds: 30,30 秒时还没起来就被判定为不健康,重启后又得重来,永远起不来。
2. 配置实战:一个 HTTP 服务的完整探针
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 3
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
containers:
- name: api
image: registry.example.com/api:v2.1
ports:
- containerPort: 8080
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
failureThreshold: 3
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
failureThreshold: 2
timeoutSeconds: 2
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
逐字段拆解:
startupProbe:每 5 秒探一次/healthz,允许失败 30 次,也就是最多等 150 秒启动。通过后这个探针就不再执行。livenessProbe:每 10 秒探一次,连续 3 次失败才重启,单次超时 3 秒。给网络抖动留了缓冲。readinessProbe:每 5 秒探一次,2 次失败就摘流量。比 Liveness 更敏感,因为摘流量代价低。
3. 健康检查端点怎么写
探针指向的 HTTP 端点需要自己在应用里实现。一个常见的写法:
func registerHealth(mux *http.ServeMux, db *sql.DB, redis *redis.Client) {
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
mux.HandleFunc("/ready", func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
if err := redis.Ping(ctx).Err(); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
})
}
/healthz 只检查进程本身——能响应就说明没死锁。/ready 检查依赖——数据库和 Redis 都通才算就绪。这样数据库挂了,Pod 不会被重启(Liveness 通过),但流量会切到其他 Pod(Readiness 失败)。
4. TCP 探针和 Exec 探针
不是所有服务都有 HTTP 端口。TCP 探针适合数据库、消息队列这类服务:
livenessProbe:
tcpSocket:
port: 3306
periodSeconds: 10
failureThreshold: 3
Exec 探针适合跑脚本检查,比如看某个文件是否存在:
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
periodSeconds: 5
Exec 探针的退出码 0 表示成功,非 0 表示失败。注意 Exec 探针会增加容器开销,每期都要 fork 一个进程,高频检查不建议用。
5. 常见坑位
| 坑 | 现象 | 解决方案 |
|---|---|---|
| Liveness 检查依赖 | 依赖挂了 Pod 不断重启 | 用 Readiness 检查依赖 |
| initialDelaySeconds 太短 | 慢启动应用被误杀 | 改用 Startup Probe |
| 探针端点太重 | 每次检查跑全量逻辑 | /healthz 只返回 ok |
| 没设 timeoutSeconds | 探针请求卡住占资源 | 设 2-3 秒超时 |
| failureThreshold = 1 | 网络抖动就重启 | 至少设 3,给缓冲 |
| 探针和业务共用端口 | 探针请求被限流 | 用单独的端口或管理端口 |
最后一个坑值得展开。有些团队把 /healthz 挂在业务端口上,然后给业务端口加了限流中间件(比如前面文章里的令牌桶)。压测时限流把探针请求也拦了,K8s 判掉所有 Pod,服务直接没了。解法是开一个管理端口(比如 8081),探针走那个端口,不经过限流。
containers:
- name: api
ports:
- containerPort: 8080 # 业务端口
- containerPort: 8081 # 管理端口
livenessProbe:
httpGet:
path: /healthz
port: 8081 # 走管理端口
跑起来之后
kubectl describe pod <name>看 Events 里的探针失败记录- 监控
kube_pod_container_status_restarts_total,重启次数飙升说明探针配置有问题 - 金丝雀发布时把 Readiness 的
failureThreshold调低,让坏版本更快被摘除 - 探针端点不要做鉴权,K8s 不带你的 token
- 点赞
- 收藏
- 关注作者
评论(0)