文章总结: 本文系统阐述了如何利用Kubernetes污点与容忍机制避免普通Pod占用GPU节点资源。核心结论是仅靠nodeSelector无法阻止非GPU工作负载调度至GPU节点,需通过给节点添加dedicated=gpu:NoSchedule污点并仅为必要工作负载添加容忍来实现隔离。关键发现包括实施前需诊断节点现状、从NoSchedule起步避免立即驱逐、需为DaemonSet等系统组件补齐容忍。可操作建议是遵循文中从诊断、灰度试点到监控回滚的完整步骤。 综合评分: 87 文章分类: 实战经验,云安全,解决方案
Kubernetes 污点与容忍:避免普通 Pod 占用 GPU 节点
点击关注 👉 点击关注 👉
马哥Linux运维
2026年7月28日 20:24 广东
在小说阅读器读本章
去阅读
Kubernetes 污点与容忍:避免普通 Pod 占用 GPU 节点
GPU 节点最常见的资源浪费,并不是 GPU 利用率低,而是无关的 Web、任务调度器、日志处理器甚至临时调试 Pod 被调度进去,先占走 CPU、内存、临时磁盘、Pod 数配额和网络连接。等真正的训练或推理工作负载提交时,调度器会因为 Insufficient cpu、Insufficient memory 或 Too many pods 拒绝它;更糟的是推理服务已被安排到 GPU 节点,却被旁路组件挤压了宿主机内存,最后演变成节点 OOM 或驱动插件失联。
这类问题不能仅靠给 GPU Pod 加 nodeSelector 解决。nodeSelector 只表达“GPU 工作负载可以去哪里”,并不表达“其他工作负载不能去哪里”。污点(Taint)和容忍(Toleration)正好补上这个反向约束:节点主动排斥没有匹配容忍的 Pod。本文以 Kubernetes 原生调度器为主,给出从现状取证、灰度加污点、为必要系统组件补齐容忍,到验证、监控和回滚的完整操作。
文中 <命名空间>、<GPU节点名>、<GPU节点标签值>、<工作负载名> 都是占位符,首次执行前要替换为实际值。所有示例使用 kubectl -n <命名空间>;对节点这类集群级资源,-n 不改变对象范围,但保留它可以避免脚本中混用默认命名空间。
一、先判断问题是否真是“普通 Pod 占用 GPU 节点”
不要一开始就给所有 GPU 节点打 NoSchedule。先证明当前节点池、标签、GPU 设备插件和业务调度规则是什么。集群里可能已有节点亲和性、Karpenter、Cluster Autoscaler、Admission Webhook 或 PodTopologySpread,直接改变约束会影响弹性扩容和控制面组件。
1. 列出节点及 GPU 资源
bash
kubectl get nodes -n <命名空间> \
-o custom-columns=NAME:.metadata.name,READY:.status.conditions[-1].type,GPU:.status.capacity.nvidia\\.com/gpu,TAINTS:.spec.taints
nvidia.com/gpu 是 NVIDIA device plugin 常用的扩展资源名;如果这里为空,不要先讨论污点,先确认设备插件、驱动和节点标签。READY 一列只是快速浏览,精确健康状态要看 Conditions。
2. 识别 GPU 节点的既有标签和污点
bash
kubectl describe node <GPU节点名> -n <命名空间> | \
sed -n '/Labels:/,/Annotations:/p;/Taints:/,/Unschedulable:/p;/Capacity:/,/Allocatable:/p'
这一步的证据包括:GPU 节点是否已有如 node.kubernetes.io/instance-type、云厂商实例标签或自定义 accelerator=nvidia 标签;是否已有 NoSchedule 污点;Allocatable 是否真的提供了 GPU。若标签由节点组或自动化控制器维护,后续要把规则放到节点组模板,而不是只手工改一台节点。
3. 按节点反查实际运行的 Pod
bash
kubectl get pods -n <命名空间> -o wide --field-selector spec.nodeName=<GPU节点名>
该命令只列出 <命名空间> 的业务 Pod。对平台组件所在的每个实际命名空间,分别替换 <命名空间> 重复执行;不要为了图省事省略 namespace。重点区分四类:GPU 训练/推理工作负载、必须驻留每个节点的 DaemonSet、平台必需组件,以及本不该在 GPU 节点的普通 Deployment、CronJob 和调试 Pod。不要把 DaemonSet 也当成“普通 Pod”,否则会误判变更风险。
4. 用资源请求而非实际瞬时利用率界定“普通 Pod”
bash
kubectl get pods -n <命名空间> --field-selector spec.nodeName=<GPU节点名> \
-o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,OWNER:.metadata.ownerReferences[0].kind,QOS:.status.qosClass
调度器依据 Pod 的 resources.requests 做放置,不依据 kubectl top 的瞬时使用率。因此一个实际 CPU 使用很低、但请求了 8 核和 16Gi 内存的普通 Pod 仍然可能阻塞 GPU 任务。ownerReferences 有助于找回源工作负载,而不是直接删除副本 Pod。
5. 导出候选 Pod 的 requests/limits 以便审计
bash
kubectl get pods -n <命名空间> --field-selector spec.nodeName=<GPU节点名> -o json | \
jq -r '.items[] |
.metadata.namespace as $ns |
.metadata.name as $pod |
.spec.containers[] |
[$ns,$pod,.name,(.resources.requests|tostring),(.resources.limits|tostring)] | @tsv'
需要本机已安装 jq。导出的表是变更前证据:如果普通 Pod 没有 GPU request 且来自可迁移控制器,才是污点策略的迁移对象。不要把“没有 GPU request”机械等同于“可迁移”;例如网络插件、监控采集、存储 CSI 节点插件通常不需要 GPU,但必须在所有节点运行。
6. 查看 GPU Pod 自身的调度约束
bash
kubectl get deployment <工作负载名> -n <命名空间> -o yaml | \
sed -n '/nodeSelector:/,/containers:/p;/affinity:/,/tolerations:/p;/tolerations:/,/containers:/p'
如果 GPU 工作负载现在仅靠 resources.limits.nvidia.com/gpu,它可能被调度到任何提供 GPU 的节点;如果有多类 GPU,必须额外通过标签或节点亲和性选中正确型号。反过来,容忍只允许 Pod 穿过污点,不保证它一定落到 GPU 节点,所以 GPU Pod 仍要保留选址约束。
7. 从 Pending 事件中确认现有调度阻塞原因
bash
kubectl describe pod <Pod名> -n <命名空间> | \
sed -n '/Events:/,$p'
事件里若出现 node(s) had untolerated taint,说明已有污点参与调度;若出现 Insufficient cpu、Insufficient memory 或 Too many pods,说明 GPU 节点容量被其他请求占用的判断有直接证据。只有基于这类事件、Pod requests 和节点 allocatable,才能把根因写成结论。
二、理解污点、容忍和效果的边界
污点写在节点上,格式由 key、可选 value 和 effect 组成。容忍写在 Pod 模板中。只有 key/value/operator/effect 匹配时,容忍才匹配污点。常见误解是“加了容忍就会调度到该节点”;事实是容忍仅移除一个排斥条件,节点选择、资源、亲和性、反亲和性、拓扑分布和优先级仍继续生效。
| effect | 对新 Pod | 对已运行 Pod | 适用场景 |
| — | — | — | — |
| NoSchedule | 无匹配容忍则不调度 | 不主动驱逐 | 为 GPU 节点建立常规隔离,首选 |
| PreferNoSchedule | 尽量不调度,但资源紧张时可落入 | 不主动驱逐 | 观测期或软隔离,不适合严格保护 |
| NoExecute | 无匹配容忍则不调度 | 无匹配容忍的既有 Pod 会被驱逐 | 节点异常隔离或严格清场,风险最高 |
对于“避免普通 Pod 占用 GPU 节点”,建议以 dedicated=gpu:NoSchedule 起步。它不清理存量 Pod,不会让刚修改规则的时刻演变为大量驱逐;通过滚动更新或受控迁移让普通 Pod 离开。确认需要在 GPU 节点运行的 DaemonSet、GPU Device Plugin、Node Feature Discovery、日志与监控组件是否已带容忍,再考虑更严格的策略。
8. 在测试命名空间验证容忍匹配语义
以下 Pod 只用于理解 Exists,不应直接用于生产。Exists 表示只要 key/effect 匹配,无论污点 value 是什么都可以容忍;它权限较宽,专用 GPU 业务通常应使用 Equal 精确限制 value。
yaml
apiVersion: v1
kind: Pod
metadata:
name: toleration-semantics-check
namespace: <命名空间>
spec:
restartPolicy: Never
tolerations:
- key: dedicated
operator: Exists
effect: NoSchedule
containers:
- name: pause
image: registry.k8s.io/pause:3.9
resources:
requests:
cpu: 10m
memory: 16Mi
9. 删除测试 Pod 前先确认它不是业务对象
bash
kubectl get pod toleration-semantics-check -n <命名空间> \
-o jsonpath='{.metadata.ownerReferences[*].kind}{"\n"}{.metadata.labels}{"\n"}'
测试 Pod 必须以独立命名、独立标签创建。删除资源是高风险操作,至少确认 namespace、对象名和 ownerReferences;没有 ownerReference 的独立测试 Pod 才可以删除。生产业务对象应在 Deployment/Job/CronJob 层修改。
10. 仅在确认后删除独立测试 Pod
bash
kubectl delete pod toleration-semantics-check -n <命名空间> \
--wait=true
这条删除仅适用于上一节创建的测试 Pod。不要用标签宽泛删除,例如不要对 -l app=<名称> 直接执行 delete,除非已通过 dry-run 和对象清单确认范围。
三、设计一条可维护的 GPU 节点隔离契约
一个可维护的方案需要同时定义节点侧和 Pod 侧的契约:节点以稳定标签标识用途,并加排斥污点;GPU 工作负载明确选择该标签、请求 GPU、精确容忍污点;普通业务没有容忍。若只给 GPU Deployment 补容忍,而节点标签与资源请求不规范,后续任何带宽泛容忍的 Pod 都可能越过边界。
推荐的最小契约如下:
text
节点标签:workload.example.com/accelerator=nvidia
节点污点:dedicated=gpu:NoSchedule
GPU 工作负载:nodeSelector + nvidia.com/gpu limit + dedicated=gpu 的 Equal 容忍
普通工作负载:不添加 dedicated=gpu 的容忍
节点级 DaemonSet:按实际需要审计后添加容忍
workload.example.com 是示例域名。生产中应使用团队拥有的 DNS 前缀,避免和云厂商、插件或其他平台团队使用的 key 冲突。若集群有 A100、H100、L40S 等不同能力的节点,不要把型号编码进同一个宽泛的 dedicated=gpu 约束;应叠加如 gpu.example.com/model=h100 标签用于精确选择。
11. 盘点目标节点是否来自同一个节点组
bash
kubectl get nodes -n <命名空间> -l workload.example.com/accelerator=nvidia \
-o custom-columns=NAME:.metadata.name,POOL:.metadata.labels.nodepool,INSTANCE:.metadata.labels.node\\.kubernetes\\.io/instance-type
只有标签已存在时才可使用选择器;若命令为空,先用实际标签替换。对于托管节点组、Cluster API 或自动伸缩节点池,应把 labels/taints 写入节点组声明或模板。手工 kubectl taint node 在节点替换、扩容和修复时不会自动继承。
12. 给一台非关键节点做标签试点
bash
kubectl label node <GPU节点名> -n <命名空间> \
workload.example.com/accelerator=nvidia --overwrite
--overwrite 会改写同名标签,执行前先用 kubectl get node ... --show-labels 核对旧值。标签修改会立即影响使用该 selector/affinity 的新调度,通常不会迁移已运行 Pod;因此它适合先在一台有冗余容量的节点灰度。
13. 备份节点污点和标签,作为回滚输入
bash
kubectl get node <GPU节点名> -n <命名空间> -o yaml \
> "node-<GPU节点名>-before-gpu-isolation.yaml"
此文件包含节点标签和污点的变更前状态,应保存到受控变更目录或工单附件,避免放入包含凭据的公共仓库。它是回滚时恢复原有 key/value 的依据;不要把节点完整 YAML 直接 kubectl apply 回去,因为 status、providerID 和控制器管理字段不适合回写。
14. 先为目标节点添加 NoSchedule 污点
bash
kubectl taint node <GPU节点名> -n <命名空间> \
dedicated=gpu:NoSchedule
这是一次有调度影响的变更:没有容忍的新增或重建 Pod 将不能进入该节点;已运行 Pod 不会被 NoSchedule 驱逐。执行前应确认至少有一个非 GPU 节点能承接普通业务的 requests,并确认目标 GPU 工作负载模板已经包含正确容忍。
15. 立即核对污点已生效且没有误用 NoExecute
bash
kubectl get node <GPU节点名> -n <命名空间> \
-o jsonpath='{range .spec.taints[*]}{.key}{"="}{.value}{":"}{.effect}{"\n"}{end}'
预期应出现 dedicated=gpu:NoSchedule。若出现 NoExecute,先停止后续滚动更新并按回滚章节移除错误污点;NoExecute 会对存量 Pod 产生驱逐影响,不能当作普通的调度测试。
四、让 GPU 工作负载“能进且只进”目标节点
GPU Deployment 的规范配置包含四件事:GPU limit、节点标签选择、精确容忍和合理资源 requests。Kubernetes 对扩展资源通常要求 GPU 在 limits 中声明;为了避免不同运行时的调度差异,通常把 GPU request/limit 使用一致的数量,并让 CPU/内存 requests 反映真实保留量。
16. GPU 推理 Deployment 的完整模板
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: <工作负载名>
namespace: <命名空间>
spec:
replicas: 1
selector:
matchLabels:
app: <工作负载名>
template:
metadata:
labels:
app: <工作负载名>
spec:
nodeSelector:
workload.example.com/accelerator: nvidia
tolerations:
- key: dedicated
operator: Equal
value: gpu
effect: NoSchedule
containers:
- name: inference
image: <镜像地址>
resources:
requests:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: "1"
limits:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "1"
先把 <镜像地址> 与业务实际启动参数补全,再进入变更流程。nodeSelector 是硬约束,适用于只有一类 GPU 节点的简单情况;如果需要表达多型号优先级、排除故障域或跨区域策略,改用 requiredDuringSchedulingIgnoredDuringExecution 节点亲和性,但不要同时添加相互冲突的 selector。
17. 应用前进行服务端 dry-run
bash
kubectl apply --dry-run=server -n <命名空间> \
-f gpu-deployment.yaml
服务端 dry-run 能经过 API 校验、准入控制和对象 schema 检查,但不会真实创建 Pod,也不能保证资源一定可调度。若集群启用了策略控制器,dry-run 很适合提前发现“禁止 privileged”“必须有 requests”等规则。
18. 创建或更新 GPU Deployment
bash
kubectl apply -n <命名空间> -f gpu-deployment.yaml
kubectl rollout status deployment/<工作负载名> -n <命名空间> --timeout=10m
rollout status 等待新 ReplicaSet 就绪。超时时不要反复 apply;先 describe 新 Pod,确认是镜像、PVC、GPU 资源还是污点未容忍导致 Pending。若这是已有生产服务,执行前要确认 PDB、副本冗余、流量摘除和灰度策略。
19. 对已有 Deployment 使用补丁前先导出当前模板
bash
kubectl get deployment <工作负载名> -n <命名空间> -o yaml \
> "deployment-<工作负载名>-before-gpu-isolation.yaml"
导出后只编辑 spec.template.spec 中的 nodeSelector、tolerations 和 resources,避免把运行时 status 一并提交。对 GitOps 管理对象,应修改 Git 中的声明源而不是直接 apply;否则下一次同步会覆盖手工变更。
20. 用 JSON Patch 精确添加容忍(适合临时应急)
bash
kubectl patch deployment <工作负载名> -n <命名空间> --type='json' -p='[
{
"op": "add",
"path": "/spec/template/spec/tolerations/-",
"value": {
"key": "dedicated",
"operator": "Equal",
"value": "gpu",
"effect": "NoSchedule"
}
}
]'
JSON Patch 假定 tolerations 数组已经存在;如果不存在,需先添加数组,或使用完整声明式 YAML。补丁适合紧急修复,但应随后回写 GitOps/Helm/Kustomize 源,否则配置漂移难以追踪。
21. 验证新 Pod 实际落点和 GPU request
bash
kubectl get pods -n <命名空间> -l app=<工作负载名> -o wide
kubectl get pods -n <命名空间> -l app=<工作负载名> -o json | \
jq -r '.items[] | [.metadata.name,.spec.nodeName,.spec.containers[].resources.requests["nvidia.com/gpu"]] | @tsv'
节点名应属于已打污点、带 GPU 标签的节点;GPU request 应与业务期望一致。只有两者都满足,才能说明“能进且只进”的第一部分成立。实际 GPU 利用率可通过 DCGM Exporter 等监控观察,但调度正确性不能用利用率替代。
22. 使用调度事件定位不能进入 GPU 节点的原因
bash
kubectl get events -n <命名空间> \
--field-selector involvedObject.kind=Pod,involvedObject.name=<Pod名> \
--sort-by='.lastTimestamp'
事件中 didn't match Pod's node affinity/selector 指向标签问题,had untolerated taint 指向容忍问题,Insufficient nvidia.com/gpu 指向设备资源不足或 GPU 已分配。不要在没有事件证据时猜测是污点导致的。
五、迁移普通工作负载,而不是冒险清空节点
加 NoSchedule 后,已经在 GPU 节点上的普通 Pod 仍会保留。正确做法是找回其控制器,确保其他节点有承载能力,通过滚动重建或伸缩迁移。不能直接删除裸 Pod 以外的对象,因为控制器可能马上创建一个新 Pod,而它可能仍因资源或亲和性回到错误位置。
23. 找出普通 Pod 的控制器来源
bash
kubectl get pod <Pod名> -n <命名空间> \
-o jsonpath='{range .metadata.ownerReferences[*]}{.kind}{"/"}{.name}{"\n"}{end}'
Deployment 创建的 Pod 通常先归属 ReplicaSet,需继续找到上层 Deployment;Job/CronJob、StatefulSet、DaemonSet 的迁移策略不同。DaemonSet 默认会尝试在每个满足 selector 的节点运行,不能用“迁出 GPU 节点”的标准判断。
24. 从 ReplicaSet 追溯到 Deployment
bash
kubectl get rs <ReplicaSet名> -n <命名空间> \
-o jsonpath='{range .metadata.ownerReferences[*]}{.kind}{"/"}{.name}{"\n"}{end}'
只有确认 Deployment 名称后,才修改其 Pod 模板。若 ownerReference 为空,先判断是否为手工 Pod;它不会自动迁移,需与创建者确认是否可删除或改为控制器管理。
25. 检查非 GPU 节点的可承载请求和污点
bash
kubectl describe node <普通节点名> -n <命名空间> | \
sed -n '/Allocatable:/,/System Info:/p;/Allocated resources:/,/Events:/p;/Taints:/,/Unschedulable:/p'
Allocated resources 是 kubelet 汇总,可能因 limits 允许超卖而与实际使用不同;迁移判断以普通 Pod 的 requests、可用 node 数和 PDB 为准。若普通节点容量不足,先扩容普通节点池或缩小可迁移副本的 requests,不能把驱逐作为容量规划替代。
26. 为普通 Deployment 显式排除 GPU 节点(可选的第二道防线)
yaml
spec:
template:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: workload.example.com/accelerator
operator: DoesNotExist
这条规则对普通业务有更强的自解释性:即使有人误加了 dedicated=gpu 容忍,它仍不能进入带 GPU 标签的节点。代价是所有普通节点都必须没有该标签;如果混合节点池有不同标识,需改为选择明确的普通节点标签,避免误排除。
27. 灰度重启一个普通 Deployment 以触发重新调度
bash
kubectl rollout restart deployment/<普通工作负载名> -n <命名空间>
kubectl rollout status deployment/<普通工作负载名> -n <命名空间> --timeout=10m
这是高影响操作:它会按 Deployment 滚动策略替换 Pod。执行前检查 maxUnavailable、maxSurge、readinessProbe、PDB 和后端容量;单副本或无就绪探针的服务应先扩到多副本或安排维护窗口。验证新 Pod 的节点名不再是 GPU 节点。
28. 对单副本服务先扩大副本再缩回
bash
kubectl scale deployment/<普通工作负载名> -n <命名空间> --replicas=2
kubectl rollout status deployment/<普通工作负载名> -n <命名空间> --timeout=10m
kubectl get pods -n <命名空间> -l app=<普通工作负载名> -o wide
只有在应用能安全水平扩展、许可证和依赖连接池允许的情况下才能这么做。先看到新副本在非 GPU 节点 Ready,再按业务允许的最小副本数缩回;不要把这套操作用于 StatefulSet、单主数据库或有本地持久卷约束的服务。
29. 审计 DaemonSet 是否需要 GPU 节点容忍
bash
kubectl get daemonset -n <命名空间> -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,DESIRED:.status.desiredNumberScheduled,READY:.status.numberReady
例如 CNI、CSI node plugin、日志采集或安全代理可能需要在 GPU 节点运行,但它们只有在节点已有污点时才需要容忍。GPU device plugin 更是关键依赖;如果污点后新 GPU 节点没有它,nvidia.com/gpu 将不再注册。逐个审计,而不要给所有 DaemonSet 加宽泛 operator: Exists。
30. 查看一个 DaemonSet 的模板污点容忍
bash
kubectl get daemonset <DaemonSet名> -n <命名空间> \
-o jsonpath='{range .spec.template.spec.tolerations[*]}{.key}{"="}{.value}{":"}{.effect}{" operator="}{.operator}{"\n"}{end}'
若 DaemonSet 确实必须覆盖 GPU 节点,可添加 dedicated=gpu:NoSchedule 的精确容忍。修改前先确认该 DaemonSet 的 image、权限和版本与平台标准一致;不要为“让它能跑”而容忍所有 NoSchedule 污点。
六、把规则固化到节点组和准入链路
手工在现有节点上成功,并不代表长期有效。GPU 节点会因自动扩容、缩容、故障替换和镜像升级被重新创建。必须在基础设施声明层为节点组加标签和污点;同时在应用交付层避免误用宽泛容忍。具体字段取决于云厂商和节点生命周期工具,下面给出不绑定某一云厂商的检查与策略思路。
31. 核对新增节点是否继承隔离契约
bash
kubectl get nodes -n <命名空间> -l workload.example.com/accelerator=nvidia \
-o json | jq -r '.items[] | .metadata.name as $n |
(.spec.taints // [])[]? | select(.key=="dedicated") |
[$n,.key,.value,.effect] | @tsv'
该查询只会输出既有 dedicated 污点的节点。节点组模板正确时,每台 GPU 节点都应有相同的 dedicated=gpu:NoSchedule;没有输出的节点是漂移信号。对于空节点列表,先检查 label selector 是否正确,不要把空结果解读为健康。
32. 用 OPA Gatekeeper 的约束模板拒绝普通命名空间的 GPU 容忍(可选)
以下为 Gatekeeper Rego 示例,只有已经部署 Gatekeeper 的集群才能使用。规则按 namespace allowlist 限制 dedicated=gpu 容忍;上线前应在 audit 模式观察违例,避免一次拒绝现有发布流水线。
yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowgputoleration
spec:
crd:
spec:
names:
kind: K8sAllowGPUToleration
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowgputoleration
violation[{"msg": msg}] {
input.review.kind.kind == "Pod"
input.review.object.metadata.namespace != input.parameters.allowedNamespaces[_]
t := input.review.object.spec.tolerations[_]
t.key == "dedicated"
t.value == "gpu"
msg := "dedicated=gpu toleration is only allowed in approved namespaces"
}
规则仅检查直接提交的 Pod;Deployment 等控制器最终创建 Pod 时也会经过准入。实际参数语法、audit 方式需以已安装 Gatekeeper 版本和团队策略为准。把该模板直接投产前,应先以约束的 enforcementAction: dryrun 检查命中对象。
33. 监测一个命名空间内带 GPU 容忍的 Pod 模板
bash
kubectl get deploy,statefulset,daemonset -n <命名空间> -o json | \
jq -r '.items[] |
select(any(.spec.template.spec.tolerations[]?; .key=="dedicated" and .value=="gpu")) |
[.kind,.metadata.name] | @tsv'
这是一条配置审计命令,不等价于运行态审计。它用于发现命名空间里哪些控制器能够越过 GPU 污点;清单应与明确的业务白名单一致。若需要跨命名空间审计,应在受控巡检环境中使用 -A 并保留结果归档。
34. Prometheus 查询 GPU 节点普通 Pod 密度
Prometheus 的指标名和标签由 kube-state-metrics 版本决定,以下以实际 exporter 暴露的指标为准。先用 Prometheus UI 或 /metrics 确认 kube_pod_info、kube_node_labels 是否存在相应标签。
promql
count by (node, namespace) (
kube_pod_info
* on (node) group_left(label_workload_example_com_accelerator)
kube_node_labels{label_workload_example_com_accelerator="nvidia"}
)
该查询统计 GPU 标签节点上的 Pod 数,而不是识别“普通 Pod”。若要排除系统 namespace,应按组织实际命名空间加过滤;PromQL 中错误的 label 名最常见,kube-state-metrics 会把 / 和 . 转换为下划线,必须以实际暴露结果验证。
35. 检测 GPU 节点的可分配 GPU 与已请求 GPU
promql
sum by (node) (kube_node_status_allocatable{resource="nvidia_com_gpu",unit="integer"})
-
sum by (node) (
kube_pod_container_resource_requests{resource="nvidia_com_gpu",unit="integer"}
)
部分 kube-state-metrics 版本可能使用不同 resource 标签值或没有 unit 标签,因此必须以实际 exporter 暴露的指标为准。该值适合观察调度层可用量,不代表显存剩余或 GPU 利用率;显存和计算利用率应接入 DCGM Exporter 指标单独监控。
36. 最小化日常巡检脚本
bash
#!/usr/bin/env bash
set -euo pipefail
NAMESPACE="<命名空间>"
GPU_LABEL='workload.example.com/accelerator=nvidia'
kubectl get nodes -n "${NAMESPACE}" -l "${GPU_LABEL}" \
-o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' |
while IFS= read -r node; do
[[ -z "${node}" ]] && continue
echo "== ${node} =="
kubectl get pods -n "${NAMESPACE}" --field-selector "spec.nodeName=${node}" \
-o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,PHASE:.status.phase
done
脚本只读,不会迁移或删除 Pod。将它接入巡检前应为运行身份授予最小 RBAC 权限:get/list nodes 和 pods;pods -A 需要跨命名空间读取权限。输出用于人工复核 owner 与业务属性,不建议直接据此自动删除资源。
七、验收、异常处理与回滚
验收不是“节点上多了一个污点”。至少确认四个结果:新增普通 Pod 不会进入 GPU 节点;GPU Pod 带精确容忍后能进入正确节点;已有平台 DaemonSet 健康;普通服务迁移过程中可用性和 PDB 没有受损。把这四项写入变更单,出现异常时才能快速判断是调度策略、容量还是应用自身问题。
37. 创建一个无容忍的测试 Pod,验证会被拒绝进入 GPU 节点
yaml
apiVersion: v1
kind: Pod
metadata:
name: no-gpu-toleration-check
namespace: <命名空间>
spec:
restartPolicy: Never
nodeSelector:
workload.example.com/accelerator: nvidia
containers:
- name: pause
image: registry.k8s.io/pause:3.9
resources:
requests:
cpu: 10m
memory: 16Mi
这个测试专门要求 GPU 标签但不带容忍,所以预期是 Pending,而不是 Running。它验证污点实际生效;如果它 Running,说明污点缺失、key/value/effect 不一致,或 Pod 得到意外注入的容忍。测试完成后按前文对象名精确删除。
38. 查看测试 Pod 的调度事件并保留证据
bash
kubectl describe pod no-gpu-toleration-check -n <命名空间> | \
sed -n '/Events:/,$p'
示例输出(仅用于说明判断方向,非真实环境结果)可能包含 node(s) had untolerated taint {dedicated: gpu}。如果同时出现资源不足,表示测试节点选择范围内还存在容量问题;污点验证应选择有余量的试点节点,或仅以事件中存在未容忍污点为通过依据。
39. 删除验收测试对象
bash
kubectl delete pod no-gpu-toleration-check -n <命名空间> --wait=true
执行前再确认对象名与 namespace。该操作删除的是独立验收 Pod;不应替换为删除 Deployment、ReplicaSet 或带通配标签的命令。
40. 紧急回滚:移除本次新增的污点
bash
kubectl taint node <GPU节点名> -n <命名空间> \
dedicated=gpu:NoSchedule-
末尾 - 表示删除该 key/value/effect 的污点。回滚会允许普通新 Pod 再次调度到该节点,不能解决已被驱逐或已被迁移的工作负载问题;因此回滚后仍要检查业务副本、PDB、告警和节点压力。若节点原先就有不同 value 的 dedicated 污点,应根据变更前备份精确恢复,而不是盲目删除。
41. 回滚 GPU 工作负载模板到上一个 Deployment 修订
bash
kubectl rollout history deployment/<工作负载名> -n <命名空间>
kubectl rollout undo deployment/<工作负载名> -n <命名空间> --to-revision=<修订号>
kubectl rollout status deployment/<工作负载名> -n <命名空间> --timeout=10m
只有确认目标 revision 是本次变更前的健康版本时才执行 undo。回滚会重建 Pod 并可能影响在线流量,执行前确认业务的流量切换、readiness、PDB 和镜像可用性。GitOps 环境应同步回滚声明源,避免控制器再次把已撤销的配置推回集群。
42. 变更后检查节点级关键 DaemonSet 覆盖
bash
kubectl get daemonset -n <命名空间> -o wide
kubectl get pods -n <命名空间> --field-selector spec.nodeName=<GPU节点名> \
-o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,READY:.status.containerStatuses[*].ready,PHASE:.status.phase
重点检查网络、存储、日志、监控和 GPU device plugin 的 Ready 状态。若 device plugin 不健康,节点 Allocatable 中的 nvidia.com/gpu 可能消失;此时应先恢复插件调度和节点健康,而不是修改业务 Pod 的 GPU request 规避问题。
结语
GPU 节点隔离的关键不是“打一个污点”,而是建立一组可验证的调度契约:节点以标签和 NoSchedule 污点声明专用用途,GPU 工作负载通过 selector、GPU requests/limits 和精确容忍进入,普通工作负载没有越过边界的容忍,节点级必要组件经过审计后覆盖。先做单节点灰度、保存变更前状态、根据调度事件取证,再把规则固化到节点组模板和交付策略,才能在节点扩缩和人员变动后持续有效。
文末阅读福利
仅目前来说,无论是运维人转型提升,还是零基础想转行IT,最好的岗位就是云计算运维&SRE岗位。
为了帮助大家早日快速入门云计算运维领域,给大家整理了一套【最新运维资料】高级运维工程师必备技能资料包(文末一键免费领取),内容有多详实丰富看下图!
1.38张最全工程师技能图谱
2.面试大礼包
3.Linux书籍
内容比较多,就不一一展示了
以上所有资料获取请扫码:
识别上方二维码
备注:2026最新运维资料
100%免费领取
(是扫码领取,不是在公众号后台回复,别看错了哦)
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:马哥Linux运维 点击关注 👉 点击关注 👉《Kubernetes 污点与容忍:避免普通 Pod 占用 GPU 节点》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论