一、
结论一句话
旧 Pod 被选中下线后,kubelet 立即给它们发 SIGTERM,最多再保留 30 秒(你设置的 terminationGracePeriodSeconds)。
真正“消失”的时点 = “对应的新 Pod 已经 Ready” + 最多 30 秒;如果容器 3 秒就自己退出,那实际上 3 秒就会消失。
为什么这样算
-
RollingUpdate 的节奏
• maxSurge = 2:允许比期望副本多出 2 个新 Pod。
• maxUnavailable = 2:一旦这些新 Pod 进入 Ready,控制器就会把 最多 2 个旧 Pod 标为 Terminating。
• 整个过程循环进行,直到旧 Pod = 0。 -
优雅退出窗口
• Pod 收到 SIGTERM → 有 30 s 自己做清理并退出。
• 超过 30 s 仍未退出 → kubelet 对每个容器发 SIGKILL,Pod 状态随即消失。 -
变量只有两个
a) 新 Pod 从 Pending → Ready 的耗时(镜像拉取 + 启动 + 健康检查)。
b) 你给的 30 s 退出宽限。
示例(replicas=6,假设新 Pod 平均 20 s Ready)
• t=0 s:Deployment 打补丁,新 RS 起 2 个 Pod(surge)
• t≈20 s:2 个新 Pod Ready → 控制器立刻把 2 个旧 Pod 置为 Terminating
└→ 旧 Pod 最晚 t≈50 s 消失(20 s+30 s)
• 接着再起第 3、4 个新 Pod……如此循环,直到全部替换完
因此
• 单个旧 Pod 从“被选中下线”到“彻底删除”的最长时间恒定为 30 s
• 整体滚动时长 = (新 Pod 启动时长 + 30 s) × ceil(旧副本数 / 2)
(因为每轮最多同时淘汰 2 个旧 Pod)
如果想让旧 Pod 更快消失
• 把 terminationGracePeriodSeconds 调小(前提是服务能够秒级退出)
• 或者让容器在 SIGTERM 后主动关闭长连接、尽早结束进程
二、旧 Pod 置为 Terminating代表旧pod已经关闭了吗?
不是。
Pod 状态被标记为 Terminating
只是说明 “删除流程已启动”,并不意味着容器已经退出。它进入的是一个“优雅关闭”阶段,具体步骤如下:
- Kubernetes API Server 为该 Pod 加上
deletionTimestamp
(并把status.phase
设为Running
+Terminating
)。 - Endpoints/EndpointSlice Controller 看到
deletionTimestamp
,会立即把它从 Service 的可用端点中移除——新请求不会再转发到它。 - Kubelet 立刻向 Pod 里的所有容器发送 SIGTERM,并开始计时
terminationGracePeriodSeconds
(你设的 30 s)。 - 在这段宽限期内:
• 如果容器自行退出 → Pod 状态转为Succeeded
/Failed
→ kubelet 删除 Pod → 资源(IP、cgroup、volume-mount)回收
• 如果超时仍未退出 → kubelet 发送 SIGKILL 强制杀进程 → 同样做清理并把 Pod 从 API Server 删除 - 当最后一个容器彻底终止并且 kubelet 已汇报完成,Pod 对集群来说才算“真正消失”。
所以:
• Terminating
= “正在关机” 而非 “已经关机”;
• 容器还可能在执行清理逻辑(刷新缓存、刷写日志、关闭连接等);
• 最多会持续 terminationGracePeriodSeconds
那么久,然后一定结束。
可视化查看:
kubectl describe pod <pod> | grep -A3 "State:"
# 看 container 的 State 是否还是 Running/Terminated
kubectl get pod <pod> -w
# 会看到 STATUS 从 Terminating → Completed(删除) 的全过程及耗时
如果需要缩短这一段“正在关机”的时间,就要:
- 让你的进程在收到 SIGTERM 时尽快退出;
- 把
terminationGracePeriodSeconds
设得更小(例如 5-10 s),确保不会影响数据完整性。
三、那我rollingUpdate: maxSurge: 2 maxUnavailable: 2 terminationGracePeriodSeconds: 30 我yaml是这个配置,重新部署deployment时,旧的pod到底多长时间会关闭?
可量化的 2 个时间段:
-
等新 Pod 就绪
• 控制器只有在“启动的某个新 Pod进入 Ready”后,才会挑选同样数量(最多 maxUnavailable=2)旧 Pod 去下线。
• 这段耗时 = 新 Pod 从 Pending → Ready 的时间(镜像拉取 + 启动 + readinessProbe),记为 T_ready。
• 你的 YAML 中没有限制这一段,所以它取决于镜像大小、探针设置等。 -
优雅退出宽限
• 旧 Pod 一旦被标记为 Terminating,kubelet 当场发 SIGTERM 并开始计时。
• 你把 terminationGracePeriodSeconds 设成 30,表示“让容器自己收尾的最长时间”。
• 如果进程 5 秒就退出,则 5 秒后 Pod 被删;若 30 秒仍未退出,就被 SIGKILL 强制删除。
因此,单个旧 Pod 从“被选中淘汰”到“完全消失”的上限 =
T_ready (取决于新 Pod 启动速度) + 30 秒(你的优雅退出窗口)
• 若新 Pod 20 秒就 Ready → 旧 Pod 最晚 20 + 30 = 50 秒后消失。
• 若新 Pod 5 秒就 Ready → 旧 Pod 最晚 5 + 30 = 35 秒后消失。
• 容器自己 2 秒退出,则总耗时就是 T_ready + 2 秒。
注意:
– “进入 Terminating”并不代表已关闭,只是退出流程开始。
– 真正从 Service 端点里剔除是在收到 Terminating 的那一瞬间完成;后面的 30 秒内,它已经不会再接新流量。
四、我不进行特殊操作,旧pod Terminating后,容器会自行退出吗?还是必须得等terminationGracePeriodSeconds ?
不会强制「等满 30 秒」;Pod 进入 Terminating 后,Kubernetes 只是给容器一个最长 30 秒的“宽限期”,真正的退出时间取决于你的进程本身。
详细时序
- Deployment 决定淘汰某个旧 Pod → API Server 给 Pod 打上 deletionTimestamp,状态显示 Terminating。
- Kubelet 立即向每个容器发送 SIGTERM(Windows 容器是 CTRL-BREAK),随后开始计时 terminationGracePeriodSeconds = 30。
- 之后出现两种情况:
• 进程在收到 SIGTERM 后 自行退出(常见框架会捕获信号并System.exit
;或你在 SIGTERM 处理里直接exit
)。
– 只要所有容器都退出,Pod 会立刻被删除;可能只用 1-2 秒就消失。
• 进程迟迟不退出或忽略 SIGTERM。
– Kubelet 会一直等,直到满 30 秒;随后发送 SIGKILL 并强制删除 Pod。
总结
• 你什么都不做也行,只是进程要么自己很快结束,要么被 kubelet 等 30 秒后硬杀。
• 如果希望旧 Pod 尽快下线,请确保应用能正确处理 SIGTERM 并快速退出,或把 terminationGracePeriodSeconds 调小。
五、进程在收到 SIGTERM 后,就会立即自行退出吗?那为什么有的进程是迟迟不退出或忽略 SIGTERM,Kubelet 会一直等,直到满 30 秒,随后发送 SIGKILL 并强制删除 Pod?
不一定会 “立刻” 结束,原因主要落在两个方面:
A) 进程 怎样接收到 SIGTERM
B) 进程 收到后做了什么
──────────────────────────────────
1 · Linux 信号的默认行为
──────────────────────────────────
• 如果一个进程从未为 SIGTERM 注册处理函数(handler),那么内核收到信号后就直接把它结束;这种场景看起来就是“秒关”。
• 只要应用或它所用的运行时(JVM、Python、Node.js 等)安装了自己的 handler,就会拦截信号,接着可以:
- 做清理再
exit()
——耗时由它自己决定 - 忽略或决定不退出——进程会继续跑,直到被 SIGKILL
──────────────────────────────────
2 · 为什么“长时间不退出”常见
──────────────────────────────────
- 业务层面
• Spring Boot / Django / Express 会在 Shutdown Hook 里先关闭 HTTP 端口,再等待连接耗尽、flush 缓冲、提交事务等——几十秒很正常。
• 消费者服务要把已拉取的消息处理完,才ack
并退出。 - 代码显式忽略
开发者为了 Debug 或误用导致进程根本不响应。signal.Ignore(syscall.SIGTERM)
- 进程根本没收到信号
• 容器的 PID 1 是/bin/sh -c "java …"
,SIGTERM 只发给 shell;子进程(真正的 Java)要靠 shell 转发 —— shell 脚本如果没trap
,信号就丢了。
• 解决:把主程序直接设为 PID 1,或用 tini/dumb-init 等 init 进程转发信号。 - 处于不可打断的系统调用
• I/O 阻塞或D
(uninterruptible sleep)状态时,直到 syscall 返回才能处理信号;卡在 NFS/GCS 网络盘时尤为明显。
──────────────────────────────────
3 · Kubernetes 的处置流程
──────────────────────────────────
- kubelet 发送 SIGTERM ➜ 开始
terminationGracePeriodSeconds
计时(30 s) - 计时内:
• 进程自愿退出 → Pod 立即删除
• 没退出 → 到点后 kubelet 发送 SIGKILL(不可捕获,强制终止) - 如果容器里还有子进程,SIGKILL 只针对 PID 1 及其进程组;若 PID 1 不转发,孤儿子进程也会被内核自动回收(cgroup 退出)。
──────────────────────────────────
4 · 如何让 Pod 既安全又快关
──────────────────────────────────
- 让主进程直接担任 PID 1,或加
--init
(Docker)/initContainers
里的 tini。 - 编程语言级做 graceful-shutdown,确保 < 几秒完成:
• Java:spring.lifecycle.timeout-per-shutdown-phase=5s
• Node:process.on('SIGTERM', () => server.close(() => process.exit(0)))
- 把
terminationGracePeriodSeconds
设到刚好覆盖最长清理时间;如已能 5 s 结束,就调小到 10 s,缩短 Deployment 滚动窗口。 - 配合
preStop
hook(短耗时)或lifecycle/Exec
先标记不再接新请求再退出。
──────────────────────────────────
答复摘要
──────────────────────────────────
SIGTERM 只是 Kubernetes 给容器的“请你下线”请求:
• 没有自定义处理时,进程立刻退出 → Pod 秒关。
• 捕获了 SIGTERM、需要做收尾,或 signal 根本没到主进程 → 可能拖满 30 s,之后被 kubelet 送 SIGKILL 强杀。
要想控制实际关闭时长,就让应用正确、迅速地处理 SIGTERM,并把 terminationGracePeriodSeconds
设到合适值。
六、更新策略RollingUpdate下 新 Pod已经Ready就会把旧pod置为Terminating状态吗?比如我的副本是3,rollingUpdate: maxSurge: 2 maxUnavailable: 2 ,这种情况是新旧pod是怎么更新的?
下面按「期望副本 = 3、maxSurge = 2、maxUnavailable = 2」一步步走完整个 RollingUpdate。先给结论,再拆动作与控制条件。
一、结论速览
• 最多同时存在 5 个 Pod(3 + maxSurge=2)。
• Ready 的 Pod 数永远 ≥ 1(replicas − maxUnavailable = 3 − 2)。
• 每当有 至多 2 个新 Pod 进入 Ready,就会有 同样数量 的旧 Pod 被标记 Terminating,直到旧 Pod 数归零。
二、控制器的两条硬性约束
- Pod 总数 ≤ replicas + maxSurge(本例 ≤ 5)
- Ready Pod 数 ≥ replicas − maxUnavailable(本例 ≥ 1)
只要同时满足这两条,Deployment Controller 就可以放手“加新、减旧”。
三、时间线(假设旧 Pod 都是 A1/A2/A3,新 Pod 叫 B1/B2/B3)
步骤 0 – 初始
A1/A2/A3 Ready 总数 3,符合约束
步骤 1 – 放 Surge
• 还能再多出 2 个 Pod → 创建 B1、B2
• 此时总数 5(达上限),Ready 仍只有 3
步骤 2 – 等新 Pod Ready
• B1、B2 先后变为 Ready → Ready = 5
• 现在满足“可删最多 2 个旧 Pod”的条件
步骤 3 – 下线旧 Pod(第一轮)
• 控制器选 A1、A2 标记 Terminating
• Ready 仍然 ≥ 3(B1/B2/A3),Unready = A1/A2
• Pod 总数 5 → 已有 2 个正在关,但还算“存在”
步骤 4 – 继续补新 Pod
• 旧 Pod 目标数还差 1 → 创建 B3(此时总数仍 ≤ 5)
• 等待 B3 Ready
步骤 5 – 下线最后一个旧 Pod(第二轮)
• B3 Ready 后,Ready = 4
• 控制器把最后的 A3 置 Terminating
• 旧 RS replica 数变 0;滚动更新逻辑结束
步骤 6 – 优雅退出窗口
• A1/A2/A3 各自进入 terminationGracePeriodSeconds(30 s)
• 谁先退出就先从集群里消失;超时者被 kubelet SIGKILL
• 只要它们完全退出,Deployment 上报 rollout 完成
四、怎么看“旧 Pod 被置 Terminating”的时机
它恰好发生在“让 Ready Pod ≥ (replicas − maxUnavailable) 并且 Pod 总数 ≤ replicas + maxSurge”这一刻。简化成一句话就是:
「一批新 Pod Ready → 立刻选同批数量的旧 Pod 进入 Terminating。」
五、若新 Pod 启动失败?
• 没 Ready 就删不了旧 Pod,因为删了就会违反“Ready ≥ 1”
• progressDeadlineSeconds(默认 600 s)到期会把 Deployment 标成失败;但旧 Pod 会继续留着,业务不掉线
六、快速验证
# 实时看滚动
kubectl rollout status deploy/myapp -w
# 持续打印 Ready/总数
watch -n1 'kubectl get rs,po -l app=myapp -owide'
七、小结
在 RollingUpdate+maxSurge 2/maxUnavailable 2 的组合下:
• 每批最多先起 2 个新 Pod;它们一旦 Ready,就触发等量旧 Pod 进入 Terminating。
• 旧 Pod 进入 Terminating 后多久彻底消失,取决于应用收到 SIGTERM 后多快退出(上限 30 s)。
七、新 Pod一旦 Ready,旧 Pod 进入 Terminating,就代表容器已经起来了吗?
可以这样理解:
• “新 Pod 到达 Ready”——说明 Pod 里的所有容器已经在运行状态,并且通过了 readinessProbe(如果你定义了);Kubernetes 会把它计入可用副本,并把 Service 流量分发给它。
• 控制器随即把同等数量的旧 Pod 标记为 Terminating。此时旧 Pod 进入优雅关停流程,Service 端点里也立即被剔除。
也就是说,Ready 基本可以视为“新容器已经真正上线、可以接流量”;只有在以下特殊情况下才可能“容器虽然跑着,但业务还没完全就绪”:
-
你根本没配置 readinessProbe
- 没探针时,Pod 只要容器状态变 Running 就会被算作 Ready,可能应用端口尚未监听。
- 解决:为关键端口/HTTP 路由加合适的 readinessProbe。
-
readinessProbe 太宽松或探测的不是关键路径
- 探针返回 200,但应用仍在热加载大模型、缓存等。
- 解决:把探针改成真正代表“可对外服务”的检查。
-
多容器 Pod:副容器未参与探针
- 只给主容器配探针,辅助容器可能出错但不影响 Ready。
- 解决:给所有重要容器都加探针,或用 PodReady++(k8s 1.27+) 条件。
只要 readinessProbe 设计得当,“新 Pod Ready” 就意味着新容器已经完全“起来并可用”;旧 Pod 进入 Terminating 只是让它优雅退场,两者之间不会并发接流量。