相互に信頼できる複数の社内 AI チームが、Kubernetes API Server、ロールベースアクセス制御(RBAC)、カスタムリソース定義(CRD)をチームごとに持ちながら1基の GPU を共有する場合、vCluster でコントロールプレーンを分け、KAI Scheduler で GPU のスケジューリング枠を配分できます。NVIDIA は 2026-08-03 公開の公式チュートリアルで、1基の L40S 上に3つのテナントクラスターを動かし、この構成を示しました。
shared nodes が提供するのは論理分離だけです。テナントはホストカーネル、物理ネットワーク、ストレージ基盤、GPU を共有します。実 Node の同期を有効にすると、選択した GPU Node オブジェクトが各 vCluster に現れるため、Node 情報が分離されるとはいえません。信頼できないテナントには、最初から vCluster private nodesを選んでください。private nodes には vCluster Platform が必要ですが無料モードを利用でき、既存の shared nodes vCluster をそのまま移行することはできません。NVIDIA L40S の仕様では「Multi-Instance GPU (MIG) Support」が「No」とされています。GPU メモリをハードウェアで分離するには、MIG 対応 GPU へ変更し、リソースとアドミッション制御を作り直す必要があります。
この境界を受け入れられる場合は、最初に GPU ランタイムを確認し、その後で KAI、Queue、vCluster を構成します。アドミッション、ネットワーク、取り戻し、GPU メモリのいずれかのテストに失敗したら、本番投入を中止してください。
shared nodes は信頼できる社内チームだけに使う
NVIDIA の例では、3チームが同じ物理ノードと GPU を共有します。分離されるのは、仮想コントロールプレーンと名前空間単位のワークロード表示です。
| 要件 | 選択 |
|---|---|
信頼できる社内チームごとに API Server、RBAC、CRD、cluster-admin が必要 |
vCluster shared nodes を出発点にする |
| 各チームに GPU の保証枠を与え、空きがあれば他チームが借用できるようにする | KAI Scheduler の階層 Queue を使う |
| 信頼済みチーム間でも通信を制限する | vCluster がホストで管理する NetworkPolicy を有効にし、コンテナネットワークインターフェース(CNI)が実際に適用することを確認する |
| テナントを信頼できず、ノード、カーネル、ネットワーク、ストレージの分離が必要 | vCluster Platform で private nodes を新規作成する。shared nodes からそのまま切り替えることはできない |
| GPU メモリをハードウェアで分離する必要がある | L40S では実現できないため、MIG 対応 GPU に変更し、スケジューラーから MIG リソースを割り当てる |
| 複数の CUDA プロセスが1デバイス上で演算能力を奪い合い、要求比率と性能 SLA が一致しなくてもよい | GPU 共有を検討できる |
本番投入前に、次の結果を確認します。
- 各チームの vCluster から見える名前空間ワークロードが、自チーム分だけである
- 選択した Node の表示項目が、承認済みのマスキング基準に収まっている
- 3つの Queue が保証枠を受け取り、設計どおりに借用と取り戻しを実行できる
- ホストの NetworkPolicy がテナント間通信と未承認の外向き通信を遮断する
- RuntimeClass、GPU 環境変数、エフェメラルコンテナから KAI Queue を迂回できない
- GPU テストコンテナ内の
nvidia-smiが成功している。Pod がRunningだけでは不十分である - L40S の GPU メモリ超過をアプリケーション制限または HAMi のソフトウェア制限で処理し、ハードウェア分離が必要なら別の GPU を使っている
再現バージョンと CDI/NRI の前提を固定する
NVIDIA の 2026-08-03 公開チュートリアルは、次の組み合わせを使っています。
| コンポーネント | 検証済みバージョン |
|---|---|
| OS | Ubuntu 24.04.4 LTS |
| Kubernetes ディストリビューション | MicroK8s 1.36.2 |
| GPU | NVIDIA L40S 1基 |
| GPU Operator | 26.3.3 |
| KAI Scheduler | 0.16.4 |
| vCluster CLI | 0.35.1 |
この再現では上記バージョンを固定します。2026-08-04 時点の最新版は KAI Scheduler 0.17.0 と vCluster 0.36.1 です。「検証済みの組み合わせ」と「最新版」は同じではありません。更新前にリリースノートを読み、プリプロダクション環境で回帰テストを実行してください。KAI 0.17.0 ではプリエンプション遅延と Dynamic Resource Allocation(DRA)ベースの拡張リソースが追加されたため、更新時には取り戻しのタイミングとホスト側アドミッションの範囲を再検証します。
CLI、ノード、StorageClass、GPU Operator Pod を確認します。
vcluster --version
kubectl get nodes -o wide
kubectl get storageclass
kubectl get pods -n gpu-operator-resources
vcluster --version は 0.35.1 でなければなりません。ディストリビューションによっては GPU Operator の名前空間が gpu-operator です。次へ進む前に、GPU ノードが Ready、vCluster で利用できるデフォルト StorageClass が存在し、NVIDIA device plugin、container toolkit、validator が継続的に失敗していないことを確認します。
この手順では GPU Operator 26.3.3 を固定し、cdi.enabled=true、cdi.nriPluginEnabled=false、nvidia RuntimeClass をランタイムの前提とします。GPU Operator 25.10.0 以降は Container Device Interface(CDI)をデフォルトで使い、Node Resource Interface(NRI)プラグインは任意です。次を実行します。
kubectl get clusterpolicy
kubectl get clusterpolicy cluster-policy \
-o jsonpath='{.spec.cdi.enabled}{"\t"}{.spec.cdi.nriPluginEnabled}{"\n"}'
kubectl get runtimeclass \
-o custom-columns=NAME:.metadata.name,HANDLER:.handler
2つの値は順に true と false、RuntimeClass 一覧には nvidia が必要です。ClusterPolicy の名前が cluster-policy でなければ、最初のコマンドで得た実名に置き換えます。CDI が false なら、GPU Operator 26.3 の CDI ドキュメントに従って修正し、validator を再実行してください。
固定した MicroK8s 環境では、各 GPU ノードにログインし、containerd がマージした有効設定も確認します。
sudo /snap/microk8s/current/bin/containerd \
--config /var/snap/microk8s/current/args/containerd.toml \
config dump | grep -E 'default_runtime_name[[:space:]]*='
デフォルトランタイムは runc または監査済みの非 NVIDIA ハンドラーでなければなりません。出力がない場合や nvidia と表示された場合は中止し、ノードで実際に使われている containerd のパスとインポートファイルを確認します。他の Kubernetes ディストリビューションでは、GPU Operator のトラブルシューティングに従い、containerd config dump または crio status config で有効設定を確認してください。
GPU Operator 26.3 は NVIDIA ハンドラーをデフォルトランタイムにしなくなり、後述のアドミッションガードはこの動作を前提にします。ノードのデフォルトランタイムが手動で NVIDIA ハンドラーに変更されていると、イメージに埋め込まれた NVIDIA_VISIBLE_DEVICES は Pod spec に現れず、アドミッションでは検出できません。その場合は中止し、利用中のランタイムに合った方法で通常のデフォルトハンドラーに戻します。RuntimeClass 一覧に nvidia-cdi、nvidia-legacy、独自名がある場合は変更計画へ記録してください。後述のポリシーでは、明示された RuntimeClass をすべて Queue ガードへ送り、別名による迂回を失敗側に倒します。
NRI が true なら、ここで中止します。NRI は nvidia RuntimeClass を削除しますが、KAI Scheduler 0.16.4 の admission.gpuFractionRuntimeClassName のデフォルト値は nvidia です。そのまま進むと、分割 GPU Pod に存在しない RuntimeClass が注入されます。この構成は、NRI を使わない場合に GPU 管理コンテナへ nvidia RuntimeClass を必須にすることで、未計上アクセスも防ぎます。そのため、KAI の RuntimeClass を空にするだけでは不十分です。NRI が必須のクラスターでは、別のホスト側アドミッションまたはランタイムポリシーが必要であり、この固定手順の対象外です。
NRI が有効で、依存する他のワークロードがないことを確認できた場合は、固定バージョンの ClusterPolicy で無効化します。
kubectl patch clusterpolicy cluster-policy --type merge \
-p '{"spec":{"cdi":{"enabled":true,"nriPluginEnabled":false}}}'
kubectl get clusterpolicy cluster-policy \
-o jsonpath='{.spec.cdi.enabled}{"\t"}{.spec.cdi.nriPluginEnabled}{"\n"}'
kubectl get runtimeclass nvidia
container toolkit、device plugin、validator が正常に戻ってから次へ進みます。KAI のインストールでは cdiEnabled=true を明示しますが、GPU reservation Pod に binder.runtimeClassName は不要です。この2つの RuntimeClass 設定は役割が異なるため、混同しないでください。以降のログに出る NVML は NVIDIA Management Library を指します。
MicroK8s を使う場合は、DNS と vCluster PVC 用ストレージも有効にします。
microk8s enable dns
microk8s enable hostpath-storage
hostpath-storage は、この単一ノード再現に限って使います。マルチノードまたは本番クラスターでは、障害復旧要件を満たす動的 StorageClass を使い、2つ目のコマンドは実行しません。
GPU 共有を有効にして KAI Scheduler をインストールする
上記のランタイム前提に従い、Open Container Initiative(OCI)形式の Helm Chart から KAI Scheduler をインストールします。GPU 共有に加え、通常コンテナによる NVIDIA_VISIBLE_DEVICES の独自指定を遮断し、CDI と RuntimeClass の動作を明示的に固定します。
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" \
--set "global.blockNvidiaVisibleDevices=true" \
--set "binder.cdiEnabled=true" \
--set "admission.gpuFractionRuntimeClassName=nvidia" \
--set "defaultPriorityClasses.enabled=true"
デプロイと KAI Config を確認します。
kubectl get pods -n kai-scheduler
kubectl get configs.kai.scheduler kai-config -o yaml
kubectl get configs.kai.scheduler kai-config \
-o jsonpath='{.spec.admission.gpuSharing}{"\t"}{.spec.admission.blockNvidiaVisibleDevices}{"\t"}{.spec.admission.gpuFractionRuntimeClassName}{"\t"}{.spec.binder.cdiEnabled}{"\n"}'
kubectl get priorityclass train inference
JSONPath は順に true true nvidia true を出力し、最後のコマンドは Chart が管理する train と inference を見つける必要があります。1項目でも異なる場合は Helm values を先に修正し、後続の Pod テストを設定確認の代わりにしないでください。
次の制御コンポーネントが含まれていることを確認します。
| 経路 | コンポーネント |
|---|---|
| アドミッションとグルーピング | admission、pod-grouper、podgroup-controller |
| スケジューリングとバインド | scheduler、binder |
| 設定と Queue | operator、queue-controller |
常駐コンポーネントはすべて Running でなければなりません。共有 GPU の要求後に reservation Pod が作成されたら、RuntimeClass または NVML エラーで繰り返し失敗していないことも確認します。schedulerName: kai-scheduler を指定しないワークロードはデフォルト kube-scheduler に残り、この KAI Queue 経路には入りません。
組織の保証枠とハード上限を設定する
KAI Scheduler の quota は保証量、limit はハード上限で、limit: -1 は上限なしを意味します。NVIDIA の例では親 Queue が quota: 1, limit: -1 です。単一 GPU 環境では物理容量が上限になりますが、同じ設定を複数 GPU クラスターへコピーすると、親 Queue が1基を超えて使う可能性があります。次の create-queues.yaml では親の limit を 1 にし、組織全体の使用量を1基に制限します。
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 }
KAI 0.16.4 の Queue ドキュメントによると、子 Queue はそれぞれ 0.33 の保証枠を持ち、兄弟 Queue に需要がなければ 1 まで借用できます。overQuotaWeight は、同じ優先度の Queue 間で保証枠を超えた容量を配分する相対重みです。Queue の priority は Queue 間の配分順を制御するもので、Pod の Kubernetes PriorityClass とは異なります。
適用して親子関係を確認します。
kubectl apply -f create-queues.yaml
kubectl get queues
kubectl describe queue ml-org
本番設定へ3等分をそのままコピーしないでください。最低スループット、ピーク時間帯、タスク優先度から quota、limit、priority、overQuotaWeight を決め、親 Queue の limit を組織が実際に使える GPU 予算へ合わせます。
vCluster を作成し、Node 情報の露出を制限する
vCluster 0.35.1 の setOwner が制御するのは、同期したホストオブジェクトをガベージコレクション用の vCluster Service に紐付けるかどうかだけです。仮想 Job、Deployment、ReplicaSet の ownerReferences の連鎖をホストに再構築する機能ではありません。true の場合、KAI は vCluster Service を物理 Pod の最上位ワークロードと誤認する可能性があります。次の vcluster.yaml では false にし、後述の単体 Pod を独立したスケジューリング単位にします。
同じ設定で、vCluster がホスト上で管理する分離ポリシーも使います。Pod Security Standard(PSS)の baseline は、特権コンテナ、hostPath などの境界越え機能を拒否します。NetworkPolicy はデフォルトで同一 vCluster のワークロード、仮想コントロールプレーン、DNS だけを許可し、ここではワークロードの公開ネットワーク向け通信も無効化します。ResourceQuota と LimitRange は CPU、メモリ、一時ストレージ、オブジェクト数を制限し、KAI が GPU だけを制限した結果として別のリソース競合が生じることを防ぎます。次を vcluster.yaml として保存します。
experimental:
syncSettings:
setOwner: false
policies:
podSecurityStandard: baseline
networkPolicy:
enabled: true
workload:
publicEgress:
enabled: false
resourceQuota:
enabled: true
quota:
requests.cpu: "4"
requests.memory: 8Gi
requests.storage: 20Gi
requests.ephemeral-storage: 20Gi
limits.cpu: "8"
limits.memory: 16Gi
limits.ephemeral-storage: 40Gi
services.nodeports: 0
services.loadbalancers: 0
count/pods: 40
count/services: 30
count/secrets: 100
count/configmaps: 100
count/persistentvolumeclaims: 10
limitRange:
enabled: true
default:
cpu: "2"
memory: 2Gi
ephemeral-storage: 4Gi
defaultRequest:
cpu: 100m
memory: 256Mi
ephemeral-storage: 1Gi
sync:
toHost:
networkPolicies:
enabled: false
resourceClaims:
enabled: false
fromHost:
runtimeClasses:
enabled: false
nodes:
enabled: true
clearImageStatus: true
selector:
labels:
nvidia.com/gpu.present: "true"
priorityClasses:
enabled: true
この数値は単一ノード再現用の上限であり、現在のマシンから算出した本番容量ではありません。ResourceQuota には vCluster コントロールプレーンの Pod と PVC も含まれます。作成前に GPU ノードの allocatable と現在の使用量を確認し、システムコンポーネントと障害復旧用の余裕を確保してから、各 quota をチームの要件に合わせます。
kubectl get nodes -l nvidia.com/gpu.present=true \
-o custom-columns=NAME:.metadata.name,CPU:.status.allocatable.cpu,MEMORY:.status.allocatable.memory,EPHEMERAL:.status.allocatable.ephemeral-storage
kubectl top nodes
kubectl top には Metrics Server が必要です。利用できない場合は既存の監視システムから同等データを取得します。3テナントの要求上限とホストシステム用の余裕を合計して、ノードの allocatable 容量を超えてはいけません。
ここでは NVIDIA の例にある selector.all: true を使いません。vCluster の Node 同期ドキュメントにあるとおり、all: true はすべてのホスト Node を各テナントへ公開します。上記のラベルセレクターは GPU ノードだけを同期し、ホストへ同期する Pod にも同じセレクターを適用します。clearImageStatus: true は status.images を消しますが、選択した Node の名前、スケジューリング用ラベルと taint(テイント)、容量、一部のランタイム情報は3つの vCluster すべてに見える可能性があります。これらも公開できない場合は、パッチで追加マスキングするか、実 Node の同期を無効化するか、private nodes を使ってください。
priorityClasses.enabled: true はホストの PriorityClass を読み取り専用でテナントクラスターへ同期します。無効だと、train または inference を参照する Pod をホストへ同期できません。デフォルトでは全 PriorityClass が対象です。絞り込む場合は vCluster PriorityClass ドキュメントに従って selector を追加し、2つの必須クラスが対象内に残ることを確認します。
sync.fromHost.runtimeClasses.enabled: false により、ホストの RuntimeClass はテナントへ公開されません。分割 GPU Pod の nvidia RuntimeClass はホスト側の KAI アドミッションが注入するため、テナントがホストハンドラーを直接選ぶ必要はありません。RuntimeClass の迂回は、後述のホスト側 --dry-run=server テストで検証します。
policies.networkPolicy は vCluster の Helm リリースがホスト上で管理し、テナント管理者が仮想クラスター内のオブジェクトを保持することには依存しません。sync.toHost.networkPolicies.enabled: false はテナント作成の NetworkPolicy をホストへ同期しないため、仮想オブジェクトを実効的なネットワーク境界と見なしてはいけません。ホストポリシーは同一 vCluster 内のワークロード通信を許可します。Kubernetes NetworkPolicy の許可は加算方式なので、同期を有効にしても、テナントの default-deny は既存のホスト管理の許可ルールを取り消せません。テナント内でアプリケーションを分割する場合は、CNI が対応する管理者ポリシーを使うかホストルールを再設計し、この設定のまま NetworkPolicy 同期を有効にしないでください。
3つのテナントクラスターを作成します。
vcluster create team-nlp \
--namespace vcluster-team-nlp \
--values vcluster.yaml --connect=false
vcluster create team-vision \
--namespace vcluster-team-vision \
--values vcluster.yaml --connect=false
vcluster create team-recommender \
--namespace vcluster-team-recommender \
--values vcluster.yaml --connect=false
vcluster list
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl get priorityclass train inference
kubectl get resourcequota,limitrange,networkpolicy -n vcluster-team-nlp
kubectl get resourcequota,limitrange,networkpolicy -n vcluster-team-vision
kubectl get resourcequota,limitrange,networkpolicy -n vcluster-team-recommender
setOwner: false は一般的なデフォルト推奨ではありません。vCluster は仮想 Pod の ownerReferences を物理 Pod のアノテーションにシリアライズしますが、KAI 0.16.4 の pod-grouper がたどるのは、ホスト上の実 metadata.ownerReferences とその上位オブジェクトです。この受け入れテストが対象にするのは単体 Pod だけです。Job、Deployment、PyTorchJob、ギャングスケジューリングへ結論を広げず、それらにはホスト PodGroup の契約を別途設計して検証してください。
ownerReferences を無効にすると、ガベージコレクションの保護も一部失われます。コンポーネント更新後、または vCluster の削除・再作成後に、対応するホスト名前空間へ孤立 Pod や PodGroup が残っていないことと、単体 Pod に物理 ownerReferences が付いていないことを再確認します。
kubectl get pods,podgroups -n vcluster-team-nlp
kubectl get pods,podgroups -n vcluster-team-vision
kubectl get pods,podgroups -n vcluster-team-recommender
NLP vCluster から特権 hostPath Pod を投入し、baseline がホストへの同期を止めることを確認します。次を invalid-privileged-pod.yaml として保存します。
apiVersion: v1
kind: Pod
metadata:
name: invalid-privileged-hostpath
spec:
containers:
- name: test
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
securityContext:
privileged: true
volumeMounts:
- name: host-dev
mountPath: /host-dev
volumes:
- name: host-dev
hostPath:
path: /dev
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl get namespace default \
-L pod-security.kubernetes.io/enforce
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl apply -f invalid-privileged-pod.yaml
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl get pod invalid-privileged-hostpath
kubectl logs -n vcluster-team-nlp statefulset/team-nlp \
-c syncer --since=5m
kubectl get pods -n vcluster-team-nlp -o name | \
grep -F invalid-privileged-hostpath
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl delete -f invalid-privileged-pod.yaml
最初のコマンドは baseline を表示する必要があります。通常、特権 Pod はテナント API の Pod Security Admission に直接拒否され、その場合はテナント側の kubectl get が NotFound を返します。テナント API が先にオブジェクトを受け入れた場合は、syncer が Pod Security Standard の拒否を記録しなければなりません。どちらの経路でも、ホストの grep は出力なしで終了コード 1 を返す必要があります。物理 Pod が存在したら直ちに本番投入を中止してください。テナント API が直接拒否した場合、最後の削除コマンドが返す NotFound は想定内です。
ホストの NetworkPolicy がテナント間通信を遮断することを確認する
vCluster shared nodes ガイドは、NetworkPolicy オブジェクトを受け入れても適用しない CNI があると警告しています。最初に Vision 内通信を到達可能な基準として確認し、その後 NLP から同じ Pod IP へ接続して、ホストポリシーが vCluster 間通信を遮断することを証明します。
vcluster connect team-vision --namespace vcluster-team-vision -- \
kubectl run np-web --image=nginx:1.29-alpine --port=80
vcluster connect team-vision --namespace vcluster-team-vision -- \
kubectl run np-client --image=busybox:1.36 --restart=Never -- sleep 3600
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl run np-client --image=busybox:1.36 --restart=Never -- sleep 3600
vcluster connect team-vision --namespace vcluster-team-vision -- \
kubectl wait --for=condition=Ready pod/np-web pod/np-client --timeout=120s
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl wait --for=condition=Ready pod/np-client --timeout=120s
VISION_POD_IP="$(kubectl get pods -n vcluster-team-vision \
-l vcluster.loft.sh/managed-by=team-vision,run=np-web \
-o jsonpath='{.items[0].status.podIP}')"
test -n "$VISION_POD_IP"
vcluster connect team-vision --namespace vcluster-team-vision -- \
kubectl exec np-client -- wget -qO- -T 3 "$VISION_POD_IP"
同一テナントの基準テストは NGINX ページを返す必要があります。公開ネットワーク向け通信を確認する前に、NLP Pod が vCluster DNS 経由で公開ドメインを名前解決できることを証明します。名前解決できない状態での wget 失敗は DNS 障害を示すだけで、egress 遮断の証拠にはなりません。
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl exec np-client -- nslookup example.com
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl exec np-client -- wget -qO- -T 3 "$VISION_POD_IP"
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl exec np-client -- wget -qO- -T 3 http://example.com
nslookup はアドレスを返す必要があります。後ろの2つの wget は内容を返さず、通常はタイムアウトして非ゼロで終了しなければなりません。どちらかが内容を返した場合、CNI が想定ポリシーを適用していないか、egress 設定が書き換えられているため、shared nodes のネットワーク境界は不合格です。モデルリポジトリや外部 API が必要な場合は、policies.networkPolicy.workload.egress に必要最小限の宛先 CIDR とポートを追加し、publicEgress 全体を有効にしないでください。イメージの pull はノードのコンテナランタイムが実行するため、Pod の egress ルールの対象外です。
検証後に3つのテスト Pod を削除します。
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl delete pod np-client
vcluster connect team-vision --namespace vcluster-team-vision -- \
kubectl delete pod np-web np-client
ホスト側でテナントと Queue の対応を強制する
vCluster の cluster-admin 権限を持つチームは任意の Queue ラベルを書けるため、テナントと Queue の対応はホスト側で強制します。MicroK8s 1.36.2 は安定版の ValidatingAdmissionPolicyに対応しています。次のポリシーは、KAI の分割アノテーション、フル GPU または MIG リソース、明示 RuntimeClass、NVIDIA_VISIBLE_DEVICES 環境変数のいずれかを GPU アクセスとして扱います。また、GPU Pod のエフェメラルコンテナ用サブリソースも遮断します。すべての RuntimeClass をガード対象にすることで、未知の NVIDIA ハンドラー別名を失敗側に倒します。Kata や gVisor など GPU 以外の RuntimeClass が必要な場合は、ハンドラーを監査してから安全な名前だけを例外にしてください。vCluster コントロールプレーンはこれらの経路を使わないため、ポリシーに一致しません。
vCluster 0.35.1 は、同期オブジェクトの管理ラベルへ実際の vCluster 名を書き込みます。この例の値は team-nlp、team-vision、team-recommender であり、固定値 vcluster ではありません。ポリシーと Binding を queue-guard.yaml として保存します。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: kai-vcluster-queue-guard
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods", "pods/ephemeralcontainers"]
matchConditions:
- name: gpu-access
expression: >-
(has(object.metadata.annotations) &&
('gpu-fraction' in object.metadata.annotations ||
'gpu-memory' in object.metadata.annotations)) ||
object.spec.?runtimeClassName.orValue('') != '' ||
object.spec.containers.exists(c,
(has(c.resources.limits) && c.resources.limits.exists(r,
r == 'nvidia.com/gpu' || r.startsWith('nvidia.com/mig-'))) ||
(has(c.resources.requests) && c.resources.requests.exists(r,
r == 'nvidia.com/gpu' || r.startsWith('nvidia.com/mig-'))) ||
c.env.exists(e, e.name == 'NVIDIA_VISIBLE_DEVICES')) ||
(has(object.spec.initContainers) && object.spec.initContainers.exists(c,
(has(c.resources.limits) && c.resources.limits.exists(r,
r == 'nvidia.com/gpu' || r.startsWith('nvidia.com/mig-'))) ||
(has(c.resources.requests) && c.resources.requests.exists(r,
r == 'nvidia.com/gpu' || r.startsWith('nvidia.com/mig-'))) ||
c.env.exists(e, e.name == 'NVIDIA_VISIBLE_DEVICES'))) ||
(has(object.spec.ephemeralContainers) &&
object.spec.ephemeralContainers.exists(c,
c.env.exists(e, e.name == 'NVIDIA_VISIBLE_DEVICES')))
validations:
- expression: >-
has(object.metadata.labels) &&
'vcluster.loft.sh/managed-by' in object.metadata.labels &&
((object.metadata.namespace == 'vcluster-team-nlp' &&
object.metadata.labels['vcluster.loft.sh/managed-by'] == 'team-nlp') ||
(object.metadata.namespace == 'vcluster-team-vision' &&
object.metadata.labels['vcluster.loft.sh/managed-by'] == 'team-vision') ||
(object.metadata.namespace == 'vcluster-team-recommender' &&
object.metadata.labels['vcluster.loft.sh/managed-by'] == 'team-recommender'))
message: "GPU Pod にはホスト名前空間と一致する vCluster 管理ラベルが必要です"
reason: Forbidden
- expression: >-
has(object.metadata.labels) &&
'kai.scheduler/queue' in object.metadata.labels &&
((object.metadata.namespace == 'vcluster-team-nlp' &&
object.metadata.labels['kai.scheduler/queue'] == 'team-nlp') ||
(object.metadata.namespace == 'vcluster-team-vision' &&
object.metadata.labels['kai.scheduler/queue'] == 'team-vision') ||
(object.metadata.namespace == 'vcluster-team-recommender' &&
object.metadata.labels['kai.scheduler/queue'] == 'team-recommender'))
message: "GPU Pod の kai.scheduler/queue は所属 vCluster と一致する必要があります"
reason: Forbidden
- expression: "object.spec.?schedulerName.orValue('') == 'kai-scheduler'"
message: "GPU Pod には schedulerName: kai-scheduler が必要です"
reason: Forbidden
- expression: >-
!has(object.spec.ephemeralContainers) ||
size(object.spec.ephemeralContainers) == 0
message: "GPU Pod にエフェメラルコンテナは追加できません。別のデバッグ Pod を作成してください"
reason: Forbidden
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: kai-vcluster-queue-guard
spec:
policyName: kai-vcluster-queue-guard
validationActions: [Deny]
matchResources:
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: In
values:
- vcluster-team-nlp
- vcluster-team-vision
- vcluster-team-recommender
テナント識別の対応表は matchConditions ではなく validations に置きます。ラベル欠落、vCluster の改名、値のずれがある場合、matchConditions が false になってポリシー全体を回避するのではなく、GPU アクセスが拒否されます。vCluster 名またはホスト名前空間を変更するときは、managed-by の値、object.metadata.namespace、Binding の values を同時に更新します。Kubernetes 1.30 未満では、admission webhook、Kyverno、Gatekeeper のいずれかで同等のホスト制約を実装してください。
この固定手順では NRI を false のままにし、vcluster.yaml でも sync.toHost.resourceClaims.enabled を false に固定します。nvidia.com/mig-* を先に照合する目的は、該当リソースがガードなしで入ることを防ぐためであり、L40S で MIG が使えることを意味しません。GPU の変更、ResourceClaim の有効化、KAI 0.17.0 の DRA 拡張リソースへの移行前に、vCluster 同期、Queue リソース、GPU アクセス条件、ネガティブテストをまとめて再設計します。
このポリシーが適用されるのは Pod の作成または更新時だけで、既存オブジェクトを遡って修復しません。ホストポリシー、Binding、後述のネガティブテストがすべて通るまで、テナントへ vCluster kubeconfig を配布しないでください。本番投入時には、3つのホスト名前空間に既存 GPU Pod がないかも確認します。ValidatingAdmissionPolicy と Binding はホストオブジェクトです。RBAC で変更と削除を制限し、両操作の監査ログを保持します。
次の6件を invalid-gpu-pods.yaml として保存します。誤った Queue、Queue 欠落、デフォルトスケジューラー、テナント識別欠落、明示 RuntimeClass、GPU 環境変数による迂回を個別に確認します。
apiVersion: v1
kind: Pod
metadata:
name: invalid-wrong-queue
namespace: vcluster-team-nlp
labels:
vcluster.loft.sh/managed-by: team-nlp
kai.scheduler/queue: team-vision
annotations:
gpu-fraction: "0.10"
spec:
schedulerName: kai-scheduler
containers:
- name: test
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
---
apiVersion: v1
kind: Pod
metadata:
name: invalid-missing-queue
namespace: vcluster-team-nlp
labels:
vcluster.loft.sh/managed-by: team-nlp
annotations:
gpu-fraction: "0.10"
spec:
schedulerName: kai-scheduler
containers:
- name: test
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
---
apiVersion: v1
kind: Pod
metadata:
name: invalid-default-scheduler
namespace: vcluster-team-nlp
labels:
vcluster.loft.sh/managed-by: team-nlp
kai.scheduler/queue: team-nlp
annotations:
gpu-fraction: "0.10"
spec:
containers:
- name: test
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
---
apiVersion: v1
kind: Pod
metadata:
name: invalid-missing-managed-by
namespace: vcluster-team-nlp
labels:
kai.scheduler/queue: team-nlp
annotations:
gpu-fraction: "0.10"
spec:
schedulerName: kai-scheduler
containers:
- name: test
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
---
apiVersion: v1
kind: Pod
metadata:
name: invalid-runtime-bypass
namespace: vcluster-team-nlp
labels:
vcluster.loft.sh/managed-by: team-nlp
kai.scheduler/queue: team-nlp
spec:
runtimeClassName: nvidia
containers:
- name: test
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
---
apiVersion: v1
kind: Pod
metadata:
name: invalid-visible-devices-bypass
namespace: vcluster-team-nlp
labels:
vcluster.loft.sh/managed-by: team-nlp
kai.scheduler/queue: team-nlp
spec:
containers:
- name: test
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
env:
- name: NVIDIA_VISIBLE_DEVICES
value: all
ポリシーを適用し、Common Expression Language(CEL)の型検査を確認してから、ホスト側で --dry-run=server を実行します。
kubectl apply -f queue-guard.yaml
kubectl get validatingadmissionpolicy kai-vcluster-queue-guard -o yaml
kubectl apply --dry-run=server -f invalid-gpu-pods.yaml
出力には型検査の完了を示す status.typeChecking が必要で、expressionWarnings は存在しないか [] でなければなりません。status がまだ生成されていない場合は、API Server の処理を待って kubectl get を再実行し、Pod テストへ進まないでください。警告が1件でもあれば先に修正します。6つの Pod はすべて kai-vcluster-queue-guard に拒否され、--dry-run=server なのでオブジェクトは残りません。invalid-runtime-bypass は GPU リソースも環境変数も指定せず、RuntimeClass だけでガード対象になることを証明します。
GPU を要求していないコンテナが KAI のアドミッション経路へ入ると、NVIDIA_VISIBLE_DEVICES=void に変更されることも確認します。次を blocked-visible-devices.yaml として保存します。
apiVersion: v1
kind: Pod
metadata:
name: blocked-visible-devices
namespace: vcluster-team-nlp
labels:
vcluster.loft.sh/managed-by: team-nlp
kai.scheduler/queue: team-nlp
spec:
schedulerName: kai-scheduler
runtimeClassName: nvidia
containers:
- name: test
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
env:
- name: NVIDIA_VISIBLE_DEVICES
value: all
kubectl apply --dry-run=server -f blocked-visible-devices.yaml \
-o jsonpath='{.spec.containers[0].env[?(@.name=="NVIDIA_VISIBLE_DEVICES")].value}{"\n"}'
出力は void でなければなりません。all のまま、要求が受理されたのに書き換えられない、KAI webhook が応答しない、のいずれかなら次へ進まないでください。
ホストの --dry-run=server だけでは物理 Pod のアドミッションしか確認できません。実際の vCluster から、誤った Queue と GPU 環境変数による迂回も投入します。次を invalid-tenant-pods.yaml として保存し、vCluster 管理ラベルは手動で追加しないでください。
apiVersion: v1
kind: Pod
metadata:
name: invalid-tenant-wrong-queue
labels:
kai.scheduler/queue: team-vision
annotations:
gpu-fraction: "0.10"
spec:
schedulerName: kai-scheduler
containers:
- name: test
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
---
apiVersion: v1
kind: Pod
metadata:
name: invalid-tenant-visible-devices-bypass
labels:
kai.scheduler/queue: team-nlp
spec:
containers:
- name: test
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
env:
- name: NVIDIA_VISIBLE_DEVICES
value: all
NLP vCluster から投入すると、テナント API が先にオブジェクトを受理する場合があります。syncer が managed-by: team-nlp を追加した後、ホストポリシーは誤った Queue と KAI scheduler 欠落を理由に、それぞれの物理 Pod を拒否します。コントロールプレーンのログを確認し、ホストに Pod がないことを証明します。
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl apply -f invalid-tenant-pods.yaml
kubectl logs -n vcluster-team-nlp statefulset/team-nlp \
-c syncer --since=5m
kubectl get pods -n vcluster-team-nlp -o name | \
grep -E 'invalid-tenant-(wrong-queue|visible-devices-bypass)'
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl delete -f invalid-tenant-pods.yaml
syncer が1回以上同期を試みた後、ログには各 Pod に対応する kai-vcluster-queue-guard の拒否が必要です。grep は出力なしで終了コード 1 を返さなければなりません。再試行により同名ログが複数出る場合があるため、ログ総数を断言に使わないでください。テナント側の kubectl apply が成功しても、ホストに物理 Pod が作成されたとは限りません。
借用と取り戻しを検証する GPU テスト Pod を投入する
次の Pod はスケジューリング、借用、取り戻しだけをテストし、本番推論サービスには使いません。値 50 のプリエンプト可能な train PriorityClass を使います。本番推論では値 125 のプリエンプト不可 inference を使い、要求を Queue の保証枠内に収めます。KAI 0.16.4 では優先度値が 100 以上のワークロードは保証枠を超えて借用できません。詳しくはワークロード優先度のドキュメントを参照してください。公開イメージタグは再現を簡単にするためのものです。本番ではスキャン済みの内部イメージをダイジェストで固定します。次を nlp-pod.yaml として保存します。
apiVersion: v1
kind: Pod
metadata:
name: nlp-batch-test
labels:
kai.scheduler/queue: team-nlp
annotations:
gpu-fraction: "0.33"
spec:
schedulerName: kai-scheduler
priorityClassName: train
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
containers:
- name: cuda-test
image: nvidia/cuda:12.4.0-base-ubuntu22.04
command: ["bash", "-c", "nvidia-smi -L && nvidia-smi && sleep infinity"]
nodeSelector:
nvidia.com/gpu.present: "true"
nlp-pod.yaml を vision-pod.yaml と recommender-pod.yaml へコピーします。Pod 名をそれぞれ vision-batch-test、recommender-batch-test に変更し、kai.scheduler/queue も対応する値へ変えてから、各 vCluster へ投入します。
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl apply -f nlp-pod.yaml
vcluster connect team-vision --namespace vcluster-team-vision -- \
kubectl apply -f vision-pod.yaml
vcluster connect team-recommender --namespace vcluster-team-recommender -- \
kubectl apply -f recommender-pod.yaml
3つの Pod は Ready になるだけでなく、ログ内の2回の nvidia-smi がどちらも成功する必要があります。固定環境には L40S が1基しかないため、3つの nvidia-smi -L 出力は同じ GPU UUID を示す必要があります。コマンドのタイムアウト、CrashLoopBackOff、GPU 一覧の欠落、NVML エラーのいずれかがあれば不合格です。
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl wait --for=condition=Ready pod/nlp-batch-test --timeout=180s
vcluster connect team-vision --namespace vcluster-team-vision -- \
kubectl wait --for=condition=Ready pod/vision-batch-test --timeout=180s
vcluster connect team-recommender --namespace vcluster-team-recommender -- \
kubectl wait --for=condition=Ready pod/recommender-batch-test --timeout=180s
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl logs pod/nlp-batch-test
vcluster connect team-vision --namespace vcluster-team-vision -- \
kubectl logs pod/vision-batch-test
vcluster connect team-recommender --namespace vcluster-team-recommender -- \
kubectl logs pod/recommender-batch-test
次にホストで、実際の管理ラベルと Queue ラベルが両方存在すること、単体 Pod に物理 ownerReferences がないことを確認します。
kubectl get pods -n vcluster-team-nlp \
-l vcluster.loft.sh/managed-by=team-nlp --show-labels
kubectl get pods -n vcluster-team-nlp \
-l vcluster.loft.sh/managed-by=team-nlp,kai.scheduler/queue=team-nlp \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.ownerReferences}{"\n"}{end}'
2つ目のコマンドで nlp-batch-test の後に表示される ownerReferences は空でなければなりません。vCluster Service や別の所有者が出た場合、setOwner: false が反映されておらず、単体 Pod のスケジューリング結果も無効です。
物理 GPU Pod が存在する状態で、ホストの ephemeralcontainers サブリソースを直接呼び出します。KAI 0.16.4 が処理しない経路から GPU Pod へエフェメラルデバッグコンテナを追加できないことを確認するテストです。
HOST_NLP_POD="$(kubectl get pods -n vcluster-team-nlp \
-l vcluster.loft.sh/managed-by=team-nlp,kai.scheduler/queue=team-nlp \
-o jsonpath='{.items[0].metadata.name}')"
test -n "$HOST_NLP_POD"
kubectl debug -n vcluster-team-nlp "$HOST_NLP_POD" \
--image=nvidia/cuda:12.4.0-base-ubuntu22.04 \
--target=cuda-test -- nvidia-smi
kubectl get pod -n vcluster-team-nlp "$HOST_NLP_POD" \
-o jsonpath='{.spec.ephemeralContainers}{"\n"}'
kubectl debug は kai-vcluster-queue-guard に Forbidden で拒否され、最後のコマンドは空配列または空値を出力する必要があります。エフェメラルコンテナが現れた場合は本番投入を中止し、ポリシーを修正してください。
KAI 0.16.4 の GPU 共有ドキュメントでは、gpu-fraction: "0.33" はデバイスメモリの 33% を要求として記録し、要求合計がデバイス容量以下の Pod を同居させます。KAI はデフォルトで実際のメモリ上限を強制しないため、Pod が要求を超えて使う可能性があります。
gpu-fraction は GPU コア、タイムスライス、スループット、レイテンシーの 33% を保証せず、性能 SLA でもありません。NVIDIA の GPU Operator 26.3 タイムスライスドキュメントも、共有要求数が比例した演算能力を表すものではないと説明しています。
テナント表示、Queue の取り戻し、GPU メモリ境界を検証する
テナント表示と Node の露出範囲を確認する
各 vCluster には、自チームの名前空間ワークロードだけが表示される必要があります。
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl get pods -A -o wide
vcluster connect team-vision --namespace vcluster-team-vision -- \
kubectl get pods -A -o wide
vcluster connect team-recommender --namespace vcluster-team-recommender -- \
kubectl get pods -A -o wide
Node はテナント専用オブジェクトではありません。各 vCluster に表示されるのはラベルセレクターで選んだ GPU ノードだけで、status.images は空でなければなりません。その他のラベル、テイント、容量、ランタイム項目を1つずつ確認し、公開可能か判断します。
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl get nodes -o yaml
vcluster connect team-vision --namespace vcluster-team-vision -- \
kubectl get nodes -o yaml
vcluster connect team-recommender --namespace vcluster-team-recommender -- \
kubectl get nodes -o yaml
機密 Node 項目が承認範囲を1つでも超えた場合は本番投入を中止し、パッチの調整、実 Node 同期の無効化、private nodes への切り替えのいずれかを行います。
ホスト配置と Queue 配分を確認する
物理 Pod、vCluster 管理ラベル、3つの Queue を確認します。
kubectl get pods -A -o wide --show-labels
kubectl get pods -n kai-resource-reservation
kubectl describe queue team-nlp
kubectl describe queue team-vision
kubectl describe queue team-recommender
NVIDIA の公式例では、3つの Queue にそれぞれ 330m の GPU requested と allocated が表示されます。330m を固定の期待値にしないでください。実際の Pod アノテーション、Queue の状態、スケジューリング Event、reservation Pod が一致して初めて合格です。
追加要求で借用と取り戻しを証明する
Queue が空き保証枠を借用したと証明するには、追加要求が必要です。次のプリエンプト可能 Pod を nlp-burst.yaml として保存します。
apiVersion: v1
kind: Pod
metadata:
name: nlp-burst
labels:
kai.scheduler/queue: team-nlp
annotations:
gpu-fraction: "0.66"
spec:
schedulerName: kai-scheduler
priorityClassName: train
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
containers:
- name: nlp-burst
image: nvidia/cuda:12.4.0-base-ubuntu22.04
command: ["bash", "-c", "nvidia-smi -L && nvidia-smi && sleep infinity"]
nodeSelector:
nvidia.com/gpu.present: "true"
他の2チームの 0.33 Pod を停止してから、この 0.66 Pod を投入します。他の Pod を停止するだけでは既存の 0.33 要求は拡大しないため、それだけでは借用の証拠になりません。
vcluster connect team-vision --namespace vcluster-team-vision -- \
kubectl delete -f vision-pod.yaml
vcluster connect team-recommender --namespace vcluster-team-recommender -- \
kubectl delete -f recommender-pod.yaml
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl apply -f nlp-burst.yaml
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl wait --for=condition=Ready pod/nlp-burst --timeout=180s
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl logs pod/nlp-burst
kubectl describe queue team-nlp
バースト Pod も Ready になり、GPU 一覧と nvidia-smi の全出力をログへ残す必要があります。借用に成功すると、NLP Queue の要求合計は約 0.99 となり、0.33 の保証枠を上回りますが 1 の上限は超えません。次に Vision の 0.33 Pod を戻します。
vcluster connect team-vision --namespace vcluster-team-vision -- \
kubectl apply -f vision-pod.yaml
kubectl get pods -A -o wide
kubectl describe queue team-nlp
kubectl describe queue team-vision
kubectl get events -A --sort-by=.lastTimestamp
保証枠を下回る Vision Queue は、チームが事前に決めたスケジューリングのサービスレベル目標(SLO)内に 0.33 を取り戻し、NLP が 0.99 を長時間維持してはいけません。0.66 Pod は分割できないスケジューリング単位であり、スケジューラーは退避させる場合も Pending に戻す場合もあります。特定の被害 Pod や一律の秒数ではなく、Queue 状態、Pod 状態、Event を合格判定に使います。取り戻し条件と階層上の例外は fair-share ドキュメントを参照してください。
Vision が合格したら Recommender を戻し、同じ確認を繰り返します。Recommender も合意済み SLO 内に 0.33 を得る必要があります。保証枠を持つ Queue が待ち続ける場合、取り戻しまたは飢餓状態のテストは不合格です。最後にバースト Pod を削除し、3つの Queue が各 0.33 を使う基準状態へ戻します。
vcluster connect team-recommender --namespace vcluster-team-recommender -- \
kubectl apply -f recommender-pod.yaml
kubectl describe queue team-nlp
kubectl describe queue team-recommender
kubectl get events -A --sort-by=.lastTimestamp
vcluster connect team-nlp --namespace vcluster-team-nlp -- \
kubectl delete -f nlp-burst.yaml
GPU メモリと障害境界をテストする
GPU メモリ使用量を段階的に増やすテストプログラムを実行し、アプリケーションが合意値を超えたときの挙動を記録します。KAI のデフォルト設定が行うのはスケジューリング上の計上だけで、プロセスが gpu-fraction または gpu-memory の要求値を超えることは止めません。同居ワークロードは GPU メモリ不足(OOM)や相互干渉を起こす可能性があります。
信頼できるチームがアプリケーション設定とは独立したソフトウェア上限を必要とする場合は、KAI 0.16.4 の HAMi 連携を検討できます。kai-resource-isolator が HAMi-core を注入し、CUDA 呼び出しを介してコンテナの GPU メモリを制限します。本番投入前に CUDA、イメージ、LD_PRELOAD の互換性を検証してください。HAMi はソフトウェア分離であり、MIG のハードウェア障害ドメインやセキュリティ境界は提供しません。
この L40S 環境には「そのまま MIG へ切り替える」選択肢がありません。テナント間の GPU メモリと演算能力の干渉を許容できない場合は、L40S を専有させるか、MIG 対応 GPU へ変更します。ハードウェア変更後は GPU Operator/MIG Manager、Queue リソース、ワークロード要求、vCluster 同期、ホストアドミッションを再定義してください。現在のアドミッションポリシーは nvidia.com/mig-* を先回りして照合するだけで、MIG 経路を検証していません。
本番投入前チェックリスト
- テナントは相互に信頼できる社内チームである。そうでなければ vCluster Platform で private nodes クラスターを新規作成済みである
- GPU Operator は正常で、CDI が
true、NRI がfalse、nvidiaRuntimeClass が存在し、NVIDIA ハンドラーはノードのデフォルトランタイムではなく、reservation Pod に RuntimeClass または NVML エラーがない - 上表の KAI 制御コンポーネントがすべて正常で、固定バージョンと更新先をプリプロダクションで検証済みである
- 親 Queue の
limitが組織の GPU 予算を超えず、子 Queue に保証枠、上限、重み、優先度が明記されている - vCluster で
baseline、ResourceQuota、LimitRange、ホスト管理 NetworkPolicy が有効で、値をノード容量から再計算している - 物理
ownerReferencesでsetOwner: falseを確認済みで、結論は単体 Pod に限定され、削除または更新後に孤立 Pod/PodGroup がない - ホスト CNI が同一 vCluster 内の基準通信を維持し、vCluster 間通信と未承認の外向き通信を遮断する
- アドミッションが Queue、識別、スケジューラー、RuntimeClass 別名、GPU リソース、環境変数、エフェメラルコンテナを対象とし、ホスト6件と vCluster 2件のネガティブテストをすべて拒否する
- KAI の
blockNvidiaVisibleDevicesがtrueで、--dry-run=serverが GPU を要求しないコンテナの可視デバイス値をvoidへ書き換える - 各チームに見えるのは自チームの名前空間ワークロードだけで、共有 Node の表示項目と
status.imagesのマスキングが承認基準に一致する - 3つの基準 Pod とバースト Pod の
nvidia-smiログが成功し、借用、取り戻し、飢餓状態の各テストに Queue、Pod、Event の証拠がある - チームが
gpu-fractionは演算能力の比率でも性能 SLA でもないと理解している - L40S のアプリケーション上限または HAMi 超過テストが合格し、ハードウェア分離が必要なら GPU 専有またはハードウェア変更と新しい MIG 受け入れテストを実施している
- ResourceClaims/DRA を無効のままにし、有効化する場合は vCluster 同期、Queue リソース、アドミッション条件、ネガティブテストをすべて再設計している
- コンポーネント更新後にこのチェックリストを再実行している