节点心跳与健康状态通信流程

目标

这篇文档只回答一个核心问题:kubelet 如何把节点健康信号汇报给控制面,控制面又如何据此做出判断和后续动作?

重点放在两类心跳信号的协作关系,以及控制面如何综合判断节点健康。

一句话摘要

kubelet 周期性更新 NodeStatus 并续约 Lease;node lifecycle controller 通过 informer/lister 观察 Node 与 Lease,综合判断节点健康,再触发 taint、Pod not-ready 传播与后续驱逐流程。


1. 流程总览

从通信视角看,这条链路包含两类并行的心跳信号:

  1. NodeStatus 心跳:携带节点条件(Ready、MemoryPressure 等)和容量信息。
  2. Lease 心跳:轻量级的存活信号,用于快速检测节点失联。

控制面会综合这两类信号来判断节点健康状态。

Mermaid:总览图

flowchart LR
    A[Kubelet] --> B[syncNodeStatus]
    A --> C[nodeLeaseController]
    B --> D[API Server NodeStatus]
    C --> E[API Server Lease]
    D --> F[NodeLifecycleController]
    E --> F
    F --> G[monitorNodeHealth]
    G --> H[Ready / NotReady / Unknown]
    H --> I[taints / pod not-ready / eviction]

双心跳机制架构图

flowchart TB
    subgraph Kubelet侧["Kubelet 节点侧"]
        direction TB
        KL[Kubelet]

        subgraph 心跳机制["双心跳机制"]
            direction LR
            NLC[nodeLeaseController]
            SNS[syncNodeStatus]
        end

        subgraph 状态收集["状态收集"]
            direction TB
            CAD[cAdvisor<br/>资源监控]
            EVI[EvictionManager<br/>压力检测]
            PRO[ProbeManager<br/>健康检查]
        end

        CAD --> SNS
        EVI --> SNS
        PRO --> SNS
        KL --> NLC
        KL --> SNS
    end

    subgraph APIServer["控制面"]
        direction TB
        API[kube-apiserver]
        ETCD[(etcd)]

        subgraph 存储["两类对象"]
            NODE[Node 对象<br/>详细状态]
            LEASE[Lease 对象<br/>轻量心跳]
        end

        API <--> ETCD
        NODE --> API
        LEASE --> API
    end

    subgraph 控制器["NodeLifecycleController"]
        direction TB
        INF[Informer<br/>watch Node/Lease]
        MNH[monitorNodeHealth<br/>健康判断]
        TM[Taint Manager<br/>taint 驱逐]

        INF --> MNH
        MNH --> TM
    end

    subgraph 后果["后续动作"]
        direction TB
        TAINT[更新 Node taint]
        PNR[Pod Ready=False]
        EVICT[驱逐 Pod]
        SCHED[Scheduler 感知]
    end

    NLC -->|PUT Lease| API
    SNS -->|PATCH Node| API
    API -->|watch| INF
    TM --> TAINT
    TM --> PNR
    TM --> EVICT
    TAINT --> SCHED

节点健康判断流程图

flowchart TB
    subgraph 输入["心跳信号输入"]
        L[Lease renewTime]
        N[NodeStatus Ready 条件]
    end

    subgraph 判断逻辑["健康判断逻辑"]
        direction TB
        C1{Lease 超时?<br/>超过40s未续约}
        C2{Ready == True?}
        C3{其他条件正常?<br/>Memory/Disk/PID}

        C1 -->|是| UNK[Unknown]
        C1 -->|否| C2
        C2 -->|否| NR[NotReady]
        C2 -->|是| C3
        C3 -->|否| NR
        C3 -->|是| RDY[Ready]
    end

    subgraph 后果["状态后果"]
        direction TB
        UNK --> U1[NoExecute taint]
        UNK --> U2[驱逐所有 Pod]
        UNK --> U3[等待恢复]

        NR --> N1[NoSchedule taint]
        NR --> N2[Pod Ready=False]
        NR --> N3[阻止新调度]

        RDY --> R1[清理健康 taint]
        RDY --> R2[允许正常调度]
    end

    L --> C1
    N --> C2

这张图想表达什么

  • NodeStatus 和 Lease 是两类不同的健康信号。
  • 控制器综合两者做出判断,而不是只看其中一个。

2. 分阶段通信流程


阶段一:kubelet 上报两类心跳

这个阶段的本质是:kubelet 通过两种不同粒度的信号向控制面汇报自己还活着。

步骤 1.1:创建 nodeLeaseController

kubelet 在初始化时,会创建 nodeLeaseController。这个控制器负责定期续约节点的 Lease 对象。

通信方向:kubelet 启动时初始化

步骤 1.2:Lease 续约

nodeLeaseController 会定期(默认每 10 秒)向 API Server 更新 Lease 对象的 renewTime。Lease 对象非常轻量,只包含节点名称和续约时间。

通信方向:kubelet → API Server(HTTP PUT Lease)

步骤 1.3:启动 syncNodeStatus 循环

kubelet 同时启动一个循环,定期执行 syncNodeStatus()。这个循环的频率比 Lease 低(默认每 10 秒检查一次,但只在状态变化时才真正更新)。

通信方向:kubelet 内部循环

步骤 1.4:收集节点状态信息

syncNodeStatus() 中,kubelet 会收集:

  • 节点条件(Ready、MemoryPressure、DiskPressure、PIDPressure、NetworkUnavailable)
  • 节点容量和可分配资源
  • 节点地址信息
  • 节点镜像列表等

通信方向:kubelet → cAdvisor / cadvisor / 系统信息

步骤 1.5:判断是否需要更新

kubelet 会对比当前收集到的状态和上次上报的状态。只有状态变化或到达强制更新间隔时,才会调用 tryUpdateNodeStatus(...)

通信方向:kubelet 内部判断

步骤 1.6:tryUpdateNodeStatus 更新 NodeStatus

tryUpdateNodeStatus(...) 会向 API Server 发送请求,更新 Node 对象的 status 字段。这个请求比 Lease 重得多。

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

步骤 1.7:快速更新路径(启动时)

在节点刚启动时,kubelet 会通过 fastNodeStatusUpdate(...) 更快地把 Ready 状态推向控制面,减少启动延迟。

通信方向:kubelet → API Server(高频更新)


阶段二:控制面观察 Node 与 Lease

这个阶段的本质是:控制器通过 watch API Server 中的对象来判断节点健康。

步骤 2.1:NodeLifecycleController 注册 informer

NodeLifecycleController 在启动时,会注册对 Node、Lease、Pod 三个资源的 informer。这样当这些资源变化时,控制器会收到事件。

通信方向:controller → API Server(informer/watch)

步骤 2.2:收到 Node/Lease 更新事件

当 kubelet 更新 Node 或 Lease 时,API Server 会通过 watch 推送事件给控制器。控制器更新本地缓存。

通信方向:API Server → controller(watch 推送)

步骤 2.3:启动 monitorNodeHealth 循环

控制器启动一个定期循环,调用 monitorNodeHealth(...)。这个循环会遍历所有节点,逐一判断健康状态。

通信方向:controller 内部循环

步骤 2.4:检查 Lease 是否最近续约

对于每个节点,控制器会检查其 Lease 的 renewTime。如果超过 nodeMonitorGracePeriod(默认 40 秒)未续约,认为节点可能失联。

通信方向:controller 内部判断,读取本地缓存

步骤 2.5:检查 NodeStatus Ready 条件

控制器同时检查 NodeStatus 中的 Ready 条件。如果 Ready 为 FalseUnknown,认为节点不健康。

通信方向:controller 内部判断,读取本地缓存

步骤 2.6:综合判断节点健康状态

控制器综合 Lease 和 NodeStatus 的信息,得出节点的最终状态:

  • Ready:Lease 最近续约,且 Ready 条件为 True
  • NotReady:Lease 正常,但 Ready 条件为 False
  • Unknown:Lease 超时或 Ready 条件为 Unknown

通信方向:controller 内部计算


阶段三:触发后续动作

这个阶段的本质是:健康判断的后果不只是标记,还会影响 Pod 调度和驱逐。

步骤 3.1:更新节点 taint

如果节点状态变为 NotReady 或 Unknown,控制器可能会更新节点 taint:

  • NoSchedule:阻止新 Pod 调度到该节点
  • NoExecute:驱逐已运行的 Pod(取决于 Pod 的 toleration)

通信方向:controller → API Server(HTTP PATCH Node)

步骤 3.2:传播 Pod not-ready 状态

控制器会更新该节点上所有 Pod 的 Ready 条件为 False。这会影响依赖 Pod readiness 的服务发现。

通信方向:controller → API Server(HTTP PATCH Pod status)

步骤 3.3:触发 taint-based eviction

如果节点有 NoExecute taint,且 Pod 没有对应的 toleration,控制器会驱逐这些 Pod。

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

步骤 3.4:节点恢复时清理 taint

如果节点恢复健康(从 NotReady/Unknown 变为 Ready),控制器会清理相关的 taint,允许 Pod 重新调度。

通信方向:controller → API Server(HTTP PATCH Node)


小结:这条链路的 2 类心跳信号

信号类型 目的 频率 携带信息
Lease 快速检测节点存活 高(默认 10 秒) 只有续约时间
NodeStatus 详细节点状态 低(状态变化时) 条件、容量、地址等

核心结论:控制面综合两类信号判断节点健康,而不是只看其中一个。


2.5 完整流程图

下面这张图把本文所有步骤串在一起,方便一眼看完整个通信链路:

flowchart TD
    subgraph 心跳上报["心跳上报"]
        A1[kubelet 启动] --> A2[nodeLeaseController]
        A1 --> A3[syncNodeStatus 循环]
        A2 --> A4[定期续约 Lease]
        A4 --> A5[PUT Lease 到 API Server]
        A3 --> A6[收集节点状态]
        A6 --> A7{状态变化?}
        A7 -->|是| A8[tryUpdateNodeStatus]
        A7 -->|否| A9[等待下一周期]
        A8 --> A10[PUT/PATCH Node status]
    end

    subgraph 健康判断["控制面健康判断"]
        B1[NodeLifecycleController] --> B2[注册 informer]
        B2 --> B3[watch Node/Lease]
        B3 --> B4[本地缓存更新]
        B4 --> B5[monitorNodeHealth 循环]
        B5 --> B6[检查 Lease renewTime]
        B5 --> B7[检查 NodeStatus Ready]
        B6 --> B8{Lease 超时?}
        B7 --> B9{Ready == True?}
        B8 -->|是| B10[标记 Unknown]
        B9 -->|否| B11[标记 NotReady]
        B8 -->|否| B9
        B9 -->|是| B12[标记 Ready]
    end

    subgraph 后续动作["后续动作"]
        B10 --> C1[NoExecute taint]
        B11 --> C2[NoSchedule taint]
        B11 --> C3[Pod Ready = False]
        B10 --> C4[驱逐 Pod]
        B12 --> C5[清理 taint]
    end

2.6 与其他组件的交互

这条链路不只是 kubelet 和 node controller 的事,还涉及多个系统的协作:

与 Scheduler 的交互

节点健康状态直接影响调度决策:

flowchart TD
    A[Node NotReady] --> B[Node 有 NoSchedule taint]
    B --> C[scheduler Filter 插件]
    C --> D[排除该节点]
    E[Node Unknown] --> F[Node 有 NoExecute taint]
    F --> G[scheduler 不调度新 Pod]
节点状态 taint 影响 调度行为
Ready 无相关 taint 正常调度
NotReady NoSchedule 不调度新 Pod
Unknown NoExecute 不调度新 Pod + 驱逐现有 Pod

关键理解:scheduler 通过 informer watch Node 对象,实时感知 taint 变化。

与 Taint Manager 的交互

Taint Manager 是 NodeLifecycleController 的一部分,负责执行 taint-based eviction:

flowchart TD
    A[Node 有 NoExecute taint] --> B[Taint Manager 检查 Pod]
    B --> C{Pod 有 toleration?}
    C -->|有| D[保留 Pod]
    C -->|无| E{超过 tolerationSeconds?}
    E -->|是| F[驱逐 Pod]
    E -->|否| G[等待超时]
taint 类型 Pod 需要 不满足时后果
NoSchedule toleration 不调度
NoExecute toleration + tolerationSeconds 驱逐

关键理解:Taint Manager 是独立于 scheduler 的驱逐机制。

与 Service/Endpoint 的交互

节点 NotReady 会影响该节点上 Pod 的服务发现:

flowchart LR
    A[Node NotReady] --> B[Pod Ready = False]
    B --> C[Endpoint Controller]
    C --> D[从 EndpointSlice 移除 Pod IP]
    D --> E[Service 不再转发流量]

关键理解:这是通过 Pod status 间接影响 Service,而不是直接修改 Endpoint。

与集群自动扩缩容的交互

节点健康状态会影响 Cluster Autoscaler 的决策:

flowchart TD
    A[多个 Node Unknown] --> B[可用容量减少]
    B --> C[Pending Pod 增加]
    C --> D[Cluster Autoscaler]
    D --> E{需要扩容?}
    E -->|是| F[创建新 Node]
    E -->|否| G[继续观察]
场景 Autoscaler 行为
节点 NotReady 但未删除 不自动替换,等待恢复
节点被删除 可能触发扩容
长期 Unknown 取决于云 provider

关键理解:Cluster Autoscaler 观察的是 Node 对象状态,而不是直接探测节点。

与云 provider 的交互

云 provider 的 controller 会参与节点生命周期:

flowchart LR
    A[云 provider] --> B[Node Controller]
    B --> C[设置 Node providerID]
    B --> D[处理云实例事件]
    D --> E[实例终止时删除 Node]
云 provider 行为 影响
实例终止 Node 被删除
实例健康检查失败 可能标记 Node 问题
实例迁移 Node 可能短暂 NotReady

关键理解:kubelet 的心跳是集群内视角,云 provider 提供集群外视角。

交互总结表

外部系统 交互方式 影响什么
Scheduler watch Node taint 调度过滤
Taint Manager watch Node + Pod 驱逐 Pod
Endpoint Controller watch Pod status Service 流量
Cluster Autoscaler watch Node + Pod 扩缩容
云 provider 云 API Node 生命周期

3. 关键时序图

Mermaid:主时序图

sequenceDiagram
    participant Kubelet
    participant APIServer
    participant Controller as NodeLifecycleController

    loop every lease interval
        Kubelet->>APIServer: renew Lease
    end
    loop every node status interval
        Kubelet->>APIServer: update NodeStatus
    end
    APIServer-->>Controller: watch Node / Lease changes
    Controller->>Controller: monitorNodeHealth()
    Controller->>Controller: compute Ready / NotReady / Unknown
    alt node unhealthy
        Controller->>APIServer: update taints
        Controller->>APIServer: mark pods NotReady
    end

时序图里的 3 个关键点

  • Lease 和 NodeStatus 是两条独立的写入路径。
  • 控制器通过 watch 观察,而不是主动轮询 kubelet。
  • 健康判断的结果会触发 taint 和 Pod 状态传播。

4. 异常与分支路径

Mermaid:异常/分支路径图

flowchart TD
    A[Kubelet sends heartbeat] --> B{Lease recent?}
    B -->|No| C[mark node Unknown]
    B -->|Yes| D{NodeStatus Ready == True?}
    D -->|No| E[mark node NotReady]
    D -->|Yes| F[mark node Ready]
    C --> G[NoExecute taint / pod eviction]
    E --> H[NoSchedule taint / pod not-ready]
    F --> I[clear health-related taints]
    J[grace period exceeded] --> K[definitely mark Unknown]
    K --> G

读这张图时要关注什么

  • Lease 超时:即使 NodeStatus 未更新,也会触发 Unknown 判断。
  • NodeStatus NotReady:即使 Lease 正常,也会触发 NotReady 判断。
  • grace period:控制面会等待一定时间后才做出最终判断,避免抖动。

5. 这个流程里谁负责什么

组件 主要职责 不负责什么
kubelet 上报 NodeStatus 和 Lease 不判断自己是否健康
nodeLeaseController 续约 Lease 不携带详细节点条件
NodeLifecycleController 综合判断节点健康并触发后续动作 不主动探测 kubelet
kube-apiserver 持久化 Node 和 Lease 不执行健康判断逻辑

6. 常见误解

误解 1:Lease 和 NodeStatus 是同一个心跳

不是。它们是两类不同但互补的健康信号。

误解 2:node controller 主动探测 kubelet

不是。controller 观察的是 API Server 中的 Node / Lease。

误解 3:只看 Lease 就够了

不是。现代节点健康判断仍结合 NodeStatus 与节点条件。

误解 4:节点异常只是标记 NotReady

不是。还包括 taint、Pod 状态传播和驱逐链路。


7. 版本差异说明

这条链路在 Kubernetes 1.27/1.28/1.29 中有一些值得注意的变化:

1.27 → 1.28 变化

变化点 说明
Node 按需状态更新 kubelet 只在状态变化时更新 NodeStatus,减少 API Server 压力
Swap 支持 (Alpha) 节点可以配置使用 swap,影响 MemoryPressure 条件判断
Node Graceful Shutdown 改进 节点关闭时的状态传播更可靠

1.28 → 1.29 变化

变化点 说明
Windows UUID 获取方式改变 停止使用 wmic,改用更现代的方式获取节点 UUID
Lease 续约可靠性改进 在高负载情况下 Lease 续约更稳定
Node Monitor Grace Period 行为 对 grace period 的判断逻辑有细微修复

向后兼容性

版本 兼容性注意
1.27+ Node 按需状态更新减少 API 调用,可能影响依赖固定频率更新的监控
1.28+ Swap 支持需要操作系统和容器运行时配合
1.29+ Windows 节点升级后可能看到节点 ID 变化

需要关注的废弃

废弃项 替代方案 移除版本
--enable-taint-manager flag Taint Manager 默认启用,flag 已无效 1.29 已移除
Node volumesAttached/volumesInUse 使用 CSI 相关字段 计划移除

7. 与后续文档的关系

建议按这个顺序阅读:

  1. docs/kubelet-status-and-event-feedback-flow.md
  2. 本文:理解节点健康信号如何影响控制面判断
  3. docs/kubelet-deep-dive.md:补充 kubelet 子系统背景

8. 代码入口(精简版)

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


9. 详细时序图

9.1 正常心跳流程时序图

sequenceDiagram
    autonumber
    participant KL as Kubelet
    participant NLC as nodeLeaseController
    participant SNS as syncNodeStatus
    participant API as kube-apiserver
    participant ETCD as etcd
    participant NLCtrl as NodeLifecycleController
    participant TM as TaintManager

    Note over KL: Kubelet 启动初始化

    loop 每 10 秒 (默认)
        NLC->>API: PUT /apis/coordination.k8s.io/v1/namespaces/kube-node-lease/leases/<node-name>
        API->>ETCD: 更新 Lease.renewTime
        ETCD-->>API: 确认
        API-->>NLC: 200 OK
    end

    loop 每 10 秒检查,状态变化时更新
        SNS->>SNS: 收集节点条件 (Ready, MemoryPressure, etc.)
        SNS->>SNS: 对比上次状态
        alt 状态有变化
            SNS->>API: PATCH /api/v1/nodes/<node-name>/status
            API->>ETCD: 更新 Node.status
            ETCD-->>API: 确认
            API-->>SNS: 200 OK
        else 无变化
            SNS->>SNS: 跳过更新
        end
    end

    API-->>NLCtrl: watch 推送 Node/Lease 变化
    NLCtrl->>NLCtrl: monitorNodeHealth()
    NLCtrl->>NLCtrl: 检查 Lease.renewTime
    NLCtrl->>NLCtrl: 检查 Node.status.conditions[Ready]

    alt 节点健康
        NLCtrl->>NLCtrl: 标记 Ready
        opt 有健康相关 taint
            NLCtrl->>API: PATCH 移除 taint
        end
    else 节点不健康
        NLCtrl->>API: PATCH 添加 taint
        TM->>API: PATCH Pod status (Ready=False)
        opt NoExecute taint 且 Pod 无 toleration
            TM->>API: DELETE Pod
        end
    end

9.2 节点故障检测时序图

sequenceDiagram
    autonumber
    participant KL as Kubelet
    participant API as kube-apiserver
    participant NLCtrl as NodeLifecycleController
    participant TM as TaintManager
    participant SCHED as Scheduler

    Note over KL: 节点网络故障/宕机

    loop 正常心跳
        KL->>API: Lease 续约
        KL->>API: NodeStatus 更新
    end

    Note over KL: ❌ 心跳停止

    API-->>NLCtrl: watch 无新事件

    Note over NLCtrl: 等待 nodeMonitorGracePeriod (默认 40s)

    NLCtrl->>NLCtrl: 检查 Lease.renewTime
    NLCtrl->>NLCtrl: 超时! 标记 Unknown

    NLCtrl->>API: PATCH Node (添加 node.kubernetes.io/unreachable:NoExecute)

    par 并行处理
        TM->>API: PATCH 所有 Pod (Ready=False)
    and
        TM->>TM: 检查每个 Pod 的 toleration
        loop 每个 Pod
            alt 有 toleration for unreachable
                TM->>TM: 保留 Pod
            else 无 toleration
                TM->>API: DELETE Pod (驱逐)
            end
        end
    and
        API-->>SCHED: watch Node taint
        SCHED->>SCHED: 过滤该节点 (不调度新 Pod)
    end

9.3 节点恢复时序图

sequenceDiagram
    autonumber
    participant KL as Kubelet
    participant API as kube-apiserver
    participant NLCtrl as NodeLifecycleController
    participant SCHED as Scheduler

    Note over KL: 节点恢复,网络重新连通

    KL->>API: PUT Lease (续约恢复)
    API-->>NLCtrl: watch 收到 Lease 更新

    KL->>KL: 收集节点状态
    KL->>API: PATCH NodeStatus (Ready=True)
    API-->>NLCtrl: watch 收到 Node 更新

    NLCtrl->>NLCtrl: monitorNodeHealth()
    NLCtrl->>NLCtrl: Lease 正常 + Ready=True
    NLCtrl->>NLCtrl: 标记 Ready

    NLCtrl->>API: PATCH Node (移除 unreachable taint)
    API-->>SCHED: watch 收到 taint 移除

    SCHED->>SCHED: 节点重新可用于调度

    Note over KL: 新 Pod 可以调度到此节点

10. 故障排查指南

10.1 常见问题与诊断方法

问题 1:节点状态一直是 NotReady

症状

1
2
3
kubectl get nodes
NAME STATUS ROLES AGE VERSION
node-1 NotReady control-plane 10d v1.29.0

排查步骤

flowchart TD
    A[节点 NotReady] --> B{检查 kubelet 是否运行}
    B -->|未运行| B1[启动 kubelet 服务]
    B -->|运行中| C{检查 kubelet 日志}

    C --> C1[查看是否有 Lease 续约错误]
    C1 --> C2{有网络错误?}
    C2 -->|是| C3[检查网络连通性<br/>检查 API Server 可达性]
    C2 -->|否| C4[检查 kubelet 配置]

    C --> D[检查 Node 对象 conditions]
    D --> D1{哪个条件为 False?}

    D1 -->|Ready| E1[检查容器运行时状态]
    D1 -->|MemoryPressure| E2[检查节点内存使用]
    D1 -->|DiskPressure| E3[检查磁盘空间]
    D1 -->|PIDPressure| E4[检查进程数]

    E1 --> F[重启 containerd/cri-o]
    E2 --> G[清理内存或扩容]
    E3 --> H[清理磁盘空间]
    E4 --> I[检查是否有进程泄漏]

诊断命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 检查 kubelet 状态
systemctl status kubelet
journalctl -u kubelet -f

# 2. 检查节点详细状态
kubectl describe node <node-name>

# 3. 检查 Lease 对象
kubectl get lease -n kube-node-lease <node-name> -o yaml

# 4. 检查 kubelet 与 API Server 的连接
curl -k https://<api-server>:6443/healthz

# 5. 检查容器运行时
crictl info
crictl pods

问题 2:节点频繁在 Ready/NotReady 之间切换

可能原因

  1. 网络不稳定导致心跳丢包
  2. API Server 负载过高导致请求超时
  3. kubelet 资源不足导致心跳延迟
  4. 时钟不同步

排查步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
# 检查 kubelet 心跳频率和延迟
journalctl -u kubelet | grep -E "(syncNodeStatus|lease)"

# 检查 API Server 响应时间
curl -w "Time: %{time_total}s\n" -k https://<api-server>:6443/healthz

# 检查节点资源使用
top
df -h
free -m

# 检查时钟同步
timedatectl status

问题 3:Pod 被意外驱逐

症状:节点上的 Pod 突然被删除

排查步骤

flowchart TD
    A[Pod 被驱逐] --> B{检查节点状态}
    B -->|Unknown/NotReady| C[节点健康问题]
    B -->|Ready| D{检查驱逐原因}

    C --> C1[参考问题1排查]

    D --> D1{检查 Node taint}
    D1 -->|有 NoExecute| D2[Pod 缺少 toleration]
    D1 -->|无异常 taint| D3[检查资源压力]

    D2 --> E[为 Pod 添加 toleration]

    D3 --> D4{检查 EvictionManager}
    D4 -->|资源超限| F[节点资源不足导致驱逐]
    D4 -->|正常| G[检查 Descheduler 等组件]

诊断命令

1
2
3
4
5
6
7
8
9
10
11
# 检查 Pod 事件
kubectl describe pod <pod-name> -n <namespace>

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

# 检查节点资源压力
kubectl describe node <node-name> | grep -A 5 Conditions

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

10.2 关键日志关键词

场景 日志关键词 含义
Lease 续约成功 successfully renewed lease 心跳正常
Lease 续约失败 failed to renew lease 心跳异常
NodeStatus 更新 Updating node status 状态同步
节点标记 NotReady node is not ready 健康检查失败
Pod 驱逐 evicting pod Taint Manager 驱逐
资源压力 eviction manager 资源不足

10.3 配置参数参考

参数 默认值 说明
--node-lease-duration-seconds 40 Lease 有效期
--node-renew-period-seconds 10 Lease 续约间隔
node-monitor-grace-period 40s 节点不响应后标记 Unknown 的等待时间
pod-eviction-timeout 5m NotReady 节点上 Pod 驱逐超时
--enable-taint-manager true (1.29 已移除) 启用 Taint Manager

10.4 健康检查命令速查

1
2
3
4
5
6
7
8
9
# 一键检查节点健康状态
echo "=== Node Status ===" && \
kubectl get nodes -o wide && \
echo -e "\n=== Node Conditions ===" && \
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.conditions[?(@.type=="Ready")].status}{"\n"}{end}' && \
echo -e "\n=== Lease Status ===" && \
kubectl get lease -n kube-node-lease -o custom-columns=NAME:.metadata.name,RENEW:.spec.renewTime && \
echo -e "\n=== Problem Nodes ===" && \
kubectl get nodes --field-selector=status.conditions[?(@.type=="Ready")].status!=True

11. 面试题与详细解答

11.1 基础概念题

Q1: Kubernetes 节点心跳机制是如何工作的?

答案

Kubernetes 使用双心跳机制来监控节点健康:

1. Lease 心跳(轻量级)

  • 频率:每 10 秒
  • 对象:Lease 对象在 kube-node-lease namespace
  • 内容:仅包含 renewTime 时间戳
  • 目的:快速检测节点是否存活

2. NodeStatus 心跳(详细)

  • 频率:每 10 秒检查,状态变化时才更新
  • 对象:Node 对象的 status 字段
  • 内容:Conditions(Ready/MemoryPressure 等)、容量、地址等
  • 目的:报告详细的节点健康状态
1
2
3
4
5
6
7
8
9
# Lease 对象示例
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
name: node-1
namespace: kube-node-lease
spec:
holderIdentity: node-1
renewTime: "2024-01-15T10:30:00Z" # 仅此一个时间戳

为什么需要两个心跳

  • Lease 轻量,减少 API Server 负载
  • NodeStatus 详细,但更新成本高
  • 分离关注点:存活检测 vs 健康状态报告

Q2: 节点的 Ready/NotReady/Unknown 状态是如何判断的?

答案

判断逻辑

flowchart TD
    A[检查 Lease] --> B{renewTime 超时?}

    B -->|是, >40s| C[标记 Unknown]
    B -->|否| D[检查 NodeStatus]

    D --> E{Ready condition?}
    E -->|True| F[标记 Ready]
    E -->|False| G[标记 NotReady]
    E -->|Unknown| C

    C --> H[触发 NoExecute taint]
    F --> I[清理健康相关 taint]
    G --> J[触发 NoSchedule taint]

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// pkg/controller/nodelifecycle/node_lifecycle_controller.go:658
func (nc *Controller) monitorNodeHealth(ctx context.Context) error {
for _, node := range nodeList {
// 1. 检查 Lease 是否超时
lease := nc.leaseLister.Leases(metav1.NamespaceNodeLease)
if leaseExceeded(lease, nodeMonitorGracePeriod) {
// Lease 超时,标记 Unknown
markNodeUnknown(node)
continue
}

// 2. 检查 NodeStatus Ready condition
readyCondition := getNodeReadyCondition(node)
if readyCondition.Status == v1.ConditionTrue {
markNodeReady(node)
} else {
markNodeNotReady(node)
}
}
}

状态含义

状态 含义 后果
Ready 节点健康,可以调度 Pod 正常调度
NotReady 节点不健康(如磁盘满、内存压力) NoSchedule taint,不调度新 Pod
Unknown 节点失联(网络问题或宕机) NoExecute taint,驱逐 Pod

11.2 进阶原理题

Q3: Taint Manager 是如何工作的?

答案

Taint Manager 是 NodeLifecycleController 的一部分,负责执行基于 taint 的驱逐。

工作流程

sequenceDiagram
    participant NLC as NodeLifecycleController
    participant TM as TaintManager
    participant API as kube-apiserver
    participant KL as Kubelet

    Note over NLC: 检测到节点 NotReady

    NLC->>API: 添加 taint: node.kubernetes.io/unreachable:NoExecute
    API-->>TM: watch taint 变化

    TM->>TM: 获取该节点上所有 Pod

    loop 每个 Pod
        TM->>TM: 检查 Pod tolerations

        alt 有匹配的 toleration
            TM->>TM: 检查 tolerationSeconds

            alt tolerationSeconds 未超时
                TM->>TM: 保留 Pod
            else tolerationSeconds 超时
                TM->>API: DELETE Pod
            end
        else 无匹配 toleration
            TM->>API: DELETE Pod (立即驱逐)
        end
    end

    API-->>KL: watch Pod DELETE
    KL->>KL: 优雅终止 Pod

Toleration 配置示例

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

代码入口

1
2
3
4
5
6
7
8
9
10
// pkg/controller/nodelifecycle/scheduler/taint_manager.go
func (tc *NoExecuteTaintManager) worker() {
for {
item, shutdown := tc.taintEvictionQueue.Get()
if shutdown {
break
}
tc.handleNodeUpdate(item) // 处理 taint 更新
}
}

Q4: nodeMonitorGracePeriod 和 podEvictionTimeout 有什么区别?

答案

这两个参数作用于不同的阶段:

timeline
    title 节点故障时间线
    t0: 节点故障/网络中断
    t1: 40s (nodeMonitorGracePeriod)
        : 控制面标记节点 Unknown
        : 添加 NoExecute taint
    t2: 5m (podEvictionTimeout)
        : 开始驱逐 NotReady 节点上的 Pod
        : 仅对无 toleration 的 Pod

参数对比

参数 位置 默认值 作用
node-monitor-grace-period kube-controller-manager 40s 节点多久无响应后标记 Unknown
pod-eviction-timeout kube-controller-manager 5m NotReady 节点上 Pod 开始驱逐的时间

配置方式

1
2
3
# kube-controller-manager 启动参数
--node-monitor-grace-period=40s
--pod-eviction-timeout=5m

注意事项

  • pod-eviction-timeout 只对 NotReady 状态生效
  • Unknown 状态立即触发 taint-based eviction
  • 如果 Pod 有 toleration,驱逐时间由 tolerationSeconds 决定

11.3 故障排查题

Q5: 节点一直显示 NotReady,如何排查?

答案

排查流程

flowchart TD
    A[节点 NotReady] --> B{检查 kubelet}

    B -->|未运行| B1[启动 kubelet 服务]
    B -->|运行中| C{检查 Conditions}

    C --> C1{哪个 Condition 异常?}

    C1 -->|Ready=False| D[检查容器运行时]
    D --> D1[systemctl status containerd]
    D --> D2[检查 CRI 连接]

    C1 -->|MemoryPressure| E[检查内存]
    E --> E1[free -m]
    E --> E2[检查内存泄漏]

    C1 -->|DiskPressure| F[检查磁盘]
    F --> F1[df -h]
    F --> F2[清理镜像/日志]

    C1 -->|NetworkUnavailable| G[检查网络]
    G --> G1[检查 CNI 配置]
    G --> G2[检查网络连通性]

    B --> H{检查日志}
    H --> H1[journalctl -u kubelet]
    H --> H2[检查心跳相关错误]

诊断命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 检查节点详细状态
kubectl describe node <node-name>

# 2. 检查 kubelet 状态
systemctl status kubelet
journalctl -u kubelet -f

# 3. 检查 Lease
kubectl get lease -n kube-node-lease <node-name> -o yaml

# 4. 检查容器运行时
systemctl status containerd
crictl info
crictl pods

# 5. 检查系统资源
free -m
df -h

常见原因及解决

原因 症状 解决方案
kubelet 未运行 服务停止 systemctl start kubelet
容器运行时故障 CRI 连接失败 重启 containerd
内存不足 MemoryPressure=True 增加内存/清理进程
磁盘满 DiskPressure=True 清理镜像/日志
网络问题 心跳超时 检查网络配置

11.4 设计思想题

Q6: 为什么使用 Lease API 而不是直接更新 Node 对象来发送心跳?

答案

设计考虑

方面 直接更新 Node 使用 Lease API
对象大小 Node 对象较大(KB 级别) Lease 对象很小(~100 字节)
etcd 负载 高(每次心跳写入大量数据) 低(仅写入时间戳)
watch 流量 高(所有组件都 watch Node) 低(只有 controller watch Lease)
历史数据 需要保留完整历史 仅需最新时间戳

演进历史

  1. v1.13 之前:只有 NodeStatus 心跳,etcd 负载高
  2. v1.14:引入 Lease API(Alpha)
  3. v1.17:Lease 心跳成为默认

代码实现

1
2
3
4
5
6
7
8
9
// pkg/kubelet/node_lease.go
func (c *controller) syncLease(ctx context.Context) error {
// 仅更新 renewTime 字段
lease.Spec.RenewTime = metav1.NowMicro()

// 使用 Patch 而非 Update,减少传输数据
_, err := c.leaseClient.Patch(ctx, leaseName, types.MergePatchType, patch, metav1.PatchOptions{})
return err
}

NodeStatus 按需更新

1
2
3
4
5
6
7
8
// pkg/kubelet/kubelet_node_status.go:518
func (kl *Kubelet) syncNodeStatus() {
// 只在状态变化时才更新 NodeStatus
if !kl.nodeStatusUpdated() {
return
}
// 发送 PATCH 请求
}

结论:Lease API 将高频轻量心跳与低频详细状态更新分离,显著降低了控制面负载。