Kubernetes 的 Pod 驱逐策略

148次阅读
没有评论

共计 7157 个字符,预计需要花费 18 分钟才能阅读完成。

前言

凌晨三点,告警响起。

登录系统一看,核心服务的 Pod 被 Evicted(驱逐)了。查看日志发现一行:The node was low on resource: memory

你可能会有这些疑问:

  • 🤔 为什么节点还有资源,我的 Pod 却被杀了?
  • 🤔 驱逐的顺序是什么?哪些 Pod 最危险?
  • 🤔 如何保护关键服务不被驱逐?

今天,我们就来深入剖析 Kubernetes 的 Pod 驱逐策略,从原理到实战,帮你彻底搞懂这个关键机制。

1. 什么是 Pod 驱逐?

核心概念

Node-pressure Eviction(节点压力驱逐) 是 kubelet 在节点资源不足时,主动终止 Pod 以回收资源的过程。

graph TD
    A[节点资源监控] --> B{达到驱逐阈值?}
    B -->| 否 | A
    B -->| 是 | C[选择要驱逐的 Pod]
    C --> D[发送 SIGTERM 信号]
    D --> E{30 秒内优雅退出?}
    E -->| 是 | F[Pod 成功终止]
    E -->| 否 | G[发送 SIGKILL 强制杀死]

    style A fill:#e1f5ff
    style B fill:#fff4e1
    style C fill:#ffe1e1
    style F fill:#e1ffe1

驱逐 vs OOM Killer

很多人会混淆这两个概念,我们来对比一下:

特性 Pod Eviction(驱逐) OOM Killer(内存杀手)
触发者 kubelet 主动触发 Linux 内核被动响应
可预测性 ✅ 可配置阈值 ❌ 不可预测
终止方式 优雅关闭(有宽限期) 立即强杀
选择逻辑 基于 QoS 和优先级 基于 OOM 分数
推荐 应该依赖 应该避免

⚠️ 关键区别:驱逐是可控的、优雅的,而 OOM Killer 是不可控的、粗暴的。合理配置驱逐策略可以避免触发 OOM Killer。


2. 驱逐信号与阈值

2.1 Kubelet 监控的资源信号

Kubelet 持续监控以下资源:

驱逐信号 说明 系统
memory.available 节点可用内存 All
nodefs.available 节点文件系统可用空间 All
nodefs.inodesFree 节点文件系统可用 inode Linux
imagefs.available 镜像文件系统可用空间 All
imagefs.inodesFree 镜像文件系统可用 inode Linux
pid.available 可用进程 ID 数量 Linux

内存信号计算方式(重要):

memory.available = 节点总内存 - 工作集内存(working set)

📌 注意:不是 free 命令显示的可用内存!Kubernetes 使用 cgroup 的数据。

2.2 硬驱逐 vs 软驱逐

graph LR
    A[资源压力] --> B{阈值类型}

    B -->|Hard 硬驱逐 | C[立即驱逐 <br/> 无宽限期]
    B -->|Soft 软驱逐 | D[观察期]

    D --> E{持续超过 <br/> 宽限期?}
    E -->| 是 | C
    E -->| 否 | F[继续观察]

    style C fill:#ff6b6b
    style D fill:#4ecdc4
    style F fill:#95e1d3

硬驱逐(Hard Eviction)

特点:阈值到达立即驱逐,无宽限期

默认配置

eviction-hard:
  memory.available: "100Mi"   # Linux
  memory.available: "500Mi"   # Windows
  nodefs.available: "10%"
  nodefs.inodesFree: "5%"
  imagefs.available: "15%"

软驱逐(Soft Eviction)

特点:有观察期,更温和

配置示例

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
evictionSoft:
  memory.available: "200Mi"
  nodefs.available: "15%"
evictionSoftGracePeriod:
  memory.available: "1m30s"  # 观察 1 分 30 秒
  nodefs.available: "2m"     # 观察 2 分钟

💡 实战建议:生产环境建议同时配置软驱逐和硬驱逐,软驱逐作为预警,硬驱逐作为保底。


3. 驱逐的优先级与顺序

3.1 QoS 等级:生死线

Kubernetes 根据 Pod 的资源配置,自动分配三种 QoS(Quality of Service)等级:

graph TD
    A[Pod 创建] --> B{资源配置检查}

    B -->| 所有容器 <br/>requests=limits| C[Guaranteed<br/> 保证级]
    B -->| 部分容器 <br/> 有 requests 或 limits| D[Burstable<br/> 突发级]
    B -->| 没有任何 <br/> 资源配置 | E[BestEffort<br/> 尽力而为级]

    C --> F[驱逐优先级: 最低 <br/> 最后才会被驱逐]
    D --> G[驱逐优先级: 中等]
    E --> H[驱逐优先级: 最高 <br/> 首先被驱逐]

    style C fill:#2ecc71
    style D fill:#f39c12
    style E fill:#e74c3c

✅ Guaranteed(保证级)- 最安全

条件:所有容器都设置了 CPU 和内存的 requests 和 limits,且相等

apiVersion: v1
kind: Pod
metadata:
  name: guaranteed-pod
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        memory: "1Gi"
        cpu: "500m"
      limits:
        memory: "1Gi"  # 与 requests 相等
        cpu: "500m"    # 与 requests 相等

特点:除非系统守护进程需要资源,否则不会被驱逐

⚠️ Burstable(突发级)- 中等风险

条件:至少一个容器设置了 requests 或 limits,但不满足 Guaranteed

apiVersion: v1
kind: Pod
metadata:
  name: burstable-pod
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        memory: "512Mi"
        cpu: "250m"
      limits:
        memory: "1Gi"   # 大于 requests
        cpu: "1000m"    # 大于 requests

⚠️ 特点:可以使用超过 requests 的资源,但超出越多越危险

❌ BestEffort(尽力而为级)- 最危险

条件:没有任何容器设置 requests 和 limits

apiVersion: v1
kind: Pod
metadata:
  name: besteffort-pod
spec:
  containers:
  - name: nginx
    image: nginx
    # 完全没有 resources 配置

特点:资源压力时首先被驱逐

3.2 完整驱逐顺序

graph TD
    A[开始驱逐选择] --> B[按 QoS 分组]

    B --> C[第 1 组: BestEffort]
    B --> D[第 2 组: Burstable 超出 requests]
    B --> E[第 3 组: Guaranteed 和 Burstable 未超出 requests]

    C --> F[按资源使用量排序]
    D --> F
    E --> F

    F --> G[按 Priority 值排序]
    G --> H[选择最终驱逐对象]

    H --> I[发送 SIGTERM]
    I --> J{30 秒内退出?}
    J -->| 否 | K[发送 SIGKILL 强杀]
    J -->| 是 | L[优雅终止]

    style C fill:#e74c3c
    style D fill:#f39c12
    style E fill:#2ecc71

驱逐顺序总结

  1. 第一梯队:BestEffort Pod(按使用量从高到低)
  2. 第二梯队:Burstable Pod 且使用量超过 requests(按超出比例从高到低)
  3. 第三梯队:Guaranteed Pod 和未超出 requests 的 Burstable Pod(按 Priority 从低到高)

4. 节点资源回收机制

在驱逐 Pod 之前,kubelet 会先尝试回收节点级资源:

回收流程

sequenceDiagram
    participant K as Kubelet
    participant N as Node
    participant C as Containers
    participant I as Images

    K->>N: 检测到资源压力
    K->>K: 开始资源回收

    K->>C: 1. 清理死亡的容器
    Note over C: 删除已停止的容器

    K->>N: 检查资源是否足够

    alt 仍不足
        K->>I: 2. 删除未使用的镜像
        Note over I: 清理不再需要的镜像
    end

    K->>N: 再次检查资源

    alt 还是不足
        K->>C: 3. 开始驱逐 Pod
    end

不同文件系统配置的回收策略

场景 1:单文件系统(nodefs)

回收顺序:1. 垃圾回收死亡 Pod 和容器
2. 删除未使用的镜像

场景 2:分离镜像文件系统(nodefs + imagefs)

nodefs 压力触发:→ 垃圾回收死亡 Pod 和容器

imagefs 压力触发:→ 删除所有未使用的镜像

5. 如何保护关键 Pod 不被驱逐

5.1 使用 PriorityClass 设置优先级

创建高优先级类

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000  # 数值越大优先级越高
globalDefault: false
description: "关键业务 Pod 使用此优先级"

在 Pod 中使用

apiVersion: v1
kind: Pod
metadata:
  name: critical-service
spec:
  priorityClassName: high-priority  # 引用优先级类
  containers:
  - name: app
    image: my-critical-app
    resources:
      requests:
        memory: "2Gi"
        cpu: "1"
      limits:
        memory: "2Gi"
        cpu: "1"

5.2 配置 Guaranteed QoS

最佳实践:关键服务必须配置 Guaranteed QoS

apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: payment
  template:
    metadata:
      labels:
        app: payment
    spec:
      priorityClassName: high-priority
      containers:
      - name: payment
        image: payment-service:v1
        resources:
          requests:
            memory: "4Gi"
            cpu: "2"
          limits:
            memory: "4Gi"  # 与 requests 相等
            cpu: "2"       # 与 requests 相等

5.3 系统关键组件的特殊处理

对于 DaemonSet 等系统组件,使用 system-node-critical 优先级:

apiVersion: v1
kind: Pod
metadata:
  name: kube-proxy
  namespace: kube-system
spec:
  priorityClassName: system-node-critical  # 系统级最高优先级
  containers:
  - name: kube-proxy
    image: k8s.gcr.io/kube-proxy:v1.28.0

💡 优先级数值参考

  • system-node-critical: 2000001000
  • system-cluster-critical: 2000000000
  • 用户自定义:建议 0-1000000

案例 1:为什么节点资源充足,Pod 还是被驱逐?

问题现象

# 节点总资源
Total: CPU 16 cores, Memory 32Gi

# 已分配资源(Requests 总和)Allocated: CPU 8 cores (50%), Memory 16Gi (50%)

# 实际使用
Actual Usage: CPU 12 cores (75%), Memory 28Gi (87.5%)

# 现象:某些 Burstable Pod 被驱逐了!

原因分析

问题出在 资源超售 + 不合理的 requests/limits 配置

graph LR
    A[问题根源] --> B[Requests 设置过小]
    A --> C[Limits 设置过大]

    B --> D[调度器认为 <br/> 节点资源充足]
    C --> E[Pod 实际使用 <br/> 远超 requests]

    D --> F[允许更多 Pod 调度]
    E --> F

    F --> G[节点实际使用 <br/> 超负荷]
    G --> H[触发驱逐机制]

    style H fill:#e74c3c

不合理配置示例

# ❌ 错误配置:容易被驱逐
resources:
  requests:
    memory: "100Mi"   # 请求很少
    cpu: "100m"
  limits:
    memory: "4Gi"     # 限制很大,可以使用 40 倍的内存
    cpu: "2"

当多个这样的 Pod 实际使用接近 limits 时,节点内存就会爆满。

正确配置示例

# ✅ 正确配置:合理估算
resources:
  requests:
    memory: "2Gi"     # 接近实际使用
    cpu: "500m"
  limits:
    memory: "3Gi"     # 留出 50% 弹性空间
    cpu: "1"

5.4 最佳实践总结

1. 合理配置资源请求

# 原则:requests 设置为正常使用量,limits 留 20-50% 弹性空间
resources:
  requests:
    memory: "2Gi"    # 正常使用量
    cpu: "1"
  limits:
    memory: "3Gi"    # 1.5 倍 requests
    cpu: "2"         # 2 倍 requests

2. 关键服务使用 Guaranteed QoS

spec:
  priorityClassName: high-priority
  containers:
  - name: app
    resources:
      requests:
        memory: "4Gi"
        cpu: "2"
      limits:
        memory: "4Gi"  # 相等
        cpu: "2"       # 相等

3. 监控和告警配置

# Prometheus 告警规则示例
groups:
- name: node-eviction
  rules:
  - alert: NodeMemoryPressure
    expr: kube_node_status_condition{condition="MemoryPressure",status="true"} == 1
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "节点 {{$labels.node}} 内存压力"

  - alert: PodEvicted
    expr: kube_pod_status_phase{phase="Failed",reason="Evicted"} == 1
    labels:
      severity: critical
    annotations:
      summary: "Pod {{$labels.pod}} 被驱逐"

4. 配置合理的驱逐阈值

# Kubelet 配置建议
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration

# 软驱逐:作为预警
evictionSoft:
  memory.available: "1Gi"
  nodefs.available: "15%"
evictionSoftGracePeriod:
  memory.available: "2m"
  nodefs.available: "5m"

# 硬驱逐:作为保底
evictionHard:
  memory.available: "500Mi"
  nodefs.available: "10%"

# 最小回收量
evictionMinimumReclaim:
  memory.available: "500Mi"
  nodefs.available: "1Gi"

6. 知识要点速记

Q1: 为什么我的 Guaranteed Pod 还是被驱逐了?

A: 如果系统守护进程(如 kubelet、系统服务)需要更多资源,即使 Guaranteed Pod 也可能被驱逐。解决方案:

  • 使用 --system-reserved--kube-reserved 为系统预留资源
  • 使用 system-node-critical 优先级

Q2: limits 设置过大有什么问题?

A:

  • 会导致节点资源超售
  • Pod 实际使用可能远超 requests
  • 容易触发驱逐甚至 OOM Killer
  • 建议 limits 不超过 requests 的 1.5-2 倍

Q3: 如何查看 Pod 的 QoS 等级?

# 查看 Pod 详情中的 qosClass 字段
kubectl get pod <pod-name> -o yaml | grep qosClass

# 或使用 describe
kubectl describe pod <pod-name> | grep "QoS Class"

Q4: DaemonSet 的 Pod 会被驱逐吗?

A: 会!但可以通过设置高优先级来保护:

spec:
  priorityClassName: system-node-critical

7. 相关资料


8. 总结

Kubernetes 的 Pod 驱逐机制是保障集群稳定性的关键:

  1. 驱逐是可控的:通过 QoS、Priority、阈值等多层保护
  2. 顺序是有规律的:BestEffort → Burstable(超限) → Guaranteed
  3. 可以提前预防:合理配置资源、设置优先级、监控告警

核心原则

  • ✅ 关键服务用 Guaranteed + 高优先级
  • ✅ requests 接近实际使用,limits 留适当弹性
  • ✅ 配置软驱逐做预警,硬驱逐做保底
  • ✅ 监控节点压力,提前扩容
正文完
 0
nwnusun
版权声明:本站原创文章,由 nwnusun 于2025-12-19发表,共计7157字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)