Kubernetes Deployment 滚动更新参数对可用性的影响
上周三晚上发了个版,发完过后没几分钟就有人反馈页面偶尔打不开,刷新一下又好了。去看了眼监控,好家伙,每次滚动更新那几十秒里,5xx都会冒出来一个小尖峰,平时一直是平的。其实以前也见过这个尖峰,一直觉得滚动更新嘛,抖一下很正常就没管,这回被用户直接投诉了才老实坐下来查。结果发现这几个滚动更新参数跟可用性关系还挺大,这玩意儿的默认值基本上就是“能更新完就行”,至于更新过程中抖不抖,它可不管你。为什么默认要这么设呢?大概是为了让更新跑得快吧...
下面把踩的坑和最后实测不会抖的配置记一下,免得下次忘了又从头查一遍。
先搞清楚滚动更新默认在干什么
Deployment 默认的更新策略是 RollingUpdate,maxSurge 和 maxUnavailable 都是 25%。拿 10 个副本举例:maxSurge 按 25% 算出来是 2.5,向上取整是 3;maxUnavailable 向下取整是 2。也就是说更新过程中 Pod 总数最多能到 13 个,但可用副本最少可以掉到 8 个。
问题就出在这儿。8 个副本去扛本来 10 个副本的量,平时可能没事,流量稍微一高或者新 Pod 起得慢,这段时间的延迟和错误率就上去了。而且 maxUnavailable 不为 0 意味着控制器是“杀旧的”和“起新的”混着来的,容量低谷几乎躲不掉。
所以第一种改法很直接,把 maxUnavailable 干成 0,maxSurge 给个写死的数字:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # <--- 副本少的话建议直接写数字,别用百分比
maxUnavailable: 0
这样更新全程可用副本只多不少,代价是更新期间会多占点资源。注意这俩不能同时为 0,不然更新直接卡住不动。副本是个位数的服务用百分比经常取整取出来一个 0,写死数字反而踏实。
minReadySeconds:新 Pod 刚 Ready 不能就算数
光改上面两个还不够。默认情况下 Pod 一变成就绪状态就算“可用”,立马计入可用副本,控制器马上接着推下一批。但很多应用刚起来那几秒是在预热,建连接池、加载缓存什么的,看着 Ready 了,实际上还没准备好接流量。要是起完几秒又崩了,k8s 也察觉不到,更新照样往下走。
加个 minReadySeconds 能好很多,意思是 Pod 必须稳定运行这么久才算可用,期间挂了这次更新就会停下来:
minReadySeconds: 15 # <--- 按自己应用的预热时间定,别瞎抄
progressDeadlineSeconds: 600
我给的 15 秒,配合 600 秒的 progressDeadlineSeconds,真卡住了十分钟内就能在事件里报出来,不至于一直干等。
就绪探针是这一切的前提
上面说的“可用”状态,全依赖 readinessProbe。要是没配探针,容器一跑起来就算 Ready,前面那些参数等于白调。所以探针必须配,而且路径要真的能反映“这个 Pod 能不能接请求”,别拿一个永远返回 200 的假接口糊弄它。
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
顺带提一句,启动特别慢的应用与其把 initialDelaySeconds 拉大,不如直接上 startupProbe,不然探针太松,应用假死了也发现不了。
最容易抖的其实是老 Pod 被杀那一下
前面说的都是新 Pod 这边的事,但实测下来,我那个 5xx 尖峰的大头其实来自另一头:更新时 k8s 给老 Pod 发 SIGTERM,同时把它的 endpoints 从 Service 里摘掉,这两件事不是同步的。经常是请求还挂在老 Pod 上没处理完,进程已经被杀了,客户端拿到一个连接重置,网关一转手就是 502。
解法很土但真管用,加个 preStop 先睡几秒,等 endpoint 摘干净再走应用的优雅退出:
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
terminationGracePeriodSeconds: 40 # <--- 要盖过sleep加上应用自己退出花的时间
sleep 多久没有标准答案,跟你的 Service 类型和客户端行为有关,可以看访问日志确认摘除要传播几秒。terminationGracePeriodSeconds 一定要比 preStop 的 sleep 加上应用处理完存量请求的时间长,不然到点还是被硬杀。
最后贴一下现在在用的完整配置
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2
maxUnavailable: 0
minReadySeconds: 15
progressDeadlineSeconds: 600
template:
spec:
terminationGracePeriodSeconds: 40
containers:
- name: app
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
改完过后又发了两次版,专门盯着监控看的,5xx 那条线全程是平的,总算不抖了。当然我这是小集群小流量的场景,副本量大的或者前面还挂着别的负载均衡层的,没准儿这些参数还得再磨一磨,建议先在测试环境把发版流程完整过一遍再上生产,不然线上真抖了你都不知道是哪个环节的问题。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。