Skip to main content
Dedicated nodes architecture provides compute isolation by assigning specific host cluster nodes to each virtual cluster. Virtual cluster workloads run only on their dedicated nodes, providing stronger isolation than shared nodes while still leveraging the host cluster’s infrastructure.

How It Works

In dedicated nodes mode, the vCluster control plane runs in a host namespace, but workloads are scheduled only on labeled host nodes assigned to that virtual cluster:
┌─────────────────────────── Host Cluster ────────────────────────────┐
│                                                                        │
│   ┌──────────────────────────────────────────────────────┐     │
│   │ Namespace: vcluster-tenant-a                     │     │
│   │  ┌──────────────────────────────────────────┐ │     │
│   │  │ vCluster Control Plane (tenant-a)     │ │     │
│   │  └──────────────────────────────────────────┘ │     │
│   │  ┌──────────────────────────────────────────┐ │     │
│   │  │ Workload Pods (from tenant-a)        │ │     │
│   │  └──────────────────────────────────────────┘ │     │
│   └──────────────────────────────────────────────────────┘     │
│                                                                        │
│   ┌──────────────────────────────────────────────────────┐     │
│   │ Dedicated Nodes for tenant-a                    │     │
│   │  - node-tenant-a-1 (label: tenant=tenant-a)      │     │
│   │    └─> Runs ONLY tenant-a workloads                │     │
│   │  - node-tenant-a-2 (label: tenant=tenant-a)      │     │
│   │    └─> Runs ONLY tenant-a workloads                │     │
│   └──────────────────────────────────────────────────────┘     │
│                                                                        │
│   ┌──────────────────────────────────────────────────────┐     │
│   │ Dedicated Nodes for tenant-b                    │     │
│   │  - node-tenant-b-1 (label: tenant=tenant-b)      │     │
│   │    └─> Runs ONLY tenant-b workloads                │     │
│   └──────────────────────────────────────────────────────┘     │
│                                                                        │
│   ┌──────────────────────────────────────────────────────┐     │
│   │ Shared Nodes (for host workloads)              │     │
│   │  - node-1, node-2, node-3                        │     │
│   └──────────────────────────────────────────────────────┘     │
│                                                                        │
└──────────────────────────────────────────────────────────────────────┘

Key Characteristics

Compute Isolation: Each vCluster’s workloads run only on assigned nodes
Real Node Visibility: Host nodes are synced into the virtual cluster
Shared Infrastructure: Still uses host CNI, CSI, and control plane
Node Selectors: Automatic node selector and toleration injection
Better Isolation: Physical separation of workload execution

Configuration

Basic Setup

The core configuration enables node syncing with label selectors:
dedicated-nodes.yaml
# Enable syncing of real nodes from host cluster
sync:
  fromHost:
    nodes:
      enabled: true
      selector:
        labels:
          tenant: my-tenant  # Only sync nodes with this label

# Automatically inject node selector and tolerations
sync:
  toHost:
    pods:
      enforceTolerations:
        - key: "tenant"
          operator: "Equal"
          value: "my-tenant"
          effect: "NoSchedule"

Prepare Host Nodes

Before creating the vCluster, label and taint the dedicated nodes:
# Label nodes for the tenant
kubectl label nodes node-1 node-2 tenant=my-tenant

# Taint nodes to prevent other workloads
kubectl taint nodes node-1 node-2 tenant=my-tenant:NoSchedule

# Verify
kubectl get nodes -l tenant=my-tenant

Create Dedicated Nodes vCluster

vcluster create my-tenant \
  --namespace vcluster-my-tenant \
  --values dedicated-nodes.yaml

Verify Node Isolation

Check that nodes are correctly synced:
# Connect to virtual cluster
vcluster connect my-tenant --namespace vcluster-my-tenant

# List nodes (should only show labeled nodes)
kubectl get nodes
# NAME     STATUS   ROLES    AGE   VERSION
# node-1   Ready    <none>   5d    v1.28.0
# node-2   Ready    <none>   5d    v1.28.0

# Create a test pod
kubectl run test --image=nginx

# Verify it runs on dedicated nodes
kubectl get pod test -o wide
# NAME   READY   STATUS    NODE
# test   1/1     Running   node-1

Use Cases

Production Multi-Tenancy

Perfect for: SaaS platforms, managed Kubernetes offerings, enterprise multi-tenancy
production-tenant.yaml
sync:
  fromHost:
    nodes:
      enabled: true
      selector:
        labels:
          tenant: "{{.Values.tenantId}}"
          tier: production
  toHost:
    pods:
      enforceTolerations:
        - key: tenant
          operator: Equal
          value: "{{.Values.tenantId}}"
          effect: NoSchedule
        - key: tier
          operator: Equal
          value: production
          effect: NoSchedule

policies:
  resourceQuota:
    enabled: true
  limitRange:
    enabled: true
  networkPolicy:
    enabled: true
Benefits:
  • Strong compute isolation
  • Predictable performance
  • Clear resource attribution
  • Compliance-friendly

GPU Workloads

Perfect for: AI/ML platforms, rendering farms, data processing
gpu-tenant.yaml
sync:
  fromHost:
    nodes:
      enabled: true
      selector:
        labels:
          gpu: "true"
          tenant: ai-team
  toHost:
    pods:
      enforceTolerations:
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
        - key: tenant
          operator: Equal
          value: ai-team
          effect: NoSchedule

# Allow GPU resource requests
rbac:
  role:
    extraRules:
      - apiGroups: [""]
        resources: ["nodes"]
        verbs: ["get", "list"]
Setup GPU Nodes:
# Label GPU nodes
kubectl label nodes gpu-node-1 gpu-node-2 \
  gpu=true \
  tenant=ai-team

# Taint GPU nodes
kubectl taint nodes gpu-node-1 gpu-node-2 \
  nvidia.com/gpu=present:NoSchedule \
  tenant=ai-team:NoSchedule
Use in Virtual Cluster:
apiVersion: v1
kind: Pod
metadata:
  name: gpu-workload
spec:
  containers:
    - name: cuda
      image: nvidia/cuda:12.0-base
      resources:
        limits:
          nvidia.com/gpu: 1  # Request GPU

Tiered Service Levels

Perfect for: Offering different performance tiers to customers
premium-tier.yaml
sync:
  fromHost:
    nodes:
      enabled: true
      selector:
        labels:
          tier: premium
          tenant: customer-123

# High resource limits
policies:
  resourceQuota:
    quota:
      requests.cpu: 64
      requests.memory: 256Gi
Node Characteristics:
  • Latest generation instances
  • NVMe SSDs
  • Higher network bandwidth

Compliance and Data Residency

Perfect for: Regulated industries, data sovereignty requirements
compliance-tenant.yaml
sync:
  fromHost:
    nodes:
      enabled: true
      selector:
        labels:
          compliance: pci-dss
          region: us-west-2
          zone: us-west-2a
  toHost:
    pods:
      enforceTolerations:
        - key: compliance
          operator: Equal
          value: pci-dss
          effect: NoExecute

policies:
  networkPolicy:
    enabled: true
    workload:
      publicEgress:
        enabled: false  # Block internet egress

Advanced Configuration

Multi-Zone Node Selection

Select nodes from specific availability zones:
sync:
  fromHost:
    nodes:
      enabled: true
      selector:
        labels:
          tenant: my-tenant
          topology.kubernetes.io/zone: us-east-1a

Dynamic Node Selection

Use expressions for complex node selection:
sync:
  fromHost:
    nodes:
      enabled: true
      selector:
        labels:
          tenant: my-tenant
        # These nodes must also have high-memory label
        matchExpressions:
          - key: node.kubernetes.io/instance-type
            operator: In
            values:
              - m5.4xlarge
              - m5.8xlarge
              - r5.4xlarge

Sync Node Changes Back

Allow users in virtual cluster to label/taint their nodes:
sync:
  fromHost:
    nodes:
      enabled: true
      selector:
        labels:
          tenant: my-tenant
      syncBackChanges: true  # Sync labels/taints back to host
Security Risk: Enabling syncBackChanges allows virtual cluster users to modify host nodes. Only enable this for trusted tenants.

Hide Node Images

Prevent users from seeing what images are cached on nodes:
sync:
  fromHost:
    nodes:
      enabled: true
      selector:
        labels:
          tenant: my-tenant
      clearImageStatus: true  # Remove image information

Networking

Pod Networking

Pods still use the host cluster’s CNI:
# Virtual cluster pod
kubectl get pod nginx -o wide
# NAME    IP           NODE
# nginx   10.244.2.5   node-1

# Uses host CNI (e.g., Calico, Cilium, etc.)

Network Policies

Network policies can isolate tenant traffic:
policies:
  networkPolicy:
    enabled: true
    workload:
      # Block cross-tenant communication
      egress:
        - to:
            - podSelector: {}  # Same vCluster only
      ingress:
        - from:
            - podSelector: {}  # Same vCluster only

Service Mesh Integration

Dedicated nodes work well with service meshes:
# Example: Istio integration
integrations:
  istio:
    enabled: true
    sync:
      toHost:
        virtualServices:
          enabled: true
        destinationRules:
          enabled: true

# Automatic sidecar injection
sync:
  toHost:
    pods:
      enabled: true
      # Preserve Istio annotations
      translateAnnotations:
        - sidecar.istio.io/*

Storage

Using Host Storage Classes

Storage classes are synced from the host:
sync:
  fromHost:
    storageClasses:
      enabled: true  # Sync all host storage classes

Dedicated Storage

You can provision storage on dedicated nodes:
# Use local storage on dedicated nodes
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: local-data
spec:
  storageClassName: local-path
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Gi
The volume will be created on one of the dedicated nodes.

Performance Characteristics

Resource Overhead

Per vCluster:
  • Control Plane: 200-500MB memory, 100-200m CPU
  • Syncer Overhead: Minimal (watches real nodes)
  • Node Resources: Full dedicated node capacity available

Capacity Planning

Example Setup:
  • Host Cluster: 100 nodes (8 CPU, 32GB RAM each)
  • Dedicated allocation: 10 vClusters with 5 nodes each
  • Remaining: 50 nodes for host workloads and additional vClusters

Scaling Limits

MetricDedicated Nodes
Nodes per vCluster1-1000+
vClusters per hostLimited by available nodes
Pods per nodeSame as host cluster
Control plane overhead~200-500MB per vCluster

Pros and Cons

Advantages

Compute Isolation: Workloads cannot interfere with each other
Predictable Performance: Dedicated resources prevent “noisy neighbor”
Real Node Access: Users see and can manage real nodes
Better Troubleshooting: Clear resource attribution
Compliance Friendly: Physical separation aids audits
GPU Support: Ideal for GPU workloads requiring isolation

Limitations

Lower Density: Requires dedicating physical nodes
Higher Cost: More nodes needed vs shared architecture
Shared CNI/CSI: Still uses host networking and storage
Shared Kernel: Workloads share the host OS kernel
Node Management: Must manage node labels and taints

vs Shared Nodes

AspectSharedDedicated
Compute Isolation
Cost Efficiency
Setup ComplexityLowMedium
Noisy NeighborPossiblePrevented
Resource VisibilityLimitedFull

vs Private Nodes

AspectDedicatedPrivate
Network Isolation
Storage Isolation
Setup ComplexityMediumHigh
Host DependencyHighLow
CNI Independence

Automation and Tooling

Automatic Node Provisioning

Use Cluster Autoscaler or Karpenter to provision nodes automatically:
# Node group for tenant
apiVersion: v1
kind: ConfigMap
metadata:
  name: cluster-autoscaler-config
data:
  tenant-nodes: |
    {
      "name": "tenant-a-pool",
      "minSize": 2,
      "maxSize": 10,
      "labels": {
        "tenant": "tenant-a"
      },
      "taints": [
        {"key": "tenant", "value": "tenant-a", "effect": "NoSchedule"}
      ]
    }

GitOps Workflow

Manage vClusters and node assignments with GitOps:
# tenants/tenant-a/vcluster.yaml
apiVersion: storage.loft.sh/v1
kind: VirtualCluster
metadata:
  name: tenant-a
  namespace: vcluster-tenant-a
spec:
  config: |
    sync:
      fromHost:
        nodes:
          enabled: true
          selector:
            labels:
              tenant: tenant-a
---
# tenants/tenant-a/nodes.yaml
apiVersion: v1
kind: Node
metadata:
  name: node-tenant-a-1
  labels:
    tenant: tenant-a
spec:
  taints:
    - key: tenant
      value: tenant-a
      effect: NoSchedule

Troubleshooting

Pods Not Scheduling

Symptom: Pods stuck in Pending state
# Check events
kubectl describe pod <pod-name>

# Common issues:
# 1. No nodes match the tolerations
# 2. Insufficient resources on dedicated nodes
# 3. Node selector mismatch
Solution:
# Verify nodes are labeled correctly
kubectl get nodes -l tenant=my-tenant

# Check node resources
kubectl top nodes

# Verify pod has correct tolerations (on host)
kubectl get pod -n vcluster-my-tenant <pod-name> -o yaml | grep -A5 tolerations

Nodes Not Appearing in Virtual Cluster

Symptom: kubectl get nodes shows no nodes
# Check syncer logs
kubectl logs -n vcluster-my-tenant <vcluster-pod> -c syncer

# Verify node labels on host
kubectl get nodes --show-labels | grep tenant
Solution: Ensure node selector matches labeled nodes:
sync:
  fromHost:
    nodes:
      enabled: true
      selector:
        labels:
          tenant: my-tenant  # Must match actual node labels

Performance Issues

Symptom: Slow performance despite dedicated nodes
# Check node resources
kubectl top nodes

# Check for resource contention
kubectl describe node <node-name> | grep -A20 "Allocated resources"
Solutions:
  • Increase node size
  • Add more dedicated nodes
  • Review resource requests/limits
  • Check for CPU throttling or memory pressure

Best Practices

Use consistent labeling and tainting schemes:
# Standard pattern
kubectl label nodes <node> tenant=<tenant-id>
kubectl taint nodes <node> tenant=<tenant-id>:NoSchedule
This prevents scheduling conflicts and simplifies automation.
Track resource usage per tenant:
# Per-node metrics
kubectl top nodes -l tenant=my-tenant

# Pod resource usage
kubectl top pods -A --sort-by=memory | grep my-tenant
Use this data for capacity planning and cost attribution.
Always set quotas to prevent over-provisioning:
policies:
  resourceQuota:
    enabled: true
    quota:
      requests.cpu: "32"  # Match dedicated node capacity
      requests.memory: 128Gi
Automate node provisioning based on demand:
  • Cluster Autoscaler: For managed Kubernetes (EKS, GKE, AKS)
  • Karpenter: For more flexible provisioning
  • Custom Controllers: For on-prem or hybrid setups
Regularly test node failures:
# Drain a node
kubectl drain node-1 --ignore-daemonsets

# Verify pods reschedule to other dedicated nodes
kubectl get pods -o wide

# Uncordon when done
kubectl uncordon node-1
Maintain documentation of node-to-tenant mappings:
# nodes-inventory.yaml
tenants:
  tenant-a:
    nodes: [node-1, node-2, node-3]
    capacity:
      cpu: 24
      memory: 96Gi
  tenant-b:
    nodes: [node-4, node-5]
    capacity:
      cpu: 16
      memory: 64Gi

Migration

From Shared to Dedicated Nodes

1

Prepare Dedicated Nodes

kubectl label nodes node-1 node-2 tenant=my-tenant
kubectl taint nodes node-1 node-2 tenant=my-tenant:NoSchedule
2

Update vCluster Configuration

vcluster upgrade my-tenant \
  --namespace vcluster-my-tenant \
  --values dedicated-nodes.yaml
3

Trigger Pod Recreation

# Pods will be rescheduled with new node selectors
kubectl rollout restart deployment -A
4

Verify

# All pods should be on dedicated nodes
kubectl get pods -A -o wide

Next Steps

Private Nodes

Take isolation further with complete CNI/CSI independence.

Shared Nodes

Compare with the shared nodes architecture.

Node Syncing

Advanced node syncing configuration options.

Auto Nodes

Combine dedicated nodes with automatic provisioning.