Kubelet 资源驱逐 (Eviction) 通信流程

目标

这篇文档只回答一个核心问题:kubelet 如何在节点资源不足时决定并执行 Pod 驱逐?

重点放在 EvictionManager 的监控机制、驱逐决策逻辑以及驱逐执行流程。

一句话摘要

kubelet 的 EvictionManager 定期监控节点资源使用,当资源超过阈值时,根据 QoS 和优先级选择 Pod 进行驱逐,以保护节点稳定性。


1. 流程总览

从通信视角看,这条链路可以拆成 3 个阶段:

  1. 资源监控阶段:EvictionManager 定期采集节点资源使用情况。
  2. 决策判断阶段:根据阈值和策略决定是否需要驱逐以及驱逐哪些 Pod。
  3. 执行驱逐阶段:通过 API Server 删除选中的 Pod。

架构总览图

flowchart TB
    subgraph 资源采集["资源采集层"]
        direction TB
        CAD[cAdvisor<br/>容器资源监控]
        SYS[系统信息<br/>节点资源]
        SIG[信号采集器<br/>Memory/Disk/PID]
    end

    subgraph EvictionManager["EvictionManager"]
        direction TB
        MON[Monitor<br/>资源监控循环]
        THRESH[ThresholdManager<br/>阈值管理]
        RANK[Ranking<br/>Pod 排序]
        EVICT[Evictor<br/>驱逐执行]
    end

    subgraph 决策依据["决策依据"]
        direction LR
        QOS[Pod QoS<br/>Guaranteed/Burstable/BestEffort]
        PRI[Priority<br/>Pod 优先级]
        USAGE[资源使用<br/>实际使用量]
    end

    subgraph 驱逐执行["驱逐执行"]
        direction TB
        API[kube-apiserver]
        KL[Kubelet]
        STATUS[节点状态更新]
    end

    CAD --> SIG
    SYS --> SIG
    SIG --> MON
    MON --> THRESH
    THRESH --> RANK
    QOS --> RANK
    PRI --> RANK
    USAGE --> RANK
    RANK --> EVICT
    EVICT --> API
    EVICT --> STATUS
    API --> KL

资源信号层次图

flowchart TB
    subgraph 软驱逐["软驱逐 (Soft Eviction)"]
        direction TB
        S1[memory.available < 1.5Gi<br/>等待 graceful period]
        S2[nodefs.available < 10%<br/>等待 graceful period]
        S3[imagefs.available < 15%<br/>等待 graceful period]
    end

    subgraph 硬驱逐["硬驱逐 (Hard Eviction)"]
        direction TB
        H1[memory.available < 200Mi<br/>立即驱逐]
        H2[nodefs.available < 5%<br/>立即驱逐]
        H3[imagefs.available < 5%<br/>立即驱逐]
        H4[pid.available < 1000<br/>立即驱逐]
    end

    subgraph 节点条件["Node Conditions"]
        direction TB
        NC1[MemoryPressure]
        NC2[DiskPressure]
        NC3[PIDPressure]
    end

    S1 --> NC1
    S2 --> NC2
    S3 --> NC2
    H1 --> NC1
    H2 --> NC2
    H3 --> NC2
    H4 --> NC3

这张图想表达什么

  • 驱逐是基于资源信号触发的,不是主动预测。
  • 软驱逐有等待期,硬驱逐立即执行。
  • 驱逐决策考虑 QoS、优先级和资源使用。

2. 分阶段通信流程


阶段一:资源监控

这个阶段的本质是:EvictionManager 定期采集节点资源使用情况。

步骤 1.1:启动监控循环

kubelet 启动时,EvictionManager 开始一个定期监控循环(默认每 10 秒)。

通信方向:EvictionManager 内部循环

步骤 1.2:采集资源信号

监控循环会采集以下资源信号:

  • memory.available:可用内存
  • nodefs.available:节点文件系统可用空间
  • nodefs.inodesFree:节点文件系统可用 inode
  • imagefs.available:镜像文件系统可用空间
  • imagefs.inodesFree:镜像文件系统可用 inode
  • pid.available:可用 PID 数

通信方向:EvictionManager → cAdvisor/系统接口

步骤 1.3:计算资源使用

EvictionManager 计算每个资源信号的使用量和可用量。

通信方向:EvictionManager 内部计算

步骤 1.4:更新节点条件

如果资源使用超过阈值,EvictionManager 会更新 Node 的 Conditions(MemoryPressure、DiskPressure、PIDPressure)。

通信方向:kubelet → API Server(PATCH Node status)


阶段二:决策判断

这个阶段的本质是:根据阈值和策略决定是否需要驱逐以及驱逐哪些 Pod。

步骤 2.1:检查阈值

EvictionManager 检查每个资源信号是否超过配置的阈值。

通信方向:ThresholdManager 内部检查

步骤 2.2:区分软硬驱逐

  • 软驱逐:超过阈值后等待 evictionSoftGracePeriod,如果持续超过则触发驱逐。
  • 硬驱逐:超过阈值立即触发驱逐。

通信方向:ThresholdManager 内部判断

步骤 2.3:确定驱逐资源

确定哪个资源信号触发了驱逐,决定驱逐策略。

通信方向:EvictionManager 内部决策

步骤 2.4:Pod 排序

根据以下规则对 Pod 进行排序:

  1. QoS 优先级:BestEffort > Burstable > Guaranteed
  2. Pod Priority:低优先级优先驱逐
  3. 资源使用:使用量高的优先驱逐(针对特定资源)

通信方向:Ranking 内部排序

步骤 2.5:选择驱逐目标

选择排序后的第一个 Pod 作为驱逐目标。

通信方向:EvictionManager 内部选择


阶段三:执行驱逐

这个阶段的本质是:通过 API Server 删除选中的 Pod,并更新状态。

步骤 3.1:记录驱逐事件

EvictionManager 记录驱逐事件,包含驱逐原因和资源使用情况。

通信方向:EventRecorder → API Server

步骤 3.2:删除 Pod

EvictionManager 通过 API Server 删除选中的 Pod。

通信方向:EvictionManager → API Server(DELETE Pod)

步骤 3.3:等待 Pod 终止

kubelet 的 podWorkers 处理 Pod 删除,优雅终止容器。

通信方向:kubelet 内部处理

步骤 3.4:更新驱逐统计

EvictionManager 更新驱逐统计信息,用于监控和告警。

通信方向:EvictionManager 内部统计


3. 详细流程图

3.1 驱逐决策流程图

flowchart TD
    subgraph 监控["资源监控"]
        A1[采集资源信号] --> A2[计算可用量]
        A2 --> A3[更新 Node Conditions]
    end

    subgraph 阈值检查["阈值检查"]
        A3 --> B1{检查硬驱逐阈值}
        B1 -->|超过| C1[立即驱逐]
        B1 -->|未超过| B2{检查软驱逐阈值}

        B2 -->|超过| B3{持续超过 grace period?}
        B3 -->|是| C1
        B3 -->|否| B4[继续监控]
        B2 -->|未超过| B4
    end

    subgraph 排序["Pod 排序"]
        C1 --> D1[获取所有 Pod]
        D1 --> D2[按 QoS 分类]
        D2 --> D3[按 Priority 排序]
        D3 --> D4[按资源使用排序]
        D4 --> D5[选择第一个 Pod]
    end

    subgraph 执行["驱逐执行"]
        D5 --> E1[记录 Event]
        E1 --> E2[DELETE Pod]
        E2 --> E3[等待终止]
        E3 --> E4[更新统计]
    end

3.2 Pod QoS 排序流程图

flowchart LR
    subgraph QoS分类["QoS 分类"]
        direction TB
        G[Guaranteed<br/>CPU/内存都设置 limits]
        B[Burstable<br/>至少设置一个资源]
        BE[BestEffort<br/>未设置任何资源]
    end

    subgraph 驱逐顺序["驱逐优先级 (高到低)"]
        direction TB
        P1[1. BestEffort Pod]
        P2[2. Burstable Pod<br/>低 Priority]
        P3[3. Burstable Pod<br/>高 Priority]
        P4[4. Guaranteed Pod<br/>低 Priority]
        P5[5. Guaranteed Pod<br/>高 Priority]
    end

    BE --> P1
    B --> P2
    B --> P3
    G --> P4
    G --> P5

3.3 资源信号处理流程图

flowchart TB
    subgraph 内存["memory.available"]
        M1[计算: node.status.capacity.memory<br/>- node.status.memory.workingSet]
        M2[阈值: memory.available < 200Mi]
        M3[排名: 按内存使用排序]
    end

    subgraph 磁盘["nodefs.available"]
        D1[计算: df -h /var/lib/kubelet]
        D2[阈值: nodefs.available < 10%]
        D3[排名: 按磁盘使用排序<br/>+ 日志量]
    end

    subgraph 镜像["imagefs.available"]
        I1[计算: df -h /var/lib/containerd]
        I2[阈值: imagefs.available < 15%]
        I3[排名: 优先驱逐使用镜像多的 Pod]
    end

    subgraph PID["pid.available"]
        P1[计算: /proc/sys/kernel/pid_max<br/>- 当前 PID 数]
        P2[阈值: pid.available < 1000]
        P3[排名: 按 PID 使用排序]
    end

    M1 --> M2 --> M3
    D1 --> D2 --> D3
    I1 --> I2 --> I3
    P1 --> P2 --> P3

4. 关键数据结构

4.1 驱逐阈值配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# kubelet 配置
evictionHard:
memory.available: "200Mi"
nodefs.available: "5%"
nodefs.inodesFree: "5%"
imagefs.available: "5%"
pid.available: "1000"

evictionSoft:
memory.available: "1.5Gi"
nodefs.available: "10%"
imagefs.available: "15%"

evictionSoftGracePeriod:
memory.available: "1m30s"
nodefs.available: "1m30s"
imagefs.available: "1m30s"

evictionMinimumReclaim:
memory.available: "0Mi"
nodefs.available: "500Mi"
imagefs.available: "2Gi"

evictionPressureTransitionPeriod: "5m"

4.2 Pod QoS 分类规则

QoS 等级 条件 驱逐优先级
Guaranteed CPU/内存 requests == limits 最低 (最后驱逐)
Burstable 至少设置一个资源 (CPU 或内存) 中等
BestEffort 未设置任何资源 最高 (最先驱逐)

4.3 节点条件映射

资源信号 Node Condition 含义
memory.available < threshold MemoryPressure 内存压力
nodefs.available < threshold DiskPressure 磁盘压力
imagefs.available < threshold DiskPressure 镜像磁盘压力
pid.available < threshold PIDPressure PID 压力

5. 详细时序图

5.1 硬驱逐时序图

sequenceDiagram
    autonumber
    participant MON as EvictionManager
    participant SIG as 信号采集器
    participant RANK as PodRanking
    participant API as kube-apiserver
    participant KL as Kubelet
    participant NODE as Node Status

    loop 每 10 秒
        MON->>SIG: 采集资源信号
        SIG-->>MON: 返回资源使用

        MON->>MON: 检查硬驱逐阈值

        alt memory.available < 200Mi
            MON->>MON: 触发硬驱逐

            MON->>RANK: 获取所有 Pod
            RANK-->>MON: Pod 列表

            MON->>MON: 按 QoS 排序
            MON->>MON: 按 Priority 排序
            MON->>MON: 按内存使用排序

            MON->>MON: 选择第一个 Pod

            MON->>API: DELETE /api/v1/namespaces/<ns>/pods/<pod>
            API-->>MON: 200 OK

            MON->>API: POST Event (Evicted)
            API-->>MON: 201 Created

            MON->>NODE: 更新 MemoryPressure=True
        else 资源正常
            MON->>MON: 继续监控
        end
    end

5.2 软驱逐时序图

sequenceDiagram
    autonumber
    participant MON as EvictionManager
    participant THRESH as ThresholdManager
    participant TIMER as GracePeriodTimer
    participant API as kube-apiserver
    participant NODE as Node Status

    loop 每 10 秒
        MON->>THRESH: 检查软驱逐阈值

        alt memory.available < 1.5Gi
            THRESH->>TIMER: 启动/继续计时器

            alt 持续超过 grace period (1m30s)
                TIMER-->>THRESH: grace period 已过
                THRESH-->>MON: 触发软驱逐

                MON->>MON: Pod 排序
                MON->>MON: 选择 Pod
                MON->>API: DELETE Pod
                API-->>MON: 200 OK

                MON->>NODE: 更新 MemoryPressure=True
            else 未超过 grace period
                TIMER-->>THRESH: 继续等待
                MON->>NODE: 更新 MemoryPressure=True
            end
        else memory.available >= 1.5Gi
            THRESH->>TIMER: 重置计时器
            MON->>NODE: 更新 MemoryPressure=False
        end
    end

5.3 驱逐与节点条件更新时序图

sequenceDiagram
    autonumber
    participant MON as EvictionManager
    participant STATUS as StatusManager
    participant API as kube-apiserver
    participant CTRL as Controllers
    participant SCHED as Scheduler

    Note over MON: 资源压力检测

    MON->>STATUS: 设置 MemoryPressure=True
    STATUS->>API: PATCH Node status

    API-->>CTRL: watch Node UPDATE
    CTRL->>CTRL: NodeLifecycleController 处理

    Note over CTRL: 可能添加 NoSchedule taint

    API-->>SCHED: watch Node UPDATE
    SCHED->>SCHED: 更新调度器缓存

    Note over MON: 执行驱逐

    MON->>API: DELETE Pod

    loop 被驱逐的 Pod
        API-->>CTRL: watch Pod DELETE
        CTRL->>CTRL: 重建 Pod (如 Deployment)
        CTRL->>API: 创建新 Pod
        API-->>SCHED: watch Pod ADDED
        SCHED->>SCHED: 调度到其他节点
    end

    Note over MON: 资源恢复

    MON->>STATUS: 设置 MemoryPressure=False
    STATUS->>API: PATCH Node status

6. 故障排查指南

6.1 常见问题与诊断方法

问题 1:Pod 被意外驱逐

症状:Pod 突然被删除,事件显示 Evicted

排查流程图

flowchart TD
    A[Pod 被驱逐] --> B{检查驱逐事件}

    B --> B1[kubectl describe pod]
    B1 --> B2[查看 Reason: Evicted]

    A --> C{检查节点资源}
    C --> C1[kubectl describe node]
    C1 --> C2[检查 Conditions]
    C2 --> C3{哪个 Condition 为 True?}

    C3 -->|MemoryPressure| D1[内存不足]
    C3 -->|DiskPressure| D2[磁盘不足]
    C3 -->|PIDPressure| D3[PID 不足]

    D1 --> E1[增加节点内存]
    D1 --> E2[减少 Pod 内存使用]
    D1 --> E3[调整驱逐阈值]

    D2 --> E4[清理磁盘空间]
    D2 --> E5[清理未使用镜像]
    D2 --> E6[调整日志保留]

    D3 --> E7[检查进程泄漏]
    D3 --> E8[调整 PID 限制]

诊断命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 检查 Pod 驱逐事件
kubectl describe pod <pod-name> -n <namespace> | grep -A 5 "Events:"

# 检查节点条件
kubectl describe node <node-name> | grep -A 10 "Conditions:"

# 检查节点资源使用
kubectl top node
kubectl describe node <node-name> | grep -A 5 "Allocated resources"

# 检查 kubelet 驱逐日志
journalctl -u kubelet | grep -i eviction

# 检查系统资源
free -m
df -h
cat /proc/sys/kernel/pid_max

问题 2:节点持续处于压力状态

症状:Node Conditions 持续显示 MemoryPressure/DiskPressure

排查流程图

flowchart TD
    A[持续压力状态] --> B{检查资源使用}

    B --> B1[内存使用高]
    B1 --> C1[检查 Pod 内存使用]
    C1 --> C1a[kubectl top pods]
    C1 --> C1b[检查内存泄漏]

    B --> B2[磁盘使用高]
    B2 --> C2[检查磁盘占用]
    C2 --> C2a[du -sh /var/lib/*]
    C2 --> C2b[检查日志大小]
    C2 --> C2c[检查镜像大小]

    B --> B3[PID 使用高]
    B3 --> C3[检查进程数]
    C3 --> C3a[ps aux | wc -l]
    C3 --> C3b[检查僵尸进程]

    A --> D{检查驱逐阈值}
    D --> D1[检查 evictionHard]
    D --> D2[检查 evictionSoft]
    D --> D3[考虑调整阈值]

诊断命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 检查 Pod 资源使用
kubectl top pods -A --sort-by=memory
kubectl top pods -A --sort-by=cpu

# 检查磁盘使用
df -h /var/lib/kubelet
df -h /var/lib/containerd
du -sh /var/lib/kubelet/*
du -sh /var/lib/containerd/*

# 检查镜像
crictl images
crictl rmi --prune # 清理未使用镜像

# 检查日志大小
journalctl --disk-usage
du -sh /var/log/*

# 检查进程数
ps aux | wc -l
cat /proc/sys/kernel/pid_max

问题 3:驱逐后 Pod 未重建

症状:Pod 被驱逐后,Deployment/ReplicaSet 未创建新 Pod

排查流程图

flowchart TD
    A[Pod 未重建] --> B{检查控制器}

    B --> B1[检查 Deployment]
    B1 --> B1a[kubectl get deployment]
    B1 --> B1b[检查 replicas]

    B --> B2[检查 ReplicaSet]
    B2 --> B2a[kubectl get rs]
    B2 --> B2b[检查控制器事件]

    A --> C{检查调度}
    C --> C1[检查新 Pod 状态]
    C1 --> C1a[Pending? 检查调度失败原因]
    C1 --> C1b[Running? 正常]

    A --> D{检查节点 taint}
    D --> D1[检查节点 taint]
    D1 --> D2{有 NoSchedule taint?}
    D2 -->|是| D3[Pod 缺少 toleration]
    D2 -->|否| D4[检查其他原因]

诊断命令

1
2
3
4
5
6
7
8
9
10
11
12
13
# 检查控制器状态
kubectl get deployment -n <namespace>
kubectl describe deployment <deployment-name> -n <namespace>

# 检查 ReplicaSet
kubectl get rs -n <namespace>
kubectl describe rs <rs-name> -n <namespace>

# 检查节点 taint
kubectl get node <node-name> -o jsonpath='{.spec.taints}'

# 检查新 Pod 事件
kubectl get events -n <namespace> --sort-by='.lastTimestamp'

6.2 关键日志关键词

场景 日志关键词 含义
驱逐触发 eviction manager: attempting to reclaim 开始回收资源
Pod 排序 ranking active pods Pod 排序
驱逐执行 evicting pod 驱逐 Pod
节点条件 Update condition 更新节点条件
阈值检查 thresholds met 阈值超过
资源监控 memory available 内存可用

6.3 配置参数参考

参数 默认值 说明
--eviction-hard memory.available<100Mi 硬驱逐阈值
--eviction-soft 软驱逐阈值
--eviction-soft-grace-period 软驱逐等待期
--eviction-minimum-reclaim 最小回收量
--eviction-pressure-transition-period 5m 压力状态转换等待
--eviction-max-pod-grace-period-seconds -1 最大优雅终止时间

6.4 一键诊断命令

1
2
3
4
5
6
7
8
9
10
11
12
13
# 驱逐问题诊断脚本
NODE_NAME=${1:-"<node-name>"}

echo "=== Node Conditions ===" && \
kubectl get node $NODE_NAME -o jsonpath='{.status.conditions}' | jq '.[] | select(.type | contains("Pressure"))' && \
echo -e "\n=== Node Resources ===" && \
kubectl describe node $NODE_NAME | grep -A 10 "Allocated resources" && \
echo -e "\n=== Top Memory Pods ===" && \
kubectl top pods -A --sort-by=memory | head -20 && \
echo -e "\n=== Recent Eviction Events ===" && \
kubectl get events -A --field-selector reason=Evicted --sort-by='.lastTimestamp' | tail -20 && \
echo -e "\n=== Kubelet Eviction Logs ===" && \
echo "Run on node: journalctl -u kubelet | grep -i eviction | tail -50"

7. 最佳实践

7.1 资源配置建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Pod 资源配置示例
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi" # Guaranteed/Burstable QoS
cpu: "200m"

# PriorityClass 配置示例
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "高优先级 Pod,最后被驱逐"

7.2 驱逐阈值调优建议

场景 建议配置
生产环境 软驱逐 + 硬驱逐,留足够缓冲
开发环境 只配置硬驱逐
内存敏感 提高 memory.available 阈值
磁盘敏感 提高磁盘阈值,配置独立 imagefs

8. 版本差异说明

1.27 → 1.28 变化

变化点 说明
驱逐指标改进 更详细的驱逐统计指标
资源监控优化 减少监控开销

1.28 → 1.29 变化

变化点 说明
驱逐优先级细化 更精细的优先级排序
优雅终止改进 更好的驱逐时优雅终止处理

9. 代码入口(精简版)

如果读者想从流程跳回实现,可从下面几个入口开始:


10. 面试题与详细解答

问题 1:kubelet 驱逐(Eviction)的软驱逐和硬驱逐有什么区别?生产环境如何配置?

回答要点

软驱逐 vs 硬驱逐对比

特性 硬驱逐(Hard) 软驱逐(Soft)
触发时机 立即触发 等待 grace period
宽限期 evictionSoftGracePeriod
适用场景 紧急资源不足 可恢复的压力状态
风险 可能丢失数据 可能错过最佳驱逐时机
优雅终止 有(maxPodGracePeriodSeconds)

工作原理

1
2
3
4
5
6
7
8
9
硬驱逐:
资源 < 阈值 → 立即驱逐 Pod(无宽限期)

软驱逐:
资源 < 软阈值 → 开始计时

持续超过 grace period → 触发驱逐(有宽限期)

资源恢复 → 重置计时器

生产环境推荐配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# kubelet 配置
evictionHard:
memory.available: "200Mi" # 最后防线
nodefs.available: "5%"
imagefs.available: "5%"
pid.available: "1000"

evictionSoft:
memory.available: "1.5Gi" # 提前预警
nodefs.available: "10%"
imagefs.available: "15%"

evictionSoftGracePeriod:
memory.available: "1m30s" # 给应用恢复时间
nodefs.available: "1m30s"
imagefs.available: "1m30s"

evictionPressureTransitionPeriod: "5m" # 避免状态抖动
evictionMaxPodGracePeriodSeconds: 30 # 最大优雅终止时间

配置建议

  1. 同时配置软硬驱逐:软驱逐作为预警,硬驱逐作为最后防线
  2. 阈值留有余量:软阈值比硬阈值高 2-3 倍
  3. grace period 合理:给应用足够时间恢复或优雅终止
  4. 考虑业务特点:内存敏感型应用提高内存阈值

问题 2:Pod QoS(Guaranteed/Burstable/BestEffort)如何影响驱逐顺序?

回答要点

QoS 分类规则

QoS 等级 条件 示例
Guaranteed 所有容器都设置了 limits(CPU 和内存),且 limits == requests limits={cpu:1,mem:1Gi}, requests={cpu:1,mem:1Gi}
Burstable 至少一个容器设置了 requests 或 limits limits={mem:1Gi}, requests={mem:512Mi}
BestEffort 没有设置任何 requests 和 limits 无资源配置

驱逐排序规则

1
2
3
4
5
6
7
8
// 驱逐优先级排序(pkg/kubelet/eviction/helpers.go)
func rankMemoryPressure(pods []*v1.Pod, stats statsFunc) {
orderedBy(
exceedMemoryRequests(stats), // 1. 超过 requests 的优先驱逐
priority, // 2. 低优先级优先驱逐
memory(stats), // 3. 内存使用量高的优先驱逐
).Sort(pods)
}

驱逐顺序(从高到低)

1
2
3
4
5
6
7
8
9
10
11
1. BestEffort Pod(最先驱逐)
└── 无资源保证,最容易牺牲

2. Burstable Pod(使用量超过 requests)
└── 超出承诺的资源,优先回收

3. Burstable Pod(使用量在 requests 内)
└── 按优先级和使用量排序

4. Guaranteed Pod(最后驱逐)
└── 有明确资源保证,最不容易被驱逐

实际影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Guaranteed Pod - 最安全
resources:
requests:
memory: "1Gi"
cpu: "1"
limits:
memory: "1Gi"
cpu: "1"

# Burstable Pod - 中等风险
resources:
requests:
memory: "512Mi"
limits:
memory: "1Gi"

# BestEffort Pod - 最容易被驱逐
# 无 resources 配置

最佳实践

  1. 关键应用使用 Guaranteed:确保资源不被抢占
  2. 普通应用使用 Burstable:设置合理 requests
  3. 避免 BestEffort:除非可以接受随时被驱逐
  4. 配合 PriorityClass:进一步控制驱逐顺序

问题 3:kubelet 驱逐与 API Server 驱逐(如 Descheduling)有什么区别?

回答要点

两种驱逐对比

特性 kubelet 驱逐 API Server 驱逐
触发者 kubelet 控制器或用户
触发条件 节点资源不足 策略决策
目标 保护节点稳定性 优化集群资源
范围 单节点 整个集群
Pod 状态 Failed (Evicted) 正常终止

kubelet 驱逐流程

1
节点资源监控 → 超过阈值 → Pod 排序 → DELETE Pod → 更新 Node Condition

API Server 驱逐类型

  1. 手动驱逐

    1
    2
    kubectl delete pod <pod-name>
    kubectl drain <node-name> # 驱逐节点上所有 Pod
  2. Descheduler 驱逐

    • 低使用率 Pod
    • 反亲和性违反
    • 节点资源不平衡
  3. PodDisruptionBudget (PDB) 保护

    1
    2
    3
    4
    5
    6
    7
    8
    9
    apiVersion: policy/v1
    kind: PodDisruptionBudget
    metadata:
    name: my-app-pdb
    spec:
    minAvailable: 2 # 至少保持 2 个 Pod 运行
    selector:
    matchLabels:
    app: my-app

关键区别

  1. PDB 保护

    • API Server 驱逐受 PDB 限制
    • kubelet 驱逐不受 PDB 限制
  2. 优雅终止

    • API Server 驱逐:遵循 terminationGracePeriodSeconds
    • kubelet 硬驱逐:可能立即终止
  3. 重建行为

    • 两者都会触发 Controller 重建 Pod
    • kubelet 驱逐的 Pod 会标记 DisruptionTarget condition

驱逐事件记录

1
2
3
4
5
6
7
# kubelet 驱逐
reason: Evicted
message: "The node was low on resource: memory."

# API Server 驱逐
reason: Deleted
message: "Pod deleted by user"

问题 4:节点出现 MemoryPressure/DiskPressure Condition 后,调度器会有什么行为?

回答要点

Node Condition 对调度的影响

1
2
3
Node Condition 更新 → API Server → Scheduler watch → 更新调度缓存

考虑 taint 和 predicate

默认行为

  1. MemoryPressure

    • 默认无自动 taint
    • Scheduler 正常调度(除非有自定义 predicate)
  2. DiskPressure

    • 默认无自动 taint
    • Scheduler 正常调度

Taint Manager 行为(如果启用):

1
2
3
4
5
# kube-controller-manager 配置
--enable-taint-manager=true

# 当节点 Condition 变化时,可以自动添加 taint
# 需要配合 NodeLifeCycleController

NoSchedule Taint 影响

1
2
3
4
5
6
7
8
9
10
11
12
# 节点 taint
spec:
taints:
- key: node.kubernetes.io/memory-pressure
value: "true"
effect: NoSchedule

# Pod 需要 toleration 才能调度
tolerations:
- key: node.kubernetes.io/memory-pressure
operator: Exists
effect: NoSchedule

调度器 Predicate 检查

1
2
3
4
// pkg/scheduler/framework/plugins/noderesources/fit.go
// CheckNodeMemoryPressurePredicate: 检查节点内存压力
// CheckNodeDiskPressurePredicate: 检查节点磁盘压力
// 如果节点有压力,拒绝调度 BestEffort Pod

实际影响

Condition 对调度的影响
MemoryPressure BestEffort Pod 不会被调度
DiskPressure 所有 Pod 不会被调度(如果配置了 predicate)
PIDPressure 通常不影响调度

最佳实践

  1. 配置 taint:自动为压力节点添加 NoSchedule taint
  2. 关键 Pod 添加 toleration:允许调度到压力节点
  3. 监控 Condition:及时告警处理
1
2
3
4
5
6
7
8
# 关键 Pod 的 toleration
tolerations:
- key: node.kubernetes.io/memory-pressure
operator: Exists
effect: NoSchedule
- key: node.kubernetes.io/disk-pressure
operator: Exists
effect: NoSchedule

问题 5:如何配置 kubelet 的资源预留(Resource Reservation)?与驱逐阈值如何配合?

回答要点

资源预留类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# kubelet 配置
kubeReserved: # 为 Kubernetes 组件预留
cpu: "100m"
memory: "1Gi"
ephemeral-storage: "1Gi"

systemReserved: # 为系统进程预留
cpu: "100m"
memory: "512Mi"
ephemeral-storage: "1Gi"

enforceNodeAllocatable: # 强制执行预留
- pods
- system-reserved
- kube-reserved

资源关系图

1
2
3
4
5
6
节点总资源 (Capacity)
├── 系统预留 (systemReserved)
├── K8s 预留 (kubeReserved)
├── 驱逐阈值 (eviction threshold)
└── 可分配资源 (Allocatable)
└── Pod 可用资源

计算公式

1
Allocatable = Capacity - systemReserved - kubeReserved - evictionThreshold

与驱逐阈值的配合

1
2
3
4
5
6
7
8
9
10
11
12
13
# 示例配置(16Gi 内存节点)
systemReserved:
memory: "1Gi"

kubeReserved:
memory: "1Gi"

evictionHard:
memory.available: "500Mi" # 当可用内存 < 500Mi 时驱逐

# 计算结果:
# Allocatable = 16Gi - 1Gi - 1Gi - 500Mi = 13.5Gi
# Pod 总共可以使用 13.5Gi 内存

enforceNodeAllocatable 作用

1
2
3
4
enforceNodeAllocatable:
- pods # 限制 Pod 总资源使用(默认启用)
- system-reserved # 为系统预留资源(可选)
- kube-reserved # 为 K8s 预留资源(可选)

cgroup 配置(启用 enforce 时):

1
2
3
/sys/fs/cgroup/kubepods/           # Pod cgroup (限制在 Allocatable 内)
/sys/fs/cgroup/system.slice/ # 系统 cgroup
/sys/fs/cgroup/kubelet.slice/ # Kubelet cgroup

最佳实践

  1. 预留足够资源:避免系统组件和 K8s 争抢资源
  2. 驱逐阈值与预留配合:确保驱逐触发前有缓冲空间
  3. 启用 enforce:强制执行资源限制
  4. 监控实际使用:根据实际情况调整预留值

推荐配置(生产环境):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 小型节点(4C8G)
kubeReserved:
cpu: "100m"
memory: "500Mi"
systemReserved:
cpu: "100m"
memory: "500Mi"
evictionHard:
memory.available: "200Mi"

# 中型节点(8C16G)
kubeReserved:
cpu: "200m"
memory: "1Gi"
systemReserved:
cpu: "200m"
memory: "1Gi"
evictionHard:
memory.available: "500Mi"

问题 6:Pod 被驱逐后如何恢复?如何避免频繁驱逐导致的业务抖动?

回答要点

Pod 被驱逐后的恢复流程

1
Pod 被驱逐 → Controller 检测 → 创建新 Pod → Scheduler 调度 → 新节点启动

恢复机制

  1. Deployment/ReplicaSet

    • 自动创建新 Pod 替换被驱逐的 Pod
    • 维持期望副本数
  2. StatefulSet

    • Pod 有稳定标识,会被重建
    • 可能需要 PVC 恢复数据
  3. DaemonSet

    • 节点恢复后自动重建 Pod

避免频繁驱逐的策略

  1. 合理配置资源

    1
    2
    3
    4
    5
    resources:
    requests:
    memory: "1Gi" # 保证调度时预留足够资源
    limits:
    memory: "2Gi" # 限制最大使用
  2. 使用 Guaranteed QoS

    1
    2
    3
    4
    5
    6
    7
    resources:
    requests:
    memory: "1Gi"
    cpu: "500m"
    limits:
    memory: "1Gi" # limits == requests
    cpu: "500m"
  3. 配置 PriorityClass

    1
    2
    3
    4
    5
    6
    7
    apiVersion: scheduling.k8s.io/v1
    kind: PriorityClass
    metadata:
    name: high-priority
    value: 1000000
    globalDefault: false
    preemptionPolicy: PreemptLowerPriority
  4. 设置 PodDisruptionBudget

    1
    2
    3
    4
    5
    6
    7
    8
    9
    apiVersion: policy/v1
    kind: PodDisruptionBudget
    metadata:
    name: my-app-pdb
    spec:
    minAvailable: 2
    selector:
    matchLabels:
    app: my-app
  5. 添加节点容忍度

    1
    2
    3
    4
    5
    tolerations:
    - key: node.kubernetes.io/memory-pressure
    operator: Exists
    effect: NoExecute
    tolerationSeconds: 300 # 5 分钟后再驱逐

监控和告警

1
2
3
4
5
6
7
8
9
10
11
# Prometheus 告警规则
groups:
- name: eviction-alerts
rules:
- alert: FrequentEviction
expr: increase(kubelet_evicted_pods_total[1h]) > 3
for: 5m
labels:
severity: warning
annotations:
summary: "节点频繁驱逐 Pod"

故障排查

1
2
3
4
5
6
7
8
9
# 查看驱逐事件
kubectl get events --field-selector reason=Evicted

# 查看节点状态
kubectl describe node <node-name> | grep -A 10 Conditions

# 查看资源使用
kubectl top node
kubectl describe node <node-name> | grep -A 5 "Allocated resources"

问题 7:PID 驱逐是什么?什么情况下会触发?如何排查 PID 资源耗尽问题?

回答要点

PID 驱逐概念

PID 驱逐是 kubelet 在系统 PID 资源不足时触发的驱逐行为。每个进程都需要一个唯一的 PID,当 PID 耗尽时,系统无法创建新进程。

触发条件

1
2
3
4
5
evictionHard:
pid.available: "1000" # 当可用 PID < 1000 时触发

# 计算方式:
# pid.available = /proc/sys/kernel/pid_max - 当前已分配 PID 数

PID 资源限制

1
2
3
4
5
6
7
8
9
# 查看系统 PID 上限
cat /proc/sys/kernel/pid_max
# 默认值:32768 或更高(取决于系统配置)

# 查看当前 PID 使用
ps aux | wc -l

# 查看 PID 分配情况
ls /proc | grep -E '^[0-9]+$' | wc -l

常见 PID 耗尽原因

  1. 进程泄漏:应用创建子进程后未正确回收
  2. 僵尸进程:父进程未 wait 子进程
  3. 并发过高:短时间创建大量进程
  4. 容器配置不当:未限制容器进程数

排查步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1. 检查节点 PID 状态
kubectl describe node <node-name> | grep -A 5 PIDPressure

# 2. 查看系统进程数
ps aux | wc -l

# 3. 查看僵尸进程
ps aux | awk '{if($8=="Z") print}'

# 4. 查看进程数最多的用户
ps aux | awk '{print $1}' | sort | uniq -c | sort -rn | head

# 5. 查看进程数最多的容器
for pod in $(crictl pods -q); do
echo "Pod: $pod"
crictl inspect $pod | grep -A 5 "pid"
done

# 6. 检查 kubelet 日志
journalctl -u kubelet | grep -i "pid"

解决方案

  1. 配置 PID 驱逐阈值

    1
    2
    3
    4
    5
    6
    evictionHard:
    pid.available: "1000"
    evictionSoft:
    pid.available: "2000"
    evictionSoftGracePeriod:
    pid.available: "1m"
  2. 限制容器进程数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # Pod spec
    spec:
    containers:
    - name: app
    resources:
    limits:
    hugepages-2Mi: 512Mi
    # Kubernetes 1.20+ 支持
    # 通过 cgroup 限制进程数
  3. 使用 PidLimit feature gate

    1
    2
    3
    4
    # kubelet 配置
    featureGates:
    SupportPodPidsLimit: true
    SupportNodePidsLimit: true
  4. 调整系统 PID 上限

    1
    2
    3
    4
    5
    6
    # 临时调整
    echo 65536 > /proc/sys/kernel/pid_max

    # 永久调整
    echo "kernel.pid_max = 65536" >> /etc/sysctl.conf
    sysctl -p

最佳实践

  1. 监控 PID 使用:设置告警监控 PID 使用率
  2. 应用设计:避免无限创建子进程
  3. 容器限制:使用 cgroup 限制容器进程数
  4. 定期清理:清理僵尸进程和无用进程