Skip to content

K8s Worker 节点高 CPU 副本排查脚本(k8s-worker-cpu-top.sh)

一键定位「哪个 worker 节点 CPU 高 → 节点上哪个 Pod/副本 CPU 高 → 是否超 limit 被 throttle → 属于哪个 Deployment」的 Bash 排查脚本,可选深入容器内查看线程级 CPU。适用于 RKE2/K3s/原生 K8s 集群。

功能清单

步骤内容
1kubectl top nodes 按 CPU% 降序,自动定位(或手动指定)CPU 最高的 worker 节点
2关联 kubectl top pods 实时用量与 Pod 的 requests/limits,按毫核(m)降序输出
3自动标记风险:!!超LIMIT(会被 CPU throttle)、!无LIMIT!无REQUEST>REQUEST
4通过 ownerReferences 追溯 Pod 归属(ReplicaSet 自动上溯到 Deployment)
5可选 -d 深入 CPU 最高的 Pod 容器内查看线程级 top

前置条件

  • 节点上有 kubectl 且已配置好 kubeconfig
  • 集群已部署 metrics-server(RKE2 默认自带)
  • jq(解析 Pod JSON)

使用方法

bash
chmod +x k8s-worker-cpu-top.sh

./k8s-worker-cpu-top.sh                  # 自动选 CPU 最高的 worker 节点排查
./k8s-worker-cpu-top.sh -n worker-01     # 指定节点
./k8s-worker-cpu-top.sh -t 500           # 只看 CPU >= 500m 的 Pod
./k8s-worker-cpu-top.sh -n worker-01 -d  # 深入容器内看线程级 CPU
./k8s-worker-cpu-top.sh --all-nodes      # 不选单节点,全集群汇总分析
./k8s-worker-cpu-top.sh --top 30         # 显示前 30 个 Pod(默认 20)

输出示例

===================================================================
[1/4] 节点 CPU 使用情况 (按 CPU% 降序)
===================================================================
NAME        CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
worker-02   1450m        72%    5201Mi          65%
worker-01   800m         40%    3800Mi          48%

>>> 自动选定 CPU 最高的 worker 节点: worker-02

===================================================================
[2/4] 节点上 Pod 实时 CPU 排序 (阈值: >= 0m)
===================================================================
CPU(m)   NAMESPACE            POD                   CONTAINER     REQ(m)   LIM(m)    %LIM  标记
980      ai-inference         vllm-7b9f4c5d6-x2k9p  vllm             500    1000     98%  !无REQUEST >REQUEST
210      monitoring           node-exporter-abcde   node-exporter    100     200    105%  !!超LIMIT(会被throttle)

===================================================================
[3/4] 高 CPU Pod 的副本归属 (Deployment/StatefulSet/DaemonSet)
===================================================================
  CPU=   980m  ai-inference/vllm-7b9f4c5d6-x2k9p  <-  Deployment/vllm
  CPU=   210m  monitoring/node-exporter-abcde     <-  DaemonSet/node-exporter

排查思路速查

  1. !!超LIMIT:进程被 CPU throttle,表现为卡顿/超时。处理:调高 limits.cpu 或优化程序;配合 Prometheus 指标 container_cpu_cfs_throttled_seconds_total 确认。

  2. !无LIMIT:该 Pod 可能挤占同节点其他 Pod 的 CPU,建议设置 requests/limits。

  3. >REQUEST:实际用量超 request,调度依据失真,建议调高 requests 保证 QoS。

  4. 副本不均衡:若 Deployment 多副本 CPU 差异大,检查负载均衡/长连接问题。

  5. 历史趋势:结合 Prometheus 查询节点级 TOP10:

    promql
    topk(10, sum by (pod, namespace)(
      rate(container_cpu_usage_seconds_total{node="<NODE>"}[5m])))

完整脚本

bash
#!/usr/bin/env bash
#
# k8s-worker-cpu-top.sh - 排查 K8s worker 节点上 CPU 使用高的 Pod(副本)
#
# 用法:
#   ./k8s-worker-cpu-top.sh                    # 自动选 CPU 最高的 worker 节点排查
#   ./k8s-worker-cpu-top.sh -n worker-01       # 指定节点
#   ./k8s-worker-cpu-top.sh -t 500             # 只显示 CPU >= 500m 的 Pod
#   ./k8s-worker-cpu-top.sh -n worker-01 -d    # 对最高 CPU 的 Pod 深入容器内分析线程
#   ./k8s-worker-cpu-top.sh --all-nodes        # 不选单节点, 直接对全部节点汇总分析
#
set -euo pipefail

# ---------- 参数解析 ----------
NODE=""                 # 指定节点名
THRESHOLD=0             # CPU 阈值(毫核), 低于此值的 Pod 不显示
DEEP=0                  # 是否深入容器内分析
ALL_NODES=0             # 是否对全部节点汇总
TOP_N=20                # 显示前 N 个 Pod

usage() { sed -n '2,22p' "$0"; exit 0; }

while [[ $# -gt 0 ]]; do
  case "$1" in
    -n|--node)      NODE="$2"; shift 2 ;;
    -t|--threshold) THRESHOLD="$2"; shift 2 ;;
    -d|--deep)      DEEP=1; shift ;;
    --all-nodes)    ALL_NODES=1; shift ;;
    --top)          TOP_N="$2"; shift 2 ;;
    -h|--help)      usage ;;
    *) echo "未知参数: $1"; usage ;;
  esac
done

# ---------- 依赖检查 ----------
for cmd in kubectl jq; do
  command -v "$cmd" >/dev/null 2>&1 || { echo "错误: 未找到命令 $cmd"; exit 1; }
done

# metrics-server 可用性检查
if ! kubectl top nodes >/dev/null 2>&1; then
  echo "错误: 'kubectl top nodes' 失败, 请确认 metrics-server 已部署且正常"
  exit 1
fi

# ---------- 小工具 ----------
# 把 1000n / 250m / 2 等 CPU 值统一转成毫核(m)整数
to_millicores() {
  local v="$1"
  if [[ "$v" =~ ^[0-9]+n$ ]]; then
    echo $(( ${v%n} / 1000000 ))
  elif [[ "$v" =~ ^[0-9]+m$ ]]; then
    echo "${v%m}"
  elif [[ "$v" =~ ^[0-9]+$ ]]; then
    echo $(( v * 1000 ))
  else
    echo 0
  fi
}

# 处理 requests/limits 里的 cpu 字段(可能是 "2" / "250m" / "0.5")
cpu_qty_to_m() {
  local v="${1:-}"
  [[ -z "$v" || "$v" == "null" ]] && { echo 0; return; }
  if [[ "$v" =~ ^[0-9]+m$ ]]; then echo "${v%m}";
  elif [[ "$v" =~ ^[0-9]+$ ]]; then echo $(( v * 1000 ));
  elif [[ "$v" =~ ^[0-9]+\.[0-9]+$ ]]; then echo "$(awk "BEGIN{printf \"%d\", $v*1000}")";
  else echo 0; fi
}

echo "==================================================================="
echo "[1/4] 节点 CPU 使用情况 (按 CPU% 降序)"
echo "==================================================================="
kubectl top nodes | (read -r header; echo "$header"; sort -k3 -hr)

# ---------- 选定目标节点 ----------
if [[ $ALL_NODES -eq 0 ]]; then
  if [[ -z "$NODE" ]]; then
    # 自动选 CPU% 最高的 worker(排除 control-plane/master)
    NODE=$(kubectl top nodes --no-headers 2>/dev/null | sort -k3 -hr | awk '{print $1}' | while read -r n; do
      if ! kubectl get node "$n" -o jsonpath='{.metadata.labels}' | grep -qE 'control-plane|master'; then
        echo "$n"; break
      fi
    done)
    [[ -z "$NODE" ]] && NODE=$(kubectl top nodes --no-headers | sort -k3 -hr | head -1 | awk '{print $1}')
    echo
    echo ">>> 自动选定 CPU 最高的 worker 节点: $NODE"
  else
    echo
    echo ">>> 指定排查节点: $NODE"
  fi
fi

echo
echo "==================================================================="
echo "[2/4] 节点上 Pod 实时 CPU 排序 (阈值: >= ${THRESHOLD}m)"
echo "==================================================================="

# 取实时指标: namespace, pod, cpu, mem
TOP_PODS=$(kubectl top pods --all-namespaces --no-headers 2>/dev/null || true)
[[ -z "$TOP_PODS" ]] && { echo "错误: kubectl top pods 无数据"; exit 1; }

# 取 pod -> node 映射, 以及每容器 requests/limits
PODS_JSON=$(kubectl get pods --all-namespaces -o json)

# 生成: node<TAB>namespace<TAB>pod<TAB>container<TAB>req<TAB>limit
POD_META=$(echo "$PODS_JSON" | jq -r '
  .items[] |
  .spec.nodeName as $node |
  .metadata.namespace as $ns |
  .metadata.name as $pod |
  (.spec.containers // [])[] |
  [ $node, $ns, $pod, .name,
    (.resources.requests.cpu // "0"),
    (.resources.limits.cpu   // "0") ] | @tsv')

# 关联实时用量与元数据, 输出排序结果
printf "%-8s %-20s %-42s %-24s %8s %8s %8s  %s\n" \
  "CPU(m)" "NAMESPACE" "POD" "CONTAINER" "REQ(m)" "LIM(m)" "%LIM" "标记"
printf -- "-%.0s" {1..150}; echo

RESULT=$(echo "$TOP_PODS" | while read -r ns pod cpu mem; do
  cpu_m=$(to_millicores "$cpu")
  [[ "$cpu_m" -lt "$THRESHOLD" ]] && continue

  # 该 pod 的元数据(可能多容器, 逐容器输出)
  meta=$(echo "$POD_META" | awk -F'\t' -v ns="$ns" -v pod="$pod" '$2==ns && $3==pod')
  [[ -z "$meta" ]] && continue

  echo "$meta" | while IFS=$'\t' read -r node mns mpod container req limit; do
    # 节点过滤
    if [[ $ALL_NODES -eq 0 && "$node" != "$NODE" ]]; then continue; fi

    req_m=$(cpu_qty_to_m "$req")
    lim_m=$(cpu_qty_to_m "$limit")

    # pod 级 cpu 均摊到容器显示(近似), 单容器 pod 则精确
    ncontainers=$(echo "$meta" | wc -l)
    show_cpu=$(( cpu_m / ncontainers ))

    pct="-"
    flag=""
    if [[ "$lim_m" -gt 0 ]]; then
      pct=$(( show_cpu * 100 / lim_m ))"%"
      [[ "$show_cpu" -ge "$lim_m" ]] && flag="!!超LIMIT(会被throttle)"
    else
      flag="!无LIMIT"
    fi
    if [[ "$req_m" -eq 0 ]]; then flag="$flag !无REQUEST"; fi
    if [[ "$req_m" -gt 0 && "$show_cpu" -gt "$req_m" ]]; then flag="$flag >REQUEST"; fi

    printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" \
      "$show_cpu" "$node" "$mns" "$mpod" "$container" "$req_m" "$lim_m" "$pct" "$flag"
  done
done | sort -t$'\t' -k1 -hr | head -n "$TOP_N")

if [[ -z "$RESULT" ]]; then
  echo "没有满足条件的 Pod (可尝试降低 -t 阈值, 当前 ${THRESHOLD}m)"
else
  echo "$RESULT" | while IFS=$'\t' read -r cpu node ns pod container req lim pct flag; do
    printf "%-8s %-20s %-42s %-24s %8s %8s %8s  %s\n" \
      "$cpu" "$ns" "$pod" "$container" "$req" "$lim" "$pct" "$flag"
  done
fi

echo
echo "==================================================================="
echo "[3/4] 高 CPU Pod 的副本归属 (Deployment/StatefulSet/DaemonSet)"
echo "==================================================================="
echo "$RESULT" | head -5 | while IFS=$'\t' read -r cpu node ns pod container req lim pct flag; do
  owner=$(kubectl get pod "$pod" -n "$ns" -o jsonpath='{.metadata.ownerReferences[0].kind}/{.metadata.ownerReferences[0].name}' 2>/dev/null || echo "无(裸Pod)")
  # 如果是 ReplicaSet, 继续往上找 Deployment
  if [[ "$owner" == ReplicaSet/* ]]; then
    rs="${owner#ReplicaSet/}"
    dep=$(kubectl get rs "$rs" -n "$ns" -o jsonpath='{.metadata.ownerReferences[0].name}' 2>/dev/null || true)
    [[ -n "$dep" ]] && owner="Deployment/$dep"
  fi
  printf "  CPU=%6sm  %s/%s  <-  %s\n" "$cpu" "$ns" "$pod" "$owner"
done

# ---------- 可选: 深入容器内部分析 ----------
if [[ $DEEP -eq 1 && -n "$RESULT" ]]; then
  echo
  echo "==================================================================="
  echo "[4/4] 深入 CPU 最高的 Pod 容器内分析线程"
  echo "==================================================================="
  top_line=$(echo "$RESULT" | head -1)
  cpu=$(echo "$top_line" | cut -f1)
  ns=$(echo "$top_line" | cut -f3)
  pod=$(echo "$top_line" | cut -f4)
  container=$(echo "$top_line" | cut -f5)
  echo ">>> 目标: $ns/$pod 容器=$container (CPU=${cpu}m)"
  echo
  echo "--- 进程/线程 top (按 CPU 排序, 前 15) ---"
  kubectl exec -n "$ns" "$pod" -c "$container" -- sh -c \
    'top -b -n 1 -H 2>/dev/null | head -30 || ps -eo pid,ppid,pcpu,pmem,comm --sort=-pcpu | head -15' \
    2>&1 || echo "(exec 失败: 容器可能没有 sh/top/ps, 或 RBAC 无权限)"
fi

echo
echo "==================================================================="
echo "排查建议"
echo "==================================================================="
cat <<'EOF'
  1. !!超LIMIT  : 进程被 CPU throttle, 表现为卡顿/超时.
                  处理: 调高 limits.cpu, 或优化程序; 可用
                  kubectl top pod 确认 + 查 container_cpu_cfs_throttled_seconds_total
  2. !无LIMIT   : 该 Pod 可能挤占同节点其他 Pod 的 CPU, 建议设置 requests/limits
  3. >REQUEST   : 实际用量超 request, 调度依据失真, 建议调高 requests 保证 QoS
  4. 副本均衡   : 若 Deployment 多副本 CPU 不均, 检查负载均衡/长连接问题
  5. 深入排查   : 加 -d 参数看容器内线程级 CPU, 或用:
                  kubectl exec -it <pod> -- top -H
                  kubectl debug 节点级: kubectl debug node/<node> -it --image=busybox
  6. 历史趋势   : 结合 Prometheus 查询:
                  topk(10, sum by (pod, namespace)(
                    rate(container_cpu_usage_seconds_total{node="<NODE>"}[5m])))
EOF

注意事项

  • 多容器 Pod 的 CPU 为 Pod 级汇总值均摊显示(近似),单容器 Pod 为精确值;精确到容器级可用 kubectl top pod <pod> -n <ns> --containers
  • 自动选节点时通过节点 label(control-plane/master)排除控制面节点;若集群控制面也承担负载,可用 --all-nodes
  • -d 深入分析依赖容器内有 shtop/ps,精简镜像(如 distroless)会失败,属正常现象。