Operator 与 Operand——按节点逐个开关六个 DaemonSet
一句话总结
GPU Operator 启动的十几个 Pod 并不是一个程序,而是 Operator 读取 ClusterPolicy 之后展开的多个 DaemonSet,每个 DaemonSet 都通过 nodeSelector 查看 nvidia.com/gpu.deploy.<이름>(占位符为名称)标签,所以可以按节点为单位开启和关闭。
为什么需要它
在 Kubernetes 中使用 GPU,节点上需要具备好几样东西。加载内核模块的驱动,在容器运行时中注册 GPU 运行时的 Container Toolkit,向 kubelet 报告 nvidia.com/gpu 资源的设备插件,把硬件信息转换为标签的 GPU Feature Discovery,导出指标的 DCGM Exporter,确认安装是否正确的验证器,如果使用 MIG,还有 MIG Manager。
如果手工来做,就得在每个节点上按顺序进行安装、升级和回滚。Operator 模式在这里加入了“把期望状态写在一个对象里,控制器就会去对齐”这一点。这个对象就是 ClusterPolicy,控制器读取它之后创建出来的东西就是 operand。
把术语理清一次,其余就容易了。Operator 是监视 ClusterPolicy 的一个控制器 Pod。operand 是 Operator 创建并管理的工作负载,也就是上面列出的那些 DaemonSet。出了事故时,“Operator 挂了”和“operand 没有启动”是完全不同的问题,要查看的地方也不同。
工作原理
operand 几乎全都是 DaemonSet。DaemonSet 并不是“每个节点一个”,而是 “每个符合条件的节点一个”,这个条件就是 nodeSelector。GPU Operator 会在这里挂上自己的标签。
| operand | 启用它的标签 |
|---|---|
| 驱动 | nvidia.com/gpu.deploy.driver |
| Container Toolkit | nvidia.com/gpu.deploy.container-toolkit |
| 设备插件 | nvidia.com/gpu.deploy.device-plugin |
| GPU Feature Discovery | nvidia.com/gpu.deploy.gpu-feature-discovery |
| DCGM Exporter | nvidia.com/gpu.deploy.dcgm-exporter |
| 验证器 | nvidia.com/gpu.deploy.operator-validator |
由这个结构得到三个实用的旋钮。
一,把整个节点从 operand 中排除。 官方文档介绍了用 nvidia.com/gpu.deploy.operands=false 标签,让该节点上不放置 operand 的方法。
kubectl label nodes $NODE nvidia.com/gpu.deploy.operands=false
二,只排除一个 operand。 只想不安装驱动的话,就用 nvidia.com/gpu.deploy.driver=false。把标签设为 false 与彻底删除它,对人来说读起来不同,但对 nodeSelector 来说完全一样——只要值不是准确的 "true",条件就不匹配。不过写上 false 会留下“是有意排除的”这一信息,所以在运维中采用这种做法。
三,在整个集群中关闭 operand。 这不是靠标签,而是靠 ClusterPolicy(或 Helm 值)。如果环境中驱动已经装在主机上,就用 driver.enabled=false;如果 GPU 运行时已经注册,就用 toolkit.enabled=false。
撤下标签后,DaemonSet 控制器会删除该节点上的 Pod。这种明明没有改过 DaemonSet,Pod 却消失了的行为,起初让人吃惊,但只要知道控制器一直在让条件和实际保持一致,就觉得理所当然了。反过来,加上标签后,几秒钟内就会出现 Pod。
顺序是有的
operand 之间互为前提。大致是这样一条链。
드라이버 → 커널 모듈이 올라가고 /dev 에 장치가 생긴다
컨테이너 툴킷 → 런타임에 nvidia 런타임이 등록된다
장치 플러그인 → kubelet 에 nvidia.com/gpu 자원을 광고한다
GFD → 모델·장수·드라이버 판을 라벨로 붙인다
검증기 → 앞의 것들이 실제로 되는지 확인한다
其中的韩文依次说明:驱动加载内核模块,并在 /dev 下生成设备;Container Toolkit 在运行时中注册 nvidia 运行时;设备插件向 kubelet 上报 nvidia.com/gpu 资源;GFD 把型号、数量和驱动版本添加为标签;验证器确认前面这些是否真正生效。
所以手工添加标签时,会产生破坏这条链的组合。 最糟糕的是没有 Container Toolkit、只有设备插件在运行的节点。设备插件会上报资源,调度器看到这个数字就把 Pod 派过去,Pod 调度成功。但运行时不会把 GPU 挂到容器上,所以只有工作负载拿不到设备。这是一种任何地方都不报错、只有结果是错的故障,所以是否具备能发现这类组合的检查,决定了响应时间。
在现场相遇的样子
第一,“明明是 GPU 节点,Pod 却起不来”。 确认顺序是标签 → DaemonSet 的 desiredNumberScheduled → 该节点上的 Pod。这三处各自说明不同的事。如果没有标签,就是什么也没有发生;如果标签正确而 desiredNumberScheduled 偏低,就是选择器或污点不匹配;如果数字正确而没有 Pod,就是在该节点上没能启动。
第二,只看数字会被骗。 desiredNumberScheduled 是 DaemonSet 控制器判断应该启动的节点数。带有不被容忍的污点的节点,根本不会进入候选,所以这个数字里也不会计入。因此判断“该有的 operand 是否都有了”时,要看的不是数字,而是该节点上实际处于 Running 的 Pod。
第三,只隔离一个节点的下意识习惯。 当出现一个 GPU 有问题的节点时,除了把整个节点 cordon,还可以只撤下 operand。用一行标签就能让该节点的 operand 停止,由于不再上报资源,工作负载自然会去别的节点。这是不扰动集群、只摘下一台的办法。
第四,已经装好驱动的节点。 在本地数据中心,很多团队会把驱动预先装进镜像。如果在这样的节点上又启动驱动 operand,就会试图把内核模块加载两次,没有任何好处。如果整个集群都是这样,就用 driver.enabled=false;如果只有部分节点是这样,就给这些节点设置 nvidia.com/gpu.deploy.driver=false。官方文档明确写道,GPU Operator 不管理预先安装在主机上的驱动的生命周期——也就是说,这些节点上的驱动升级由你自己负责。
第五,本环境诚实的局限。 实验环境中既没有 GPU,也没有 GPU Operator,无法应用 ClusterPolicy。所以要亲手写出 Operator 本该展开出来的 DaemonSet。不过 DaemonSet 控制器和调度器是真实的,所以按标签创建和删除 Pod,以及带污点的节点被排除出候选,都不是模拟,而是真实的行为。驱动是否真的装好了,是看不到的,所以也不去看。
参考文档
- GPU Operator 入门(operand 放置标签与部署场景):https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/getting-started.html
- 安装 GPU Operator 与 Chart 值(driver.enabled、toolkit.enabled):https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/install-gpu-operator.html
- GPU Operator 概述:https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/overview.html
- DaemonSet:https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/
- 把 Pod 放置到节点上(nodeSelector):https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/
下一项实验要做什么
用标签亲手放置 Operator 本该展开的五个 DaemonSet。看到有两个 GPU 节点,却只有一个节点启动了驱动;撤下一个标签,确认 Pod 消失;只靠手工添加标签,故意制造出没有 Container Toolkit、只有设备插件在运行的节点。然后制作判断“该有的 operand 是否都有了”和“是否存在顺序错乱的节点”的工具,并确认即使再加入一个节点,它也不会说谎。