上一篇 下一篇 分享链接 返回 返回顶部

如何在容器化环境(Docker/K8s)下做GPU资源编排与负载感知调度的实战方案,适用于更大规模的分布式转码集群部署

发布人:Minchunlin 发布时间:2025-08-06 11:29 阅读量:825


在最初部署视频转码系统时,我们依赖的是香港 MEGA-i 机房内的几台 Dell R750xa 裸金属服务器,采用 GPU + FFmpeg 本地运行脚本调度,配合 Redis 队列消费任务。这种方式在小规模(3 台内)部署时还算可控,但当我们的 GPU 节点扩展到 10+ 台、任务量突破每日 30,000 条时,以下问题接踵而至:

  • 任务调度混乱:节点资源状态全靠 nvidia-smi + 脚本轮询,调度不智能,抢占常见。
  • 跨节点资源浪费:某些 GPU 长期空闲,其他节点却排队堆积。
  • 负载感知缺失:系统无感知 GPU 温度、编码 session、显存压力等负载指标。
  • 上线/下线新节点需要手工配置:维护成本高,出错概率大。

那时候我下定决心:必须把整个 GPU 编码集群容器化,并接入调度系统自动感知资源状态,实现弹性与高效的任务调度。

于是,我们基于 Docker + Kubernetes + NVIDIA GPU Operator + 自研调度器 构建了一套可扩展、高可用、智能调度的视频转码平台。以下是整个部署与演进过程的完整实操记录。

一、香港机房环境与基础设施构建

1. 硬件部署情况

机房位置:MEGA-i 香港 8/F 区域

  • 服务器数量:12 台 GPU 节点(R740xa + A5000),3 台控制节点

网络配置:

  • 内部网络使用 VXLAN overlay,10GbE 交换网络
  • 公网采用 BGP 多线冗余出网
  • K8s pod 网络使用 Calico(BGP 模式)联通多个机房 VLAN

2. Kubernetes 集群环境

组件 版本
Kubernetes 1.26
Containerd 1.6.x
NVIDIA GPU Operator 23.3.x
Helm 3.12
Calico 3.25

3. GPU 节点配置

每台 GPU 节点规格:

  • CPU:Intel Xeon Gold 6338 ×2
  • GPU:NVIDIA A5000 ×2(或 A30 ×4)
  • 内存:256GB DDR4
  • 存储:1TB NVMe 本地缓存 + Ceph RBD 回源

二、Docker + NVIDIA Container Toolkit 的 GPU 基础打通

虽然最终部署用的是 K8s,但一开始我们在单节点上使用 Docker 原型验证 GPU 编码容器。

1. 安装 NVIDIA Container Toolkit

sudo apt install nvidia-container-toolkit
sudo systemctl restart containerd

验证容器能访问 GPU:

docker run --gpus all nvidia/cuda:12.2-base nvidia-smi

2. 构建 GPU 编码用 FFmpeg 镜像

我们基于 CUDA 11.8 + FFmpeg 编译了支持 h264_nvenc 的镜像:

FROM nvidia/cuda:11.8-runtime-ubuntu22.04

RUN apt update && apt install -y \
  ffmpeg \
  libnpp-dev libx264-dev libx265-dev libfdk-aac-dev

COPY entrypoint.sh /usr/local/bin/

CMD ["ffmpeg"]

最终镜像打包后传入私有 Harbor 仓库(可在多节点复用)。

三、在 Kubernetes 上实现 GPU 编排与调度

1. 安装 NVIDIA GPU Operator

GPU Operator 是我们集群最关键的基础模块,它负责自动在 GPU 节点上安装驱动、运行 device-plugin 并暴露资源给调度器。

使用 Helm 部署:

helm repo add nvidia https://nvidia.github.io/gpu-operator
helm upgrade --install gpu-operator nvidia/gpu-operator \
  --namespace gpu-operator \
  --create-namespace

部署后,所有 GPU 节点都会自动暴露资源:

kubectl describe node <node-name> | grep nvidia.com/gpu

2. 编排 GPU 容器任务

部署一个使用 GPU + FFmpeg 的 Job:

apiVersion: batch/v1
kind: Job
metadata:
  name: transcode-job
spec:
  template:
    spec:
      containers:
      - name: transcoder
        image: my-registry/transcoder:latest
        resources:
          limits:
            nvidia.com/gpu: 1
        command: ["ffmpeg", "-hwaccel", "cuda", "-i", "input.mp4", "-c:v", "h264_nvenc", "-b:v", "5M", "output.mp4"]
      restartPolicy: Never
  backoffLimit: 2

每个 Job 会调度到有空闲 GPU 的节点上运行,实现并发视频转码。

3. 使用自研调度器进行负载感知调度

K8s 默认的调度器并不能精确理解 GPU 的负载(比如 session 数、功耗、温度)。为了解决这个问题,我们:

  • 使用 DCGM Exporter 暴露 GPU 指标到 Prometheus
  • 编写了一个 K8s Scheduling Extender(调度扩展器),根据 GPU 当前 session 数、温度、显存使用情况,对所有候选节点进行打分
  • 在节点压力大的时候,打分降低,优先调度到空闲 GPU 节点

调度打分逻辑示意:

score = 100
score -= (session_count * 10)
score -= (gpu_temp - 50) * 0.5
score -= (memory_used_pct * 0.5)

调度器使用 HTTP 扩展 API 接入 kube-scheduler,实现动态排序。

四、实战中的问题与解决方案

问题 1:GPU 资源浪费严重

表现:K8s 只根据 nvidia.com/gpu: 1 做粗粒度调度,导致每块 GPU 只能跑一个任务,利用率低。

解决方案:

使用 NVIDIA MPS(Multi-Process Service) 启用多进程共享 GPU

修改容器并发数逻辑,控制每个容器可在一块 GPU 上跑多个 FFmpeg 实例(我们限制为 3~4 个)

结合调度器逻辑动态感知 session 状态

问题 2:转码任务卡死,Pod 不释放 GPU

表现:有时 FFmpeg 异常退出,Pod 处于 CrashLoopBackOff 状态,但 GPU 仍被 nvidia-smi 显示占用

解决方案:

设置 terminationGracePeriodSeconds,在容器退出时主动 kill GPU 编码进程

使用 nvidia-smi pmon 配合 preStop hook 强制清理进程

问题 3:跨节点文件同步慢,转码卡顿

表现:CephFS 拉原始视频到 GPU 节点时出现延迟,转码容器卡在 I/O 阶段

解决方案:

改为 Sidecar 模式:每个 GPU Pod 中添加一个缓存 sidecar,用于从中央对象存储拉文件到本地 SSD

使用 InitContainer 先完成数据拉取,然后再启动转码容器

输出文件在本地缓存盘落地后,通过异步同步推送到 OSS

五、最终效果与可视化监控

通过这套 K8s 容器化调度系统,我们实现了:

  • 每张 GPU 平均可并发处理 3~4 个 1080p 任务
  • 整体 GPU 利用率由原先的 45% 提升至 80% 以上
  • 支持横向扩展,新增 GPU 节点只需打个 Label 自动加入调度池
  • 通过 Grafana 实时查看每个 GPU 节点的 session 数、温度、功耗等指标

总结:容器化不是目标,而是持续优化的开始

这次将 GPU 转码系统容器化、调度系统智能化的实践,让我们从“小作坊式”的裸跑任务,过渡到了一个能够 支撑弹性扩展、高负载感知、GPU 高效利用的生产级平台。

我特别有感触的一点是:K8s 本身并不能解决 GPU 的智能调度问题,关键还是你是否设计了足够精细的调度逻辑、负载采集与业务协调机制。

目录结构
全文