bigywater.tech
Kubernetes · 35

terminationGracePeriodSeconds:SIGTERM 与 SIGKILL 之间发生了什么

为什么有的 Pod 删除要卡 30 秒?terminationGracePeriodSeconds 到底影响什么?这篇用同样的 busybox 容器,只改 1 号进程怎么对待 SIGTERM,把删除耗时一项项量出来,并验证 kubectl delete 的 --grace-period 覆盖和 sh -c 包一层的信号转发陷阱。

30.3 秒
不写 terminationGracePeriodSeconds 时,删除一个不处理 SIGTERM 的 Pod 的耗时(默认值 30)
0.8~1.2 秒
应用自己 trap SIGTERM 后的删除耗时,与宽限期 30 秒无关
5.6 秒 vs 0.5 秒
sh -c 包着 tail 时,没有转发与用 trap 转发信号的删除耗时(宽限期 5 秒)

不处理 SIGTERM 就要等满

实测 · 本机执行输出
  terminationGracePeriodSeconds=3,命令 sh -c "sleep 3600"(busybox 的 sleep 是 shell 内置,不 fork,1 号进程就是 sh 自己,没有 SIGTERM 处理函数):从 kubectl delete 到 Pod 消失 3.9 秒
  terminationGracePeriodSeconds=8,命令 sh -c "sleep 3600"(busybox 的 sleep 是 shell 内置,不 fork,1 号进程就是 sh 自己,没有 SIGTERM 处理函数):从 kubectl delete 到 Pod 消失 8.8 秒
  同样的宽限期 3 秒,但命令是 exec sleep 3600(sleep 本身是 1 号进程,同样没有处理函数):3.8 秒——Linux 不会因为一个没有处理函数的 SIGTERM 终止 1 号进程,所以都要等满宽限期

kubelet 先给 1 号进程发 SIGTERM,宽限期用完再 SIGKILL。命令是 sh -c "sleep 3600"(busybox 的 sleep 是 shell 内置,不 fork,1 号进程就是 sh 本身),terminationGracePeriodSeconds: 3 时删除用了 3.9 秒,设成 8 时用了 8.8 秒;改成 exec sleep 3600(sleep 自己是 1 号进程)同样等满。原因是 Linux 对 1 号进程特殊对待:没有处理函数的信号(SIGKILL 除外)会被忽略,所以进程不会因为 SIGTERM 退出。

处理了就很快

实测 · 本机执行输出
  宽限期 30 秒,sh 里 trap exit 0 TERM、循环 sleep 1:从 kubectl delete 到 Pod 消失 1.2 秒(sh 要等当前的 sleep 1 结束才处理信号)
  trap exit 0 TERM; sleep 3600 & wait(wait 会被信号立即打断):0.8 秒

应用自己处理 SIGTERM:trap exit 0 TERM 加循环 sleep 1,删除用了 1.2 秒(shell 要等当前这次 sleep 1 结束才跑 trap);trap … TERM; sleep 3600 & wait 里 wait 会被信号立即打断,0.8 秒。宽限期设成 30 秒也不会白等,宽限期只是上限。

覆盖与零

实测 · 本机执行输出
  Pod 里写的 terminationGracePeriodSeconds=60,kubectl delete --grace-period=2:2.8 秒
  --grace-period=0 --force:0.1 秒(API 对象立即删除)
  spec.terminationGracePeriodSeconds=0:0.1 秒
  不写 terminationGracePeriodSeconds 时服务端补上的默认值:30
  按默认值删除一个不处理 SIGTERM 的 Pod(sh -c sleep 3600):30.3 秒

宽限期可以在删除时覆盖:Pod 里写的是 60 秒,kubectl delete --grace-period=2 用了 2.8 秒。--grace-period=0 --force 立即从 API 里删掉对象(0.1 秒),容器随后被直接杀掉;spec.terminationGracePeriodSeconds: 0 也是 0.1 秒。不写这个字段时服务端补上默认值 30,不处理 SIGTERM 的 Pod 删除就卡 30.3 秒。

sh -c 的陷阱

实测 · 本机执行输出
  没有转发:sh 作 1 号进程,tail 是它的子进程:容器里的进程 ['1 sh -c tail -f /dev/null; echo done', '11 tail -f /dev/null'];terminationGracePeriodSeconds=5,删除用了 5.6 秒
  转发:sh 的 trap 把信号转给 tail:容器里的进程 ["1 sh -c tail -f /dev/null & c=$!; trap 'kill $c' TERM; wait $c; echo d", '11 tail -f /dev/null'];terminationGracePeriodSeconds=5,删除用了 0.5 秒

sh -c "tail -f /dev/null; echo done":容器里 1 号是 sh、11 号是 tail,SIGTERM 发给 1 号,sh 没有处理函数就忽略了,tail 根本没收到,5 秒宽限期的 Pod 5.6 秒才被 SIGKILL。把 tail 放到后台、trap 'kill $c' TERM 再 wait $c 把信号转发给子进程,0.5 秒就退出。更常见的做法是用 exec 让真正的程序成为 1 号进程,或在镜像里放 tini 一类的 init。

分步演示

SIGTERM 与 SIGKILL
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0);所有 Pod 用 busybox:1.36;删除耗时是 kubectl delete --wait=true 的整体耗时(含 kubectl 与 API 往返,约 0.1~0.5 秒的开销);容器里的进程列表用 kubectl exec 的 ps 读出;每种情形各一次
    • 没有验证:真正的应用(Java、Go 程序)的 SIGTERM 处理;shareProcessNamespace 下 pause 作为 1 号进程的效果;preStop 与宽限期的组合(上一篇);Pod 的 DeletionGracePeriod 在节点优雅关机时的缩短;kubectl delete --now;grace-period 为负数;SIGKILL 之后容器运行时清理失败时 Pod 的残留
    京ICP备2026067502号-1京公网安备11010502063487号