主题
K8s Worker 节点高 CPU 副本排查脚本(k8s-worker-cpu-top.sh)
一键定位「哪个 worker 节点 CPU 高 → 节点上哪个 Pod/副本 CPU 高 → 是否超 limit 被 throttle → 属于哪个 Deployment」的 Bash 排查脚本,可选深入容器内查看线程级 CPU。适用于 RKE2/K3s/原生 K8s 集群。
功能清单
| 步骤 | 内容 |
|---|---|
| 1 | kubectl 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排查思路速查
!!超LIMIT:进程被 CPU throttle,表现为卡顿/超时。处理:调高limits.cpu或优化程序;配合 Prometheus 指标container_cpu_cfs_throttled_seconds_total确认。!无LIMIT:该 Pod 可能挤占同节点其他 Pod 的 CPU,建议设置 requests/limits。>REQUEST:实际用量超 request,调度依据失真,建议调高 requests 保证 QoS。副本不均衡:若 Deployment 多副本 CPU 差异大,检查负载均衡/长连接问题。
历史趋势:结合 Prometheus 查询节点级 TOP10:
promqltopk(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深入分析依赖容器内有sh和top/ps,精简镜像(如 distroless)会失败,属正常现象。