主题
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').csvjq 写法解析
.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.jsonjq 技巧:两个 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_list5.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 卡在 Terminating | pvc-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 在输入为空时报错 | 未加 -r | 加 xargs -r,空输入时不执行 |
| 副本均衡删 Pod 后仍集中 | 调度器偏好或资源倾斜 | 考虑 descheduler 组件做持续均衡 |