每次公司做版本滚动更新,Sentry 上的报错和用户投诉就准时像雪花一样飘过来,绝大多数都是网关报出来的 502 Bad Gateway。开发觉得很无辜:“我们明明用了 K8s 默认的 RollingUpdate 滚动发布,而且新旧容器也是交替启动的,为什么还会影响用户访问?”
这里面有一个新手最容易踩的大坑:把“容器跑起来了(Running 状态)”等同于“服务能对外提供访问了”。
对于那些稍微重一点的微服务应用(比如 Java Spring Boot,或者需要连接多个数据库、进行 Redis 预热的 Node.js 应用),容器从启动到能够处理第一个 HTTP 请求,中间往往有 10 到 40 秒的 cold start 时间。如果你的 YAML 里没有配置 readinessProbe,Kubernetes 只要看到容器的 Entrypoint 进程起来了,就会立马把这个 Pod 加进 Service 的 Endpoints 列表里,并把老 Pod 杀掉。于是大量的外部用户流量瞬间涌入一个还在加载依赖、初始化数据库连接的“空壳”容器中,不报 502 才怪!
不仅如此,有些开发为了防止服务还没起来就被 K8s 误杀,干脆把 livenessProbe 的 initialDelaySeconds 设得极大(比如 120 秒)。这又带来了另外一个心智负担:如果容器在启动阶段真的卡死了,K8s 却要干等两分钟才会去重启它,极大地拉长了故障恢复时间。
为了彻底摆脱 502 轰炸,我们必须合理配置 Kubernetes 的三大健康检查探针:startupProbe(启动探针)、readinessProbe(就绪探针)和 livenessProbe(存活探针)。
以下是我们团队为生产环境 Java 微服务总结出的一套高可用、黄金配比的 Deployment YAML 配置文件示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service-deployment
namespace: production
labels:
app: payment-service
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # 确保发布过程中,可用副本数绝不低于期望值
selector:
matchLabels:
app: payment-service
template:
metadata:
labels:
app: payment-service
spec:
containers:
- name: payment-service-container
image: registry.company.com/library/payment-service:v2.1.4
ports:
- containerPort: 8080
name: http-port
# 1. 启动探针:先由它来接管冷启动阶段。在它成功前,其他两个探针都处于禁用状态
startupProbe:
httpGet:
path: /actuator/health/liveness
port: http-port
# 每次检测间隔 5 秒,最多重试 30 次,一共给应用 150 秒的启动宽限期
failureThreshold: 30
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 2
# 2. 就绪探针:只决定 Pod 是否挂载到流量入口。如果失败,仅剔除流量,不重启容器
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http-port
# 启动探针通过后,每 10 秒检测一次,只要有 1 次失败就立刻从 Service Endpoints 中剔除
periodSeconds: 10
failureThreshold: 2
successThreshold: 1
timeoutSeconds: 3
# 3. 存活探针:只负责决定是否重启容器。尽量使用轻量的端点,避免因数据库抖动导致容器被无脑重启
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http-port
# 启动探针通过后,每 15 秒检测一次,连续 3 次失败则重启 Pod
periodSeconds: 15
failureThreshold: 3
successThreshold: 1
timeoutSeconds: 3
在这套配置中,startupProbe 作为前哨,负责处理漫长的类加载与初始化工作。当它成功之后,readinessProbe 和 livenessProbe 才会启动。
这里还有个关键细节:readinessProbe 的 failureThreshold 设置得比较敏感(为 2),这样一旦下游数据库发生抖动,或者应用内部线程池打满拒绝请求时,Pod 能够迅速被摘除,不再接收新流量。而 livenessProbe 的阈值设得比较宽松(为 3),这样能容忍短期的瞬时波动,避免容器在业务高峰期因为短暂响应慢而被无脑杀掉重启,从而引发雪崩效应。
引入这套三层防线配置之后,我们项目的滚动发布终于实现了真正的“0 故障无缝平滑过渡”。
你们的集群里有遇到过因为探针写得太重(比如就绪探针直接去 SELECT 数据库),导致数据库抖动引发所有 Pod 被 K8s 疯狂循环重启的情况吗?这种情况下你们是怎么做降级处理的?