跳转至

调度器静态分配与真实负载失衡

01 前置理论与核心机制

1. K8s 默认调度器的静态账面机制

  • 不看物理负载,仅看账面配额:K8s 的默认调度器(kube-scheduler)在分配 Pod 时,不会调用底层操作系统的 top 或查询真实的物理 CPU/内存使用率。其调度决策完全依赖保存在 etcd 中的 YAML 账面数字,即节点上所有存量 Pod 声明的 requests(资源请求量)总和。
  • 打分机制的局限性:调度器在打分(Score)阶段,默认采用 LeastAllocated 策略(优先分配给账面最空闲的节点),底层核心公式为 (总可分配容量 - 账面已申请量) / 总可分配容量。只要某个节点上的高负载 Pod 未声明 requests,哪怕该节点的物理 CPU 已经达到 100% 满载,调度器依然会判定该节点处于“高度空闲”状态,并持续向其派发新 Pod。

2. 账实不符的两种典型表现

  • 低报高用(未声明 Requests):高负载业务容器启动时未在 YAML 中规范声明 requests。结果导致节点物理资源耗尽,但调度账面剩余额度充足,引发调度器持续倾斜派发,最终导致节点过载崩溃。
  • 高报低用(配额虚占):部分业务为保障峰值性能,在 YAML 中声明了远超实际需求的 requests,但日常运行时实际使用率极低。结果导致节点账面额度被提前占满,而底层物理资源大量闲置,集群整体资源利用率低下。

02 故障排查与干预闭环 SOP

当发现新发布的 Pod 持续集中调度到物理 CPU 已满载的节点,或物理空闲节点始终无法接收新 Pod 时,按以下链路排查与治理:

步骤一:比对节点真实物理负载与账面分配率

直接对比节点的实时物理监控指标与调度器的资源分配账本:

# 1. 查看真实物理负载:获取节点实时 CPU/内存使用率 (需部署 metrics-server)
kubectl top nodes

# 2. 查看调度账面配额:查看调度器视角下的节点账面已分配率 (Allocated resources)
kubectl describe node  | grep -A 5 "Allocated resources"
  • 定性基线:若物理满载节点的 Allocated requests 比例极低(如 15%),而物理空闲节点的比例极高(如 85%),即可确认为业务未规范声明 requests 导致的“账实不符”与调度失衡。

步骤二:应急止损 (节点隔离与驱逐)

当满载节点因持续涌入新 Pod 面临过载风险时,优先隔离问题节点并疏散流量:

# 1. 将物理满载节点标记为不可调度,阻止调度器继续向其分配新 Pod
kubectl cordon

# 2. 若节点已严重过载,安全驱逐其上的业务 Pod,使其重调度至其他节点
kubectl drain  --ignore-daemonsets --delete-emptydir-data

步骤三:生产环境标准治理方案 (引入动态负载调度组件)

原生调度器逻辑基于静态配额设计,生产环境严禁直接修改 K8s 核心源码,标准方案是基于 Scheduling Framework 扩展机制引入动态负载感知组件: - 部署动态负载调度插件 (如 Koordinator / Trimaran):在集群中安装基于真实负载的调度器插件。组件通过节点 Agent 或指标接口实时采集各节点的物理 CPU/内存负载,并在调度器的 Filter 与 Score 阶段介入打分。物理负载越高的节点得分越低,从架构层面解决静态账面调度失衡。 - 部署重调度器 (Descheduler):配合动态调度插件定时扫描集群。当检测到节点间实际负载差异超过阈值时,自动驱逐高负载节点上的部分 Pod,触发重新调度以恢复集群负载均衡。

03 真实生产故障现象与案例库

案例 1:JVM 服务配额虚高占位,导致集群资源闲置且无法扩容

  • 故障现象:集群拥有 10 台高配物理节点,监控显示整体物理 CPU 利用率常年不足 10%。但每次发版扩容新服务时,Pod 均处于 Pending 状态,报错 0/10 nodes are available: 10 Insufficient cpu。
  • 排查与根因:集群内运行着大量 Java 微服务,研发人员为防止启动慢或 OOM,在 Helm Chart 模板中将所有 Pod 的 requests 统一配置为 4C 8G。服务启动后日常处于低负载状态,但由于 K8s 调度过滤阶段严格校验账面配额,10 台节点的账面可分配额度已被全部占满,导致新服务无法调度。
  • 处置方案:引入 VPA(垂直 Pod 自动扩缩容)的推荐模式分析历史实际水位,统一将日常闲置服务的 requests 下调至 500m~1C 以贴合实际基线,仅保留较高的 limits 用于应对突发流量,释放了超过 70% 的可调度配额。

案例 2:未声明 Requests 的计算密集型任务引发节点过载

  • 故障现象:某工作节点物理 CPU 持续 100% 满载,导致同节点上的核心网关 Pod 大面积响应超时;与此同时,集群新扩容的 Pod 仍在源源不断地调度至该满载节点。
  • 排查与根因:某业务团队上线了视频转码任务(CPU 密集型),但部署 YAML 中未配置 requests 和 limits(QoS 级别为 BestEffort)。该任务启动后占满了宿主机的物理 CPU,但在调度器账本上,该节点的 CPU 申请量仅为系统守护进程的占用(不到 15%)。调度器判定该节点“最空闲”,导致后续发布的 Pod 被全部吸引至该节点。
  • 处置方案:先对该节点执行 cordon 隔离并限制转码容器配额;随后在所有业务 Namespace 下配置 LimitRange 准入控制,为未声明资源配额的 Pod 自动注入默认 requests/limits。

04 生产排障速查表(调度失衡与治理)

异常现象排查层级执行命令 / 观测点根因定性标准处置动作满载节点持续接收新 Pod账面与物理对比kubectl top node 与 describe node低报高用。高耗能进程未配置 requests,账面配额空闲执行 cordon 隔离节点,为高负载业务补齐 requests 声明空闲节点无法调度新 Pod账面分配核查kubectl describe node 查看 Allocated高报低用。高 requests 的低频业务将账面额度占满下调闲置业务的 requests 申请值,释放虚占配额调度器事件报资源不足调度事件排查kubectl get events提示 Insufficient cpu,但物理监控显示空闲账实严重背离,在 Filter 阶段被静态账本拦截长期架构治理调度组件演进部署 Koordinator / Trimaran原生静态调度机制不感知物理水位引入动态负载感知打分,配合 Descheduler 自动均衡

05 面试应答参考话术

面试官问:“集群里两台工作节点,一台真实 CPU 100%,一台只有 4%。新发版 5 个 Pod 全被调度到了 100% 的那台机器上。排除亲和性和污点,你觉得是为什么?怎么解决?”

可以这样回答:这个问题考察的是 K8s 默认调度器(kube-scheduler)的资源核算机制。K8s 默认调度器在筛选节点时,不会去查询操作系统真实的物理 CPU 负载,而是完全依赖保存在 etcd 中的静态资源账本,即节点上所有 Pod 声明的 requests 总和。在这个场景下: 那台物理 CPU 100% 的节点,必然是因为上面运行的高负载业务没有在 YAML 中声明 requests(或声明值极小)。因此在调度器的账本里,该节点的已分配率极低,账面剩余额度非常充足,在默认的 LeastAllocated 打分策略下拿到了高分。 而那台物理 CPU 只有 4% 的空闲节点,则是因为上面运行的业务在 YAML 中声明了过大的 requests,把节点的账面额度占满了,导致打分极低。最终 5 个新 Pod 就被全部调度到了账面空闲、但物理已经满载的节点上。解决该问题分为应急与根治两步: 第一步是应急止损与规范约束。 先用 kubectl cordon 将物理满载的节点隔离,防止新 Pod 继续进入;然后通过 Namespace 级别的 LimitRange 强制要求所有业务 Pod 规范配置合理的 requests 和 limits,消除账实不符。 第二步是架构层面的彻底治理。 在生产集群中引入动态负载感知调度插件(例如阿里的 Koordinator 或社区的 Trimaran)。它们会实时采集节点真实的物理 CPU 和内存使用率并在调度打分阶段介入,主动压低高物理负载节点的得分;再配合 Descheduler 重调度器,即可从根本上解决静态调度倾斜问题。

06 面试高频追问与应答预案

追问 1:如果在 Pod YAML 中不配置 requests,只配置 limits 会怎样?

  • 回答要点:
  • 根据 Kubernetes 的默认资源填充机制,如果一个容器只声明了 limits 而未声明 requests,API Server 会在创建时自动将其 requests 设置为与 limits 完全相等的值。
  • 若所有容器均如此配置,该 Pod 的 QoS 级别会被划分为最高优先级的 Guaranteed(完全保障型)。这虽然能避免因漏配 requests 导致的节点过载,但若 limits 设置偏高,极易造成“高报低用”的账面配额浪费。

追问 2:如何通过 K8s 原生机制,强制要求开发人员必须配置 requests?

  • 回答要点:
  • 结合使用 LimitRange 与 ResourceQuota 准入控制器:
  • 一是在业务 Namespace 下配置 ResourceQuota 限制该命名空间的总 requests.cpu 和 requests.memory 上限;一旦开启了该配额限制,K8s 会强制要求该命名空间下创建的所有 Pod 必须显式包含 requests 声明,否则直接报错拒绝创建。
  • 二是同步配置 LimitRange 定义 defaultRequest 与 default(默认 Limit),当开发人员未填写时,由准入控制器在创建阶段自动注入合理的默认资源配额。

07 节点资源账实背离度自动巡检脚本 (check_k8s_resource_skew.sh)

该脚本用于自动对比当前集群中各节点的 真实物理 CPU 使用率 与 账面 Requests 分配率,快速定位存在严重“账实不符”及调度倾斜风险的节点。

#!/usr/bin/env bash
# ==============================================================================
# 脚本名称: check_k8s_resource_skew.sh
# 功能描述: 自动比对 K8s 节点的真实物理 CPU 负载与账面 Requests 分配率 (纯只读)
# 依赖工具: kubectl, awk (需集群已安装 metrics-server)
# ==============================================================================

set -o pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PLAIN='\033[0m'

echo -e "${BLUE}================================================================================================${PLAIN}"
echo -e "${BLUE}                     K8S 节点资源账实背离度 (真实负载 vs 账面分配) 自动化巡检                   ${PLAIN}"
echo -e "${BLUE}================================================================================================${PLAIN}"

# 1. 检查指标采集基础组件
if ! kubectl top nodes &>/dev/null; then
    echo -e "${RED}[FATAL] 无法获取真实负载指标,请确认集群已安装并正常运行 metrics-server。${PLAIN}"
    exit 1
fi

# 修复了 printf 表头的 \% 和 \vert{} 乱码
printf "${YELLOW}%-25s | %-18s | %-26s | %s${PLAIN}\n" "节点名称 (Node)" "物理 CPU 实际使用" "账面 CPU 已分配 (Requests)" "调度健康度诊断"
echo -e "------------------------------------------------------------------------------------------------"

# 获取所有节点名称
NODES=$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}')

for node in $NODES; do
    # 提取真实物理 CPU 使用率 (%)
    REAL_CPU_PCT=$(kubectl top node "$node" --no-headers 2>/dev/null | awk '{print $3}' | tr -d '%')

    # 提取账面 CPU Requests 申请率 (%)
    ALLOC_CPU_PCT=$(kubectl describe node "$node" 2>/dev/null | grep -A 5 "Allocated resources" | awk '$1 == "cpu" {print $3}' | tr -d '(%)')

    # 处理空白或非数字情况
    if [[ ! "$REAL_CPU_PCT" =~ ^[0-9]+$ ]]; then REAL_CPU_PCT=0; fi
    if [[ ! "$ALLOC_CPU_PCT" =~ ^[0-9]+$ ]]; then ALLOC_CPU_PCT=0; fi

    # 诊断逻辑判定
    if [ "$REAL_CPU_PCT" -gt 85 ] && [ "$ALLOC_CPU_PCT" -lt 40 ]; then
        DIAGNOSIS="${RED}[高危倾斜] 物理负载极高但账面空闲!未配 Requests 的业务易引发节点过载!${PLAIN}"
    elif [ "$REAL_CPU_PCT" -lt 20 ] && [ "$ALLOC_CPU_PCT" -gt 80 ]; then
        DIAGNOSIS="${YELLOW}[配额虚占] 物理负载极低但账面已满!高 requests 闲置业务阻断新 Pod 调度。${PLAIN}"
    else
        DIAGNOSIS="${GREEN}[正常] 账面分配与实际物理负载基本相符。${PLAIN}"
    fi

    # 格式化输出对齐 (%b 支持解析颜色转义序列)
    # 修复了末尾分配率参数的 \% 乱码
    printf "%-25s | %-18s | %-26s | %b\n" "$node" "${REAL_CPU_PCT}%" "${ALLOC_CPU_PCT}%" "$DIAGNOSIS"
done

echo -e "\n${BLUE}================================================================================================${PLAIN}"
echo -e "${BLUE}排障建议:针对标红的高危节点,请立刻排查并执行 cordon 隔离,强制业务 Pod 规范配置 requests 参数。${PLAIN}"
echo -e "${BLUE}架构演进:建议部署 Koordinator 或 Trimaran 等动态负载调度插件接管原生 kube-scheduler。${PLAIN}"
echo -e "${BLUE}================================================================================================${PLAIN}"