小Cの已经记不起来的博客

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 那条线全程是平的,总算不抖了。当然我这是小集群小流量的场景,副本量大的或者前面还挂着别的负载均衡层的,没准儿这些参数还得再磨一磨,建议先在测试环境把发版流程完整过一遍再上生产,不然线上真抖了你都不知道是哪个环节的问题。

评论

还没有评论。

发表评论

提交后评论将经过自动审核,审核通过后公开展示。

未在播放