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