Pod驱逐机制:节点压力驱逐、污点与容忍度、PodDisruptionBudget配置、优雅终止时间设置
说实话,节点故障这块,我踩过的坑比走过的路还多。
尤其是vLLM推理服务,一个Pod被驱逐,可能意味着几十个正在处理的请求直接断掉。用户那边看到的,就是「服务不可用」五个大字。嗯,今天咱们就把Pod驱逐这件事彻底聊透。
一、节点压力驱逐:K8s的「自我保护」机制
节点压力驱逐,说白了就是节点快扛不住了,K8s帮你做决断。
我遇到过一台GPU节点,显存被某个异常进程吃满,结果kubelet直接开始驱逐Pod。当时我还在睡梦中,被报警电话叫醒……
节点压力驱逐的触发条件,主要看这几个指标:
- memory.available:可用内存低于阈值
- nodefs.available:根文件系统空间不足
- nodefs.inodesFree:inode耗尽
- imagefs.available:容器镜像存储空间不足
K8s会为每个资源设置两个阈值:
| 阈值类型 | 含义 | 行为 |
|---|---|---|
| soft eviction | 软驱逐阈值 | 超过后等待宽限期,再驱逐 |
| hard eviction | 硬驱逐阈值 | 超过后立即驱逐,不给面子 |
核心要点:vLLM推理服务对显存和内存需求极高。我建议你在节点上配置合理的驱逐阈值,避免因为内存抖动导致推理Pod被误杀。
配置示例(kubelet参数):
--eviction-hard=memory.available<500Mi,nodefs.available<10%
--eviction-soft=memory.available<1Gi
--eviction-soft-grace-period=memory.available=2m30s
--eviction-max-pod-grace-period=120
二、污点与容忍度:让Pod「挑」节点
污点(Taint)是节点给Pod贴的标签,意思是「我不欢迎你」。容忍度(Toleration)是Pod的「通行证」。
你想想看,如果某个GPU节点有硬件故障风险,你肯定不希望推理Pod调度上去。这时候污点就派上用场了。
常用的污点效果有三种:
- NoSchedule:不调度新Pod上去
- PreferNoSchedule:尽量不调度,但不强制
- NoExecute:不仅不调度,已有的Pod也要驱逐
我在项目中遇到过这样一个场景:一台A100节点风扇故障,温度飙升。我给它打了污点:
kubectl taint nodes gpu-node-01 hardware-risk=true:NoExecute
然后给vLLM推理Pod加上容忍度:
tolerations:
- key: "hardware-risk"
operator: "Equal"
value: "true"
effect: "NoExecute"
tolerationSeconds: 300 # 最多容忍300秒
我的习惯:对于推理服务,我通常设置tolerationSeconds。这样节点出问题时,Pod有足够时间完成正在处理的请求,而不是被秒杀。
三、PodDisruptionBudget:给驱逐上个「保险」
PDB(PodDisruptionBudget)是K8s提供的「优雅驱逐」机制。它保证在主动驱逐时,你的服务不会全部挂掉。
说白了,就是告诉K8s:「你可以赶走我的Pod,但至少给我留几个干活儿的。」
配置方式有两种:
- minAvailable:最少可用的Pod数量
- maxUnavailable:最多不可用的Pod数量
对于vLLM推理服务,我推荐这样配置:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: vllm-inference-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: vllm-inference
避坑指南:我曾经遇到过PDB配置了minAvailable: 100%,但副本数只有1的情况。结果节点维护时,Pod死活驱逐不了,运维操作被卡住。记住,PDB不能保证100%可用,它只是尽力而为。
PDB生效的场景:
- 节点维护(drain)
- 集群自动缩放(Cluster Autoscaler)
- 主动驱逐(kubectl evict)
但注意,节点压力驱逐不受PDB约束。节点快挂了,K8s可不管你的PDB,直接动手。
四、优雅终止时间:给推理请求一个「体面」的告别
优雅终止(Graceful Shutdown),是K8s给Pod的最后体面。
当Pod被驱逐时,K8s会发送SIGTERM信号,然后等待一段时间(默认30秒),如果Pod还没退出,就发SIGKILL强制杀掉。
对于vLLM推理服务,30秒远远不够。一个长文本生成请求可能耗时几分钟。强行杀掉,用户那边就是「生成到一半,没了」。
我建议这样配置:
apiVersion: v1
kind: Pod
metadata:
name: vllm-inference-pod
spec:
terminationGracePeriodSeconds: 300 # 5分钟
containers:
- name: vllm
image: vllm/vllm-openai:latest
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "vllm drain --timeout=240"]
关键点:terminationGracePeriodSeconds要大于preStop脚本的执行时间。我一般留出20%的余量。
优雅终止的完整流程:
- K8s将Pod状态标记为Terminating
- 从Service的Endpoints中移除Pod
- 执行preStop hook(如果有)
- 发送SIGTERM信号
- 等待terminationGracePeriodSeconds
- 发送SIGKILL强制终止
vLLM的drain命令会做两件事:
- 停止接收新请求
- 等待正在处理的请求完成
我的经验:对于推理服务,terminationGracePeriodSeconds设置300秒是比较稳妥的。如果模型特别大、推理时间特别长,可以适当延长到600秒。但别太长,否则节点维护会卡住。
五、知识体系总览
下面这张图,是我梳理的Pod驱逐机制核心逻辑:
这张图把四个核心机制串起来了。节点压力驱逐是「被动触发」,污点与容忍度是「主动标记」,PDB是「保护伞」,优雅终止是「最后防线」。四者配合,才能让vLLM推理服务在节点故障时平稳过渡。
嗯,今天就聊到这儿。记住一句话:不要等到节点挂了才想怎么处理,提前配置好这些机制,比你半夜爬起来修要好得多。