vCluster Integration
vCluster provides control-plane-level isolation. vNode adds node-level isolation on top. Together they deliver full-stack tenant isolation. Each tenant gets a dedicated control plane and a hardened workload sandbox on shared nodes.
Two layers of isolation
A shared Kubernetes cluster exposes two places where tenants can interfere with each other.
Control plane. Tenants that share an API server and etcd can read each other's secrets, modify each other's resources, and exhaust API capacity. vCluster eliminates this by giving each tenant a dedicated API server and etcd. Tenants interact with their own cluster and have no visibility into the control plane cluster or other tenants.
Worker nodes. Even with dedicated control planes, tenant pods still run on shared worker nodes. A container escape gives an attacker access to the host, where they can read other tenants' pod filesystems, processes, and secrets. vNode eliminates this by wrapping each tenant's pods in a hardened sandbox. A container escape reaches the vNode boundary, not the host.
The stack looks like this when both layers are active:
vCluster isolates each tenant's control plane, while vNode isolates the tenant's pods on shared worker nodes.
How they integrate
vNode operates at the container runtime layer, below the Kubernetes API. It is registered as a Kubernetes RuntimeClass (vnode or vnode-launcher). Tenant clusters can reference this RuntimeClass in pod specs, or set it as the cluster-wide default in the vCluster configuration. When configured as the default, workload manifests do not need to change.
To set vNode as the runtime for all workloads in a tenant cluster, add the following to your vCluster configuration (requires vCluster 0.23 or later):
sync:
toHost:
pods:
runtimeClassName: vnode
With this configuration, all pods in the tenant cluster automatically use the vNode runtime. You do not need to set runtimeClassName: vnode in individual pod specs — and you should not, because the vnode RuntimeClass exists on the control plane cluster but is not synced into the tenant cluster by default. Setting it in a pod spec inside the tenant cluster returns runtime class vnode does not exist.
To apply the runtime to specific workloads only, omit the vCluster-level config and add runtimeClassName: vnode directly to each pod spec on the control plane cluster:
spec:
runtimeClassName: vnode
vCluster schedules workloads as normal. The vNode runtime intercepts at the containerd layer, not at the Kubernetes API layer.
Let tenant workloads choose their own runtime class
The two options above are both driven from the control plane cluster. Either every synced pod gets vnode, or specific pods get it because their spec was written on the control plane cluster. Neither lets someone working inside the tenant cluster set runtimeClassName: vnode on their own pod spec. That fails with runtime class vnode does not exist, because the RuntimeClass isn't synced into the tenant cluster by default.
To allow that, sync the RuntimeClass itself into the tenant cluster:
sync:
fromHost:
runtimeClasses:
enabled: true
vCluster mirrors the RuntimeClass into the tenant cluster under the same name. vnode stays vnode with no renaming. If the host vnode RuntimeClass pins scheduling to specific nodes (scheduling.nodeSelector or tolerations, as in the DGX/private GPU node case), that constraint is preserved on the synced copy, so tenant pods still land only where the platform admin intended.
The selector matches labels on the host RuntimeClass object itself, not on tenant clusters or namespaces. RuntimeClass is cluster-scoped, so there's no way to restrict it per-namespace. What the selector actually controls is which host RuntimeClasses a given tenant cluster is allowed to import. Label the RuntimeClass on the control plane cluster:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: vnode
labels:
tenant-tier: gpu
handler: vnode
Then reference the label from the tenant cluster's own vCluster configuration:
sync:
fromHost:
runtimeClasses:
enabled: true
selector:
matchLabels:
tenant-tier: gpu
Because this selector lives in each tenant cluster's own configuration, different tenant clusters can expose different sets of host RuntimeClasses simply by using different selectors (or none at all). Once a RuntimeClass is imported this way, every namespace in that tenant cluster can reference it. The selector controls which RuntimeClasses exist in the tenant cluster, not which namespaces within it can use them.
If a pod references a RuntimeClass that a tenant cluster's selector excluded, the RuntimeClass simply doesn't exist in that tenant cluster, and the tenant cluster's own API server rejects the pod at admission with an error such as pod rejected: RuntimeClass "vnode" not found. vCluster's syncer also re-checks the selector before syncing an already-existing pod to host, as a safeguard against races, and skips the sync with a SyncWarning event if the pod's RuntimeClass no longer matches. The admission rejection is what most users will actually see.
sync.toHost.pods.runtimeClassName always winsSetting sync.toHost.pods.runtimeClassName overwrites every synced pod's runtime class unconditionally, including pods that already set their own runtimeClassName using fromHost.runtimeClasses. The two are mutually exclusive in practice. Use the global setting to force vnode everywhere, or use fromHost.runtimeClasses to let tenant workloads opt in individually. Don't use both.
Deployment mode and vCluster topology
vNode's default per-namespace isolation mode aligns naturally with a standard vCluster topology. Each tenant cluster's pods land in their own namespace on the control plane cluster. One vNode runs per namespace per node, which means one vNode per tenant cluster per node. Pods in the same tenant cluster can see each other's processes inside the vNode sandbox, as they would in a normal cluster.
If tenant clusters share namespaces on the control plane cluster, or if workloads require stronger pod-level boundaries, switch to per-pod mode. Set VNODE_PER_POD=true on the vNode DaemonSet. Every pod then gets its own isolated sandbox, regardless of namespace.
Why both layers matter
Neither layer alone provides full tenant isolation.
- vCluster without vNode. Tenants have separate control planes but share node-level risk. A container escape reaches the host.
- vNode without vCluster. Workloads are sandboxed at the node level, but tenants share a Kubernetes API. Tenant resources remain visible to each other.
- vCluster with vNode. Both the control plane and node layers are isolated. Tenant workloads are isolated across the Kubernetes API, etcd, process, filesystem, and host-node boundaries.
Inference providers
Inference providers combine dedicated control planes, private GPU nodes, and vNode runtime isolation to serve untrusted or privileged model workloads on shared GPU infrastructure. For where each isolation tier fits, see Tenancy Models. For the full build, including GPU capacity, endpoint routing, and Day 2 operations, see the Inference Provider production path in the vCluster docs.