在 Kubernetes 上运行 AI 和机器学习工作负载,因其能够灵活应对波动的资源需求、确保跨环境的可移植性(保证配置和依赖项的一致性)以及支持分布式训练(提供更高的容错能力和大幅缩短训练时间)等诸多优势,已成为一种理想选择。本文旨在提供一个快速、便捷的 Kubernetes 集群搭建方案,通过必要的集群配置,实现 GPU 加速工作负载的运行和扩展,免去复杂组件的配置工作。我们将聚焦于在 K3d 集群上部署 NVIDIA GPU Operator,构建本地或云端 GPU 加速工作负载运行的基础。

K3d:轻量级 Kubernetes 解决方案

K3d 是一款轻量级的工具,允许你在 Docker 容器内运行 K3s(Kubernetes 的轻量级版本)。它特别适用于在本地机器上快速启动 Kubernetes 集群,尤其适合在不需要大量开销的情况下进行测试或开发。相较于传统的 Kubernetes 集群部署方式,K3d 简化了部署流程,降低了资源占用,使得开发者可以更专注于应用程序的开发和测试。

例如,在开发一个需要 GPU 加速的图像识别应用时,你可以在本地使用 K3d 快速搭建一个包含 GPU 支持的 Kubernetes 集群。这比配置一个完整的 Kubernetes 集群要快得多,也更加方便。你可以使用 K3d 提供的命令 k3d cluster create 快速创建一个集群,并使用 Docker Compose 或者 Helm 等工具部署你的应用程序。

K3d 的优势在于其轻量级和快速部署能力。一个 K3d 集群可以在几分钟内启动,这使得它成为开发和测试 GPU 加速应用的首选工具。此外,K3d 还支持多集群管理,你可以同时运行多个 K3d 集群,每个集群可以有不同的配置和应用程序。

NVIDIA GPU Operator:自动化 GPU 管理

NVIDIA GPU Operator 能够自动化在 Kubernetes 集群中启用 GPU 所需的一切步骤。它避免了手动配置和管理驱动程序、库以及每个节点所需运行时的复杂性。通过 GPU Operator,可以确保整个集群(或多个集群)的版本一致性,从而避免版本偏差。

想象一下,你需要在一个拥有数百个节点的 Kubernetes 集群上部署一个 AI 推理服务。如果没有 GPU Operator,你需要在每个节点上手动安装 NVIDIA 驱动程序、CUDA 工具包和其他依赖项。这不仅耗时,而且容易出错。使用 GPU Operator,你可以通过简单的 Helm 命令自动完成这些任务。GPU Operator 会负责安装和配置所有必需的组件,确保每个节点都正确配置了 GPU。

GPU Operator 不仅仅是一个安装工具,它还是一个持续监控和管理 GPU 的智能代理。它可以检测 GPU 的健康状况,并自动重启失败的 GPU。它还可以动态调整 GPU 的配置,以优化性能和资源利用率。

根据 NVIDIA 的数据,使用 GPU Operator 可以将 GPU 的部署时间从数小时缩短到几分钟,并将 GPU 的利用率提高 20% 以上。这对于需要大规模部署 GPU 加速应用的组织来说,是一个巨大的优势。

准备工作:环境搭建

为了顺利完成本文的实践,你需要确保你的宿主机满足以下条件:

  • 拥有一块 NVIDIA 显卡(集成显卡或 eGPU 均可)
  • 安装了 NVIDIA Container Toolkit (能够运行 nvidia-smi 命令并看到至少一个 GPU 设备)
  • 具备 Kubernetes 的基本知识

在安装了 NVIDIA Container Toolkit 之后,你就可以使用 Docker 来运行 GPU 加速的容器。你可以使用 docker run --gpus all 命令来启动一个可以使用所有 GPU 的容器。

此外,你还需要安装 Kubectl、K3d 和 Helm 这三个工具,分别用于管理 Kubernetes 集群、创建 K3d 集群和安装 Helm Chart。具体版本如下:

  • Docker Engine (Ubuntu 22.04)
  • Kubectl (v1.33.0)
  • K3D (v5.8.3)
  • Helm (v3.17.3)
  • GPU Operator (v25.3.0)

创建 K3d 管理的 Registry

为了方便管理镜像,我们创建一个由 K3d 管理的 Registry,用于存放节点镜像。

# 创建一个映射到本地端口的 Registry
k3d registry create myregistry.localhost - port 12345

运行 docker ps 命令,你应该能够看到 Registry 映射到本地端口 12345。这个 Registry 将作为我们存储自定义节点镜像的地方。

构建 Dockerfile:定制 GPU 节点镜像

我们需要构建一个 Docker 镜像,作为 Kubernetes 节点的模板,使其能够利用 GPU。参考 K3d 的 Running CUDA workloads 指南,我们定义一个镜像,该镜像:

  • 基于 K3s 基础镜像 (K8s v1.33.0) 创建构建阶段
  • 基于 CUDA 基础镜像 (Ubuntu 22.04) 创建构建阶段
  • 安装 NVIDIA Container Toolkit
  • 在 containerd 启动时将默认 RuntimeClass 设置为 nvidia

以下是 Dockerfile 的内容:

ARG K3S_TAG="v1.33.0-k3s1"
ARG CUDA_TAG="12.8.0-base-ubuntu22.04"

FROM rancher/k3s:$K3S_TAG AS k3s
FROM nvidia/cuda:$CUDA_TAG

# 安装 NVIDIA Container Toolkit
RUN apt-get update && apt-get install -y curl \
    && curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
    && curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
      sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
      tee /etc/apt/sources.list.d/nvidia-container-toolkit.list \
    && apt-get update && apt-get install -y nvidia-container-toolkit \
    && nvidia-ctk runtime configure --runtime=containerd \
    && mkdir -p /etc/rancher/k3s && echo "default-runtime: nvidia" > /etc/rancher/k3s/config.yaml # 创建配置文件将默认 runtime 设置为 nvidia

COPY --from=k3s / / --exclude=/bin
COPY --from=k3s /bin /bin

VOLUME /var/lib/kubelet
VOLUME /var/lib/rancher/k3s
VOLUME /var/lib/cni
VOLUME /var/log

ENV PATH="$PATH:/bin/aux"

ENTRYPOINT ["/bin/k3s"]
CMD ["agent"]

这个 Dockerfile 首先从 NVIDIA 的 CUDA 镜像开始,然后安装 NVIDIA Container Toolkit。然后,它将默认的容器运行时设置为 nvidia,这样 Kubernetes 就可以使用 GPU。最后,它从 K3s 镜像复制必要的文件,并设置环境变量。

构建镜像并推送到 K3d Registry

现在,我们需要构建镜像并将其推送到 K3d 管理的 Registry。

# 构建 Dockerfile 中指定的镜像。指定 Registry 中的 repository 名称。
docker build -t k3d-myregistry.localhost:12345/<REPOSITORY_NAME>:v1.0.0 --annotation "k3s=1.33.0" --annotation "cuda=12.8.0-base-ubuntu22.04" .

确保将 <REPOSITORY_NAME> 替换为你想要使用的 Repository 名称。

构建完成后,你应该能看到新创建的镜像。

# 将本地镜像推送到 K3d 管理的 Registry
docker push k3d-myregistry.localhost:12345/<REPOSITORY_NAME>:v1.0.0

镜像推送完成后,你可以运行以下命令来确认 Repository 中的所有标签:

# 列出 K3d 管理的 Registry 中的标签
curl http://localhost:12345/v2/<REPOSITORY_NAME>/tags/list

创建 K3d 集群

接下来,我们使用新创建的镜像创建一个 K3d 集群。

# 使用该镜像创建集群,并允许 GPU 透传到 Docker。
k3d cluster create <CLUSTER_NAME> --registry-use=k3d-myregistry.localhost:12345 --image=k3d-myregistry.localhost:12345/<REPOSITORY_NAME>:v1.0.0 --gpus=all

<CLUSTER_NAME> 替换为你想要使用的集群名称。 --registry-use 参数指定使用我们创建的 K3d 管理的 Registry。 --image 参数指定使用我们构建的自定义节点镜像。 --gpus=all 参数允许 GPU 透传到 Docker 容器中。

安装 GPU Operator

现在,我们可以使用 Helm 安装 GPU Operator。因为我们默认使用 containerd 运行时,所以需要为 GPU Operator 设置额外的参数。

# 添加并更新 Helm Repository
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia \
    && helm repo update

# 安装 GPU Operator。我们已经安装了 NVIDIA Container Toolkit,并且 GPU 驱动程序配置通过 Docker 传递。
helm install gpu-operator -n gpu-operator --create-namespace \
  nvidia/gpu-operator \
    --version=v25.3.0 \
    --set toolkit.enabled=false \
    --set driver.enabled=false \
    --set toolkit.env[0].name=CONTAINERD_CONFIG \
    --set toolkit.env[0].value=/etc/containerd/config.toml \
    --set toolkit.env[1].name=CONTAINERD_SOCKET \
    --set toolkit.env[1].value=/run/k3s/containerd/containerd.sock \
    --set toolkit.env[2].name=CONTAINERD_RUNTIME_CLASS \
    --set toolkit.env[2].value=nvidia \
    --set toolkit.env[3].name=CONTAINERD_SET_AS_DEFAULT \
    --set-string toolkit.env[3].value=true \
    --set validator.driver.env[0].name=DISABLE_DEV_CHAR_SYMLINK_CREATION \
    --set-string validator.driver.env[0].value=true

这些参数配置了 GPU Operator,使其能够与 containerd 运行时协同工作,并正确配置 GPU。toolkit.enabled=falsedriver.enabled=false 参数禁用了 GPU Operator 的工具包和驱动程序管理功能,因为我们已经通过 Docker 镜像安装了 NVIDIA Container Toolkit。

安装完成后,你应该能够看到 gpu-operator 命名空间中的 Pod。等待它们启动并运行。

部署 K3d Worker 节点

到目前为止,我们已经部署了一个包含 GPU Operator 的集群。接下来,让我们创建一个节点,看看 GPU 如何工作。

# 使用相同的镜像创建一个 worker 节点。在 K3d 中,你创建的任何节点都使用控制平面的镜像,所以技术上你可以省略 --image 标志
k3d node create <NODE_NAME> -c <CLUSTER_NAME> --role agent --image k3d-myregistry.localhost:12345/<REPOSITORY_NAME>:v1.0.0

<NODE_NAME> 替换为你想要使用的节点名称。-c <CLUSTER_NAME> 参数指定将节点添加到哪个集群。 --role agent 参数指定节点为 worker 节点。

你可以看到,Operator 发现了新节点,并在其上部署了必要的软件组件。

描述 GPU worker 节点将显示你当前的 GPU 容量。

运行测试工作负载

现在我们已经部署了一个 worker 节点,让我们运行一个 GPU 加速的示例工作负载,以确认我们设置正确。我们将使用 NGC Catalog 中的一个 CUDA 示例容器来运行基准测试。复制 YAML 清单并在你的集群中运行它。

apiVersion: v1
kind: Pod
metadata:
  name: nbody-gpu-benchmark
  namespace: default
spec:
  restartPolicy: OnFailure
  containers:
  - name: cuda-container
    image: nvcr.io/nvidia/k8s/cuda-sample:nbody
    args: ["nbody", "-gpu", "-benchmark"]
    resources:
      limits:
        nvidia.com/gpu: 1
    env:
    - name: NVIDIA_VISIBLE_DEVICES
      value: all
    - name: NVIDIA_DRIVER_CAPABILITIES
      value: all

这个 Pod 定义了一个容器,该容器运行 CUDA nbody 示例,并使用一个 GPU。resources 部分指定容器需要一个 nvidia.com/gpuNVIDIA_VISIBLE_DEVICESNVIDIA_DRIVER_CAPABILITIES 环境变量允许容器访问所有可用的 GPU 和驱动程序功能。

你应该会看到类似于下面的日志输出:

[CUDA Device Query] Starting...

CUDA Device Count: 1
  Device 0: "NVIDIA GeForce RTX 3060"
    CUDA Driver Version / Runtime Version          12.2 / 12.0
    CUDA Capability Major/Minor version number:    8.6
    Total amount of global memory:                 12039 MBytes (12623314944 bytes)
...

这表明你已成功运行 CUDA 工作负载,并拥有一个可以利用 NVIDIA GPU 的工作集群环境。

创建 CPU 节点(可选)

除了 GPU 节点外,我们还可以在 Kubernetes 集群中混合使用 CPU 节点,因为某些服务可能不需要 GPU 访问。为此,我们将创建一个额外的 worker 节点,并观察 GPU Operator 在创建时的行为。

# 创建仅 CPU 节点
k3d node create <NODE_NAME> -c <CLUSTER_NAME> --role agent --k3s-node-label "nvidia.com/gpu.deploy.operands=false"

我们可以看到,我们的 CPU 节点被 Operator 发现了,但由于它被标记为 nvidia.com/gpu.deploy.operands=false,因此没有部署软件组件。

GPU 时间分片(可选)

到目前为止,我们在演示中将一个完整的 GPU 公开给节点。如果有多个用户,每个用户都有多个想要使用同一 GPU 的工作负载怎么办? 允许我向您介绍 GPU 时间分片。 它从同一底层 GPU 定义一组 GPU 副本,供多个工作负载共享。 有关更多信息,请查看 Time-Slicing GPUs。

让我们配置并测试时间分片。 复制下面的清单并创建 ConfigMap。

apiVersion: v1
kind: ConfigMap
metadata:
  name: time-slicing-config
data:
  any: |-
    version: v1
    flags:
      migStrategy: none
    sharing:
      timeSlicing:
        renameByDefault: false
        failRequestsGreaterThanOne: false
        resources:
          - name: nvidia.com/gpu
            replicas: 4
# 创建 ConfigMap
kubectl create -n gpu-operator -f time-slicing-config.yaml

# 修改设备插件以使用 ConfigMap 设置
kubectl patch clusterpolicies.nvidia.com/cluster-policy \
    -n gpu-operator --type merge \
    -p '{"spec": {"devicePlugin": {"config": {"name": "time-slicing-config", "default": "any"}}}}'

更新集群策略后,节点上的设备插件资源将重新启动。

描述节点显示基于 nvidia.com/gpu.count 标签存在单个 GPU。 在此示例中,我们将 GPU 分成 4 个副本,因此 GPU 内存将为总内存的 1/4。

节点配置中,Pod 级别分配了四个 GPU 切片。

请记住,设置 GPU 资源限制时,这将与副本数量相关联。

apiVersion: v1
kind: Pod
metadata:
  name: app-1
  namespace: default
spec:
  containers:
    ...
    resources:
      limits:
        nvidia.com/gpu: 1

总结

希望这篇文章对您有所启发,并鼓励您亲自尝试。 我计划跟进其他概念,所以请关注我,或者如有任何问题和反馈,请访问我的 Linkedin www.linkedin.com/in/noel-osagie。

感谢您的阅读!

通过在 K3d 集群上使用 GPU Operator,我们可以轻松地构建和管理 GPU 加速的 Kubernetes 环境。这为 AI 和机器学习工作负载的开发和部署提供了极大的便利。随着大模型技术的不断发展,GPU Operator 在 Kubernetes 集群中的重要性将日益凸显。无论是本地开发还是云端部署,GPU Operator 都能帮助我们高效地利用 GPU 资源,加速 AI 工作负载的运行。希望这篇文章能够帮助你更好地理解和使用 GPU Operator,并在你的 AI 项目中取得成功。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注