Kubernetes 中服务(Pod)被终止是一个系统性的决策过程,其背后有多重原因和一套清晰的 escalation 机制。
第一部分:服务被杀或优雅终止的原因
Pod 被终止的两大根本原因是:主动决策 和 被动驱逐。
类型 | 原因 | 触发者 | 行为 |
---|---|---|---|
主动决策 (主动终止) | 1. 用户/控制器操作:手动删除 (kubectl delete )、滚动更新、应用缩容 (HPA/VPA)、Job完成。 | 用户、Deployment、StatefulSet、CronJob、HPA | 优雅终止。发送 SIGTERM,等待 terminationGracePeriodSeconds 。 |
2. 健康检查失败:livenessProbe 探测失败。 | kubelet | 强制重启。先优雅终止,再重启容器(根据 restartPolicy )。 | |
被动驱逐 (被动杀死) | 3. 资源不足:节点上的资源(CPU、内存等)耗尽。 | kubelet | 驱逐 (Eviction)。优雅终止整个 Pod,并在其他节点重建(如果由控制器管理)。 |
4. 节点问题:节点宕机、网络分区、非优雅关闭。 | node-controller | API 层面删除。直接删除 Pod 对象,由控制器在其他节点重建。 |
第二部分:资源不够用涉及哪些类型的资源?
Kubernetes 监控两大类资源不足的情况:
- 可压缩资源 (Compressible Resources):
- CPU:当 CPU 不足时,Pod 的运行会变慢( throttled ),但不会被杀死。因为CPU是可以被“限制”和“压缩”的。
- 不可压缩资源 (Incompressible Resources):
- 内存 (Memory):是最常见的驱逐原因。内存一旦耗尽,无法回收,必须通过杀死 Pod 来释放。
- 本地存储 (Local Storage):
ephemeral-storage
(容器日志、可写层、emptyDir)。nodefs
:守护进程(kubelet、容器运行时)所需的存储(根分区)。
- PID (Process IDs):节点上的进程数达到上限。
- 磁盘压力:与存储相关,但更关注 inode 或整体磁盘空间。
资源不足的层级:
- Pod 级别:Pod 请求的资源 (
requests
) 无法满足。 - 节点级别:节点整体资源耗尽。
第三部分:资源如何影响服务运行及导致终止的步骤
这个过程是一个逐步升级的响应链,核心执行者是 kubelet。
第一步:监控与压力检测
kubelet 持续监控节点上的资源使用情况(通过 cAdvisor)。当资源消耗达到一定阈值时,节点会进入压力状态。
MemoryPressure
:内存不足DiskPressure
:磁盘空间不足PIDPressure
:进程数过多
第二步:保护机制启动 – Node OOM (Out Of Memory) 行为
这是最直接、最暴力的方式,发生在 kubelet 驱逐之前,由 Linux 内核触发。
- 节点上的某个或多个进程(可能是某个容器)疯狂消耗内存,导致系统可用内存极低。
- Linux 内核的 OOM Killer (Out-of-Memory Killer) 被激活。
- OOM Killer 根据每个进程的
oom_score
(评分)来选择要杀死的进程。通常,内存消耗越多、优先级越低的进程得分越高。 - Kubernetes 通过设置
oom_score_adj
来影响这个评分,确保重要的系统进程(如kubelet
)得分极低(不容易被杀),而 Pod 中的容器进程得分较高(更容易被杀)。 - 内核 OOM Killer 直接杀死选中的容器进程,没有优雅终止。这可能导致数据不一致。
- kubelet 检测到容器非正常退出,然后根据 Pod 的
restartPolicy
决定是否重启它。
OOM 杀死是系统最后的自我保护,不是 Kubernetes 的首选方案。
第三步:主动管理 – kubelet 驱逐 (Eviction)
Kubernetes 更倾向于一种更优雅、更可控的资源释放机制,这就是 驱逐。
kubelet 在资源达到驱逐阈值时(比 OOM 发生的阈值更早)就会主动开始驱逐 Pod。这个过程是一步一步的:
- 决策阶段 (Who to kill?)
kubelet 会按照以下顺序和策略对 Pod 进行排序,选择最优的驱逐候选者:- 1. 资源使用量:优先驱逐实际使用量超过请求量 (
requests
) 最大的 Pod。这确保了最“超支”的 Pod 先被处理。 - 2. 优先级 (Priority):驱逐低优先级 (
priorityClass
) 的 Pod,保留高优先级的 Pod。 - 3. 资源使用比例:比较 Pod 的
(内存请求量) / (节点内存总量)
,优先驱逐占用比例最小的 Pod,以最小化影响。
- 1. 资源使用量:优先驱逐实际使用量超过请求量 (
- 执行阶段 (How to kill?)
一旦选中目标 Pod,kubelet 会启动优雅终止流程:- 1. 发送 SIGTERM:kubelet 向 Pod 内所有容器的主进程发送
SIGTERM
信号。 - 2. 执行 PreStop Hook:如果 Pod 配置了
preStop
钩子,kubelet 会执行它(例如,通知上游解除注册、完成剩余任务)。这是一个关键的优雅处理窗口。 - 3. 等待宽限期:kubelet 会等待
terminationGracePeriodSeconds
(默认30秒)的时间,让容器自行退出。 - 4. 强制终止:如果宽限期结束后容器仍未停止,kubelet 会发送
SIGKILL
信号强制杀死进程。 - 5. 清理与通知:清理 Pod 占用的资源(网络、存储卷),并向 API Server 报告 Pod 状态为
Failed
。
- 1. 发送 SIGTERM:kubelet 向 Pod 内所有容器的主进程发送
- 后续阶段 (What’s next?)
- 如果被驱逐的 Pod 是由
Deployment
、StatefulSet
等控制器管理的,控制器会发现 Pod 数量不足,并立即尝试在其他资源充足的节点上调度一个新的 Pod 副本。 - 对于
DaemonSet
Pod,它会在当前节点上被重新创建。
- 如果被驱逐的 Pod 是由
第四步:节点级故障 – 控制平面驱逐
如果整个节点宕机或失联(NotReady
/Unreachable
),kubelet 本身已无法工作。
node-controller
在node-monitor-grace-period
(默认40s)后将该节点标记为NotReady
。- 再经过一段时间(由 Pod 上的
tolerationSeconds
决定),控制平面会直接从 API Server 中删除该节点上的所有 Pod 对象。 - 这些 Pod 的控制器(Deployment等)检测到 Pod 被删除,会在其他健康节点上重建新的 Pod。
总结流程图:从资源不足到服务终止
graph TD A[节点资源消耗] --> B{资源是否达到驱逐阈值?} B -- No --> C[正常服务] B -- Yes --> D[kubelet 主动驱逐] A --> E{资源是否极度匮乏?} E -- Yes --> F[Linux 内核 OOM Killer 被触发] F --> G[根据 oom_score 强制杀死进程<br>无优雅终止] D --> H[kubelet 按策略选择驱逐目标Pod] H --> I[启动优雅终止流程] I --> J[1.发送 SIGTERM] J --> K[2.执行 preStop Hook] K --> L[3.等待 terminationGracePeriodSeconds] L --> M{容器是否退出?} M -- Yes --> N[驱逐成功] M -- No --> O[4.发送 SIGKILL 强制杀死] O --> N subgraph "后续操作" N --> P[控制器在其他节点重建Pod] G --> Q[kubelet 根据 restartPolicy 可能重启容器] end
核心结论:
- Kubernetes 优先通过 kubelet 驱逐 来优雅地处理资源不足,这是设计上的首选方案。
- 内核 OOM 是底层系统的暴力保护机制,应通过合理设置 Pod 的
requests
和limits
来尽量避免。 - 理解这个过程,可以更好地配置资源请求/限制、设置优先级和探针,从而保证服务的稳定性和可预测性。