K8s 生产集群排障实战:Pod 驱逐与资源争用的底层逻辑
K8s 生产集群排障实战:Pod 驱逐与资源争用的底层逻辑
一、凌晨三点的告警风暴:当节点资源耗尽引发连锁驱逐
凌晨三点,手机连续震动 47 次。打开一看,某个 K8s 节点上的 Pod 批量进入 Evicted 状态,业务线开始报 502。这不是偶发事件,而是生产环境中反复上演的经典故障模式。
核心痛点在于:K8s 的资源管理机制在节点压力下会自动驱逐 Pod,但驱逐策略的触发条件、优先级排序和回收逻辑,如果不深入理解,排障时只能靠重启和祈祷。更麻烦的是,驱逐后的 Pod 调度可能再次选中同一个压力节点,形成"驱逐-调度-再驱逐"的死循环。
生产环境中,这类问题的根因往往不是单一资源不足,而是 CPU、内存、磁盘 IO、PID 的多重争用叠加。理解 K8s 资源管理的底层机制,是从根本上解决驱逐问题的关键。
二、Kubelet 驱逐机制的底层原理:从信号检测到 Pod 杀死
Kubelet 是节点上资源管理的执行者。它持续监控节点的内存、磁盘等资源指标,当资源使用量突破驱逐阈值时,按优先级杀死 Pod 以回收资源。
flowchart TD A[Kubelet 周期性采集节点资源指标] --> B{内存使用率 > 驱逐阈值?} B -->|否| C{磁盘使用率 > 驱逐阈值?} B -->|是| D[触发 MemoryPressure 条件] C -->|否| E[继续监控] C -->|是| F[触发 DiskPressure 条件] D --> G[按 QoS 类别排序 Pod] F --> G G --> H[BestEffort 优先驱逐] H --> I[Burstable 次之] I --> J[Guaranteed 最后] J --> K[同 QoS 内按优先级排序] K --> L[发送 SIGTERM, 优雅终止期后 SIGKILL] L --> M[回收资源, 更新 NodeCondition] M --> E关键机制解析:
1. 驱逐信号(Eviction Signals)
Kubelet 支持多种驱逐信号,每种信号对应一个硬阈值和软阈值。硬阈值一旦触及,立即驱逐;软阈值触及后,允许在宽限期内观察,超过宽限期才执行驱逐。
| 驱逐信号 | 含义 | 默认硬阈值 |
|---|---|---|
memory.available | 节点可用内存 | < 100Mi |
nodefs.available | 节点文件系统可用空间 | < 10% |
nodefs.inodesFree | 节点文件系统可用 inode | < 5% |
imagefs.available | 镜像存储可用空间 | < 15% |
2. QoS 等级与驱逐顺序
K8s 将 Pod 分为三个 QoS 等级,驱逐时严格按此排序:
- BestEffort:未设置 requests 和 limits,最先被驱逐
- Burstable:设置了 requests 但未设置 limits,或两者不等,次之
- Guaranteed:requests 等于 limits,最后被驱逐
3. 驱逐后的调度陷阱
被驱逐的 Pod 进入 Pending 状态后,Scheduler 重新调度。如果节点资源压力未真正缓解,且没有配置合理的 Pod 拓扑分布约束,Pod 可能再次调度到同一节点,形成驱逐循环。
三、生产级防驱逐配置与排障脚本
3.1 合理设置 requests 和 limits
apiVersion: v1 kind: Pod metadata: name: critical-service labels: qos: guaranteed spec: containers: - name: app image: registry.example.com/app:v2.3.1 # requests 必须等于 limits 才能获得 Guaranteed QoS # 这样在资源争用时最后被驱逐,保障核心业务稳定 resources: requests: cpu: "2" memory: "4Gi" limits: cpu: "2" memory: "4Gi" # 配置存活探针,避免因应用假死被误判驱逐 livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 3 # 拓扑分布约束:避免 Pod 集中调度到同一节点 topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: critical-service3.2 Kubelet 驱逐阈值自定义
# /var/lib/kubelet/config.yaml 中配置驱逐阈值 # 提前触发驱逐,给系统留出缓冲空间,避免到达内核 OOM 才动作 evictionHard: memory.available: "500Mi" # 可用内存低于 500Mi 时硬驱逐 nodefs.available: "15%" # 文件系统可用低于 15% 时硬驱逐 imagefs.available: "15%" # 镜像存储可用低于 15% 时硬驱逐 evictionSoft: memory.available: "1Gi" # 可用内存低于 1Gi 时软驱逐 evictionSoftGracePeriod: memory.available: "2m" # 软驱逐宽限期 2 分钟 evictionMaxPodGracePeriod: 60 # 驱逐时最大优雅终止期 evictionMinimumReclaim: # 每次驱逐至少回收的资源量 memory.available: "200Mi" nodefs.available: "5%"3.3 排障脚本:快速定位驱逐根因
#!/bin/bash # 驱逐故障快速定位脚本 # 设计思路:按层级排查,从节点状态到 Pod 日志,缩小问题范围 set -euo pipefail NODE="${1:?用法: $0 <node-name>}" echo "===== 节点 $NODE 资源使用 =====" kubectl describe node "$NODE" | awk '/Allocated resources/,/^$/' echo "" echo "===== 节点 Condition 状态 =====" kubectl get node "$NODE" -o jsonpath='{range .status.conditions[*]}{.type}: {.status} ({.reason})\n{end}' echo "" echo "===== 被驱逐的 Pod 列表 =====" kubectl get pods --all-namespaces -o json | \ jq -r '.items[] | select(.status.reason=="Evicted") | "\(.metadata.namespace)/\(.metadata.name): \(.status.message)"' 2>/dev/null || \ echo "未发现被驱逐的 Pod" echo "" echo "===== 节点 Top 信息 =====" kubectl top node "$NODE" 2>/dev/null || echo "metrics-server 未部署,无法获取 Top 数据" echo "" echo "===== 占用内存最高的 5 个 Pod =====" kubectl top pods --all-namespaces --sort-by=memory 2>/dev/null | head -6 || \ echo "无法获取 Pod 资源使用" echo "" echo "===== 内核 OOM 记录 =====" # 通过 kubectl debug 在节点上检查 dmesg,确认是否有 OOM Killer 动作 kubectl debug node/"$NODE" -it --image=busybox -- \ dmesg | grep -i "oom-kill" | tail -5 2>/dev/null || \ echo "无法获取内核 OOM 记录"四、驱逐机制的架构权衡与适用边界
权衡一:QoS 等级与资源利用率的对立
将所有 Pod 设为 Guaranteed 可以最大限度避免驱逐,但这会导致集群资源利用率大幅下降。因为 K8s 调度以 requests 为准,Guaranteed Pod 的 requests 等于 limits,无法超卖。生产中需要按业务重要性分级:核心服务 Guaranteed,辅助服务 Burstable,批处理任务 BestEffort。
权衡二:驱逐阈值与可用容量的博弈
提高驱逐阈值(如memory.available: 1Gi)可以更早触发保护,但意味着更多资源被预留,节点实际可承载的 Pod 数量减少。阈值设置需要结合节点规格和业务峰值综合评估,不能一刀切。
权衡三:软驱逐的宽限期与故障恢复速度
软驱逐给予应用宽限期来自行回收资源,但宽限期内系统可能进一步恶化。对于内存敏感型业务,宽限期不宜超过 2 分钟,否则可能从软压力恶化为硬压力甚至内核 OOM。
适用边界:
- 驱逐机制适用于可容忍短暂中断的无状态服务。对于有状态服务(如数据库),应通过 Node Affinity 和 Taint/Toleration 将其隔离到专用节点,避免被驱逐波及。
- 对于 GPU 节点,驱逐机制不直接管理 GPU 显存,需要额外部署 DCGM-Exporter 配合自定义指标告警。
- 在超大规模集群(>5000 节点)中,Kubelet 驱逐可能引发雪崩效应,需要配合 Cluster Autoscaler 和 Descheduler 协同工作。
禁用场景:
- 单节点集群或开发环境,驱逐机制反而增加调试复杂度,建议关闭。
- 使用 Kubevirt 运行虚拟机的场景,驱逐可能导致虚拟机被强制杀死,需要配置 Live Migration 而非直接驱逐。
五、总结
K8s Pod 驱逐是资源管理的重要保护机制,但如果不理解其底层逻辑,排障时容易陷入"驱逐-调度-再驱逐"的恶性循环。核心要点如下:
- 合理设置 QoS 等级:核心业务 Guaranteed,辅助业务 Burstable,批处理 BestEffort,形成资源争用时的明确优先级。
- 调优驱逐阈值:根据节点规格和业务峰值,设置合理的硬阈值和软阈值,预留足够缓冲空间。
- 配置拓扑分布约束:避免被驱逐的 Pod 再次调度到同一压力节点,打破驱逐循环。
- 建立分层排障流程:从节点 Condition 到 Pod QoS 再到内核 OOM 日志,逐层缩小问题范围。
落地路线建议:先在预发环境通过stress-ng模拟内存和磁盘压力,观察驱逐行为是否符合预期;再逐步调整阈值和 QoS 配置,找到业务稳定性和资源利用率的最优平衡点;最后将排障脚本集成到运维平台,实现驱逐故障的一键定位。
