Skip to content

K8s 大集群批量运维脚本手册

在大规模 Kubernetes 集群(数百节点、数千 Deployment)的日常运维中,逐个点开控制台操作既不现实也容易出错。本文整理了一组经过生产验证的批量运维脚本,覆盖资源审计、镜像仓库迁移、批量设置 requests/limits、镜像拉取授权、异常 Pod 清理、节点绑定残留清除、PV/PVC 强删等高频场景。

使用前提:

  • 已配置好目标集群的 kubeconfig,并确认当前 context 指向正确集群
  • 安装 jq(资源审计脚本依赖)与 GNU awk/sed
  • 所有批量操作默认先 echo 打印命令、确认无误后再去掉注释真正执行

文中所有地址均为示例:旧镜像仓库 192.168.10.15:5000,新仓库 harbor.example.com:60000(备份仓库 harbor-backup.example.com:60000),节点地址 192.168.10.x,请按实际环境替换。

一、批量导出 Deployment 的 request/limit 为 CSV(资源审计)

用途说明

将全集群所有 Deployment 的命名空间、名称、副本数、每个容器的 CPU/内存 requests 和 limits 导出为 CSV,用于容量审计、资源配额核查,或交给财务/架构团队做成本分析。

使用场景

  • 每季度资源审计:哪些应用没配 requests、哪些 limit 配得离谱
  • 集群拆分或迁移前,导出全量资源画像作为基线
  • 排查"调度不上"问题时,先盘点各节点 request 总量

脚本

基础版(导出 namespace、名称、副本数、容器 resources):

bash
kubectl get deploy --all-namespaces -o json | \
  jq -r '.items[] |
    .metadata.namespace + ", " +
    .metadata.name + ", " +
    (.spec.replicas // 0 | tostring) + ", " +
    (.spec.template.spec.containers[]? |
      (.name // "default") + ", " +
      (.resources.requests.cpu // "0") + ", " +
      (.resources.requests.memory // "0") + ", " +
      (.resources.limits.cpu // "0") + ", " +
      (.resources.limits.memory // "0")
    )' > all_deploy_list.csv

增强版(额外导出 nodeName 和 nodeSelector,用于审计"被钉死在节点上"的应用):

bash
kubectl get deploy --all-namespaces -o json | \
  jq -r '.items[] |
    .metadata.namespace + ", " +
    .metadata.name + ", " +
    (.spec.replicas // 0 | tostring) + ", " +
    (.spec.template.spec.containers[]? |
      (.name // "default") + ", " +
      (.resources.requests.cpu // "0") + ", " +
      (.resources.requests.memory // "0") + ", " +
      (.resources.limits.cpu // "0") + ", " +
      (.resources.limits.memory // "0")
    ) + ", " +
    (.spec.template.spec.nodeName // "null") + ", " +
    (.spec.template.spec.nodeSelector // {} | to_entries | map("\(.key)=\(.value)") | join(";"))' \
    > csv_all_deploy_list_$(date '+%d-%H-%M').csv

jq 写法解析

  • .items[]:遍历所有 Deployment。
  • // 0// "0"// "null":jq 的替代运算符,字段不存在时给默认值,避免输出 null 污染 CSV。
  • .containers[]?? 表示容器列表为空时不报错,防止个别异常对象中断整个导出。
  • nodeSelector // {} | to_entries | map("\(.key)=\(.value)") | join(";"):把 nodeSelector 的 map 结构压平成 key1=val1;key2=val2 的单行字符串,这是把 JSON map 塞进 CSV 单列的经典写法。
  • 一个 Deployment 有多个容器时会输出多行(每容器一行),namespace/name/replicas 会重复,这是有意为之,方便在 Excel 里按容器筛选。

注意事项

  • jq -r 必须加,否则字符串会带 JSON 引号。
  • 大集群(上万 Deployment)导出可能需要几十秒,建议直接落盘再看。
  • 只想看 JSON 结构而不拼 CSV 时,可用下面两条快速查看:
bash
# 查看所有 Pod 的 request/limit
kubectl get pods --all-namespaces -o json | \
  jq '.items[] | {name: .metadata.name, namespace: .metadata.namespace, requests: .spec.containers[].resources.requests, limits: .spec.containers[].resources.limits}'

# 查看所有 Deployment 的 request/limit
kubectl get deployments --all-namespaces -o json | \
  jq '.items[] | {name: .metadata.name, namespace: .metadata.namespace, requests: .spec.template.spec.containers[].resources.requests, limits: .spec.template.spec.containers[].resources.limits}'

二、批量替换镜像仓库前缀(迁移 Harbor 场景)

用途说明

镜像仓库整体迁移(例如从自建 registry 192.168.10.15:5000 迁到 Harbor harbor.example.com:60000)时,需要分两步:先把镜像本体同步到新仓库,再批量修改线上 Deployment 的 image 字段。

使用场景

  • 旧 registry 下线、Harbor 上线,全量镜像迁移
  • 多环境(生产/测试)共用一套镜像,仓库地址统一收口

脚本

第一步:盘点哪些 Deployment 还在用旧仓库。

bash
# 列出全集群 Deployment 及镜像(jsonpath 单行写法,适合管道处理)
kubectl get deployments --all-namespaces \
  -o=jsonpath="{range .items[*]}{.metadata.namespace} {.metadata.name} {'image: '}{.spec.template.spec.containers[*].image}{'\n'}{end}"

# 带副本数的 CSV 版本,grep 出使用旧仓库的清单
kubectl get deployments --all-namespaces \
  -o=jsonpath="{range .items[*]}{.metadata.namespace},{.metadata.name},{.spec.template.spec.containers[*].image},{.spec.replicas}{'\n'}{end}" \
  | grep '192.168.10.15:5000' > old_registry_deploy.csv

第二步:把镜像本体同步到新仓库(在能同时访问两个仓库的跳板机上执行)。

bash
kubectl get deployments --all-namespaces \
  -o=jsonpath="{range .items[*]}{.spec.template.spec.containers[*].image}{'\n'}{end}" > all_images_list

grep '192.168.10.15:5000' all_images_list | sort -u > old_images_list
: > err_image_list   # 清空失败记录文件

while read -r line; do
    echo "==> $line"
    docker pull "$line"
    new_image=$(echo "$line" | sed 's|192\.168\.10\.15:5000|harbor\.example\.com:60000|')
    docker tag "$line" "$new_image"

    if docker push "$new_image"; then
        echo "push ok: $new_image"
    else
        echo "push FAILED: $new_image" | tee -a err_image_list
    fi

    # 清理本地镜像,防止跳板机磁盘被打满
    docker rmi "$line" "$new_image"
done < old_images_list

第三步:批量修改 Deployment 的 image 字段。建议先处理副本数为 0 的已下线应用验证流程,再推及在线应用。

bash
# 筛出副本数为 0、且仍使用旧仓库的 Deployment
kubectl get deployments --all-namespaces \
  -o=jsonpath="{range .items[?(@.spec.replicas==0)]}{.metadata.namespace}{' '}{.metadata.name}{' '}{.spec.template.spec.containers[*].image}{'\n'}{end}" \
  | grep '192.168.10.15:5000' > migrate_list

while read -r line; do
    ns=$(echo "$line" | awk '{print $1}')
    deploy=$(echo "$line" | awk '{print $2}')
    image=$(echo "$line" | awk '{print $3}')
    new_image=$(echo "$image" | sed 's|192\.168\.10\.15:5000|harbor\.example\.com:60000|')

    echo "ns=$ns deploy=$deploy"
    echo "  old: $image"
    echo "  new: $new_image"

    kubectl patch deployment "$deploy" -n "$ns" --type=json \
      -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value": "'"$new_image"'"}]'
done < migrate_list

如需在新仓库侧准备认证(镜像推送/拉取账号),一律用环境变量,不要把密码写进脚本:

bash
docker login -u "$HARBOR_USER" -p "$HARBOR_PASS" harbor.example.com:60000

注意事项

  • --type=json 的 patch 路径中 /containers/0/image 只改第一个容器;多容器 Deployment 需要按容器索引逐个 patch,或改用 kubectl set image deployment/<name> <container>=<image> -n <ns>
  • jsonpath 中的 [?(@.spec.replicas==0)] 是过滤器语法,等价于在服务端输出后再筛,能省一层 grep。
  • 同步镜像时 sort -u 去重非常重要,大集群中同一镜像被几十个 Deployment 引用是常态。
  • err_image_list 里的失败项(网络抖动、镜像层损坏等)要人工二次处理,不能默认成功。
  • 修改 image 会触发滚动更新,在线应用务必分批进行并观察业务指标。

三、批量设置 requests/limits

用途说明

给全集群(或指定范围)的 Deployment 统一补齐 requests,解决"应用裸奔没有资源声明导致调度混乱"的问题。

使用场景

  • 集群治理:统一给所有应用加上最低 requests(如 100m/256Mi)
  • 新接入集群的历史应用批量补资源声明

脚本

bash
# 值以实际容量规划为准,这里只是兜底值
for ns in $(kubectl get ns --no-headers | awk '{print $1}'); do
    for deploy in $(kubectl get deploy -n "$ns" --no-headers | awk '{print $1}'); do
        echo "$ns $deploy"
        kubectl set resources deployment "$deploy" \
          --requests=cpu=100m,memory=256Mi -n "$ns"
    done
done

如需同时设置 limits,追加 --limits=cpu=500m,memory=512Mi 即可。

注意事项

  • 与原文版本相比,这里用 --no-headers 替代了手工判断 NAME 表头的写法,更干净。
  • --requests 会覆盖该 Deployment 所有容器的 requests(多容器时每个容器都会被设为同样的值),精细化场景请用 kubectl patch 按容器名设置。
  • 修改会触发滚动更新,生产环境建议先圈定命名空间范围灰度执行。
  • 只改 requests 不改 limits 是安全做法;盲目加 limits 可能引发 OOMKill 或 CPU throttle。

四、批量绑定 imagePullSecret 到 ServiceAccount

用途说明

私有镜像仓库启用认证后,需要在各命名空间创建 docker-registry secret,并绑定到 ServiceAccount,让该命名空间下的 Pod 自动携带拉取凭据,免去每个 Deployment 单独配置 imagePullSecrets。

使用场景

  • Harbor 开启私有项目后,批量给全集群命名空间授权
  • 新命名空间开通时初始化拉取凭据

脚本

创建 secret(凭据用环境变量传入):

bash
URL="harbor.example.com:60000"
SECRET_NAME="harbor-reg"
ns="business-a"

kubectl create secret docker-registry "$SECRET_NAME" \
  --docker-server="$URL" \
  --docker-username="xxx" \
  --docker-password="xxx" \
  --docker-email="ops@example.com" \
  -n "$ns"

批量绑定到所有命名空间的 default ServiceAccount:

bash
for ns in $(kubectl get ns --no-headers | awk '{print $1}'); do
    kubectl patch serviceaccount default \
      -p '{"imagePullSecrets": [{"name": "harbor-reg"}]}' -n "$ns"
done

绑定到所有命名空间下的所有 ServiceAccount(更彻底,前提是每个命名空间都已创建同名 secret):

bash
for ns in $(kubectl get ns --no-headers | awk '{print $1}'); do
    echo "###### $ns ######"
    for sa in $(kubectl get serviceaccount -n "$ns" --no-headers | awk '{print $1}'); do
        echo "  patch sa: $sa"
        kubectl patch serviceaccount "$sa" \
          -p '{"imagePullSecrets": [{"name": "harbor-reg"}]}' -n "$ns"
    done
done

注意事项

  • secret 是命名空间级资源,每个命名空间都要先创建同名 secret 再绑定,否则 Pod 会报 secret not found
  • imagePullSecrets 的 patch 是整体覆盖,若 SA 上已有其他 secret,需要先读出来合并后再 patch。
  • 已在运行的 Pod 不会自动生效,需要重启(删除重建)后才会带上新凭据。

五、异常 Pod 批量处理(重启 / 副本置 0 / 强删)

5.1 找出拉不到镜像的 Pod 及其镜像地址

bash
kubectl get pods -A -o json | \
  jq '.items[]
      | select(.status.phase == "Pending")
      | select(.status.containerStatuses[].state.waiting.reason == "ImagePullBackOff")
      | {namespace: .metadata.namespace, name: .metadata.name, images: [.spec.containers[].image]}' \
  > image_pull_err_list.json

jq 技巧:两个 select 串联做条件过滤,比 grep 文本匹配可靠得多——直接读结构化状态字段,不会被 Pod 名字里碰巧含有 "ImagePullBackOff" 字样的情况干扰。

5.2 异常应用副本置 0

适用于"镜像已下架/依赖故障,应用起不来,但又不能直接删 Deployment"的止血场景。

bash
kubectl get pod -A | grep ImagePullBackOff  >  cur_scale0_list
kubectl get pod -A | grep ErrImagePull     >> cur_scale0_list
kubectl get pod -A | grep Evicted          >> cur_scale0_list

while read -r line; do
    ns=$(echo "$line" | awk '{print $1}')
    pod=$(echo "$line" | awk '{print $2}')
    # Deployment 管理的 Pod 名形如 <deploy>-<rs-hash>-<pod-hash>,去掉最后两段即 deploy 名
    d1=${pod%-*}
    deploy=${d1%-*}
    echo "kubectl scale deploy $deploy --replicas=0 -n $ns"
    kubectl scale deploy "$deploy" --replicas=0 -n "$ns"
done < cur_scale0_list

${pod%-*} 是 bash 参数扩展:从变量末尾删除最短的 -* 匹配,连用两次就剥掉 Pod 名的两段随机后缀。局限性:如果 Deployment 名本身含 -,剥出来的名字可能不对,执行前务必用 echo 核对。

5.3 批量强删非正常 Pod

bash
# 按需选择过滤条件:Evicted / Terminating / OutOfcpu / ContainerStatusUnknown / Error
kubectl get pod -A | grep Evicted > abnormal_pod_list
# 也可以按节点 IP 过滤某台问题节点上的 Pod:
# kubectl get pod -A -owide | grep '192.168.10.79' > abnormal_pod_list

while read -r line; do
    ns=$(echo "$line" | awk '{print $1}')
    pod=$(echo "$line" | awk '{print $2}')
    echo "$ns $pod"
    kubectl delete pod "$pod" -n "$ns" --force --grace-period=0
done < abnormal_pod_list

5.4 指定命名空间/节点批量重启 Pod

bash
# 删除某命名空间下所有 Pod(由控制器重建,相当于批量重启)
ns="business-a"
for pod in $(kubectl get pod -n "$ns" --no-headers | awk '{print $1}'); do
    echo "$pod"
    kubectl delete pod "$pod" -n "$ns" --force --grace-period=0
done

# 随机删除某台节点上的部分 Pod(用于节点负载均衡或故障演练)
node_ip="192.168.10.17"
kubectl get pod -owide -n default | grep "$node_ip" \
  | awk '{print $1}' | head -n 30 \
  | xargs -r kubectl delete pod -n default --force --grace-period=0

注意事项

  • --force --grace-period=0 只是从 API Server 删除记录,节点上的容器可能仍在运行;若 kubelet 本身异常,需要登节点处理容器运行时。
  • xargs-r(GNU 扩展):输入为空时不执行命令,避免误删。
  • DaemonSet 的 Pod 置 0 无效(没有副本概念),强删后会立即重建。
  • 批量操作前把清单落盘(如 abnormal_pod_list),出事时能回溯。

六、清除 Deployment 的 nodeName/nodeSelector 残留

用途说明

历史遗留或人工调试时给 Deployment 加了 nodeName/nodeSelector,导致应用被钉死在固定节点,节点下线或维护时无法漂移。批量扫描并清除这些绑定。

使用场景

  • 节点退役前,发现一批应用因 nodeSelector 无法调度走
  • 生产同步测试环境后,清理指向旧节点的绑定残留

脚本

bash
#!/bin/bash
# 清理所有 Deployment 上的 nodeName/nodeSelector

kubectl get deploy -A --no-headers > all_deploy_list

while read -r line; do
    ns=$(echo "$line" | awk '{print $1}')
    deploy=$(echo "$line" | awk '{print $2}')

    echo "###### $deploy in $ns ######"

    nodename=$(kubectl get deploy "$deploy" -n "$ns" -o=jsonpath='{.spec.template.spec.nodeName}')
    nodeselector=$(kubectl get deploy "$deploy" -n "$ns" -o=jsonpath='{.spec.template.spec.nodeSelector}')

    # nodeName 或 nodeSelector 任一存在则清除
    if [ -n "$nodename" ] || [ -n "$nodeselector" ]; then
        echo "Patching $deploy in $ns (nodeName: $nodename, nodeSelector: $nodeselector)"
        kubectl patch deployment "$deploy" -n "$ns" --type merge \
            --patch '{"spec":{"template":{"spec":{"nodeName":"", "nodeSelector":null}}}}'
    else
        echo "No nodeName or nodeSelector found, skipping."
    fi
done < all_deploy_list

rm -f all_deploy_list

注意事项

  • merge patch 中 nodeName: ""nodeSelector: null 语义不同:字符串字段置空串、map 字段置 null,两者都不能省略,否则清不干净。
  • 清除绑定后 Deployment 会触发滚动更新,Pod 重新调度到任意可用节点,注意目标节点资源是否充足。
  • 执行结果建议与第一节导出的 CSV 对照复查(nodeName/nodeSelector 列应全为 null/空)。

七、强制删除卡死的 PV/PVC(finalizers 处理)

用途说明

PVC 卡在 Terminating 状态,通常是因为 kubernetes.io/pvc-protection finalizer 等待 Pod 释放,而 Pod 早已不存在。通过置空 finalizers 强制释放。

使用场景

  • 命名空间删除后残留 PVC/PV 无法回收
  • 存储后端已删除卷,K8s 侧对象卡死

脚本

bash
pvc="my-pvc-name"
ns="business-a"

# 确认 PVC 所在命名空间和状态
kubectl get pvc -A | grep "$pvc"

# 置空 finalizers,绕过保护机制
kubectl patch pvc "$pvc" -n "$ns" \
  -p '{"metadata":{"finalizers":null}}'

kubectl delete pvc "$pvc" -n "$ns"

PV 卡死同理:

bash
pv="my-pv-name"
kubectl patch pv "$pv" -p '{"metadata":{"finalizers":null}}'
kubectl delete pv "$pv"

注意事项

  • 置空 finalizers 等于跳过所有清理钩子:后端存储上的数据不会被自动回收,需人工确认卷已处理,否则会造成存储泄漏。
  • 强删前确认没有活跃 Pod 正在挂载该卷,否则会造成数据损坏。
  • PV/PVC 强删是最后手段,常规流程应先删 Pod 再删 PVC,让保护机制正常走完。

八、集群副本手动均衡与副本数限制(辅助脚本)

8.1 节点间副本均衡

节点扩缩容后,Pod 分布可能不均。通过对节点 cordon/uncordon 加删除部分 Pod 的方式触发重新调度:

bash
ips="192.168.10.79 192.168.10.80"

# 先 cordon 再 uncordon,刷新节点调度状态
for ip in $ips; do kubectl cordon "$ip"; done
for ip in $ips; do kubectl uncordon "$ip"; done

# 统计各节点 Pod 数量
kubectl get pod -A -owide > all_pod
for ip in $ips; do
    echo -n "$ip: "
    grep "$ip" all_pod | wc -l
done

# 将负载偏高节点上的部分 Pod 删除,让调度器重新分布
grep '192.168.10.79' all_pod > mv_pod_list
for pod in $(head -n 20 mv_pod_list | awk '{print $2}'); do
    kubectl delete pod "$pod" -n default --force --grace-period=0
done

注意:上面删除 Pod 的命名空间写死为 default 是原文的简化写法,实际应从 mv_pod_list 第一列取命名空间。另外更稳妥的均衡方式是用 descheduler 组件,手工删 Pod 只适合应急。

8.2 测试环境副本数看门狗

限制测试环境任何 Deployment 副本数不超过 2,防止资源被无意打满:

bash
while true; do
    ns="default"
    kubectl get deploy -n "$ns" --no-headers > deploy_list

    while read -r line; do
        deploy=$(echo "$line" | awk '{print $1}')
        num=$(echo "$line" | awk '{print $2}')   # READY 列之外的 DESIRED 副本数
        # --no-headers 输出列: NAME READY UP-TO-DATE AVAILABLE AGE,取 UP-TO-DATE 或按需要调整
        if [ "$num" -gt 2 ] 2>/dev/null; then
            echo "$line"
            kubectl scale deploy "$deploy" --replicas=2 -n "$ns"
        fi
    done < deploy_list

    sleep 2h
done

注意:原文按第 4 列取副本数对应的是带表头的 AVAILABLE 列,使用 --no-headers 后列序为 NAME READY UP-TO-DATE AVAILABLE AGE,脚本中已相应调整,使用前请先用一条 kubectl get deploy 输出核对列位置。该脚本为守护式循环,建议放 tmux 或 systemd 里跑,不要在登录会话中裸跑。

故障速查表

现象可能原因处理
Pod 一直 Pending,reason 为 ImagePullBackOff镜像仓库地址不可达或未认证用 5.1 的 jq 命令捞出镜像地址;确认 secret 已创建并绑定 SA(第四节)
Deployment 改完 image 仍拉旧镜像patch 只改了 containers[0],多容器未覆盖kubectl set image 按容器名逐个设置
PVC 卡在 Terminatingpvc-protection finalizer 等待 Pod 释放确认无挂载后置空 finalizers(第七节),并人工回收后端卷
Pod 强删后又出现属 DaemonSet/Deployment 管理先 scale 到 0(5.2)或删控制器,再删 Pod
应用无法调度、事件提示 node selector 不匹配nodeName/nodeSelector 残留用第六节脚本批量清除绑定
批量 patch SA 后 Pod 仍拉镜像失败该命名空间没有同名 secret,或旧 Pod 未重建每个命名空间都创建 secret;删除 Pod 让其重建
jq 导出 CSV 出现 null 或中断个别对象缺字段检查是否漏了 // 默认值和 []? 容错写法
xargs 在输入为空时报错未加 -rxargs -r,空输入时不执行
副本均衡删 Pod 后仍集中调度器偏好或资源倾斜考虑 descheduler 组件做持续均衡