使用节点标签实现 GPU 任务亲和性调度

解读

在国内互联网、金融、AI 公司面试中,这道题考察的是“如何把 GPU 资源当成一等公民”纳入 Docker Swarm 或 Kubernetes 的调度体系,而不是手动 ssh 到某台机器启动容器。
面试官真正想听的是:

  1. 你能否用节点标签把“有没有 GPU、GPU 型号、驱动版本、剩余卡数”等元数据标准化地打到节点上;
  2. 你能否在 Compose 文件或 stack yml 里用亲和性约束把任务精准地钉到满足条件的节点;
  3. 你能否保证调度结果可观测、可灰度、可回滚,而不是“跑起来就行”。
    答不到“标签生命周期管理”和“调度失败兜底策略”,基本会被判定为“只背过命令”。

知识点

  1. 节点标签(node label)

    • 是 Swarm 里 etcd 存储的 k/v 元数据,属于集群级资源,重启 Docker Engine 不会丢
    • 只有 manager 能写,worker 只能读,防止租户越权
    • key 命名建议:gpu.enable=truegpu.model=Tesla-V100gpu.driver=470.82.01gpu.free=2,方便后期做范围匹配优先级调度
  2. 亲和性约束(constraint)

    • Compose v3.7+ 使用 deploy.placement.constraints
    • 支持 ==!=、正则,如 node.labels.gpu.model==Tesla-V100
    • 多条约束取交集,因此可以把“业务等级 + GPU 型号 + 剩余卡数”组合成复杂规则。
  3. GPU 卡数隔离

    • Swarm 原生只能把节点当作整体调度,无法识别 GPU 卡级资源
    • 国内通行做法是:
      a) 由运维脚本周期性检测 /proc/driver/nvidia/gpus/docker node update --label-add gpu.free=N
      b) 任务启动后通过 nvidia-docker 的 NV_GPU 环境变量 绑定具体卡,防止同一节点多任务抢占;
      c) 任务退出时回调脚本把 gpu.free 加回去,实现简单版二次调度
  4. 安全与可观测

    • 最小镜像:使用官方 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 内告警
  5. CI/CD 集成

    • 在 GitLab CI 里用 docker/buildx 做多阶段构建,CPU 阶段编译,GPU 阶段仅拷贝二进制,镜像体积下降 60%;
    • 合并请求阶段只调度 CPU 节点,master 分支才允许打上 gpu.schedule=true 标签,防止开发分支耗尽昂贵资源。

答案

  1. 给 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
  1. 编写 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
  1. 启动并验证
docker stack deploy -c docker-compose.yml gpu-job
# 查看调度结果
docker stack ps gpu-job --no-trunc
# 确认落到目标节点
docker inspect <task-id> | grep NodeID
  1. 资源回收
    容器退出后,通过 PostStop Hook 脚本回调
docker node update --label-add gpu.free=$(($cur_free + 1)) node-gpu-01

实现卡数自动归还,避免资源泄漏。

  1. 灰度与回滚
    利用 docker service update --constraint-rm / --constraint-add 动态把任务迁移到新驱动版本节点,一旦训练精度异常,30 s 内回滚到旧标签节点,保证业务连续性。

拓展思考

  1. 混合云场景:如果部分 GPU 在阿里云 ebmgn7i、部分在本地机房,内网 RTT 差异 30 ms,可通过 node.labels.zone=alibabacloudnode.labels.zone=idc拓扑感知调度,并把跨域拉取镜像的延迟降到 10 s 以内;
  2. 多租户配额:基于 Swarm 的 label selector 做软多租还不够,可引入 OpenPolicyAgent + docker-authz-plugin 做** admission webhook**,防止租户 A 把 gpu.free 手动改大;
  3. 向 Kubernetes 过渡:Swarm 社区已停止功能迭代,国内大厂 2025 年前基本完成 Swarm → K8s + Device Plugin 迁移,建议提前掌握 node-feature-discovery + GPU Operator自动标签注入方案,面试时主动提及可加分;
  4. 绿色计算:在晚高峰电力紧张时段,通过 cron + label 把非紧急 GPU 任务强制调度到 PUE<1.2 的液冷机房节点,实现碳排放账单可追踪,符合国内“双碳”合规要求。