题干与适用场景
一个 HTTP 服务使用 Deployment 滚动发布。Pod 被删除后,部分请求仍进入旧实例,长轮询和后台任务偶发中断。请说明 Kubernetes、Service、负载均衡和进程之间的终止时间线,并设计排空、取消、重试与验证方案。核心考察运行中服务的连接与副作用边界,因此归入 backend。
面试官考察点
高质量回答会区分 Pod 删除、EndpointSlice 更新、负载均衡传播、preStop、SIGTERM 和 SIGKILL,不把“收到 SIGTERM”当成流量已经停止。还应覆盖长连接、队列任务、幂等重试、节点意外断电和 grace period 不足。
回答前需要澄清的问题
- 服务是短请求、SSE、WebSocket 还是长轮询?是否有连接迁移能力?
- readiness、liveness、startup 探针分别检查什么,是否把停机状态写入应用?
- preStop 是 sleep、HTTP hook 还是应用自身排空接口,谁负责最终退出?
- Pod 是否运行 worker,任务租约和重复投递如何处理?
- 入口负载均衡、Service mesh 和云 LB 的传播延迟是多少?
30 秒回答框架
“停机先把应用标记为 not-ready,停止接收新工作,再等待 Endpoint 和负载均衡传播。随后应用进入 draining,停止 accept、让短请求完成、关闭或迁移长连接,并让 worker 停止领取新任务、完成或释放租约。preStop 只提供协调窗口,SIGTERM 触发应用清理,terminationGracePeriodSeconds 必须覆盖传播、排空和清理的 p99;超时后才会 SIGKILL。用发布压测、连接和任务指标验证没有丢请求或重复副作用。”
分步骤深入解答
先画时间线:控制器删除 Pod,kubelet 执行终止流程,EndpointSlice 和各级负载均衡逐步移除端点;容器可能同时开始 preStop,随后收到 SIGTERM。传播是异步的,所以应用必须在收到停机意图后立即拒绝新请求,即使部分流量仍到达旧地址。
应用应维护 ready 与 draining 状态。停机入口先将状态切为 draining,使 readiness 失败并停止 accept 新连接或返回可重试响应。已经建立的短请求继续完成;SSE、WebSocket 和长轮询要发送结束信号、迁移游标或让客户端带上恢复令牌重连。
preStop 的 sleep 不能替代应用逻辑。它最多为端点传播留时间,且会消耗 grace period。更可靠的是调用应用的 drain endpoint,记录开始时间和剩余预算;SIGTERM handler 负责停止新工作、关闭服务器 listener、等待活动请求和释放连接池。
后台 worker 必须与 HTTP 排空分开设计。停止领取新消息,正在处理的任务用租约或可见性超时保护;在剩余预算不足时主动释放,让其他 worker 重试。任务写入和外部副作用要使用幂等键,不能因为 Pod 被杀就再次扣款或发货。
terminationGracePeriodSeconds 应按实测 p99 计算:入口传播延迟、最长允许请求、连接关闭、worker 收尾和日志刷新都要计入,并留出抖动余量。grace period 到期后 kubelet 会强制终止,未完成请求和进程内状态不能假设会执行 finally。
节点维护与 Pod 删除不同。Kubernetes 的节点关机流程会给 kubelet 和 Pod 预留关机时间,但断电、内核崩溃或宿主机强制重启无法提供优雅窗口。关键任务要持久化状态、跨副本运行,并用探针和队列监控恢复。
验证要在滚动发布、手动删除、节点排空、LB 慢传播、长连接和 SIGTERM 后超时等场景执行。检查 5xx、连接中断、请求完成率、draining 拒绝数、任务重试、重复副作用、Endpoint 移除延迟和 Pod 强杀数,确保指标能对齐同一发布版本。
高质量示范回答
“我不会把 preStop: sleep 30 当作优雅停机。收到停机信号后,应用立即把自己标记为 draining,让 readiness 失败并停止接收新工作;因为 Endpoint 和 LB 更新有传播延迟,旧实例仍需拒绝新请求。短请求继续完成,SSE/WebSocket 发送结束或恢复令牌,worker 停止领取新任务并释放无法完成的租约。
preStop 只为传播留窗口,SIGTERM handler 关闭 listener、等待活动请求、释放连接池,并在统一截止时间前退出。grace period 按实测传播 p99、最长请求、worker 收尾和日志刷新计算。验证时覆盖滚动发布、节点排空、长连接、进程崩溃和强杀,检查没有丢请求、重复副作用或未恢复任务。”
常见错误
- 只等待 SIGTERM → 流量传播仍会把请求送到旧 Pod → 先失败 readiness 并进入 draining。
- 用固定 sleep 代替排空 → 传播或请求时长变化会失效 → 以剩余截止时间驱动应用逻辑。
- 停止 Pod 却继续领取队列 → 任务在强杀时重复 → 先停止领取并释放租约。
- 把长连接当普通请求 → 客户端无从恢复 → 发送关闭信号和恢复游标。
- grace period 只按平均耗时 → p99 请求被 SIGKILL → 用尾延迟和传播测量设值。
- 假设 finally 一定执行 → 强杀和断电不会等待清理 → 关键状态持久化。
- 只看 Pod 状态 → 看不到 LB 慢传播和连接中断 → 对齐端点、请求和发布指标。
- 让 liveness 在 draining 时失败 → 触发不必要重启 → readiness 表示可接流量,liveness 表示进程健康。
追问及应对
追问一:为什么 readiness 失败后仍会收到请求?
Endpoint、Service mesh 和云负载均衡都有传播延迟,已有连接也不会自动迁移。因此应用必须在 draining 状态拒绝新工作并安全结束已有连接。
追问二:preStop sleep 应该设多少?
不能凭经验固定。应测量端点和 LB 传播的 p99,再与请求、worker 清理预算一起计算,并把 sleep 视为有限协调窗口。
追问三:WebSocket 如何优雅关闭?
停止接受新连接,向现有连接发送关闭码和重连提示;客户端使用会话或游标恢复,服务端保存必要状态,避免从头执行副作用。
追问四:任务执行到一半 Pod 被杀怎么办?
任务状态和租约必须持久化。新 worker 根据可见性超时重新领取,副作用使用幂等键或状态查询,不能依赖进程内 finally。
追问五:节点断电还能优雅停机吗?
不能保证。节点关机机制只覆盖可观测的计划流程;断电和内核崩溃要求跨副本、持久化检查点和可重试任务。
追问六:如何选择 grace period?
把端点传播、最长允许请求、长连接收尾、worker 租约释放、连接关闭和日志刷新相加,再以实测 p99 与余量验证,不能只看平均值。
追问七:如何防止 draining 触发重启风暴?
停机时让 readiness 失败,保持 liveness 成功;探针职责分离,避免把“暂时不接流量”误判为进程崩溃。