news 2026/10/21 7:57:32

Pod 删除卡在 Terminating?一文拆解 K8s 优雅终止全流程与实战排查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Pod 删除卡在 Terminating?一文拆解 K8s 优雅终止全流程与实战排查

2. Pod 删除的完整链路,从上到下拆一遍

做过 Kubernetes 平台运维的人,几乎都被同一个问题问过:“我就删个 Pod,它怎么一直卡在 Terminating 状态?是不是集群出问题了?” 其实这不是故障,而是 Kubernetes 在按设计流程执行一次优雅终止。整个过程可以拆成三个动作,按顺序依次是:preStop 钩子执行 → 向容器发送 SIGTERM 信号 → 宽限期耗尽后发送 SIGKILL 强制杀死。这条链路如果只靠文档看,会感觉很简单,但实际跑起来,你会发现里面藏着大量细节:信号能不能传到应用进程、preStop 执行时间到底占不占宽限期、为什么有的应用收不到 SIGTERM、为什么宽限期明明设置了 30 秒 Pod 却等了 60 秒才消失。

这篇文章我会从整条流程的设计初衷讲起,逐步拆解每个环节的执行机制,再把我在真实集群里踩过的坑、验证手段、最佳参数配置一并给出来。如果你是做 Kubernetes 平台开发、应用容器化改造、或者正在排查 Pod 删除相关的诡异问题,这篇值得认真看完。我会尽量少贴源码,多讲行为,多讲可操作的验证方式。

1. 为什么 Pod 删除不能被“一刀切”处理

1.1 一次删除背后牵连的“用户资产”

很多刚接触容器的人不理解:一个 Pod 无非就是一组进程,删掉就删掉,为什么还要搞出一套 preStop、SIGTERM、SIGKILL 的流程?

原因很简单:进程可以随时杀掉,但进程承载的业务状态不行。举个例子,一个 Java 应用正在处理一笔支付回调,这时候如果你直接把进程 kill 掉,这笔回调可能会记录成“处理中”永远无法终结;一个消息消费程序正在从队列里取消息,如果消息已经取出来但还没提交 offset,进程被杀掉后这消息就会重新投递,虽然能保证不丢,但可能造成业务重复;一个 Nginx 正在给客户端回放视频流,你把连接强行断开,客户端就会立刻感知到异常。这些情况里,直接杀掉进程,和给进程一点时间把自己“收拾干净”再退出,对用户体验的影响是截然不同的。

所以 Kubernetes 在删除 Pod 时,特意设计了一套“给应用留后事时间”的流程。它不是第一时间杀进程,而是先让应用有机会停掉新请求、释放外部资源、完成必要的数据刷盘。这套流程就是我们说的优雅终止。而整个优雅终止的时间上限,由 Pod 的一个字段控制,也就是terminationGracePeriodSeconds,默认值是 30 秒。

1.2 三个环节各自的定位

如果把整个流程类比成一家餐厅打烊,就很好理解了:

  • preStop 钩子:相当于餐厅在门口挂出“停止接客”的牌子,同时开始清点库存、结账、让厨师关闭灶台。这一步是 Pod 自己主动去做的准备工作,Kubernetes 只负责触发,具体做什么由你的配置决定。
  • SIGTERM 信号:相当于服务员口头通知所有还在用餐的客人“我们要打烊了,请尽快用餐”。应用收到 SIGTERM 之后,应当自行停止接受新请求,处理完正在进行的请求,然后主动退出。这是一个“协商式”的关闭信号,进程可以捕获它、处理它,甚至可以忽略它。
  • SIGKILL 信号:相当于到了打烊时间,保安开始清场,不管你有没有吃完,直接把人请出去。SIGKILL 进程无法捕获、无法忽略、无法处理,内核收到这个信号后会立即结束进程。

这三个环节是有严格先后顺序的:preStop 先执行,执行完才会发 SIGTERM;SIGTERM 发出后,Kubernetes 不会无限期等下去,宽限期耗尽后就会补一发 SIGKILL。SIGKILL 不是失控时的意外,而是流程中设计好的兜底。

1.3 哪些场景会触发这条链路

理解了流程本身,再来看哪些场景会触发它。最常见的是下面几类:

  • 手动删除:kubectl delete pod foo,这是最直接的触发方式。
  • 滚动更新:Deployment 发布新版本时,老版本 Pod 会进入终止流程,同时新 Pod 被创建出来。此时你能明显看到新旧 Pod 并存,然后旧 Pod 逐个消失。
  • 缩容:副本数从 5 降到 3,多余的 Pod 会被终止。
  • 节点排水:kubectl drain节点时,节点上的 Pod 会被驱逐,同样走这套终止流程。
  • 节点压力驱逐:节点磁盘或内存不足时,kubelet 会驱逐 Pod。
  • 抢占和优先级导致的驱逐。

这些场景里,滚动更新和节点排水是最容易暴露问题的,因为它们的触发频率高,而且往往同时波及多个 Pod。所以理解这条终止链路,不只是为了“看懂 K8s”,更是为了排查问题方便。我见过太多的人以为 Pod 卡在 Terminating 是“集群坏了”,实际上就是某个 preStop 脚本里写了一个sleep 60。

2. preStop 钩子:信号发出前,应用最后的“准备时间”

2.1 preStop 是什么、什么时候执行

preStop 是 Pod 容器生命周期钩子(Lifecycle Hook)中的一种,它和 postStart 对应。postStart 是在容器启动后立刻执行,preStop 则是在容器即将被终止之前执行。关键点在于它的执行时机:在 Kubernetes 给容器发送 SIGTERM 之前。

也就是说,当 kubelet 决定要终止一个 Pod 时,它会先检查 Pod 里的容器有没有配置 preStop。如果有,kubelet 会先通过容器运行时(containerd、Docker 等)在容器内执行这个钩子,等钩子执行完毕,才会发送 SIGTERM。这个先后顺序是硬性的,不会反过来。

这里有一个容易误解的点:很多人以为“Pod 进入 Terminating 状态时,容器就已经收到 SIGTERM 了”。实际不是这样。Pod 被标记为 Terminating 之后,preStop 可能还在执行中,此时容器内的主进程完全没有收到任何信号,还可以正常对外提供服务。但需要注意的是,同一个时刻,如果这个 Pod 在 Service 的 Endpoint 里面,请求仍然可能被转发过来,所以 preStop 阶段容器的负载并不会立刻降为零。这也解释了为什么 preStop 里通常都会做“摘流量”或“注销注册中心”的操作:趁着 SIGTERM 还没来,先把容器从对外服务列表里摘掉。

2.2 配置 preStop 的两种姿势

preStop 有两种配置方式:Exec和HTTP GET。

Exec 方式是在容器内执行一条命令,命令执行完成(或者失败)后,Kubernetes 才继续流程。下面是一个典型配置:

apiVersion: v1 kind: Pod metadata: name: app-pod spec: containers: - name: app image: my-app:1.0 lifecycle: preStop: exec: command: - /bin/sh - -c - "curl -X POST http://127.0.0.1:8080/api/shutdown"

这段配置的意思是:Pod 被删除时,先在容器内执行curl -X POST http://127.0.0.1:8080/api/shutdown,如果这个接口正常返回,Kubernetes 就会继续发送 SIGTERM。如果命令执行失败(比如 curl 返回非零退出码),Kubernetes 仍然会继续流程,不会因为 preStop 失败而取消删除操作。

HTTP GET 方式则是向容器的某个地址发起 HTTP 请求:

lifecycle: preStop: httpGet: path: /shutdown port: 8080 scheme: HTTP

HTTP GET 方式的语义和 Exec 类似,它会把 HTTP 请求发到一个指定的端口和路径上,如果返回 2xx 或 3xx 就认为执行成功,其他状态码算失败。需要强调的一点是:这个 HTTP 请求是在容器自己的网络命名空间里发起的,所以地址用127.0.0.1没有问题,不需要经过 Service。

从我实际使用经验来看,Exec 方式远比 HTTP GET 方式常用,因为 Exec 可以做更多复杂操作,比如“调用本地接口 + 等待 5 秒 + 执行清理脚本”。而 HTTP GET 方式受限于只能请求单一地址,灵活性差一些。

2.3 preStop 执行与宽限期的“时间账”

这是整篇文章的核心难点,也是网上讨论最多的地方:preStop 执行的时间,到底算不算在terminationGracePeriodSeconds里面?

先说结论:在不同 Kubernetes 版本里,这个行为是有差异的。在较老版本的实现中,整条链路的总时长上限就是terminationGracePeriodSeconds,preStop 占用的是这个总时长的一部分。也就是说,如果你的宽限期是 30 秒,而 preStop 脚本执行了 25 秒,那么 SIGTERM 发送之后,应用只有 5 秒的时间来优雅退出,超时就只能吃 SIGKILL。

在较新的版本中,kubelet 在处理 preStop 时已经做了一些调整,preStop 的执行时间不再简单粗暴地占用宽限期,而是会被“挪”到宽限期之前单独计算,或者动态延长宽限期。但问题是,这个行为并不是所有集群、所有版本都一致,它取决于 kubelet 的具体实现和配置。我在实际测试不同的集群版本时,确实观察到了宽限期结束时刻有明显差异。

所以我的建议是:不要把 preStop 当作无限拖延的工具,不要把复杂耗时的逻辑都塞进 preStop。最好的实践是:把 preStop 需要的时间、应用 SIGTERM 之后优雅退出的时间,都计算进terminationGracePeriodSeconds,然后额外留 5-10 秒的余量。如果你非要在 preStop 里做比较耗时的事情,比如等待某个外部服务确认,那一定要在测试环境实测整个链路的总耗时,再根据结果调整宽限期。

另外一个容易被忽视的细节:preStop 命令一旦执行就没有回头路。如果 preStop 脚本里写了sleep 60,而宽限期只有 30 秒,那 preStop 还没执行完,宽限期就已经到了,容器会直接被 SIGKILL 杀掉。写在 preStop 里的sleep不是“让应用多活一会儿”,而是“抢走应用后面优雅退出的时间”。

3. SIGTERM:信号从 kubelet 到 PID 1 的完整旅程

3.1 从 kubelet 到容器运行时再到 PID 1

当 preStop 钩子执行完毕(或者根本没有配置 preStop),kubelet 就会调用容器运行时的接口,要求它终止容器。这个请求里会带上剩余的宽限期。容器运行时收到请求后,第一个动作就是向容器内的主进程发送SIGTERM信号。

完整的调用链是这样的:kubelet → Container Runtime Interface(CRI)→ containerd / Docker → 容器运行时内部的 shim 进程 → 容器内 PID 1 进程。

链条上看似每一环都在传递同一个信号,但要注意:容器运行时实际发送信号的对象,是容器内 PID 1 的进程。所谓 PID 1,就是容器启动时执行的第一个进程。如果 Dockerfile 里最后一行是CMD ["java", "-jar", "app.jar"],那么 Java 进程就是 PID 1;如果你的启动命令写成CMD ["/start.sh"],那 PID 1 就是 shell 脚本,Java 进程反而是它的子进程。

这个区别在终止流程里影响巨大。原因在于:SIGTERM 发给 PID 1 之后,PID 1 进程如果不做特殊处理,子进程根本收不到这个信号。很多业务应用就是这么“死得不明不白”的:Pod 删除了,等宽限期一到直接被 SIGKILL,但应用的日志里完全没有 SIGTERM 的记录,连给应用配置的优雅停机代码都没执行。

3.2 PID 1 进程为什么最关键

在 Linux 信号机制里,PID 1 进程承担着“孤儿进程回收”“信号转发”等 init 职责。但在普通的容器环境里,应用进程直接作为 PID 1 时,它并不会主动去干这些事儿。这就带来了两个层面的问题:

一是信号不转发。典型场景是CMD ["/start.sh"]这种启动方式。start.sh 作为 PID 1 在跑,里面又启动了一个 Java 进程。容器终止时,SIGTERM 只会发给 start.sh,start.sh 如果没有trap信号转发逻辑,Java 进程就完全感受不到 SIGTERM,还在那儿正常处理请求。直到宽限期结束,内核直接发 SIGKILL,进程被强杀。你看到的日志往往只有“进程退出”,没有“优雅关闭”的记录。

二是僵尸进程积累。如果进程退出后没有父进程去执行wait()回收资源,它就会变成僵尸进程。PID 1 不下场处理,僵尸进程就会一直挂在容器里。这也是为什么很多基础镜像会引入tini(一个小型 init 系统)作为 PID 1,然后让业务进程变成它的子进程。tini的好处是:它会把收到的信号转发给子进程,并在子进程退出后自动执行wait()回收,避免僵尸进程。

我给你的强烈建议是:如果容器里同时存在多个进程,或者启动脚本里还有子进程,一定要用 tini 或者类似 init 系统来兜底,不要裸跑脚本。

3.3 应用收到 SIGTERM 后怎么算“处理完”

SIGTERM 发出后,Kubernetes 不会立刻检查应用是否退出,而是进入等待状态。这个等待的最大时长就是宽限期剩余的时间。应用收到 SIGTERM 后,可以做几件事:

  • 停止接收新的请求。
  • 处理完正在进行的请求。
  • 关闭数据库连接、消息队列连接、外部服务连接。
  • 把内存中未写完的数据刷到磁盘。
  • 向注册中心注销自己。
  • 最后调用exit(0)退出。

这些逻辑需要应用自身来实现。比如 Java Spring Boot 程序,从 2.3 版本开始内置了优雅停机能力,你只需要在配置里加上:

server.shutdown=graceful

应用收到 SIGTERM 后,Spring Boot 会停止接收新请求,等待已进入处理流程的请求完成,然后再退出。但需要注意的是,SIGTERM 发出后,应用必须在自己可控制的超时时间内完成这些工作。如果 Spring Boot 的优雅停机内部超时时间很短,而宽限期设置得很长,应用就会提前退出;反过来,如果应用内部超时时间很长,但宽限期很短,应用就会在没处理完请求时被 SIGKILL。

对于自己写的脚本或工具,一个最简单的 SIGTERM 捕获处理是这样:

#!/bin/bash trap 'echo "received SIGTERM, doing cleanup..."; sleep 5; exit 0' TERM while true; do echo "working..." sleep 2 done

这段脚本把TERM信号的处理逻辑设置成了“打印日志、清理、退出”。启动之后,你可以在另一个终端里找到它的 PID,然后发送kill -TERM <pid>,观察它会不会走完清理流程再退出。这是验证信号处理逻辑最基础的方法。

4. SIGKILL 兜底:宽限期耗尽的那一刻发生了什么

4.1 SIGKILL 的语义与触发条件

到了宽限期截止时间点,如果容器还在运行,kubelet 就会发出SIGKILL信号。SIGKILL 是 Linux 信号中最强硬的终止手段,它不走用户态,直接由内核执行,进程无法捕获、无法忽略、无法通过任何代码拦截。应用无论处于什么状态(比如正在写数据库事务),都会被强行结束掉。

这就带来不可控的后果:如果应用的优雅退出逻辑没跑完,它最后做了一半的处理很可能会残留。比如消息队列消息已经取走但 offset 没提交,数据库事务已经写了日志但没提交回滚,这些都是 SIGKILL 直接杀进程可能导致的结果。

所以 SIGKILL 在正常流程里只是一个兜底,它的存在是为了保证“Pod 一定能被删掉”,而不是为了追求优雅。实际应用里,如果你的应用在 SIGTERM 之后崩溃或者一直卡住,SIGKILL 就是那个“掀桌子”的人。

4.2 调 terminationGracePeriodSeconds 的正确姿势

terminationGracePeriodSeconds控制整个终止流程的总时长,默认 30 秒。这个默认值对于很多轻量应用够用,但对于重负载应用(比如 Java 内存占用大、需要长时间清理连接池、依赖外部资源注销)来说,30 秒太紧张了。

我的经验是:先给应用配置好 SIGTERM 的处理逻辑,然后再来调整这个宽限期参数。不要盲目调大。如果你把宽限期设置成 300 秒,然后应用在收到 SIGTERM 后 5 秒就退出了,那 Pod 自然会在 5 秒后消失,后面的 295 秒不会浪费。宽限期的本质是一个“上限”,不是固定等待时间。所以调大一点通常没有坏处,但前提是你确认应用确实会主动退出。如果应用不会主动退出,那不管你设置成 30 秒还是 300 秒,最终都是走 SIGKILL,那这个参数调整就没有意义。

我的常用配置习惯是这样:

应用类型建议宽限期说明
纯静态文件服务(Nginx)15-30 秒短一点,连接断开重连成本低
普通 Java 服务(Spring Boot)30-60 秒给 JVM 优雅停机留足够时间
重负载 Java 服务(大量数据库连接)60-120 秒连接池释放、外部注销比较耗时
数据消费类应用(消息队列)45-90 秒需要提交 offset、处理中消息比较耗时
批处理任务型 Pod按任务最长时间设置这种通常不需要用 Deploy 管理

4.3 宽限期为 0 的特殊场景

有一种特殊情况值得单独说:terminationGracePeriodSeconds: 0。当宽限期为 0 时,kubelet 会跳过 preStop、跳过 SIGTERM,直接发送 SIGKILL。也就是说,Pod 的删除会立即完成,没有任何优雅终止的机会。

这种配置一般只用在测试环境、或者你已经确认这个 Pod 没有任何优雅终止必要性的场景。比如你要跑一个临时调试用的 Pod,删掉它就希望它立刻消失,那可以设置成 0。但对于正常业务应用,千万不要随意设置 0,否则滚动更新的时候,每一次发版都等于强制重启,用户连接会大面积异常。

除了在 YAML 里配置terminationGracePeriodSeconds: 0,kubectl delete命令也提供类似的强制删除模式:

kubectl delete pod <pod-name> --grace-period=0 --force

这个命令会绕过优雅终止流程,直接在 API Server 层面删除 Pod 对象。它一般用于清理那些卡在 Terminating 状态、无法通过正常手段删除的 Pod。注意,数据库相关的 StatefulSet Pod 非常不建议用--force删除,很容易引发数据一致性问题。

5. 实战排查:Pod 卡在 Terminating 是哪里出了问题

5.1 用 describe 和 events 看流程走到哪一步

做 Kubernetes 运维的人,遇到“Pod 一直处于 Terminating 状态”的次数,可能比遇到其他任何问题的次数都多。而处理它的第一步,永远都是先弄清楚流程卡在哪一环。

首先用kubectl get pod确认 Pod 状态,如果它是 Terminating,接着用kubectl describe pod查看事件:

kubectl describe pod <pod-name>

在Events一栏里,你会看到类似这样的记录:

Normal Killing 30s kubelet Stopping container app

这条事件意味着 kubelet 已经开始停止容器了。如果这条事件之后很长时间都没有新的进展,大概率是容器没有在宽限期内收到 SIGTERM,或者收到了却没有主动退出。

如果是 preStop 卡住,事件里可能会看到 kubelet 一直在执行某个命令的超时记录。比如:

Normal Killing 2m kubelet Stopping container app

然后 Pod 一直保持 Terminating,直到 2m 后才消失。这时你要重点怀疑 preStop 脚本是不是执行了耗时操作。

另一种常见情况是 Pod 已经明显超过了宽限期,但 API Server 上的对象还一直存在。这种情况除了容器本身的问题,还可能是 Pod 上挂有finalizer。finalizer 是 Kubernetes 提供的一种资源清理机制,如果某个控制器在 Pod 上注册了 finalizer,Pod 必须等 finalizer 被移除后才会从 API Server 上消失。此时容器可能已经被杀掉了,但 Pod 对象还在。排查方法是用kubectl get pod <pod-name> -o yaml查看metadata.finalizers字段,看看有没有异常的内容。

5.2 典型问题一:preStop 脚本卡死

有一次我做滚动更新,Deployment 里的新 Pod 已经起来了,但旧 Pod 始终不退出,等了 10 分钟还是 Terminating。用 describe 看事件,发现 kubelet 一直在执行 preStop。打开 preStop 脚本一看,里面是一个 curl 调用,但脚本写的是:

curl -X POST http://example-service:8080/shutdown

问题出在这个域名解析上。preStop 执行的时候,容器内的 DNS 解析如果依赖所在的网络环境,而该网络环境中example-service这个 Service 已经不存在或者域名解析超时,curl 就会一直卡住,直到 TCP 超时。preStop 一直执行不完,SIGTERM 就永远发不出去。

这种情况的处理方法比较简单:给 preStop 的所有命令都加上超时控制。比如:

timeout 10 curl -X POST http://127.0.0.1:8080/shutdown

用timeout命令包一层,命令到了时间主动退出,不会一直卡死。这个习惯非常值得养成。

另外一个普遍陷阱是:preStop 脚本里访问外部服务时,不应该依赖 Service DNS,因为 Pod 删除的过程可能伴随着 Service 变化。最好的做法是访问本机地址,或者把外部服务的地址提前写进脚本里。

5.3 典型问题二:应用没有正确处理 SIGTERM

这是我最常遇到的情况,尤其在业务应用容器化改造的初期。应用本身完全没处理 SIGTERM,直接忽略了它。Kubernetes 发送 SIGTERM 后,应用毫无反应,一直等到宽限期耗尽,被 SIGKILL 强杀。

验证这种问题的方法很简单:观察容器日志里有没有留下 SIGTERM 相关的记录。如果应用配置了优雅停机但完全没日志,那就说明信号可能根本没到应用;如果日志里连启动时的内容都没有,那更可能是 PID 1 的问题。

另一种常见场景是:Java 应用收到 SIGTERM 后伸出了退出流程,但 JVM 退出的时间超过了宽限期。这时日志里会看到 JVM 已经开始关闭 Spring 容器、关闭线程池,但没等做完就被 SIGKILL 了。

解决起来也不复杂:一是确认应用本身有没有实现 SIGTERM 处理,二是合理设置宽限期。如果两者都做了还是没有好转,就要检查容器的 PID 1 是不是 shell 进程。

5.4 验证 SIGTERM 是否到达应用的实战方法

在排查这类问题时,最直接的工具是kubectl exec进入容器,在里面观察进程树和信号传递。举个例子:

# 查看容器内进程树,PID 1 是什么 kubectl exec <pod-name> -- ps -ef # 查看 PID 1 进程的 pid kubectl exec <pod-name> -- cat /proc/1/comm

如果 PID 1 是java,说明 Java 进程直接接收信号,处理逻辑在它自身。如果是sh或bash,就要小心信号被 shell 挡住,转发不到子进程了。

你还可以临时起一个测试 Pod,故意在里面放一个捕获 SIGTERM 的脚本,模拟一次删除操作,看看信号传递和日志记录是否符合预期。这是最稳妥的方式:

apiVersion: v1 kind: Pod metadata: name: sigterm-test spec: containers: - name: test image: bash:latest command: - /bin/bash - -c - | trap 'echo "got SIGTERM, cleaning up..." > /proc/1/fd/1; sleep 3; exit 0' TERM echo "started, PID is $$" while true; do sleep 1; done

创建这个 Pod 后,执行kubectl delete pod sigterm-test,观察终端输出。如果打印了got SIGTERM, cleaning up...,说明 SIGTERM 成功传到了 PID 1,而且捕获逻辑生效了。这个测试方法在整个排查流程里最直观,也最推荐先在测试环境里跑一遍。

6. 我这几年总结下来的最佳实践

关于 Pod 终止流程,最后单独把我觉得最实用的一些经验拿出来讲一讲。

先说宽限期设置。默认 30 秒并不是一个对业务应用友好的值,尤其现在微服务链路复杂,一个服务的优雅退出涉及注册中心注销、缓存刷新、连接池释放、正在处理的请求收尾。我在 Java 系服务上基本都会设置 60 秒以上,核心交易类服务直接给到 90-120 秒。Node.js 和 Go 服务通常 30-60 秒就够了,它们的启动和清理都比 JVM 轻量很多。

然后是 preStop 的定位。preStop 适合做“摘流量”“注销注册中心”这类对外宣告动作,不适合做“让应用睡眠 x 秒”这类纯等待动作。等待这个动作应该交给应用自身的 SIGTERM 处理逻辑去完成。如果你确实需要在终止前等待几秒钟,我建议你在应用代码里实现,而不是写在 YAML 里。比如 Java 应用可以在收到信号后先睡 3 秒再做清理,这样宽限期计时仍然统一在应用自己的掌控下。

再补一个容易踩的小坑:不要把 preStop 和 SIGTERM 处理全部寄托在 Kubernetes 默认行为上。有些容器守护进程(比如通过 Docker 启动的容器)对 SIGTERM 的转发细节和 containerd 不一样。如果你跑的是自己维护的集群,最好统一容器运行时版本,避免同一套配置在不同节点上行为不一致。

最后说说我踩过最难忘的一次坑。那次是一个 StatefulSet 的 Pod 在节点排水时卡了 20 分钟都没删掉。我翻遍各种可能,最后发现,问题是应用容器里配了两个进程,一个是主业务进程,一个是脚本监控进程。启动命令是个 shell 脚本,导致 PID 1 是 shell,所有 SIGTERM 都被 shell 吞掉了,业务进程完全无感知。最后我把启动方式改成直接用 tini 托管主进程,才彻底解决了这一类问题。从那以后我再看到启动脚本套启动脚本的镜像,心里都会咯噔一下。

还有一个小技巧我用得很多:在做滚动更新或者手动删除 Pod 之前,可以在应用日志里打印一个启动时间戳,然后在删除之后对比 Pod 的结束日志和宽限期,基本一眼就能判断流程走了多久、在哪里耗了时间。如果你想定量验证,还可以在kubectl delete pod前面包一层time,看整体耗时和terminationGracePeriodSeconds的差异。这个差异如果明显小于宽限期,说明应用提前退出了,还有优化空间;如果接近或等于宽限期,说明应用很可能是被 SIGKILL 杀掉的。

这套东西其实不复杂,但要真正掌握,一定得亲手测一次。建议你先在自己的测试环境里建一个最简单的脚本 Pod,按照我上面给的验证方法走一遍,把 preStop、SIGTERM、SIGKILL 三个阶段都打印出日志,再对比实际耗时。跑过一遍之后,很多概念就扎实了。后面再遇到线上 Pod 卡在 Terminating,你就不会慌,而是知道该从哪里开始查了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 0:45:10

DeepSeek企业级部署七步法:稳准省落地指南

1. 为什么2026年企业必须重新思考大模型部署——不是“能不能用”&#xff0c;而是“怎么用得稳、用得准、用得省”DeepSeek系列模型在2024年底完成R1版本全量开源后&#xff0c;迅速成为国内企业私有化大模型选型的高频关键词。但真实情况是&#xff1a;某制造类企业去年采购了…

作者头像 李华
网站建设 2026/10/20 8:30:48

FORTIFY_SOURCE下的栈溢出:GDB调试与绕过memcpy_chk

题目被卡住的那一晚上&#xff0c;我在 GDB 里来回跑了十几遍&#xff0c;每次程序都死在同一句话上&#xff1a;*** buffer overflow detected ***: terminated。栈顶明明已经被我打得歪歪扭扭&#xff0c;可它偏偏不是在函数返回时报错&#xff0c;而是在一次memcpy调用里直接…

作者头像 李华
网站建设 2026/10/19 3:10:23

基于STM32与云IoT平台的Qt跨平台智能家居控制系统实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/21 7:56:58

H.264 POC不是时间戳:解码器DPB调度的核心机制

1. 为什么POC不是“播放时间戳”&#xff0c;而是解码器的内部调度凭证刚接触H.264标准时&#xff0c;我跟很多初学者一样&#xff0c;下意识把pic_order_cnt&#xff08;Picture Order Count&#xff0c;简称POC&#xff09;当成视频帧的“播放时间戳”——就像MP4里的ctts盒子…

作者头像 李华
网站建设 2026/10/20 16:06:20

手写ViT:从Patch Embedding到注意力机制的图像分类实战

如果你已经跟着这个系列看到第十七章&#xff0c;前面那些 Python 语法、NumPy 操作、PyTorch 张量基础、CNN 图像分类&#xff0c;应该都不是陌生词汇了。这一章我安排了一个很多初学者会一直往后拖的内容&#xff1a;视觉 Transformer&#xff0c;也就是 ViT。我的建议是不要…

作者头像 李华
网站建设 2026/10/20 15:59:00

Windows版 Python安装与环境配置全程详细教学

Windows版 Python安装与环境配置全程详细教学 Windows版 Python的安装与环境配置 1.下载Python Python下载地址&#xff1a;Download Python | Python.org 可以在这里直接点击下载&#xff0c;就会下载你电脑对应的最新版本 如果你要是不想下载对应的最新版或者因为某些原因…

作者头像 李华