强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

OpenGL / OpenCL 编程指南 / 第 16 章:Docker 中的 GPU

第 16 章:Docker 中的 GPU

在容器化部署和 CI/CD 流水线中使用 GPU 已成为现代开发的常见需求。本章讲解如何在 Docker 容器中正确配置 GPU 访问、OpenGL 渲染和 OpenCL 计算。


16.1 为什么在 Docker 中使用 GPU?

场景需求
CI/CD 中的图形测试无物理显示器的 GPU 渲染
服务器端渲染批量生成 3D 图像/视频
机器学习推理GPU 加速的容器化推理服务
科学计算环境一致的 GPU 计算集群
多租户隔离安全隔离的 GPU 资源共享

16.2 NVIDIA 容器运行时

16.2.1 安装 NVIDIA Container Toolkit

# 添加 NVIDIA 仓库
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
    sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg

curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
    sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
    sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

# 安装
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit

# 配置 Docker 运行时
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

# 验证
docker run --rm --gpus all nvidia/cuda:12.2.0-base-ubuntu22.04 nvidia-smi

16.2.2 GPU 分配策略

# 使用所有 GPU
docker run --gpus all myimage

# 使用特定数量的 GPU
docker run --gpus 2 myimage

# 使用特定 GPU(通过 ID)
docker run --gpus '"device=0,2"' myimage

# 使用环境变量
docker run --env NVIDIA_VISIBLE_DEVICES=0,1 myimage
docker run --env NVIDIA_VISIBLE_DEVICES=all myimage
docker run --env NVIDIA_VISIBLE_DEVICES=none myimage  # 禁用 GPU

16.3 Docker 中的 OpenGL 渲染

16.3.1 问题:容器没有显示器

容器环境通常没有 X11 显示器。解决方案有三种:

方案适用场景性能
EGL 离屏渲染服务器端渲染
X11 转发开发调试
软件渲染 (llvmpipe)CI/CD 测试

16.3.2 EGL 离屏渲染

EGL 是 OpenGL ES 的本地平台接口,支持无显示器的 GPU 渲染:

# Dockerfile - EGL 离屏渲染环境
FROM nvidia/cuda:12.2.0-devel-ubuntu22.04

RUN apt-get update && apt-get install -y \
    build-essential cmake git \
    libgl1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev \
    libglew-dev libglfw3-dev libglm-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . .

RUN mkdir build && cd build && \
    cmake .. -DUSE_EGL=ON && \
    make -j$(nproc)

CMD ["./build/render_headless"]

16.3.3 EGL 初始化代码

// egl_headless.cpp - EGL 离屏渲染
#include <EGL/egl.h>
#include <GL/gl.h>
#include <cstdio>

int main() {
    // 1. 获取 EGL 显示
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (display == EGL_NO_DISPLAY) {
        printf("Failed to get EGL display\n");
        return -1;
    }

    // 2. 初始化 EGL
    EGLint major, minor;
    eglInitialize(display, &major, &minor);
    printf("EGL Version: %d.%d\n", major, minor);

    // 3. 选择配置
    EGLint configAttribs[] = {
        EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_ALPHA_SIZE, 8,
        EGL_DEPTH_SIZE, 24,
        EGL_NONE
    };

    EGLConfig config;
    EGLint numConfigs;
    eglChooseConfig(display, configAttribs, &config, 1, &numConfigs);

    // 4. 创建 PBuffer 表面(离屏渲染目标)
    EGLint pbufferAttribs[] = {
        EGL_WIDTH, 1920,
        EGL_HEIGHT, 1080,
        EGL_NONE
    };
    EGLSurface surface = eglCreatePbufferSurface(display, config, pbufferAttribs);

    // 5. 绑定 OpenGL API
    eglBindAPI(EGL_OPENGL_API);

    // 6. 创建上下文
    EGLint contextAttribs[] = {
        EGL_CONTEXT_MAJOR_VERSION, 4,
        EGL_CONTEXT_MINOR_VERSION, 6,
        EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
        EGL_NONE
    };
    EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
    eglMakeCurrent(display, surface, surface, context);

    // 7. 现在可以正常使用 OpenGL 渲染
    printf("Renderer: %s\n", glGetString(GL_RENDERER));
    printf("Version:  %s\n", glGetString(GL_VERSION));

    // ... 执行渲染 ...

    // 8. 读取渲染结果
    unsigned char *pixels = new unsigned char[1920 * 1080 * 4];
    glReadPixels(0, 0, 1920, 1080, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

    // 9. 保存为图片(使用 stb_image_write)
    // stbi_write_png("output.png", 1920, 1080, 4, pixels, 1920 * 4);

    // 10. 清理
    delete[] pixels;
    eglDestroyContext(display, context);
    eglDestroySurface(display, surface);
    eglTerminate(display);
    return 0;
}

16.3.4 X11 转发方式

# 允许本地 X11 连接
xhost +local:docker

# 运行容器,挂载 X11 socket
docker run -it --gpus all \
    -e DISPLAY=$DISPLAY \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    -v $HOME/.Xauthority:/root/.Xauthority:ro \
    myimage ./my_gl_app

# 用完后恢复
xhost -local:docker

16.4 Docker 中的 OpenCL

16.4.1 OpenCL 容器配置

FROM nvidia/cuda:12.2.0-devel-ubuntu22.04

RUN apt-get update && apt-get install -y \
    build-essential cmake \
    ocl-icd-opencl-dev \
    opencl-headers \
    clinfo \
    && rm -rf /var/lib/apt/lists/*

# 验证 OpenCL 可用
RUN clinfo

WORKDIR /app
COPY . .
RUN mkdir build && cd build && cmake .. && make -j$(nproc)
CMD ["./build/opencl_compute"]
# 运行 OpenCL 容器
docker run --gpus all myimage clinfo

16.5 Docker Compose 配置

# docker-compose.yml
version: '3.8'

services:
  renderer:
    build: .
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1        # 使用 1 个 GPU
              capabilities: [gpu, compute, graphics]
    environment:
      - NVIDIA_VISIBLE_DEVICES=0
      - LIBGL_ALWAYS_SOFTWARE=0
    volumes:
      - ./output:/app/output    # 渲染结果输出目录
      - ./assets:/app/assets    # 资源文件

  compute:
    build:
      context: .
      dockerfile: Dockerfile.compute
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all       # 使用所有 GPU
              capabilities: [gpu, compute]

16.6 CI/CD 集成

16.6.1 GitHub Actions GPU 测试

# .github/workflows/gpu-test.yml
name: GPU Build & Test

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build with software renderer
        run: |
          docker build -t myapp-test .
          docker run --rm \
            -e LIBGL_ALWAYS_SOFTWARE=1 \
            -e GALLIUM_DRIVER=llvmpipe \
            myapp-test ./run_tests --headless

16.6.2 GitLab CI GPU Runner

# .gitlab-ci.yml
gpu-test:
  image: nvidia/cuda:12.2.0-devel-ubuntu22.04
  tags:
    - gpu
  script:
    - apt-get update && apt-get install -y libgl1-mesa-dev
    - mkdir build && cd build && cmake .. && make
    - ./build/run_tests
  variables:
    NVIDIA_VISIBLE_DEVICES: "all"

16.7 多 GPU 容器编排

16.7.1 Kubernetes GPU 调度

# k8s-gpu-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gl-renderer
spec:
  replicas: 4  # 4 个渲染实例
  template:
    spec:
      containers:
        - name: renderer
          image: myregistry/gl-renderer:latest
          resources:
            limits:
              nvidia.com/gpu: 1  # 每个 Pod 1 个 GPU
          env:
            - name: NVIDIA_VISIBLE_DEVICES
              value: "all"

16.8 完整 Dockerfile 示例

# Dockerfile - 完整的 OpenGL/OpenCL 开发环境
FROM nvidia/cuda:12.2.0-devel-ubuntu22.04

# 避免交互式安装
ENV DEBIAN_FRONTEND=noninteractive

# 系统依赖
RUN apt-get update && apt-get install -y \
    build-essential cmake git wget \
    # OpenGL
    libgl1-mesa-dev libglu1-mesa-dev \
    libegl1-mesa-dev libgles2-mesa-dev \
    libglew-dev libglfw3-dev libglm-dev \
    # OpenCL
    ocl-icd-opencl-dev opencl-headers \
    # 工具
    clinfo mesa-utils \
    # 图片库
    libpng-dev libjpeg-dev \
    && rm -rf /var/lib/apt/lists/*

# stb 头文件
RUN mkdir -p /usr/local/include/stb && \
    wget -O /usr/local/include/stb/stb_image.h \
    https://raw.githubusercontent.com/nothings/stb/master/stb_image.h && \
    wget -O /usr/local/include/stb/stb_image_write.h \
    https://raw.githubusercontent.com/nothings/stb/master/stb_image_write.h

WORKDIR /app
COPY CMakeLists.txt .
COPY src/ src/
COPY libs/ libs/
COPY shaders/ shaders/
COPY assets/ assets/

RUN mkdir build && cd build && \
    cmake .. -DCMAKE_BUILD_TYPE=Release && \
    make -j$(nproc)

# 验证
RUN nvidia-smi && clinfo | head -20

CMD ["./build/MyApp"]

16.9 注意事项

⚠️ NVIDIA 驱动版本兼容性:容器内的 CUDA 版本必须 ≤ 主机驱动支持的版本。使用 nvidia-smi 查看主机支持的最高 CUDA 版本。

⚠️ DISPLAY 环境变量:使用 EGL 离屏渲染时不需要设置 DISPLAY。如果设置了 X11 转发但没有 X 服务器,OpenGL 初始化会失败。

⚠️ 权限问题:Docker 容器中的 /dev/dri 设备需要适当的权限。使用 --device /dev/dri--privileged(不推荐)。

⚠️ 共享内存大小:Docker 默认的 /dev/shm 只有 64MB,某些 GPU 应用需要更大的共享内存。使用 --shm-size=1g 增大。


16.10 业务场景

场景 1:服务器端 3D 渲染服务

将 OpenGL 渲染引擎容器化,通过 REST API 接收渲染请求,返回渲染结果图片。

场景 2:GPU 计算集群

使用 Kubernetes + NVIDIA device plugin 管理数百个 GPU 节点的 OpenCL 计算任务。

场景 3:自动化 3D 内容生成

CI/CD 流水线中自动生成产品 3D 预览图、数据可视化图表。


16.11 扩展阅读

资源说明
NVIDIA Container Toolkit官方文档
EGL Native Platform InterfaceEGL API 参考
Docker GPU 支持Docker GPU 文档
Kubernetes GPU 调度K8s GPU 文档

本章小结

  • NVIDIA Container Toolkit 是 Docker GPU 支持的基础
  • EGL 离屏渲染是服务器端 OpenGL 渲染的标准方案
  • X11 转发适合开发调试,但不适合生产环境
  • CI/CD 中可使用 Mesa 软件渲染进行基础编译测试
  • 多 GPU 环境通过环境变量或 K8s 调度控制 GPU 分配
  • 务必注意 CUDA 驱动版本兼容性

上一章第 15 章:Vulkan 入门 下一章第 17 章:常见问题与调试