Alle Artikel

GPU-Berechtigungen in Kubernetes mit DRA und vCluster

GPU-Sharing mit Kubernetes DRA und vCluster im Detail: Ein Team erhält dediziertes A100-Kontingent, ein anderes einen MIG-Slice – sauber voneinander isoliert.

·

Jannis Schoormann

Chat GPT Image 30 Juni 2026 19 09 42

Hinweis: Dieser Beitrag wurde aus dem Englischen übersetzt. Fachbegriffe, Code und Befehle wurden bewusst im Original belassen.

GPU-Sharing in Kubernetes ist einfach – bis Mandanten unterschiedliche Hardware-Berechtigungen haben. Ein Team benötigt womöglich exklusive A100-Kapazität, ein anderes nur eine kleine MIG-Slice. Das Schwierige daran ist nicht allein das Scheduling dieser Geräte, sondern sicherzustellen, dass jeder Mandant nur die GPU-Klassen sieht und anfordern kann, die ihm zustehen.

Dieser Beitrag führt eine Antwort von Anfang bis Ende durch. Dynamic Resource Allocation (DRA) liefert das Modell für die GPU-Zuteilung, und der selektorbasierte Sync von vCluster gibt jedem Mandanten eine gefilterte Sicht auf den GPU-Katalog. Am Ende betreiben Sie einen GKE-Cluster mit zwei virtuellen Mandanten-Clustern: legal-ai mit Zugriff auf eine Whole-GPU-Klasse und research-ai mit Zugriff auf eine MIG-Slice-Klasse. Jeder Mandant sieht ausschließlich seine eigene GPU-Berechtigung.

Alles Folgende lässt sich aus einem leeren GCP-Projekt heraus nachvollziehen. Die Demo-Dateien liegen unter github.com/Liquid-Reply/vCluster-Demo.

Grob gesagt können Ressourcen in einem virtuellen Cluster eine GPU auf zwei Arten nutzen: Ein Mandant belegt eine ganze Karte, oder mehrere Mandanten belegen jeweils einen Hardware-Ausschnitt einer einzelnen Karte. Beide Modelle laufen nebeneinander auf demselben Host. Dieselbe physische GPU-Kapazität lässt sich in mandantenspezifische Berechtigungen aufteilen, sodass mehrere Mandanten GPU-Infrastruktur sicher gemeinsam nutzen, ohne die Zuteilungsklassen der jeweils anderen zu sehen oder anzufordern.

Skizze eines Host-Clusters, in dem der vCluster legal-ai eine ganze GPU nutzt, während drei research-ai-vCluster jeweils einen MIG-Slice einer zweiten GPU belegen

Für wen dieser Beitrag ist

Er richtet sich an Platform Engineers, die mandantenfähige GPU-Cluster betreiben, in denen Mandanten unterschiedliche Berechtigungen besitzen und die Hardware der anderen nicht sehen sollen. Denken Sie an ein reguliertes Unternehmen mit getrennten Teams (Recht, Forschung, ein gehostetes LLM-Produkt), die sich eine gemeinsame Infrastruktur teilen.

Sie sollten mit Kubernetes und den Grundlagen des GPU-Scheduling vertraut sein. Vorerfahrung mit DRA oder vCluster ist nicht erforderlich.

Die Bausteine

DRA (Dynamic Resource Allocation) ist der strukturierte Nachfolger des Device-Plugin-Modells. Der GPU-Treiber veröffentlicht ResourceSlice-Objekte, die die realen Geräte und deren Eigenschaften beschreiben. Das Plattformteam definiert DeviceClass-Objekte – benannte, selektorbasierte Sichten auf diese Geräte. Ein Workload fordert Hardware anschließend über einen ResourceClaim an, der eine DeviceClass per Namen referenziert, und DRA weist ein passendes Gerät zu. DRA ist in Kubernetes 1.34 unter resource.k8s.io/v1 GA geworden – der API-Version, die in diesem Beitrag durchgängig verwendet wird.

Der entscheidende Punkt: Eine DeviceClass ist lediglich ein gelabeltes, selektierbares Objekt. Genau das macht sie zum Dreh- und Angelpunkt dieses Setups, denn alles, was nach Labels filtern kann, entscheidet darüber, welche Klassen ein Mandant zu sehen bekommt. Behalten Sie das im Hinterkopf, wenn gleich vCluster ins Spiel kommt.

Das folgende Diagramm zeigt, wie diese Objekte zusammenhängen. Die DeviceClass steht in der Mitte, definiert vom Plattformteam und referenziert von jedem Workload-Claim:

Skizze der Objekte der Kubernetes Dynamic Resource Allocation: Der GPU-Treiber veröffentlicht einen ResourceSlice, die DeviceClass filtert ihn, und ein Workload referenziert einen ResourceClaim, dem ein GPU-Gerät zugewiesen wird

vCluster betreibt eine echte Kubernetes-Control-Plane innerhalb eines Namespace des Host-Clusters. Jeder Mandant verfügt über einen eigenen API-Server, eigene CRDs und eigenes RBAC, während die Nodes des Hosts gemeinsam genutzt werden. Da es sich um eine echte Control Plane handelt und nicht bloß um einen gelabelten Namespace, ist die Isolation strukturell: Ein Mandant hat schlicht keinen API-Zugriff auf Objekte, die nicht zu ihm synchronisiert wurden.

Dieser Sync ist die andere Hälfte der Geschichte. Die bidirektionale Sync-Schicht bestimmt, welche Ressourcen zwischen virtuellem Cluster und Host fließen – und das lässt sich per Label-Selektor filtern. Erinnern Sie sich: DeviceClass ist ein gelabeltes Objekt. Genau daran setzt der Filter an. Der Host-Katalog fließt zu jedem Mandanten herunter, aber nur mit den Klassen, deren Labels zu diesem Mandanten passen. Dieser eine Filter macht aus einem gemeinsamen GPU-Katalog eine mandantenspezifische Sicht.

vCluster-Architekturdiagramm: High-Level- und Low-Level-Ressourcen aus zwei virtuellen Namespaces werden vom Syncer auf umbenannte Pods und Services im physischen Host-Cluster abgebildet, während Nutzer die vCluster-Control-Plane per kubectl erreichen

Voraussetzung: DRA Sync ist eine Platform-Funktion von vCluster. Sie ist ab der Stufe vCluster Free verfügbar. Ohne sie stürzt der Syncer beim Start ab mit you are trying to use a vCluster pro feature 'DRA Sync'.

Warum nicht einfach Namespaces?

Ein Namespace ist eine gute Grenze für namespace-gebundene Objekte: Pods, Services, die meisten Anwendungsressourcen. Der GPU-Katalog in dieser Demo ist jedoch kein namespace-gebundener Workload-Zustand. DRA führt cluster-weite Objekte wie DeviceClass und ResourceSlice ein, die den gemeinsamen Hardwarebestand des gesamten Host-Clusters beschreiben.

Mit einfachen Namespaces sprechen weiterhin alle Mandanten mit demselben API-Server. Sie können über RBAC und Admission Policies einschränken, was sie auflisten oder anlegen dürfen – doch Sie bauen all das um eine gemeinsame Control Plane und einen gemeinsamen Satz cluster-weiter Ressourcen herum. Das bringt Sie in eine unangenehme Lage: Dürfen Mandanten DeviceClass-Objekte entdecken, sehen sie mehr vom GPU-Katalog, als sie sollten. Dürfen sie es nicht, bricht das Self-Service-Erlebnis zusammen. Und da ein ResourceClaim eine DeviceClass per Namen referenziert, brauchen Sie noch weitere Admission Controls, nur um zu verhindern, dass ein Mandant eine Klasse außerhalb seiner Berechtigung anfordert. Jede Zusicherung wird zu einer Policy, die Sie schreiben – und für immer korrekt halten – müssen.

vCluster verändert die Form des Problems. Jeder Mandant erhält einen eigenen API-Server, eigenes RBAC, eigene CRDs und eine eigene Objektsicht. Das Host-Plattformteam behält die Hoheit über den echten GPU-Katalog, und vCluster synchronisiert nur die erlaubten DeviceClass-Objekte in den jeweiligen Mandanten-Cluster. Aus Sicht des Mandanten scheitert eine unzulässige GPU-Klasse nicht an der Autorisierung – sie existiert schlicht nicht.

Deshalb hat vCluster hier seine Berechtigung. Nicht, weil Namespaces nutzlos wären, sondern weil dieses Muster mandantenspezifische API-Sichtbarkeit für cluster-weite Ressourcen benötigt – und die kann ein Namespace nicht liefern.

Die Architektur

Das Host-Plattformteam besitzt die DeviceClass-Objekte. vCluster synchronisiert nur die passende Klasse zu jedem Mandanten und synchronisiert die vom Mandanten erstellten ResourceClaim-Objekte zur Zuteilung zurück zum Host.

Skizze der Demo-Architektur: Der Host-Cluster besitzt die DeviceClasses gpu-whole und gpu-slice, die gefiltert in die vCluster legal-ai und research-ai synchronisiert werden, während ResourceClaims der Mandanten zurück zum Host fließen

Ein wichtiges Detail: Die Kapazität für ganze GPUs und für MIG-Slices stammt in dieser Demo aus unterschiedlichen GPU-Node-Pools. Eine einzelne A100 wird nicht gleichzeitig als vollständige GPU und als MIG-Slices bereitgestellt. Der Punkt der Demo ist, dass eine gemeinsame Kubernetes-Plattform beide Zuteilungsmodelle nebeneinander anbieten kann: exklusive Whole-GPU-Kapazität für den einen Mandanten und MIG-gestützte Slice-Kapazität für den anderen. Der Slice-Pfad wird hier mit einem einzelnen Mandanten gezeigt, doch das Muster lässt sich erweitern, indem weitere vClusters hinzukommen und jedem eine eigene MIG-gestützte Slice-Berechtigung zugeordnet wird, wenn sich mehrere Mandanten eine physische GPU teilen sollen.

Was Sie benötigen

Werkzeug

Mindestversion

gcloud-CLI

v400+

kubectl

passend zu einem Cluster ab 1.34 (DRA GA)

Helm

v3.12+

vCluster-CLI

v0.35+

Schritt 0 – Repository klonen und Umgebung setzen

git clone https://github.com/Liquid-Reply/vCluster-Demo.git
cd vCluster-Demo

export PROJECT_ID=your-gcp-project-id
export CLUSTER=dra-gpu-sync-demo
export PLAT_NS=vcluster-platform
export GPU_TYPE=nvidia-tesla-a100
export GPU_MACHINE_TYPE=a2-highgpu-1g
export MIG_PROFILE=1g.5gb
export WHOLE_GPU_NODEPOOL=a100-whole-pool
export SLICE_GPU_NODEPOOL=a100-mig-1g5gb-pool

gcloud config set project $PROJECT_ID
git clone https://github.com/Liquid-Reply/vCluster-Demo.git
cd vCluster-Demo

export PROJECT_ID=your-gcp-project-id
export CLUSTER=dra-gpu-sync-demo
export PLAT_NS=vcluster-platform
export GPU_TYPE=nvidia-tesla-a100
export GPU_MACHINE_TYPE=a2-highgpu-1g
export MIG_PROFILE=1g.5gb
export WHOLE_GPU_NODEPOOL=a100-whole-pool
export SLICE_GPU_NODEPOOL=a100-mig-1g5gb-pool

gcloud config set project $PROJECT_ID
git clone https://github.com/Liquid-Reply/vCluster-Demo.git
cd vCluster-Demo

export PROJECT_ID=your-gcp-project-id
export CLUSTER=dra-gpu-sync-demo
export PLAT_NS=vcluster-platform
export GPU_TYPE=nvidia-tesla-a100
export GPU_MACHINE_TYPE=a2-highgpu-1g
export MIG_PROFILE=1g.5gb
export WHOLE_GPU_NODEPOOL=a100-whole-pool
export SLICE_GPU_NODEPOOL=a100-mig-1g5gb-pool

gcloud config set project $PROJECT_ID

A100 wird nicht in jeder Zone angeboten, und ein regionaler Cluster kann keine GPU-Node-Pools aus einer Zone beziehen, die er nicht abdeckt. Wählen Sie also zuerst eine Zone und passen Sie die Region darauf an:

gcloud compute accelerator-types list \
  --filter="name=($GPU_TYPE)" \
  --format="table(name,zone)"
gcloud compute accelerator-types list \
  --filter="name=($GPU_TYPE)" \
  --format="table(name,zone)"
gcloud compute accelerator-types list \
  --filter="name=($GPU_TYPE)" \
  --format="table(name,zone)"

Wählen Sie eine Zone aus der Ausgabe und setzen Sie sowohl GPU_ZONE als auch die zugehörige LOCATION. Wenn beispielsweise europe-west4-a erscheint:

export GPU_ZONE=europe-west4-a
export LOCATION

export GPU_ZONE=europe-west4-a
export LOCATION

export GPU_ZONE=europe-west4-a
export LOCATION

Legen Sie den Cluster erst an, wenn beides korrekt gesetzt ist.

Schritt 1 – APIs aktivieren und den GKE-Cluster anlegen

gcloud services enable \
  container.googleapis.com compute.googleapis.com iam.googleapis.com \
  cloudresourcemanager.googleapis.com serviceusage.googleapis.com

gcloud container clusters create $CLUSTER \
  --location=$LOCATION \
  --machine-type=e2-standard-4 \
  --num-nodes=1 \
  --release-channel=regular \
  --enable-ip-alias

gcloud container clusters get-credentials $CLUSTER --location=$LOCATION

kubectl create clusterrolebinding cluster-admin-binding \
  --clusterrole=cluster-admin \
  --user=<YOUR_EMAIL>

kubectl get

gcloud services enable \
  container.googleapis.com compute.googleapis.com iam.googleapis.com \
  cloudresourcemanager.googleapis.com serviceusage.googleapis.com

gcloud container clusters create $CLUSTER \
  --location=$LOCATION \
  --machine-type=e2-standard-4 \
  --num-nodes=1 \
  --release-channel=regular \
  --enable-ip-alias

gcloud container clusters get-credentials $CLUSTER --location=$LOCATION

kubectl create clusterrolebinding cluster-admin-binding \
  --clusterrole=cluster-admin \
  --user=<YOUR_EMAIL>

kubectl get

gcloud services enable \
  container.googleapis.com compute.googleapis.com iam.googleapis.com \
  cloudresourcemanager.googleapis.com serviceusage.googleapis.com

gcloud container clusters create $CLUSTER \
  --location=$LOCATION \
  --machine-type=e2-standard-4 \
  --num-nodes=1 \
  --release-channel=regular \
  --enable-ip-alias

gcloud container clusters get-credentials $CLUSTER --location=$LOCATION

kubectl create clusterrolebinding cluster-admin-binding \
  --clusterrole=cluster-admin \
  --user=<YOUR_EMAIL>

kubectl get

Schritt 2 – vCluster Platform installieren

Platform muss laufen, bevor Sie Mandanten-vClusters anlegen.

vcluster platform start
vcluster platform start
vcluster platform start

Damit wird Platform ausgerollt und dessen Konfigurationsoberfläche geöffnet. Warten Sie, bis alle Pods den Status Running erreichen:

kubectl get pods -n $PLAT_NS
kubectl get pods -n $PLAT_NS
kubectl get pods -n $PLAT_NS

Prüfen Sie, dass die CLI angemeldet ist und das Standardprojekt sieht. Und nicht vergessen: Die Installation muss für DRA Sync lizenziert sein, sonst stürzt der Syncer später ab.

vcluster platform list projects   # expect: default
vcluster platform list projects   # expect: default
vcluster platform list projects   # expect: default

Schritt 3 – GPU-Nodes und DRA-Treiber hinzufügen

Zwei kleine Node Pools: eine ganze A100 für „legal” und eine A100, aufgeteilt in 1g.5gb-MIG-Slices für „research”. Beide skalieren auf null und tragen einen GPU-Taint.

# Whole-GPU pool
gcloud container node-pools create $WHOLE_GPU_NODEPOOL \
  --cluster=$CLUSTER --location=$LOCATION --node-locations=$GPU_ZONE \
  --machine-type=$GPU_MACHINE_TYPE \
  --accelerator=type=$GPU_TYPE,count=1,gpu-driver-version=latest \
  --num-nodes=1 --image-type=COS_CONTAINERD \
  --enable-autoscaling --min-nodes=0 --max-nodes=1 \
  --node-taints=nvidia.com/gpu=present:NoSchedule

# MIG-sliced pool (one A100 -> seven 1g.5gb slices)
gcloud container node-pools create $SLICE_GPU_NODEPOOL \
  --cluster=$CLUSTER --location=$LOCATION --node-locations=$GPU_ZONE \
  --machine-type=$GPU_MACHINE_TYPE \
  --accelerator=type=$GPU_TYPE,count=1,gpu-partition-size=$MIG_PROFILE,gpu-driver-version=latest \
  --num-nodes=1 --image-type=COS_CONTAINERD \
  --enable-autoscaling --min-nodes=0 --max-nodes=1 \
  --node-taints=nvidia.com/gpu

# Whole-GPU pool
gcloud container node-pools create $WHOLE_GPU_NODEPOOL \
  --cluster=$CLUSTER --location=$LOCATION --node-locations=$GPU_ZONE \
  --machine-type=$GPU_MACHINE_TYPE \
  --accelerator=type=$GPU_TYPE,count=1,gpu-driver-version=latest \
  --num-nodes=1 --image-type=COS_CONTAINERD \
  --enable-autoscaling --min-nodes=0 --max-nodes=1 \
  --node-taints=nvidia.com/gpu=present:NoSchedule

# MIG-sliced pool (one A100 -> seven 1g.5gb slices)
gcloud container node-pools create $SLICE_GPU_NODEPOOL \
  --cluster=$CLUSTER --location=$LOCATION --node-locations=$GPU_ZONE \
  --machine-type=$GPU_MACHINE_TYPE \
  --accelerator=type=$GPU_TYPE,count=1,gpu-partition-size=$MIG_PROFILE,gpu-driver-version=latest \
  --num-nodes=1 --image-type=COS_CONTAINERD \
  --enable-autoscaling --min-nodes=0 --max-nodes=1 \
  --node-taints=nvidia.com/gpu

# Whole-GPU pool
gcloud container node-pools create $WHOLE_GPU_NODEPOOL \
  --cluster=$CLUSTER --location=$LOCATION --node-locations=$GPU_ZONE \
  --machine-type=$GPU_MACHINE_TYPE \
  --accelerator=type=$GPU_TYPE,count=1,gpu-driver-version=latest \
  --num-nodes=1 --image-type=COS_CONTAINERD \
  --enable-autoscaling --min-nodes=0 --max-nodes=1 \
  --node-taints=nvidia.com/gpu=present:NoSchedule

# MIG-sliced pool (one A100 -> seven 1g.5gb slices)
gcloud container node-pools create $SLICE_GPU_NODEPOOL \
  --cluster=$CLUSTER --location=$LOCATION --node-locations=$GPU_ZONE \
  --machine-type=$GPU_MACHINE_TYPE \
  --accelerator=type=$GPU_TYPE,count=1,gpu-partition-size=$MIG_PROFILE,gpu-driver-version=latest \
  --num-nodes=1 --image-type=COS_CONTAINERD \
  --enable-autoscaling --min-nodes=0 --max-nodes=1 \
  --node-taints=nvidia.com/gpu

Warten Sie, bis die GPU-Nodes beitreten (kubectl get nodes -w), und installieren Sie dann den NVIDIA-DRA-Treiber. Auf GKE müssen Sie ihn auf den von GKE verwalteten Treiberpfad verweisen:

helm install dra-driver-nvidia-gpu \
  oci://registry.k8s.io/dra-driver-nvidia/charts/dra-driver-nvidia-gpu \
  --version 0.4.0 \
  --namespace dra-driver-nvidia-gpu --create-namespace \
  --set gpuResourcesEnabledOverride=true \
  --set resources.computeDomains.enabled=false \
  --set nvidiaDriverRoot

helm install dra-driver-nvidia-gpu \
  oci://registry.k8s.io/dra-driver-nvidia/charts/dra-driver-nvidia-gpu \
  --version 0.4.0 \
  --namespace dra-driver-nvidia-gpu --create-namespace \
  --set gpuResourcesEnabledOverride=true \
  --set resources.computeDomains.enabled=false \
  --set nvidiaDriverRoot

helm install dra-driver-nvidia-gpu \
  oci://registry.k8s.io/dra-driver-nvidia/charts/dra-driver-nvidia-gpu \
  --version 0.4.0 \
  --namespace dra-driver-nvidia-gpu --create-namespace \
  --set gpuResourcesEnabledOverride=true \
  --set resources.computeDomains.enabled=false \
  --set nvidiaDriverRoot

Prüfen Sie, dass der Treiber seine Klassen registriert hat und Device Slices veröffentlicht:

kubectl get pods -n dra-driver-nvidia-gpu
kubectl get deviceclasses          # expect gpu.nvidia.com, mig.nvidia.com, ...
kubectl get resourceslices -o wide # expect slices for both drivers
kubectl get pods -n dra-driver-nvidia-gpu
kubectl get deviceclasses          # expect gpu.nvidia.com, mig.nvidia.com, ...
kubectl get resourceslices -o wide # expect slices for both drivers
kubectl get pods -n dra-driver-nvidia-gpu
kubectl get deviceclasses          # expect gpu.nvidia.com, mig.nvidia.com, ...
kubectl get resourceslices -o wide # expect slices for both drivers

Das klassische NVIDIA-Device-Plugin stellt nvidia.com/gpu bereit und ist nicht dasselbe wie DRA. Die gesamte Demo hängt an dem oben genannten DRA-Treiber.

Schritt 4 – Das Plattformteam definiert die GPU-Klassen der Mandanten

Diese leben ausschließlich auf dem Host-Cluster. Jede trägt ein Mandanten-Label und einen CEL-Selektor, der sie an einen Treiber bindet (und bei der Slice zusätzlich an ein MIG-Profil):

apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: gpu-slice
  labels:
    gpu.platform/tenant: research-ai
    gpu.platform/allocation: slice
    kubernetes.io/device.class: nvidia
spec:
  selectors:
    - cel:
        expression: "device.driver == 'mig.nvidia.com' && device.attributes['gpu.nvidia.com'].profile == '1g.5gb'"
---
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: gpu-whole
  labels:
    gpu.platform/tenant: legal-ai
    gpu.platform/allocation: whole
    kubernetes.io/device.class: nvidia
spec:
  selectors:
    - cel:
        expression: "device.driver == 'gpu.nvidia.com'"
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: gpu-slice
  labels:
    gpu.platform/tenant: research-ai
    gpu.platform/allocation: slice
    kubernetes.io/device.class: nvidia
spec:
  selectors:
    - cel:
        expression: "device.driver == 'mig.nvidia.com' && device.attributes['gpu.nvidia.com'].profile == '1g.5gb'"
---
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: gpu-whole
  labels:
    gpu.platform/tenant: legal-ai
    gpu.platform/allocation: whole
    kubernetes.io/device.class: nvidia
spec:
  selectors:
    - cel:
        expression: "device.driver == 'gpu.nvidia.com'"
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: gpu-slice
  labels:
    gpu.platform/tenant: research-ai
    gpu.platform/allocation: slice
    kubernetes.io/device.class: nvidia
spec:
  selectors:
    - cel:
        expression: "device.driver == 'mig.nvidia.com' && device.attributes['gpu.nvidia.com'].profile == '1g.5gb'"
---
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: gpu-whole
  labels:
    gpu.platform/tenant: legal-ai
    gpu.platform/allocation: whole
    kubernetes.io/device.class: nvidia
spec:
  selectors:
    - cel:
        expression: "device.driver == 'gpu.nvidia.com'"
kubectl apply -f dra-gpu-sync/gpu-deviceclasses.yaml
kubectl get deviceclasses --show-labels
kubectl apply -f dra-gpu-sync/gpu-deviceclasses.yaml
kubectl get deviceclasses --show-labels
kubectl apply -f dra-gpu-sync/gpu-deviceclasses.yaml
kubectl get deviceclasses --show-labels

gpu-whole wählt ein vollständiges GPU-Gerät aus; gpu-slice wählt ausschließlich eine MIG-Partition vom Typ 1g.5gb. Genau diese Unterscheidung macht daraus eine echte Whole-vs.-Sliced-Demo.

Schritt 5 – Für jeden Mandanten einen vCluster mit gefilterter Sicht anlegen

Das ist der eigentliche Mechanismus. Die vCluster-Konfiguration aktiviert DRA Sync und bindet den Filter fromHost.deviceClasses an ein Mandanten-Label:

# legal-ai vCluster config for DRA sync.
sync:
  toHost:
    resourceClaims:
      enabled: true
  fromHost:
    deviceClasses:
      enabled: true
      selector:
        matchLabels:
          gpu.platform/tenant

# legal-ai vCluster config for DRA sync.
sync:
  toHost:
    resourceClaims:
      enabled: true
  fromHost:
    deviceClasses:
      enabled: true
      selector:
        matchLabels:
          gpu.platform/tenant

# legal-ai vCluster config for DRA sync.
sync:
  toHost:
    resourceClaims:
      enabled: true
  fromHost:
    deviceClasses:
      enabled: true
      selector:
        matchLabels:
          gpu.platform/tenant

Die Konfiguration für research-ai ist identisch, abgesehen vom Label-Wert (gpu.platform/tenant: research-ai).

  • fromHost.deviceClasses plus Selektor: Der Host-Katalog fließt herunter, aber nur mit den Klassen, die zu diesem Mandanten passen. legal-ai erhält also gpu-whole und bekommt gpu-slice nie zu sehen.

  • toHost.resourceClaims: Vom Mandanten erstellte Claims fließen hinauf zum Host, wo der DRA-Treiber die Zuteilung vornimmt.

Der Filter matchLabels teilt einen gemeinsamen Katalog in zwei mandantenspezifische Sichten:

Skizze, wie ein matchLabels-Filter den gemeinsamen Host-Katalog aufteilt, sodass der vCluster legal-ai nur gpu-whole und der vCluster research-ai nur gpu-slice sieht

Einschränkung: In der getesteten vCluster-Version liegt der Label-Filter auf `fromHost.deviceClasses`. `toHost.resourceClaims` ist aktiviert, wird aber selbst nicht nach Labels gefiltert. Der Selektor verschafft jedem Mandanten eine gefilterte Sicht auf den GPU-Katalog, doch gefilterte Sichtbarkeit sollte nicht als alleinige Sicherheitsgrenze betrachtet werden. Ein Mandant könnte weiterhin versuchen, einen `ResourceClaim` anzulegen, der auf einen erratenen `DeviceClass`-Namen verweist. Ergänzen Sie im Produktivbetrieb eine hostseitige Admission Policy, die jeden synchronisierten `ResourceClaim` gegen die erlaubte `DeviceClass`-Liste des Mandanten prüft.

Legen Sie beide vClusters über die Platform an:

vcluster create legal-ai \
  --driver platform --project default --cluster loft-cluster \
  --values dra-gpu-sync/legal-ai-vcluster-config.yaml --connect=false

vcluster create research-ai \
  --driver platform --project default --cluster loft-cluster \
  --values dra-gpu-sync/research-ai-vcluster-config.yaml --connect=false

vcluster list --driver platform   # wait until both are ready
vcluster create legal-ai \
  --driver platform --project default --cluster loft-cluster \
  --values dra-gpu-sync/legal-ai-vcluster-config.yaml --connect=false

vcluster create research-ai \
  --driver platform --project default --cluster loft-cluster \
  --values dra-gpu-sync/research-ai-vcluster-config.yaml --connect=false

vcluster list --driver platform   # wait until both are ready
vcluster create legal-ai \
  --driver platform --project default --cluster loft-cluster \
  --values dra-gpu-sync/legal-ai-vcluster-config.yaml --connect=false

vcluster create research-ai \
  --driver platform --project default --cluster loft-cluster \
  --values dra-gpu-sync/research-ai-vcluster-config.yaml --connect=false

vcluster list --driver platform   # wait until both are ready

Schritt 6 – Mandanten fordern Hardware in ihren eigenen Clustern an

Jedes Team arbeitet vollständig innerhalb seines vCluster und sieht den Host nie. Das Rechtsteam fordert eine ganze GPU an:

apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
  name: legal-whole-gpu
  labels:
    gpu.platform/tenant: legal-ai
    gpu.platform/allocation: whole
spec:
  devices:
    requests:
      - name: gpu
        exactly:
          deviceClassName: gpu-whole
          allocationMode: ExactCount
          count: 1
apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
  name: legal-whole-gpu
  labels:
    gpu.platform/tenant: legal-ai
    gpu.platform/allocation: whole
spec:
  devices:
    requests:
      - name: gpu
        exactly:
          deviceClassName: gpu-whole
          allocationMode: ExactCount
          count: 1
apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
  name: legal-whole-gpu
  labels:
    gpu.platform/tenant: legal-ai
    gpu.platform/allocation: whole
spec:
  devices:
    requests:
      - name: gpu
        exactly:
          deviceClassName: gpu-whole
          allocationMode: ExactCount
          count: 1
vcluster connect legal-ai --namespace legal-ai
kubectl apply -f dra-gpu-sync/legal-whole-gpu-resourceclaim.yaml
kubectl apply -f dra-gpu-sync/contract-summarizer-dra.yaml   # the legal workload (from the repo)
kubectl get pods -w
kubectl logs contract-summarizer    # should show a whole A100, not a MIG device
vcluster connect legal-ai --namespace legal-ai
kubectl apply -f dra-gpu-sync/legal-whole-gpu-resourceclaim.yaml
kubectl apply -f dra-gpu-sync/contract-summarizer-dra.yaml   # the legal workload (from the repo)
kubectl get pods -w
kubectl logs contract-summarizer    # should show a whole A100, not a MIG device
vcluster connect legal-ai --namespace legal-ai
kubectl apply -f dra-gpu-sync/legal-whole-gpu-resourceclaim.yaml
kubectl apply -f dra-gpu-sync/contract-summarizer-dra.yaml   # the legal workload (from the repo)
kubectl get pods -w
kubectl logs contract-summarizer    # should show a whole A100, not a MIG device

Im eigenen Cluster fordert das Forschungsteam eine Slice derselben Form, aber einer anderen Klasse an:

apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
  name: research-gpu-slice
  labels:
    gpu.platform/tenant: research-ai
    gpu.platform/allocation: slice
spec:
  devices:
    requests:
      - name: gpu
        exactly:
          deviceClassName: gpu-slice
          allocationMode: ExactCount
          count: 1
apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
  name: research-gpu-slice
  labels:
    gpu.platform/tenant: research-ai
    gpu.platform/allocation: slice
spec:
  devices:
    requests:
      - name: gpu
        exactly:
          deviceClassName: gpu-slice
          allocationMode: ExactCount
          count: 1
apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
  name: research-gpu-slice
  labels:
    gpu.platform/tenant: research-ai
    gpu.platform/allocation: slice
spec:
  devices:
    requests:
      - name: gpu
        exactly:
          deviceClassName: gpu-slice
          allocationMode: ExactCount
          count: 1
vcluster connect research-ai --namespace research-ai
kubectl apply -f dra-gpu-sync/research-gpu-slice-resourceclaim.yaml
kubectl apply -f dra-gpu-sync/research-training-dra.yaml      # the research workload (from the repo)
kubectl get pods -w
kubectl logs research-training-job   # should show e.g. "MIG 1g.5gb Device 0"
vcluster connect research-ai --namespace research-ai
kubectl apply -f dra-gpu-sync/research-gpu-slice-resourceclaim.yaml
kubectl apply -f dra-gpu-sync/research-training-dra.yaml      # the research workload (from the repo)
kubectl get pods -w
kubectl logs research-training-job   # should show e.g. "MIG 1g.5gb Device 0"
vcluster connect research-ai --namespace research-ai
kubectl apply -f dra-gpu-sync/research-gpu-slice-resourceclaim.yaml
kubectl apply -f dra-gpu-sync/research-training-dra.yaml      # the research workload (from the repo)
kubectl get pods -w
kubectl logs research-training-job   # should show e.g. "MIG 1g.5gb Device 0"

vCluster synchronisiert jeden Claim hinauf zum Host, der NVIDIA-DRA-Treiber weist das reale Gerät zu, und das Mandanten-Label wird für die hostseitige Auditierung mitgeführt.

Das Ergebnis: nachprüfbare Berechtigungen und Sichtbarkeit

Verbinden Sie sich mit jedem Mandanten und listen Sie dessen sichtbare Klassen auf:

# legal-ai sees only the whole-GPU class
$ vcluster connect legal-ai --namespace legal-ai
$ kubectl get deviceclasses
NAME        LABELS
gpu-whole   gpu.platform/tenant=legal-ai,gpu.platform/allocation=whole,...

# research-ai sees only the MIG-slice class
$ vcluster connect research-ai --namespace research-ai
$ kubectl get deviceclasses
NAME        LABELS
gpu-slice   gpu.platform/tenant=research-ai,gpu.platform/allocation

# legal-ai sees only the whole-GPU class
$ vcluster connect legal-ai --namespace legal-ai
$ kubectl get deviceclasses
NAME        LABELS
gpu-whole   gpu.platform/tenant=legal-ai,gpu.platform/allocation=whole,...

# research-ai sees only the MIG-slice class
$ vcluster connect research-ai --namespace research-ai
$ kubectl get deviceclasses
NAME        LABELS
gpu-slice   gpu.platform/tenant=research-ai,gpu.platform/allocation

# legal-ai sees only the whole-GPU class
$ vcluster connect legal-ai --namespace legal-ai
$ kubectl get deviceclasses
NAME        LABELS
gpu-whole   gpu.platform/tenant=legal-ai,gpu.platform/allocation=whole,...

# research-ai sees only the MIG-slice class
$ vcluster connect research-ai --namespace research-ai
$ kubectl get deviceclasses
NAME        LABELS
gpu-slice   gpu.platform/tenant=research-ai,gpu.platform/allocation

Keiner der beiden Mandanten kann die Klasse des anderen über seine vCluster-API entdecken. Für eine produktionsreife Autorisierungsgrenze sollte der Host zusätzlich synchronisierte ResourceClaims validieren und jeden Claim ablehnen, der auf eine DeviceClass außerhalb der Mandantenberechtigung verweist. Vom Host aus behält das Plattformteam die volle Sicht auf jeden synchronisierten Claim und den zugewiesenen Treiber:

# switch back to the host context first
kubectl get resourceclaims -A
kubectl get resourceclaims -A -o yaml | grep -A20 legal-whole-gpu     # device from gpu.nvidia.com
kubectl get resourceclaims -A -o yaml | grep -A20 research-gpu-slice  # device from mig.nvidia.com
# switch back to the host context first
kubectl get resourceclaims -A
kubectl get resourceclaims -A -o yaml | grep -A20 legal-whole-gpu     # device from gpu.nvidia.com
kubectl get resourceclaims -A -o yaml | grep -A20 research-gpu-slice  # device from mig.nvidia.com
# switch back to the host context first
kubectl get resourceclaims -A
kubectl get resourceclaims -A -o yaml | grep -A20 legal-whole-gpu     # device from gpu.nvidia.com
kubectl get resourceclaims -A -o yaml | grep -A20 research-gpu-slice  # device from mig.nvidia.com

Schritt 7 – Alles wieder abbauen (unbedingt tun!)

A100s werden minutengenau abgerechnet. Skalieren Sie die Pools auf null oder löschen Sie sie:

gcloud container clusters resize $CLUSTER --location=$LOCATION \
  --node-pool=$WHOLE_GPU_NODEPOOL --num-nodes=0
gcloud container clusters resize $CLUSTER --location=$LOCATION \
  --node-pool=$SLICE_GPU_NODEPOOL --num-nodes=0

# or remove entirely
gcloud container node-pools delete $WHOLE_GPU_NODEPOOL --cluster=$CLUSTER --location=$LOCATION
gcloud container node-pools delete $SLICE_GPU_NODEPOOL --cluster=$CLUSTER --location=$LOCATION
gcloud container clusters resize $CLUSTER --location=$LOCATION \
  --node-pool=$WHOLE_GPU_NODEPOOL --num-nodes=0
gcloud container clusters resize $CLUSTER --location=$LOCATION \
  --node-pool=$SLICE_GPU_NODEPOOL --num-nodes=0

# or remove entirely
gcloud container node-pools delete $WHOLE_GPU_NODEPOOL --cluster=$CLUSTER --location=$LOCATION
gcloud container node-pools delete $SLICE_GPU_NODEPOOL --cluster=$CLUSTER --location=$LOCATION
gcloud container clusters resize $CLUSTER --location=$LOCATION \
  --node-pool=$WHOLE_GPU_NODEPOOL --num-nodes=0
gcloud container clusters resize $CLUSTER --location=$LOCATION \
  --node-pool=$SLICE_GPU_NODEPOOL --num-nodes=0

# or remove entirely
gcloud container node-pools delete $WHOLE_GPU_NODEPOOL --cluster=$CLUSTER --location=$LOCATION
gcloud container node-pools delete $SLICE_GPU_NODEPOOL --cluster=$CLUSTER --location=$LOCATION

Was diese Demo gezeigt hat

Diese Demo hat gezeigt, wie eine gemeinsame Kubernetes-Plattform unterschiedlichen Mandanten unterschiedliche GPU-Berechtigungen bereitstellen kann, ohne ihnen Zugriff auf den Host-Cluster, die physischen Nodes oder den vollständigen GPU-Katalog zu geben.

Das Plattformteam besitzt den realen GPU-Bestand auf dem Host-Cluster und definiert die mandantenseitigen DeviceClass-Objekte. In diesem Beispiel steht gpu-whole für exklusive Whole-GPU-Kapazität für legal-ai, während gpu-slice eine MIG-gestützte 1g.5gb-Slice für research-ai repräsentiert.

vCluster verwandelt diesen gemeinsamen Host-Katalog in mandantenspezifische API-Sichten. legal-ai sieht nur die Whole-GPU-Klasse. research-ai sieht nur die MIG-Slice-Klasse. Die Mandanten bedienen sich anschließend selbst über gewöhnliche ResourceClaim-Objekte in ihren eigenen virtuellen Clustern. Diese Claims werden zum Host synchronisiert, wo DRA die Zuteilung gegen den ResourceSlice-Bestand des NVIDIA-Treibers vornimmt. Sind passende Geräte verfügbar, wird der Claim zugeteilt und der Workload läuft. Ist die Kapazität erschöpft, bleibt der Claim unzugeteilt und der nutzende Pod im Status Pending.

Die wesentliche Trennung lautet:

  • vCluster steuert, was jeder Mandant sehen kann.

  • DRA steuert, was tatsächlich zugeteilt werden kann.

  • MIG liefert hardwaregestützte Partitionierung für aufgeteilte GPUs.

  • Eine hostseitige Admission Policy sollte durchsetzen, welche DeviceClass-Namen jeder Mandant referenzieren darf.

In dieser Anleitung erhielt legal-ai Zugriff auf eine Whole-A100-Klasse und sah die MIG-Slice-Klasse nicht. research-ai erhielt Zugriff auf eine MIG-Slice-Klasse vom Typ 1g.5gb und sah die Whole-GPU-Klasse nicht. Der Slice-Pfad wird hier mit einem einzelnen Mandanten gezeigt, doch dasselbe Muster lässt sich erweitern, indem weitere vClusters hinzukommen und jedem eine eigene MIG-gestützte Slice-Berechtigung zugeordnet wird. So lässt sich eine physische GPU über mehrere Mandanten hinweg teilen, wenn die Plattform eine feingranularere Zuteilung statt exklusiven Zugriffs auf eine ganze Karte anstrebt.

Für den Produktivbetrieb sollte gefilterte DeviceClass-Sichtbarkeit nicht der einzige Autorisierungsmechanismus sein. Validieren Sie jeden synchronisierten ResourceClaim gegen die erlaubte DeviceClass-Liste des Mandanten, setzen Sie GPU-Quotas je Mandant durch, alarmieren Sie bei lange ausstehenden Claims und überwachen Sie die ResourceSlice-Kapazität gegenüber den zugeteilten ResourceClaims.

Das ist das Modell, das man mitnehmen sollte: vCluster gibt jedem Mandanten den GPU-Katalog, den er sehen darf, DRA nimmt die eigentliche Zuteilung auf dem Host vor, und MIG stellt aufgeteilte GPU-Kapazität als hardwaregestützte Partitionen bereit. Zusammen bieten sie einen sauberen Weg, knappe und teure GPU-Kapazität auf einem gemeinsamen Kubernetes-Cluster zu verteilen – und dabei Policy, Zuteilung und Auditierbarkeit in der Hand des Plattformteams zu belassen.

Sprechen wir darüber

Platform Engineering auf Kubernetes ist unser Metier – Mandantenfähigkeit und Isolationsfragen wie diese ausdrücklich eingeschlossen. Wenn Sie sich mit Tenancy, DRA oder vCluster auf gemeinsam genutzter Infrastruktur beschäftigen, melden Sie sich. Wir denken gerne mit, wo Sie feststecken.

Teilen