bigywater.tech
Kubernetes · 34

preStop 与终止:优雅退出的时序

优雅退出的时序到底是什么?preStop 睡一会儿真的能让负载均衡有时间摘除吗?宽限期是怎么被 preStop 和应用退出分掉的?这篇用节点上的 /proc/uptime 时间戳和 EndpointSlice 的状态变化,把删除一个 Pod 的每一步都对到同一条时间轴上。

+0.15 s vs +5.15 s
端点被标记 terminating 的时刻,与 preStop 睡 5 秒之后 SIGTERM 送达的时刻
+6.07 s
preStop 比宽限期(6 秒)还长时,SIGTERM 终于送达的时刻——钩子被截断
8.5 秒
宽限期 6 秒、应用 5 秒才肯退出时,kubectl delete 到 Pod 消失的耗时(SIGTERM 后又多给了约 2 秒)

preStop 在 SIGTERM 之前

实测 · 本机执行输出
  preStop 睡 5 秒、应用收到 SIGTERM 后再睡 1 秒才退出:kubectl delete 从发出到 Pod 消失用了 7.2 秒
    容器日志 start     T0 -2.83s
    容器日志 prestop-start T0 +0.13s
    容器日志 prestop-end T0 +5.13s
    容器日志 sigterm   T0 +5.15s
    容器日志 exit      T0 +6.15s
    EndpointSlice 里端点的 (ready, serving, terminating) = ((True, True, False),)  T0 -0.64s
    EndpointSlice 里端点的 (ready, serving, terminating) = ((False, True, True),)  T0 +0.15s
    EndpointSlice 里端点的 (ready, serving, terminating) = (没有端点)  T0 +6.32s
    事件 Killing:Stopping container app

以 kubectl delete 发出的时刻为 T0:+0.13 秒 preStop 开始,+5.13 秒结束,SIGTERM 在 +5.15 秒才到,+6.15 秒应用退出,kubectl delete 一共等了 7.2 秒。EndpointSlice 里这个端点在 +0.15 秒就变成 (ready=false, serving=true, terminating=true):从被删除那一刻起 Service 就不再把新流量发给它,不用等 preStop。常见的 preStop: sleep 5 就是利用这段时间,让负载均衡、kube-proxy 有机会把它摘掉,再收到 SIGTERM。

没有 preStop 的对照

实测 · 本机执行输出
  preStop 是个空命令(true):kubectl delete 从发出到 Pod 消失用了 1.5 秒
    容器日志 start     T0 -2.85s
    容器日志 sigterm   T0 +0.07s
    容器日志 exit      T0 +1.08s
    EndpointSlice 里端点的 (ready, serving, terminating) = ((True, True, False),)  T0 -0.62s
    EndpointSlice 里端点的 (ready, serving, terminating) = ((False, True, True),)  T0 +0.08s
    EndpointSlice 里端点的 (ready, serving, terminating) = (没有端点)  T0 +1.18s
    事件 Killing:Stopping container app

preStop 是空命令时 SIGTERM 在 +0.07 秒就到,端点在 +0.08 秒变成 terminating,两件事几乎同时发生——如果负载均衡摘除得慢,就有新请求被送到一个已经开始关闭的进程。这是为什么要用 preStop 睡一会儿。

钩子与宽限期共用预算

实测 · 本机执行输出
  terminationGracePeriodSeconds=6,preStop 睡 10 秒,应用收到 SIGTERM 后要 5 秒才肯退出:kubectl delete 从发出到 Pod 消失用了 8.5 秒
    容器日志 start     T0 -2.86s
    容器日志 prestop-start T0 +0.07s
    容器日志 sigterm   T0 +6.07s
    EndpointSlice 里端点的 (ready, serving, terminating) = ((True, True, False),)  T0 -0.61s
    EndpointSlice 里端点的 (ready, serving, terminating) = ((False, True, True),)  T0 +0.07s
    EndpointSlice 里端点的 (ready, serving, terminating) = (没有端点)  T0 +8.28s
    事件 Killing:Stopping container app
    事件 FailedPreStopHook:PreStopHook failed

preStop 睡 10 秒但 terminationGracePeriodSeconds 只有 6 秒:+6.07 秒宽限期用完,钩子被截断(事件 FailedPreStopHook),SIGTERM 这时才送达;应用要 5 秒才肯退出,来不及了,Pod 在 +8.5 秒消失——SIGTERM 之后又被额外给了约 2 秒才被 SIGKILL,日志里没有应用的 exit。所以 preStop 的时间要计入宽限期,宽限期要设得比 preStop 加应用退出时间更大。

preStop 失败

实测 · 本机执行输出
  preStop 执行 exit 1:kubectl delete 从发出到 Pod 消失用了 1.6 秒
    容器日志 start     T0 -2.97s
    容器日志 prestop-fail T0 +0.11s
    容器日志 sigterm   T0 +0.12s
    容器日志 exit      T0 +1.12s
    EndpointSlice 里端点的 (ready, serving, terminating) = ((True, True, False),)  T0 -0.71s
    EndpointSlice 里端点的 (ready, serving, terminating) = ((False, True, True),)  T0 +0.13s
    EndpointSlice 里端点的 (ready, serving, terminating) = (没有端点)  T0 +1.28s
    事件 Killing:Stopping container app
    事件 FailedPreStopHook:PreStopHook failed

钩子 exit 1:事件 FailedPreStopHook: PreStopHook failed,但终止照常进行——SIGTERM 紧接着在 +0.12 秒到,应用 +1.12 秒退出。preStop 不能当作“一定会执行完的清理步骤”;重要的清理要写在应用自己处理 SIGTERM 的代码里。

分步演示

删除时间线
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0);应用是 busybox sh,trap TERM 后写日志、睡指定秒数、写日志、退出,日志写在 hostPath 卷里,时间取自 /proc/uptime,T0 取删除前一刻节点的 uptime;EndpointSlice 每 50 毫秒轮询一次(宿主机时钟),两种时钟之间有几十毫秒误差;Service 没有 readinessProbe;每种情形各一次,Pod 消失用时包含 kubectl 的开销
    • 没有验证:真实的流量(没有用客户端持续请求去观察丢包);kube-proxy 在各节点上摘除规则的延迟;preStop 用 httpGet 与 sleep 类型(sleep 钩子)的差别;terminationGracePeriodSeconds 被 kubectl delete --grace-period 覆盖的情形(下一篇);ingress / 云负载均衡的摘除时间;多容器 Pod 的终止顺序
    京ICP备2026067502号-1京公网安备11010502063487号