数据中心/云端

如何在共享 GPU 基础架构上运行隔离的租户 Kubernetes 集群

每个团队运行专用的 Kubernetes 集群通常会导致隔离程度超出组织所需的程度。虽然一个集群可以在多个团队之间成功共享,但协调成本会随着团队数量的增加而增加。挑战包括 CRD 版本冲突、RBAC 重叠,以及没有明确的方法将 GPU 容量划分到团队级别的预算。在一定程度上,团队可能会开始要求自己的集群来重新获得自主性。

本文提供了一种模式,即在不分割硬件的情况下保持团队自主性。此解决方案涉及具有 GPU 池的单个控制平面集群、按团队配额共享的 GPU,以及每个团队隔离的 Kubernetes 控制平面,包括 API 服务器、控制器、数据存储、同步器和调度程序。这可以使用两种开源工具来实现:KAI Scheduler 和 vCluster。

按照教程步骤操作,您将有三个团队在自己的租户集群中运行真实的 GPU Kubernetes Pod,并且都共享一个物理 GPU。您还可以验证每个团队只看到自己的工作负载。

教程预备知识和说明

为了让资源有限的用户能够重现此过程,本教程使用了一个集群,其中包含一个 NVIDIA L40S GPU,以及三个共享部分 GPU 的团队。这使得移动部件易于查看和尝试。该流程在包含数百个 GPU 节点和数十个团队的大型集群上也是如此。您可以扩展节点池、队列层次结构和租户集群的数量。

KAI Scheduler 是一款稳健、高效且可扩展的拓扑感知型 Kubernetes 调度程序,专为优化 AI 工作负载的 GPU 资源分配而构建。它旨在管理包含数千个节点的大规模 GPU 集群,以及高吞吐量的工作负载。借助 KAI Scheduler,您可以为工作负载动态分配 GPU 资源。它可以与默认的 kube-scheduler 一起运行。任何具有 schedulerName: kai-scheduler 的 Pod 都由 KAI Scheduler 处理。其他一切都会通过通常的 kube-scheduler 流程完成。

vCluster Kubernetes 平台可在您的基础设施上或直接在裸金属上提供完全隔离的租户集群。每个租户集群都有自己的 API 服务器、自定义资源定义 (CRD) 和基于角色的访问控制 (RBAC) ,与专用的 Kubernetes 集群别无二致,同时共享底层节点和硬件。租户无法看到虚拟化控制平面:没有共享的控制平面节点、没有集群内的智能体 Pod,以及环境之间没有横向路径。这使得 vCluster 非常适合 GPU 基础架构,因为团队需要在不拆分硬件的情况下获得自己的整洁集群体验。

本教程使用 vCluster 共享节点模型,因此团队共享 GPU 节点,而每个团队都获得各自独立的控制平面,这非常适合值得信赖的内部团队。对于需要节点级、网络级和存储级分离的未受信任租户,这种模式也适用于 vCluster 私有节点。

本文中的示例使用了三个团队:NLP 团队、视觉团队和推荐系统团队。NLP 团队希望安装自己的 CRD。视觉团队需要集群管理员来调试调度。推荐系统团队正在使用不同的 Kubeflow 版本。没有人想共享 kubectl 上下文,并意外破坏彼此的环境。

使用 vCluster,每个团队都可以获得各自独立的控制平面、RBAC、命名空间和 CRD。它们都可以拥有集群管理员权限。在下方,所有租户集群共享相同的节点和 GPU。

演示环境

此演示在 NVIDIA Brev GPU 实例上运行,具有:

  • 1 个 NVIDIA L40S、40 个 vCPU、160 GiB RAM、256 GiB 磁盘 (48 GB VRAM)
  • Ubuntu 24.04.4 LTS
  • MicroK8s v1.36.2 – Kubernetes 由 Brev 预配置,包括 MicroK8s GPU 插件,该插件将 NVIDIA GPU Operator 预安装到 gpu-operator-resources 命名空间
  • KAI 调度程序 v0.16.4
  • vCluster CLI 0.35.1

注意:对于不同的设置,GKE/ EKS/ AKS/ Vanilla k8s/k3s 上的集群创建和 GPU Operator 安装步骤将有所不同。在任何已启用容器设备接口 (CDI) 并运行 NVIDIA GPU Operator 的 Kubernetes 上,第 3 步 ( KAI 调度程序) 相同。

第 1 步:本地工具

安装独立的 kubectl 和 helm,以避免在每个命令前加上 microk8s。然后连接 kubeconfig 并固定 MicroK8,以便在演示期间 Snap 不会自动升级控制平面。

sudo snap refresh --hold microk8s

sudo snap install kubectl --classic --channel=1.35/stable
sudo snap install helm --classic

mkdir -p ~/.kube
sudo microk8s config > ~/.kube/config
sudo chown $USER:$USER ~/.kube/config
chmod 600 ~/.kube/config

接下来,确保已启用所需的 MicroK8s 插件:

microk8s enable dns
microk8s enable hostpath-storage    # vCluster needs PVCs

然后验证:

kubectl get nodes -o wide
kubectl get storageclass
NAME             STATUS   ROLES    AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION                CONTAINER-RUNTIME
brev-8dq0cch1j   Ready    <none>   57d   v1.36.2   10.0.0.20     <none>        Ubuntu 24.04.4 LTS   6.11.0-1016-nvidia (amd64)   containerd://2.2.3

NAME                          PROVISIONER            RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
microk8s-hostpath (default)   microk8s.io/hostpath   Delete          WaitForFirstConsumer   false                  20m

第 2 步:添加 Helm 资源库

添加 NVIDIA Helm 资源库:

helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update

第 3 步:确认 GPU Operator

kubectl get pods -n gpu-operator-resources
NAME                                                         READY   STATUS      RESTARTS   AGE
gpu-feature-discovery-wpcjm                                  1/1     Running     0          8m
gpu-operator-57d75775c8-npjzz                                1/1     Running     0          9m
gpu-operator-node-feature-discovery-...                      1/1     Running     0          9m
nvidia-container-toolkit-daemonset-ft4hb                     1/1     Running     0          8m
nvidia-cuda-validator-8vqdv                                  0/1     Completed   0          8m
nvidia-device-plugin-daemonset-l6fj6                         1/1     Running     0          8m
nvidia-operator-validator-cdnmj                              1/1     Running     0          8m

如果您使用的是较旧的 GPU Operator,请升级到 26.3.x:

helm upgrade gpu-operator nvidia/gpu-operator \
   -n gpu-operator-resources \
   --version v26.3.3 \
   --reset-then-reuse-values

如有需要,请直接安装 NVIDIA GPU Operator:

helm install gpu-operator nvidia/gpu-operator \
    -n gpu-operator-resources --create-namespace \
    --version v26.3.3 \
    --set driver.enabled=false \
    --set operator.defaultRuntime=containerd \
    --set toolkit.env[0].name=CONTAINERD_CONFIG \
    --set toolkit.env[0].value=/var/snap/microk8s/current/args/containerd.toml \
    --set toolkit.env[1].name=CONTAINERD_SOCKET \
    --set toolkit.env[1].value=/var/snap/microk8s/common/run/containerd.sock \
    --set-string toolkit.env[2].name=CONTAINERD_SET_AS_DEFAULT \
    --set-string toolkit.env[2].value=1

第 4 步:安装 KAI Scheduler

接下来,安装 KAI Scheduler:

helm upgrade -i kai-scheduler \
  oci://ghcr.io/kai-scheduler/kai-scheduler/kai-scheduler \
  -n kai-scheduler --create-namespace \
  --version v0.16.4 \
  --set "global.gpuSharing=true"

然后验证:

kubectl get pods -n kai-scheduler
NAME                                     READY   STATUS    RESTARTS   AGE
admission-57556f949-sp98t                1/1     Running   0          47s
binder-66785d8dd9-9frgk                  1/1     Running   0          46s
kai-operator-6fdf595c4d-292d7            1/1     Running   0          52s
kai-scheduler-default-6bb667b767-vq2q4   1/1     Running   0          46s
pod-grouper-84dfc7759b-v5qtb             1/1     Running   0          47s
podgroup-controller-5878f48dbb-v7wcn     1/1     Running   0          47s
queue-controller-7796bb8984-hdn5r        1/1     Running   0          46s

第 5 步:定义团队队列

KAI Scheduler 使用队列 CRD 对组织+ 团队层次结构进行建模。此步骤涉及创建一个父队列 (ml-org,总预算为一个 GPU) 和三个子队列。每个 GPU 都保证拥有 0.33 的 GPU,并允许在其他 GPU 空闲时增加到完整的 GPU。

将以下内容另存为 create-queues.yaml:

apiVersion: scheduling.run.ai/v2
kind: Queue
metadata:
  name: ml-org
spec:
  resources:
    gpu: { quota: 1, limit: -1, overQuotaWeight: 1 }
---
apiVersion: scheduling.run.ai/v2
kind: Queue
metadata:
  name: team-nlp
spec:
  parentQueue: ml-org
  priority: 100
  resources:
    gpu: { quota: 0.33, limit: 1, overQuotaWeight: 1 }
---
apiVersion: scheduling.run.ai/v2
kind: Queue
metadata:
  name: team-vision
spec:
  parentQueue: ml-org
  priority: 100
  resources:
    gpu: { quota: 0.33, limit: 1, overQuotaWeight: 1 }
---
apiVersion: scheduling.run.ai/v2
kind: Queue
metadata:
  name: team-recommender
spec:
  parentQueue: ml-org
  priority: 100
  resources:
    gpu: { quota: 0.33, limit: 1, overQuotaWeight: 1 }

此处,quota 是有保证的最小值,limit 是允许的最大值,overQuotaWeight 控制剩余部分的分割方式。

接下来,应用并列出:

kubectl apply -f create-queues.yaml
kubectl get queues
queue.scheduling.run.ai/ml-org created
queue.scheduling.run.ai/team-nlp created
queue.scheduling.run.ai/team-vision created
queue.scheduling.run.ai/team-recommender created

NAME                   PRIORITY   PARENT                 CHILDREN                                        DISPLAYNAME
default-parent-queue                                     ["default-queue"]
default-queue                     default-parent-queue
ml-org                                                   ["team-nlp","team-vision","team-recommender"]
team-nlp               100        ml-org
team-recommender       100        ml-org
team-vision            100        ml-org

请注意,default-parent-queue 和 default-queue 在首次安装时由 KAI Scheduler 自动创建。它们是任何未指定 POD 的后备队列。

第 6 步:为每个团队启动 vCluster

接下来,安装 vCluster CLI:

curl -L -o vcluster "https://github.com/loft-sh/vcluster/releases/latest/download/vcluster-linux-amd64"
sudo install -m 755 vcluster /usr/local/bin/vcluster
rm vcluster
vcluster --version
vcluster version 0.35.1

定义 vCluster 配置。关键设置为 setOwner: false。KAI Scheduler pod-grouper 将所有权链 (工作+ Pod、部署+ ReplicaSet+ Pod) 引入自动对工作负载进行分组。禁用 vCluster 所有者重写可让 KAI Scheduler 查看真实的层次结构。

cat > vcluster.yaml <<'EOF'
experimental:
  syncSettings:
    setOwner: false 

sync:
  fromHost:
    nodes:
      enabled: true
      selector:
        all: true
EOF

然后为每个团队创建一个 vCluster:

vcluster create team-nlp          --values vcluster.yaml --connect=false
vcluster create team-vision       --values vcluster.yaml --connect=false
vcluster create team-recommender  --values vcluster.yaml --connect=false

验证三个 vClusters 是否都在运行:

vcluster list
        NAME       |         NAMESPACE         | STATUS  | VERSION | CONNECTED | AGE
  -------------------+---------------------------+---------+---------+-----------+-------
    team-nlp         | vcluster-team-nlp         | Running | 0.35.1  |           | 102s
    team-recommender | vcluster-team-recommender | Running | 0.35.1  |           | 88s
    team-vision      | vcluster-team-vision      | Running | 0.35.1  |           | 93s

每个团队都能看到真实节点,包括 GPU 节点:

vcluster connect team-nlp -- kubectl get nodes
19:09:22 done vCluster is up and running
NAME             STATUS   ROLES    AGE     VERSION
brev-8dq0cch1j   Ready    <none>   6m20s   v1.36.2

第 7 步:部署每个团队的工作负载

每个团队都通过自己的 vCluster 部署 GPU 工作负载。Pod 规格很简单,有三个字段告诉 KAI Scheduler 要做什么:

vcluster connect team-nlp -- kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: nlp-sentiment-model
  labels:
    kai.scheduler/queue: team-nlp # Which team 
  annotations:
    gpu-fraction: "0.33"          # How much GPU
spec:
  schedulerName: kai-scheduler.   # Use KAI, not default
  tolerations:
    - key: nvidia.com/gpu
      operator: Exists
      effect: NoSchedule
  containers:
    - name: nlp-inference
      image: nvidia/cuda:12.4.0-base-ubuntu22.04
      command: ["bash", "-c", "nvidia-smi; sleep infinity"]
  nodeSelector:
    nvidia.com/gpu.present: "true"
EOF
19:13:34 done vCluster is up and running
pod/nlp-sentiment-model created

对每个团队执行相同的部署操作,更改队列名称。

现在,验证:

NAMESPACE                   NAME                                             READY   STATUS    RESTARTS   AGE    IP             NODE
vcluster-team-nlp           nlp-sentiment-model-x-default-x-team-nlp         1/1     Running   0          110s   10.1.171.180   brev-8dq0cch1j
vcluster-team-recommender   recommender-model-x-default-x-team-recommender   1/1     Running   0          6s     10.1.171.187   brev-8dq0cch1j
vcluster-team-vision        vision-classifier-model-x-default-x-team-vision  1/1     Running   0          14s    10.1.171.145   brev-8dq0cch1j

三个 POD、三个不同的 vcluster-team-* 命名空间,均在同一物理节点 brev-8dq0cch1j 上运行。

每个团队只能在其 vCluster 内部看到自己的 Pod:

vcluster connect team-nlp         -- kubectl get pods -o wide
vcluster connect team-vision      -- kubectl get pods -o wide
vcluster connect team-recommender -- kubectl get pods -o wide
NAME                  READY   STATUS    RESTARTS   AGE     IP             NODE             NOMINATED NODE   READINESS GATES
nlp-sentiment-model   1/1     Running   0          3m11s   10.1.171.180   brev-8dq0cch1j   <none>           <none>

NAME                      READY   STATUS    RESTARTS   AGE     IP             NODE             NOMINATED NODE   READINESS GATES
vision-classifier-model   1/1     Running   0          3m59s   10.1.171.145   brev-8dq0cch1j   <none>           <none>

NAME                READY   STATUS    RESTARTS   AGE     IP             NODE             NOMINATED NODE   READINESS GATES
recommender-model   1/1     Running   0          2m45s   10.1.171.187   brev-8dq0cch1j   <none>           <none>

最后,队列确认分配。您可以同时查看这三个选项:

kubectl describe queue team-nlp         | grep -A4 Status
kubectl describe queue team-vision      | grep -A4 Status
kubectl describe queue team-recommender | grep -A4 Status

您将看到相同的结果:

Status:
  Allocated:
    nvidia.com/gpu:  330m
  Requested:
    nvidia.com/gpu:  330m

请注意,KAI Scheduler 负责处理调度,即哪些 POD 以何种比例登陆哪个 GPU。使用 GPU 共享时,它不会在硬件级别执行 GPU 显存隔离。应用需要考虑其显存容量 (例如,在 vLLM 中设置 – gpu-memory-utilization) 。

实际上,GPU 时间片会在内核边界的每个 POD 的 CUDA 上下文之间切换。对于受支持硬件上的硬盘显存隔离,NVIDIA 多实例 GPU (MIG) 提供了硬件级分区,也可以通过 KAI Scheduler 进行调度。

开始运行独立的租户 Kubernetes 集群

KAI Scheduler 公平地确定谁将获得 GPU 切片,并使用 GPU 共享和 DRA 驱动程序支持、具有有保证的配额和超额功能的分层队列、分组调度和拓扑感知,将其调度为一组。完成 AI 工作负载调度后,团队需要拥有自己的集群。vCluster 为每个团队提供各自独立的 Kubernetes 控制平面,而无需单独的基础架构。

KAI Scheduler 和 vCluster 共同为三个团队提供专用集群体验,且在单个 GPU 上实现零浪费。答案并不总是在于增加 GPU 数量,而是在于更好地利用基础设施。

准备好开始了吗?查看 GitHub 上的 KAI Scheduler、vCluster 和 NVIDIA GPU Operator。

参加 11 月 9 日至 12 日举行的 KubeCon 2026 North America 大会,详细了解 KAI Scheduler 和 vCluster 集成。

标签