高级配置
cuda

如果你想在 K3s 容器上运行 CUDA 工作负载,则需要对容器进行定制。

CUDA 工作负载需要 NVIDIA 容器运行时,因此需要将 containerd 配置为使用该运行时。K3s 容器本身也需要使用这个运行时来运行。

如果你使用的是 Docker,可以安装 NVIDIA 容器工具包。

构建定制的 K3s 镜像

为了在 K3s 镜像中获得 NVIDIA 容器运行时,你需要构建自己的 K3s 镜像。原生的 K3s 镜像是基于 Alpine 的,但 Alpine 目前还不支持 NVIDIA 容器运行时。为了解决这个问题,我们需要使用受支持的基础镜像来构建该镜像。

ARG K3S_TAG="v1.28.8-k3s1"
ARG CUDA_TAG="12.4.1-base-ubuntu22.04"
 
FROM rancher/k3s:$K3S_TAG as k3s
FROM nvcr.io/nvidia/cuda:$CUDA_TAG
 
# Install the 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
 
COPY --from=k3s / / --exclude=/bin
COPY --from=k3s /bin /bin
 
# Deploy the nvidia driver plugin on startup
COPY device-plugin-daemonset.yaml /var/lib/rancher/k3s/server/manifests/nvidia-device-plugin-daemonset.yaml
 
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 基于 K3s 的 Dockerfile 构建,做了以下修改:

更换基础镜像:将基础镜像换成 nvidia/cuda:12.4.1-base-ubuntu22.04,这样就能安装 NVIDIA 容器工具包了。cuda:xx.x.x 这个版本号必须和你打算使用的 CUDA 版本一致。

添加清单文件:添加了一个用于 Kubernetes 的 NVIDIA 驱动插件的清单文件,其中包含 RuntimeClass 定义。具体可查看 K3s 文档。

NVIDIA 设备插件

若要在 Kubernetes 上启用 NVIDIA GPU 支持,你还需要安装 NVIDIA 设备插件。该设备插件以守护进程集(DaemonSet)的形式运行,它能让你自动实现以下功能:

  • 显示集群中每个节点上的 GPU 数量。

  • 跟踪 GPU 的健康状态。

  • 在 Kubernetes 集群中运行支持 GPU 的容器。

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: nvidia
handler: nvidia
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nvidia-device-plugin-daemonset
  namespace: kube-system
spec:
  selector:
    matchLabels:
      name: nvidia-device-plugin-ds
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        name: nvidia-device-plugin-ds
    spec:
      runtimeClassName: nvidia # Explicitly request the runtime
      tolerations:
      - key: nvidia.com/gpu
        operator: Exists
        effect: NoSchedule
      # Mark this pod as a critical add-on; when enabled, the critical add-on
      # scheduler reserves resources for critical add-on pods so that they can
      # be rescheduled after a failure.
      # See https://kubernetes.io/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/
      priorityClassName: "system-node-critical"
      containers:
      - image: nvcr.io/nvidia/k8s-device-plugin:v0.15.0-rc.2
        name: nvidia-device-plugin-ctr
        env:
          - name: FAIL_ON_INIT_ERROR
            value: "false"
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop: ["ALL"]
        volumeMounts:
        - name: device-plugin
          mountPath: /var/lib/kubelet/device-plugins
      volumes:
      - name: device-plugin
        hostPath:
          path: /var/lib/kubelet/device-plugins

对原始的 NVIDIA 守护进程集(DaemonSet)进行了两项修改:

在 YAML 前置元数据中添加 RuntimeClass 定义

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: nvidia
handler: nvidia

在 Pod 规范中添加 runtimeClassName: nvidia

注意:若要使用 GPU,你必须在所有 Pod 规范中明确添加 runtimeClassName: nvidia。具体可参考 K3s 文档。

构建 K3s 镜像

要构建自定义镜像,我们需要构建 K3s,因为我们需要其生成的输出。

将以下文件放在一个目录中:

  • Dockerfile
  • device - plugin - daemonset.yaml
  • build.sh
  • cuda - vector - add.yaml

build.sh 脚本通过 export 命令进行配置,默认版本为 v1.28.8+k3s1。请至少设置 IMAGE_REGISTRY 变量!该脚本会执行以下步骤来构建包含 NVIDIA 驱动的自定义 K3s 镜像。

#!/bin/bash
 
set -euxo pipefail
 
K3S_TAG=${K3S_TAG:="v1.28.8-k3s1"} # replace + with -, if needed
CUDA_TAG=${CUDA_TAG:="12.4.1-base-ubuntu22.04"}
IMAGE_REGISTRY=${IMAGE_REGISTRY:="MY_REGISTRY"}
IMAGE_REPOSITORY=${IMAGE_REPOSITORY:="rancher/k3s"}
IMAGE_TAG="$K3S_TAG-cuda-$CUDA_TAG"
IMAGE=${IMAGE:="$IMAGE_REGISTRY/$IMAGE_REPOSITORY:$IMAGE_TAG"}
 
echo "IMAGE=$IMAGE"
 
docker build \
  --build-arg K3S_TAG=$K3S_TAG \
  --build-arg CUDA_TAG=$CUDA_TAG \
  -t $IMAGE .
docker push $IMAGE
echo "Done!"

使用 k3d 运行和测试自定义镜像

你可以在 k3d 中使用该镜像:

k3d cluster create gputest --image=$IMAGE --gpus=1

部署一个测试 Pod:

kubectl apply -f cuda-vector-add.yaml
kubectl logs cuda-vector-add

输出结果应该类似于以下内容:

$ kubectl logs cuda-vector-add
[Vector addition of 50000 elements]
Copy input data from the host memory to the CUDA device
CUDA kernel launch with 196 blocks of 256 threads
Copy output data from the CUDA device to the host memory
Test PASSED
Done

如果 cuda-vector-add Pod 一直处于 Pending(挂起)状态,很可能是设备驱动守护进程集没有从自动部署清单中正确部署。在这种情况下,你可以通过 kubectl apply -f device-plugin-daemonset.yaml 手动应用它。