Kubernetes污点与容忍:避免普通Pod占用GPU节点

admin 2026-08-04 06:16:22 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文系统阐述了如何利用Kubernetes污点与容忍机制避免普通Pod占用GPU节点资源。核心结论是仅靠nodeSelector无法阻止非GPU工作负载调度至GPU节点,需通过给节点添加dedicated=gpu:NoSchedule污点并仅为必要工作负载添加容忍来实现隔离。关键发现包括实施前需诊断节点现状、从NoSchedule起步避免立即驱逐、需为DaemonSet等系统组件补齐容忍。可操作建议是遵循文中从诊断、灰度试点到监控回滚的完整步骤。 综合评分: 87 文章分类: 实战经验,云安全,解决方案


cover_image

Kubernetes 污点与容忍:避免普通 Pod 占用 GPU 节点

点击关注 👉 点击关注 👉

马哥Linux运维

2026年7月28日 20:24 广东

在小说阅读器读本章

去阅读

Kubernetes 污点与容忍:避免普通 Pod 占用 GPU 节点

GPU 节点最常见的资源浪费,并不是 GPU 利用率低,而是无关的 Web、任务调度器、日志处理器甚至临时调试 Pod 被调度进去,先占走 CPU、内存、临时磁盘、Pod 数配额和网络连接。等真正的训练或推理工作负载提交时,调度器会因为 Insufficient cpuInsufficient 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 <命名空间> \
&nbsp; -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 <命名空间> | \
&nbsp; sed -n&nbsp;'/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节点名> \
&nbsp; -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&nbsp;'.items[] |
&nbsp; .metadata.namespace as $ns |
&nbsp; .metadata.name as $pod |
&nbsp; .spec.containers[] |
&nbsp; [$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&nbsp;'/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&nbsp;'/Events:/,$p'

事件里若出现 node(s) had untolerated taint,说明已有污点参与调度;若出现 Insufficient cpuInsufficient 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:&nbsp;v1
kind:&nbsp;Pod
metadata:
&nbsp;&nbsp;name:&nbsp;toleration-semantics-check
&nbsp;&nbsp;namespace:&nbsp;<命名空间>
spec:
&nbsp;&nbsp;restartPolicy:&nbsp;Never
&nbsp;&nbsp;tolerations:
&nbsp; &nbsp;&nbsp;-&nbsp;key:&nbsp;dedicated
&nbsp; &nbsp; &nbsp;&nbsp;operator:&nbsp;Exists
&nbsp; &nbsp; &nbsp;&nbsp;effect:&nbsp;NoSchedule
&nbsp;&nbsp;containers:
&nbsp; &nbsp;&nbsp;-&nbsp;name:&nbsp;pause
&nbsp; &nbsp; &nbsp;&nbsp;image:&nbsp;registry.k8s.io/pause:3.9
&nbsp; &nbsp; &nbsp;&nbsp;resources:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;requests:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;cpu:&nbsp;10m
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;memory:&nbsp;16Mi

9. 删除测试 Pod 前先确认它不是业务对象

bash

kubectl get pod toleration-semantics-check -n <命名空间> \
&nbsp; -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 <命名空间> \
&nbsp; --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 \
&nbsp; -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 <命名空间> \
&nbsp; workload.example.com/accelerator=nvidia --overwrite

--overwrite 会改写同名标签,执行前先用 kubectl get node ... --show-labels 核对旧值。标签修改会立即影响使用该 selector/affinity 的新调度,通常不会迁移已运行 Pod;因此它适合先在一台有冗余容量的节点灰度。

13. 备份节点污点和标签,作为回滚输入

bash

kubectl get node <GPU节点名> -n <命名空间> -o yaml \
&nbsp; >&nbsp;"node-<GPU节点名>-before-gpu-isolation.yaml"

此文件包含节点标签和污点的变更前状态,应保存到受控变更目录或工单附件,避免放入包含凭据的公共仓库。它是回滚时恢复原有 key/value 的依据;不要把节点完整 YAML 直接 kubectl apply 回去,因为 status、providerID 和控制器管理字段不适合回写。

14. 先为目标节点添加 NoSchedule 污点

bash

kubectl taint node <GPU节点名> -n <命名空间> \
&nbsp; dedicated=gpu:NoSchedule

这是一次有调度影响的变更:没有容忍的新增或重建 Pod 将不能进入该节点;已运行 Pod 不会被 NoSchedule 驱逐。执行前应确认至少有一个非 GPU 节点能承接普通业务的 requests,并确认目标 GPU 工作负载模板已经包含正确容忍。

15. 立即核对污点已生效且没有误用 NoExecute

bash

kubectl get node <GPU节点名> -n <命名空间> \
&nbsp; -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:&nbsp;apps/v1
kind:&nbsp;Deployment
metadata:
&nbsp;&nbsp;name:&nbsp;<工作负载名>
&nbsp;&nbsp;namespace:&nbsp;<命名空间>
spec:
&nbsp;&nbsp;replicas:&nbsp;1
&nbsp;&nbsp;selector:
&nbsp; &nbsp;&nbsp;matchLabels:
&nbsp; &nbsp; &nbsp;&nbsp;app:&nbsp;<工作负载名>
&nbsp;&nbsp;template:
&nbsp; &nbsp;&nbsp;metadata:
&nbsp; &nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;app:&nbsp;<工作负载名>
&nbsp; &nbsp;&nbsp;spec:
&nbsp; &nbsp; &nbsp;&nbsp;nodeSelector:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;workload.example.com/accelerator:&nbsp;nvidia
&nbsp; &nbsp; &nbsp;&nbsp;tolerations:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;key:&nbsp;dedicated
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;operator:&nbsp;Equal
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;value:&nbsp;gpu
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;effect:&nbsp;NoSchedule
&nbsp; &nbsp; &nbsp;&nbsp;containers:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;name:&nbsp;inference
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;image:&nbsp;<镜像地址>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;resources:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;requests:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;cpu:&nbsp;"4"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;memory:&nbsp;16Gi
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;nvidia.com/gpu:&nbsp;"1"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;limits:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;cpu:&nbsp;"8"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;memory:&nbsp;32Gi
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;nvidia.com/gpu:&nbsp;"1"

先把 <镜像地址> 与业务实际启动参数补全,再进入变更流程。nodeSelector 是硬约束,适用于只有一类 GPU 节点的简单情况;如果需要表达多型号优先级、排除故障域或跨区域策略,改用 requiredDuringSchedulingIgnoredDuringExecution 节点亲和性,但不要同时添加相互冲突的 selector。

17. 应用前进行服务端 dry-run

bash

kubectl apply --dry-run=server -n <命名空间> \
&nbsp; -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 \
&nbsp; >&nbsp;"deployment-<工作负载名>-before-gpu-isolation.yaml"

导出后只编辑 spec.template.spec 中的 nodeSelectortolerations 和 resources,避免把运行时 status 一并提交。对 GitOps 管理对象,应修改 Git 中的声明源而不是直接 apply;否则下一次同步会覆盖手工变更。

20. 用 JSON Patch 精确添加容忍(适合临时应急)

bash

kubectl patch deployment <工作负载名> -n <命名空间> --type='json'&nbsp;-p='[
&nbsp; {
&nbsp; &nbsp; "op": "add",
&nbsp; &nbsp; "path": "/spec/template/spec/tolerations/-",
&nbsp; &nbsp; "value": {
&nbsp; &nbsp; &nbsp; "key": "dedicated",
&nbsp; &nbsp; &nbsp; "operator": "Equal",
&nbsp; &nbsp; &nbsp; "value": "gpu",
&nbsp; &nbsp; &nbsp; "effect": "NoSchedule"
&nbsp; &nbsp; }
&nbsp; }
]'

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 | \
&nbsp; jq -r&nbsp;'.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 <命名空间> \
&nbsp; --field-selector involvedObject.kind=Pod,involvedObject.name=<Pod名> \
&nbsp; --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 <命名空间> \
&nbsp; -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 <命名空间> \
&nbsp; -o jsonpath='{range .metadata.ownerReferences[*]}{.kind}{"/"}{.name}{"\n"}{end}'

只有确认 Deployment 名称后,才修改其 Pod 模板。若 ownerReference 为空,先判断是否为手工 Pod;它不会自动迁移,需与创建者确认是否可删除或改为控制器管理。

25. 检查非 GPU 节点的可承载请求和污点

bash

kubectl describe node <普通节点名> -n <命名空间> | \
sed -n&nbsp;'/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:
&nbsp;&nbsp;template:
&nbsp; &nbsp;&nbsp;spec:
&nbsp; &nbsp; &nbsp;&nbsp;affinity:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;nodeAffinity:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;requiredDuringSchedulingIgnoredDuringExecution:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;nodeSelectorTerms:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;matchExpressions:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;key:&nbsp;workload.example.com/accelerator
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;operator:&nbsp;DoesNotExist

这条规则对普通业务有更强的自解释性:即使有人误加了 dedicated=gpu 容忍,它仍不能进入带 GPU 标签的节点。代价是所有普通节点都必须没有该标签;如果混合节点池有不同标识,需改为选择明确的普通节点标签,避免误排除。

27. 灰度重启一个普通 Deployment 以触发重新调度

bash

kubectl rollout restart deployment/<普通工作负载名> -n <命名空间>
kubectl rollout status deployment/<普通工作负载名> -n <命名空间> --timeout=10m

这是高影响操作:它会按 Deployment 滚动策略替换 Pod。执行前检查 maxUnavailablemaxSurge、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 <命名空间> \
&nbsp; -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 \
&nbsp; -o json | jq -r&nbsp;'.items[] | .metadata.name as $n |
&nbsp; (.spec.taints // [])[]? | select(.key=="dedicated") |
&nbsp; [$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:&nbsp;templates.gatekeeper.sh/v1
kind:&nbsp;ConstraintTemplate
metadata:
&nbsp;&nbsp;name:&nbsp;k8sallowgputoleration
spec:
&nbsp;&nbsp;crd:
&nbsp; &nbsp;&nbsp;spec:
&nbsp; &nbsp; &nbsp;&nbsp;names:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;kind:&nbsp;K8sAllowGPUToleration
&nbsp;&nbsp;targets:
&nbsp; &nbsp;&nbsp;-&nbsp;target:&nbsp;admission.k8s.gatekeeper.sh
&nbsp; &nbsp; &nbsp;&nbsp;rego:&nbsp;|
&nbsp; &nbsp; &nbsp; &nbsp; package k8sallowgputoleration
&nbsp; &nbsp; &nbsp; &nbsp; violation[{"msg": msg}] {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; input.review.kind.kind == "Pod"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; input.review.object.metadata.namespace != input.parameters.allowedNamespaces[_]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t := input.review.object.spec.tolerations[_]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t.key == "dedicated"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t.value == "gpu"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; msg := "dedicated=gpu toleration is only allowed in approved namespaces"
&nbsp; &nbsp; &nbsp; &nbsp; }

规则仅检查直接提交的 Pod;Deployment 等控制器最终创建 Pod 时也会经过准入。实际参数语法、audit 方式需以已安装 Gatekeeper 版本和团队策略为准。把该模板直接投产前,应先以约束的 enforcementAction: dryrun 检查命中对象。

33. 监测一个命名空间内带 GPU 容忍的 Pod 模板

bash

kubectl get deploy,statefulset,daemonset -n <命名空间> -o json | \
jq -r&nbsp;'.items[] |
&nbsp; select(any(.spec.template.spec.tolerations[]?; .key=="dedicated" and .value=="gpu")) |
&nbsp; [.kind,.metadata.name] | @tsv'

这是一条配置审计命令,不等价于运行态审计。它用于发现命名空间里哪些控制器能够越过 GPU 污点;清单应与明确的业务白名单一致。若需要跨命名空间审计,应在受控巡检环境中使用 -A 并保留结果归档。

34. Prometheus 查询 GPU 节点普通 Pod 密度

Prometheus 的指标名和标签由 kube-state-metrics 版本决定,以下以实际 exporter 暴露的指标为准。先用 Prometheus UI 或 /metrics 确认 kube_pod_infokube_node_labels 是否存在相应标签。

promql

count by (node, namespace) (
&nbsp; kube_pod_info
&nbsp; * on (node) group_left(label_workload_example_com_accelerator)
&nbsp; &nbsp; 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) (
&nbsp; 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&nbsp;-euo pipefail

NAMESPACE="<命名空间>"
GPU_LABEL='workload.example.com/accelerator=nvidia'

kubectl get nodes -n&nbsp;"${NAMESPACE}"&nbsp;-l&nbsp;"${GPU_LABEL}"&nbsp;\
&nbsp; -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}'&nbsp;|
while&nbsp;IFS=&nbsp;read&nbsp;-r node;&nbsp;do
&nbsp; [[ -z&nbsp;"${node}"&nbsp;]] &&&nbsp;continue
&nbsp;&nbsp;echo&nbsp;"==&nbsp;${node}&nbsp;=="
&nbsp; kubectl get pods -n&nbsp;"${NAMESPACE}"&nbsp;--field-selector&nbsp;"spec.nodeName=${node}"&nbsp;\
&nbsp; &nbsp; -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:&nbsp;v1
kind:&nbsp;Pod
metadata:
&nbsp;&nbsp;name:&nbsp;no-gpu-toleration-check
&nbsp;&nbsp;namespace:&nbsp;<命名空间>
spec:
&nbsp;&nbsp;restartPolicy:&nbsp;Never
&nbsp;&nbsp;nodeSelector:
&nbsp; &nbsp;&nbsp;workload.example.com/accelerator:&nbsp;nvidia
&nbsp;&nbsp;containers:
&nbsp; &nbsp;&nbsp;-&nbsp;name:&nbsp;pause
&nbsp; &nbsp; &nbsp;&nbsp;image:&nbsp;registry.k8s.io/pause:3.9
&nbsp; &nbsp; &nbsp;&nbsp;resources:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;requests:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;cpu:&nbsp;10m
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;memory:&nbsp;16Mi

这个测试专门要求 GPU 标签但不带容忍,所以预期是 Pending,而不是 Running。它验证污点实际生效;如果它 Running,说明污点缺失、key/value/effect 不一致,或 Pod 得到意外注入的容忍。测试完成后按前文对象名精确删除。

38. 查看测试 Pod 的调度事件并保留证据

bash

kubectl describe pod no-gpu-toleration-check -n <命名空间> | \
sed -n&nbsp;'/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 <命名空间> \
&nbsp; dedicated=gpu:NoSchedule-

末尾 - 表示删除该 key/value/effect 的污点。回滚会允许普通新 Pod 再次调度到该节点,不能解决已被驱逐或已被迁移的工作负载问题;因此回滚后仍要检查业务副本、PDB、告警和节点压力。若节点原先就有不同 value 的 dedicated 污点,应根据变更前备份精确恢复,而不是盲目删除。

41. 回滚 GPU 工作负载模板到上一个 Deployment 修订

bash

kubectl rollout&nbsp;history&nbsp;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节点名> \
&nbsp; -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 节点》

评论:0   参与:  0