欢迎光临
我们一直在努力

K8s的Pod如何做到优雅终止

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}

在 Kubernetes 中,你无法完全阻止 K8s Kill 掉一个 Pod(例如节点故障、资源驱逐、强制删除 –force 等场景)。但是,你可以实现**“优雅终止”(Graceful Termination)**机制。

这个机制的核心是:在容器真正被杀死之前,给应用一段缓冲时间(默认 30 秒),并发送 SIGTERM 信号通知应用进行清理工作。

以下是实现“拦截”和“通知”的完整方案,分为 K8s 配置层 和 应用代码层。


一、 核心机制:优雅终止流程

当 K8s 决定删除 Pod 时,会发生以下步骤:

  • Endpoint 移除:Pod 从 Service 的 Endpoints 中移除,不再接收新流量。
  • 发送 SIGTERM:Kubelet 向容器内的主进程(PID 1)发送 SIGTERM 信号。
  • 等待期(Pre-stop Hook / Grace Period):应用收到信号后,开始执行清理逻辑(关闭连接、保存数据等)。此时容器仍在运行。
  • 发送 SIGKILL:如果超过 terminationGracePeriodSeconds(默认 30s)应用仍未退出,Kubelet 发送 SIGKILL 强制杀死进程。

  • 二、 方案 1:应用层捕获 SIGTERM(最推荐,标准做法)

    这是最通用、最可靠的方式。你的应用程序需要监听操作系统的 SIGTERM 信号。

    1. Python 示例

    import signal
    import sys
    import time

    def gracefull_shutdown(signum, frame):
    print("️ 收到 SIGTERM 信号!开始执行清理工作…")

    # 1. 停止接收新请求
    # 2. 等待当前正在处理的请求完成
    # 3. 关闭数据库连接、释放文件锁等
    time.sleep(5) # 模拟清理耗时

    print("✅ 清理完成,准备退出。")
    sys.exit(0)

    # 注册信号处理器
    signal.signal(signal.SIGTERM, gracefull_shutdown)
    signal.signal(signal.SIGINT, gracefull_shutdown)

    print("🚀 应用启动,正在运行…")
    while True:
    time.sleep(1)

    2. Java (Spring Boot) 示例

    Spring Boot 默认支持优雅停机(Spring Boot 2.3+):

    # application.yml
    server:
    shutdown: graceful # 启用优雅停机
    spring:
    lifecycle:
    timeout-per-shutdown-phase: 30s # 最大等待时间

    3. Go 示例

    package main

    import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
    )

    func main() {
    // 创建上下文用于取消操作
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // 监听信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    go func() {
    <-quit
    fmt.Println("⚠️ 收到 SIGTERM,开始清理…")

    // 执行清理逻辑
    cleanup()

    fmt.Println("✅ 清理完成")
    cancel() // 通知其他 goroutine 退出
    }()

    fmt.Println("🚀 服务运行中…")
    <-ctx.Done()
    }

    func cleanup() {
    time.Sleep(2 * time.Second) // 模拟清理
    }


    三、 方案 2:使用 preStop Hook(辅助手段)

    如果你无法修改应用代码,或者需要在应用收到 SIGTERM 之前执行一些特殊命令(如刷新 Nginx 缓存、调用外部 API 通知),可以使用 lifecycle.preStop。

    注意:preStop 执行期间,Pod 仍然处于 Terminating 状态,但 SIGTERM 会在 preStop 执行完后才发送(除非你配置了 sleep)。通常建议结合使用。

    apiVersion: v1
    kind: Pod
    metadata:
    name: important–pod
    spec:
    terminationGracePeriodSeconds: 60 # 增加宽限期到 60 秒
    containers:
    – name: my–app
    image: my–image
    lifecycle:
    preStop:
    exec:
    command: ["/bin/sh", "-c", "echo 'Preparing to shut down'; sleep 10"]
    # … 其他配置

    常见陷阱:如果在 preStop 中只是 sleep,而应用没有正确处理 SIGTERM,可能会导致问题。最佳实践是:preStop 用于触发应用内部的健康检查失败或平滑下线,真正的清理逻辑应在应用内通过 SIGTERM 处理。


    四、 方案 3:延长宽限期 (terminationGracePeriodSeconds)

    默认的 30 秒可能不够你的重要 Pod 完成清理。你可以显式增加这个时间。

    apiVersion: v1
    kind: Pod
    metadata:
    name: important–pod
    spec:
    # 关键配置:给应用更多时间处理 SIGTERM
    terminationGracePeriodSeconds: 120 # 120 秒
    containers:
    – name: my–app
    image: my–image


    五、 方案 4:防止“意外”删除(防护网)

    虽然不能阻止所有 Kill,但可以防止人为误操作或非必要的驱逐。

    1. 设置高优先级(避免被驱逐)

    如果是因为节点资源不足导致 Pod 被驱逐(Eviction),可以通过提高 QoS 类来保护。

    • Guaranteed QoS:确保 requests == limits。这类 Pod 最后才会被驱逐。

    resources:
    requests:
    memory: "2Gi"
    cpu: "2"
    limits:
    memory: "2Gi"
    cpu: "2"

    2. 使用 PDB (Pod Disruption Budget)

    防止因自愿维护(如 kubectl drain)导致过多 Pod 同时被杀。

    apiVersion: policy/v1
    kind: PodDisruptionBudget
    metadata:
    name: my–pdb
    spec:
    minAvailable: 1 # 至少保持 1 个 Pod 运行
    selector:
    matchLabels:
    app: important–app

    3. 禁止强制删除

    教育运维团队不要使用 kubectl delete pod <name> –force –grace-period=0,这会跳过优雅终止流程直接发送 SIGKILL。


    六、 完整最佳实践架构图

    Application CodeImportant PodKubeletK8s Control PlaneApplication CodeImportant PodKubeletK8s Control Plane#mermaid-svg-vWYZjpM6WIKQSCY4{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vWYZjpM6WIKQSCY4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vWYZjpM6WIKQSCY4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vWYZjpM6WIKQSCY4 .error-icon{fill:#552222;}#mermaid-svg-vWYZjpM6WIKQSCY4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vWYZjpM6WIKQSCY4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vWYZjpM6WIKQSCY4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vWYZjpM6WIKQSCY4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vWYZjpM6WIKQSCY4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vWYZjpM6WIKQSCY4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vWYZjpM6WIKQSCY4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vWYZjpM6WIKQSCY4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vWYZjpM6WIKQSCY4 .marker.cross{stroke:#333333;}#mermaid-svg-vWYZjpM6WIKQSCY4 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vWYZjpM6WIKQSCY4 p{margin:0;}#mermaid-svg-vWYZjpM6WIKQSCY4 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vWYZjpM6WIKQSCY4 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-vWYZjpM6WIKQSCY4 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-vWYZjpM6WIKQSCY4 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-vWYZjpM6WIKQSCY4 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-vWYZjpM6WIKQSCY4 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-vWYZjpM6WIKQSCY4 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-vWYZjpM6WIKQSCY4 .sequenceNumber{fill:white;}#mermaid-svg-vWYZjpM6WIKQSCY4 #sequencenumber{fill:#333;}#mermaid-svg-vWYZjpM6WIKQSCY4 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-vWYZjpM6WIKQSCY4 .messageText{fill:#333;stroke:none;}#mermaid-svg-vWYZjpM6WIKQSCY4 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vWYZjpM6WIKQSCY4 .labelText,#mermaid-svg-vWYZjpM6WIKQSCY4 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-vWYZjpM6WIKQSCY4 .loopText,#mermaid-svg-vWYZjpM6WIKQSCY4 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-vWYZjpM6WIKQSCY4 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-vWYZjpM6WIKQSCY4 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-vWYZjpM6WIKQSCY4 .noteText,#mermaid-svg-vWYZjpM6WIKQSCY4 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-vWYZjpM6WIKQSCY4 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vWYZjpM6WIKQSCY4 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vWYZjpM6WIKQSCY4 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vWYZjpM6WIKQSCY4 .actorPopupMenu{position:absolute;}#mermaid-svg-vWYZjpM6WIKQSCY4 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-vWYZjpM6WIKQSCY4 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vWYZjpM6WIKQSCY4 .actor-man circle,#mermaid-svg-vWYZjpM6WIKQSCY4 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-vWYZjpM6WIKQSCY4 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}1. 用户执行 kubectl delete 或节点漂移2. 执行 preStop Hook (可选)3. 发送 SIGTERM (关键步骤)4. 强制杀死alt[应用在宽限期内退出][超时 (超过 terminationGracePeriodSeconds)]标记 Pod 为 Terminating从 Service Endpoints 移除 (停止新流量)执行 preStop 命令执行预处理 (如 sleep, flush logs)发送 SIGTERM 信号捕获 SIGTERM停止接受新请求等待现有请求完成/保存数据关闭资源进程正常退出 (Exit Code 0)Pod 终止成功发送 SIGKILL进程被强制终止Pod 终止

    七、 总结建议

  • 必须做:在应用代码中监听 SIGTERM 并执行优雅关闭逻辑(这是“拦截”的核心)。
  • 建议做:将 terminationGracePeriodSeconds 设置为足够长的时间(如 60-120 秒)。
  • 建议做:确保 Pod 是 Guaranteed QoS 类,减少被资源驱逐的概率。
  • 建议做:配置 PDB 防止维护期间的并发删除。
  • 不要依赖:不要试图通过脚本或外部工具去“阻断” kill 命令,这在分布式系统中是不可靠且危险的。信任 Kubernetes 的优雅终止机制,并完善你的应用对 SIGTERM 的处理。
  • 赞(0)
    未经允许不得转载:171主机测评 » K8s的Pod如何做到优雅终止
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址