我把 K8s 发布事故率从 30% 降到 0,只用对了这 3 个配置
摘要
K8s 滚动更新频繁失败?服务中断、请求 502、回滚手忙脚乱?我踩过所有坑。从 readinessProbe 配置到资源限制,从更新策略到优雅关闭,分享一套完整的生产级发布方案。实测发布事故率从 30% 降到 0,零停机成为常态。配置模板已开源。
开篇:Kubernetes介绍
Kubernetes(简称 K8s) 是由 Google开源的容器编排管理平台,用于自动化部署、扩展和管理容器化应用,现由云原生计算基金会(CNCF)托管维护 。它有以下特点:
一、核心运维能力
(一)自动化部署与回滚
Kubernetes 支持分步骤完成应用及配置的上线操作,全程监控应用运行状态,避免实例被同时终止;若上线过程出现异常,可自动触发版本回滚,保障应用服务稳定性。
(二)自我修复能力
重启故障容器,节点故障时自动替换并重新调度容器资源;
终止未通过用户定义健康检查的容器,待容器就绪后再对外提供服务;
混合部署关键业务与尽力而为类工作负载,平衡资源利用率与服务可用性。
(三)弹性扩缩机制
支持通过命令、UI 手动触发应用扩缩;
可基于 CPU 使用率等指标自动完成应用水平扩缩,适配不同流量场景。
二、网络与存储能力
(一)网络管理
服务发现与负载均衡:为容器分配独立 IP 地址与 DNS 名称,无需修改应用代码即可实现服务发现与跨容器负载均衡;
双协议栈支持:为 Pod 和 Service 同时分配 IPv4 与 IPv6 地址,适配多网络环境。
(二)存储编排
自动挂载各类存储系统,覆盖存储类型如下:
本地存储;
公有云存储(如 AWS、GCP 提供的存储服务);
网络存储(如 NFS、iSCSI、Ceph、Cinder)。
三、配置与安全能力
(一)配置与密钥管理
支持部署及更新 Secret(密钥)与应用配置,无需重新构建容器镜像,避免敏感配置信息暴露在软件堆栈中。
(二)资源智能调度
根据容器资源需求与各类限制条件,自动完成容器节点分配,最大化集群资源利用率。
四、扩展与批量作业能力
(一)批量作业管理
除常规服务类工作负载外,可统一管理批处理任务与 CI 工作负载,支持自动替换失效容器,保障批量任务执行效率。
(二)集群扩展性
基于开源生态设计,无需修改上游源码即可实现 Kubernetes 集群的自定义扩展,适配多样化业务场景需求。
去年双十一前夕,我们有个核心服务要上线新版本。测试环境一切正常,结果生产环境一发布,直接炸了。
滚动更新过程中,旧 Pod 被杀掉,新 Pod 还没 ready,流量打过来全是 502。监控告警响成一片,老板在群里@我:"怎么回事?用户投诉电话都打爆了。"
最后紧急回滚,但已经影响了大概 5 分钟。虽然时间不长,但那天订单量巨大,损失不小。
事后复盘,问题其实很低级:readinessProbe 没配,资源限制没设,更新策略用的默认值。三个坑,我全踩了。
接下来一个月,我把 K8s 滚动更新的配置从头到尾研究了一遍,参考了官方文档、社区最佳实践,还有大厂开源的配置模板。最后整理出一套生产级方案,之后半年多,发布再也没出过事故。
今天就把这套配置完整分享出来。如果你也在用 K8s 做服务发布,希望能帮你避开我踩过的坑。
问题出在哪?先搞懂滚动更新的流程
很多人配 K8s,就是把 YAML 复制粘贴,改个镜像 tag 就完事了。结果一发布就翻车。
得先搞明白滚动更新到底发生了什么:
1. 创建新 Pod(ReplicaSet 扩容) 2. 新 Pod 启动,通过 readinessProbe 检查 3. 新 Pod ready 后,流量切过来 4. 旧 Pod 被标记为 Terminating 5. 旧 Pod 执行 preStop hook(如果有) 6. 旧 Pod 收到 SIGTERM,开始优雅关闭 7. 宽限期结束后,SIGKILL 强制杀掉问题往往出在 2、5、6 这三个环节。新 Pod 没 ready 就接流量,旧 Pod 没处理完请求就被杀掉,中间就会出现服务中断。
第一招:readinessProbe 必须配(而且得配对)
2.1 为什么 livenessProbe 不够用
很多人只配 livenessProbe,觉得 Pod 活着就能接流量。大错特错。
livenessProbe 只管 Pod 死活,readinessProbe 才管 Pod 能不能接客。我见过太多案例:Pod 启动了,但应用初始化还没完成(比如连数据库、加载缓存),这时候 livenessProbe 过了,但 readinessProbe 应该没过。
# 错误示范:只配了 livenessProbe livenessProbe: httpGet: path:/health port:8080 initialDelaySeconds:10 periodSeconds:10 # 正确做法:两个都配,且 readinessProbe 更严格 readinessProbe: httpGet: path:/ready port:8080 initialDelaySeconds:5 periodSeconds:5 failureThreshold:3 timeoutSeconds:2 livenessProbe: httpGet: path:/health port:8080 initialDelaySeconds:30 periodSeconds:10 failureThreshold:3这里有个细节:/ready和/health应该是两个不同的端点。/ready检查应用是否准备好接客(依赖是否就绪),/health检查应用是否还活着(进程是否在)。
2.2 初始化依赖的检查
如果应用启动时需要连数据库、Redis、消息队列,别在代码里重试就算了,直接把依赖检查写进 readinessProbe:
readinessProbe: exec: command: - /bin/sh - -c - | curl -f http://localhost:8080/ready || exit 1 # 额外检查数据库连接 pg_isready -h $DB_HOST -p $DB_PORT || exit 1 initialDelaySeconds: 10 periodSeconds: 5说实话,这个配置救过我很多次。有次数据库升级,新 Pod 启动后连不上旧版数据库,readinessProbe 一直失败,流量就没切过去,避免了线上事故。
第二招:更新策略得调优(默认值真的不行)
3.1 maxSurge 和 maxUnavailable
K8s 滚动更新有两个关键参数:
maxSurge:最多可以超出期望副本数的 Pod 数量maxUnavailable:最多可以不可用的 Pod 数量
默认值是 25%,但这个值对于不同场景可能不合适。
strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 每次多创建 1 个 Pod maxUnavailable: 0 # 不允许有 Pod 不可用对于核心服务,我建议maxUnavailable设为 0,保证发布过程中始终有足够容量的 Pod 在处理流量。maxSurge设为 1 或 2,控制发布速度。
如果集群资源紧张,可以反过来:maxSurge: 0, maxUnavailable: 1,但这样发布期间容量会下降,得评估是否能承受。
3.2 minReadySeconds
这个参数很多人不知道,但非常有用。它指定 Pod ready 后,至少要等多久才算真正可用。
minReadySeconds: 30为什么需要这个?因为 readinessProbe 通过后,应用可能还在做最后的初始化(比如预热缓存、建立连接池)。给它 30 秒缓冲,避免流量进来时应用还没完全准备好。
我有个服务,readinessProbe 过了之后还需要大概 20 秒加载热点数据到内存。一开始没配 minReadySeconds,结果发布后前几秒请求延迟很高。加上这个配置,问题解决了。
第三招:优雅关闭必须做(SIGTERM 不是摆设)
4.1 preStop hook
旧 Pod 被杀掉之前,会先收到 SIGTERM 信号。但很多应用根本没处理这个信号,直接硬死。
更好的做法是加 preStop hook,在收到 SIGTERM 后主动停止接收新请求:
lifecycle: preStop: exec: command: - /bin/sh - -c - | # 先从服务发现中摘除自己 curl -X POST http://localhost:8080/admin/deregister # 等 10 秒,让已有请求处理完 sleep 10这个 sleep 很关键。因为 K8s 从把 Pod 标记为 Terminating 到实际发送 SIGTERM,中间有时间差。preStop 执行期间,Endpoint 会被移除,流量不会再进来。sleep 这段时间,专门用来处理已经在处理的请求。
4.2 terminationGracePeriodSeconds
这个参数指定从发送 SIGTERM 到强制 SIGKILL 的宽限期。默认 30 秒,对于很多应用可能不够。
terminationGracePeriodSeconds: 60我建议根据应用实际情况设置。如果有关键事务处理(比如订单支付),给长一点;如果是无状态服务,30 秒可能够了。
但注意,这个时间不是越长越好。设置太长会导致发布变慢,而且如果应用真的卡死了,长时间无法释放资源。
资源限制:别省这点内存
发布翻车的另一个常见原因:新 Pod 因为资源不足启动失败,或者启动后被 OOMKilled。
resources: requests: cpu: "500m" memory: "512Mi" limits: cpu: "1000m" memory: "1Gi"requests 是调度时预留的资源,limits 是硬上限。我建议:
requests 设为应用正常运行时的实际用量(可以通过监控历史数据得出)
limits 设为 requests 的 1.5-2 倍,给突发流量留缓冲
千万别把 limits 设得太紧。有次我把内存 limits 设为 512Mi,结果某个版本代码有内存泄漏,Pod 反复被 OOMKilled,滚动更新卡住了。
踩坑记录
坑 1:健康检查端点实现不对
很多人/health端点直接返回 200,什么都不检查。这等于没配。
正确的健康检查应该包括:
数据库连接是否正常
关键依赖服务是否可达
应用内部状态是否正常(比如线程池是否耗尽)
坑 2:镜像拉取策略
如果用imagePullPolicy: Always,每次发布都要拉镜像,发布时间会很长。建议用 tag 版本管理,配合IfNotPresent。
坑 3:忽略节点资源
发布时新 Pod 调度失败,因为集群资源不足。建议设置 PodDisruptionBudget,保证发布期间至少有 N 个 Pod 可用:
apiVersion: policy/v1 kind:PodDisruptionBudget metadata: name:myapp-pdb spec: minAvailable:2 selector: matchLabels: app:myapp完整配置模板
apiVersion: apps/v1 kind:Deployment metadata: name:myapp spec: replicas:3 strategy: type:RollingUpdate rollingUpdate: maxSurge:1 maxUnavailable:0 minReadySeconds:30 template: spec: terminationGracePeriodSeconds:60 containers: -name:myapp image:myapp:v1.2.3 imagePullPolicy:IfNotPresent ports: -containerPort:8080 readinessProbe: httpGet: path:/ready port:8080 initialDelaySeconds:5 periodSeconds:5 failureThreshold:3 timeoutSeconds:2 livenessProbe: httpGet: path:/health port:8080 initialDelaySeconds:30 periodSeconds:10 failureThreshold:3 lifecycle: preStop: exec: command:["/bin/sh","-c","curl -X POST http://localhost:8080/admin/deregister && sleep 10"] resources: requests: cpu:"500m" memory:"512Mi" limits: cpu:"1000m" memory:"1Gi"这个模板我们用了半年多,发布过几十次,零事故。
写在最后
K8s 滚动更新这个功能本身很强大,但默认配置真的不适合生产环境。我分享的这些配置,每个都是用事故换来的经验。
如果你也在用 K8s 做服务发布,建议对照检查一下:readinessProbe 配了吗?更新策略调了吗?优雅关闭做了吗?
觉得有用的话,点个赞或者在看,让我知道这篇文章帮到你了。也欢迎关注,后续会分享更多 K8s 实战经验。
配置模板我整理成了 GitHub 仓库,有需要的可以自取。评论区也可以聊聊你们在 K8s 发布中遇到过什么坑。
官方网站:
https://kubernetes.p2hp.com/