使用节点标签实现 GPU 任务亲和性调度
解读
在国内互联网、金融、AI 公司面试中,这道题考察的是“如何把 GPU 资源当成一等公民”纳入 Docker Swarm 或 Kubernetes 的调度体系,而不是手动 ssh 到某台机器启动容器。
面试官真正想听的是:
- 你能否用节点标签把“有没有 GPU、GPU 型号、驱动版本、剩余卡数”等元数据标准化地打到节点上;
- 你能否在 Compose 文件或 stack yml 里用亲和性约束把任务精准地钉到满足条件的节点;
- 你能否保证调度结果可观测、可灰度、可回滚,而不是“跑起来就行”。
答不到“标签生命周期管理”和“调度失败兜底策略”,基本会被判定为“只背过命令”。
知识点
-
节点标签(node label)
- 是 Swarm 里 etcd 存储的 k/v 元数据,属于集群级资源,重启 Docker Engine 不会丢;
- 只有 manager 能写,worker 只能读,防止租户越权;
- key 命名建议:
gpu.enable=true、gpu.model=Tesla-V100、gpu.driver=470.82.01、gpu.free=2,方便后期做范围匹配与优先级调度。
-
亲和性约束(constraint)
- Compose v3.7+ 使用
deploy.placement.constraints; - 支持
==、!=、正则,如node.labels.gpu.model==Tesla-V100; - 多条约束取交集,因此可以把“业务等级 + GPU 型号 + 剩余卡数”组合成复杂规则。
- Compose v3.7+ 使用
-
GPU 卡数隔离
- Swarm 原生只能把节点当作整体调度,无法识别 GPU 卡级资源;
- 国内通行做法是:
a) 由运维脚本周期性检测/proc/driver/nvidia/gpus/并docker node update --label-add gpu.free=N;
b) 任务启动后通过 nvidia-docker 的 NV_GPU 环境变量 绑定具体卡,防止同一节点多任务抢占;
c) 任务退出时回调脚本把gpu.free加回去,实现简单版二次调度。
-
安全与可观测
- 最小镜像:使用官方
nvidia/cuda:11.8.0-base-ubuntu22.04而非完整 devel 镜像,减少 2 GB 攻击面; - 非 root 运行:在 Dockerfile 里
USER 1001:1001,并在 yml 中补充group_add: [video]解决 /dev/nv* 权限; - Secrets 管理:把 NGC API Key、HuggingFace Token 挂到 Swarm secret,禁止写进镜像层;
- 可观测:通过
docker events --filter label=gpu.task=true实时推送至阿里云 SLS 或腾讯云 CLS,调度失败 30 s 内告警。
- 最小镜像:使用官方
-
CI/CD 集成
- 在 GitLab CI 里用
docker/buildx做多阶段构建,CPU 阶段编译,GPU 阶段仅拷贝二进制,镜像体积下降 60%; - 合并请求阶段只调度 CPU 节点,master 分支才允许打上 gpu.schedule=true 标签,防止开发分支耗尽昂贵资源。
- 在 GitLab CI 里用
答案
- 给 GPU 节点打标签
# 在 manager 节点执行
docker node update \
--label-add gpu.enable=true \
--label-add gpu.model=Tesla-V100 \
--label-add gpu.driver=470.82.01 \
--label-add gpu.free=2 \
node-gpu-01
- 编写 docker-compose.yml(stack 文件)
version: "3.8"
services:
ai-training:
image: registry.cn-hangzhou.aliyuncs.com/ai/cuda-app:1.3.0
user: "1001:1001"
group_add:
- video
environment:
- NV_GPU=0,1 # 绑定具体卡,防止争抢
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
deploy:
replicas: 1
placement:
constraints:
- node.labels.gpu.enable == true
- node.labels.gpu.model == Tesla-V100
- node.labels.gpu.free >= 1
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
labels:
- "gpu.task=true"
secrets:
- ngc_api_key
secrets:
ngc_api_key:
external: true
- 启动并验证
docker stack deploy -c docker-compose.yml gpu-job
# 查看调度结果
docker stack ps gpu-job --no-trunc
# 确认落到目标节点
docker inspect <task-id> | grep NodeID
- 资源回收
容器退出后,通过 PostStop Hook 脚本回调
docker node update --label-add gpu.free=$(($cur_free + 1)) node-gpu-01
实现卡数自动归还,避免资源泄漏。
- 灰度与回滚
利用docker service update --constraint-rm / --constraint-add动态把任务迁移到新驱动版本节点,一旦训练精度异常,30 s 内回滚到旧标签节点,保证业务连续性。
拓展思考
- 混合云场景:如果部分 GPU 在阿里云 ebmgn7i、部分在本地机房,内网 RTT 差异 30 ms,可通过
node.labels.zone=alibabacloud与node.labels.zone=idc做拓扑感知调度,并把跨域拉取镜像的延迟降到 10 s 以内; - 多租户配额:基于 Swarm 的 label selector 做软多租还不够,可引入 OpenPolicyAgent + docker-authz-plugin 做** admission webhook**,防止租户 A 把
gpu.free手动改大; - 向 Kubernetes 过渡:Swarm 社区已停止功能迭代,国内大厂 2025 年前基本完成 Swarm → K8s + Device Plugin 迁移,建议提前掌握 node-feature-discovery + GPU Operator 的自动标签注入方案,面试时主动提及可加分;
- 绿色计算:在晚高峰电力紧张时段,通过 cron + label 把非紧急 GPU 任务强制调度到 PUE<1.2 的液冷机房节点,实现碳排放账单可追踪,符合国内“双碳”合规要求。