🧭 How toInfrastructureIntermediate✨ AI-assisted

How to Set Up a GKE Node Pool for microVM Sandbox Pods

WittyTech Daily··4 min read
#gke#microvm#sandbox#kubernetes

Your agent writes code and then runs it. Do you really want that code sharing a kernel with every other Pod on the node? A microVM gives each Pod its own guest kernel, so a container escape has to get through a hypervisor first.

This guide takes you from an empty cluster to a working GKE microVM node pool, a Deployment scheduled onto it, and a check that proves the Pods are sandboxed. Google made the microVM sandbox type generally available in the GKE release notes on 7 October 2026.

What you need before you start

  • Nodes on GKE 1.37.0-gke.4713000 or later.
  • A Standard cluster, with node pools created through gcloud or the GKE API, because the Cloud console can't select microVM.
  • A machine type that supports nested virtualization (a VM inside the node VM, which is how GKE builds the microVMs). The docs examples use N4, C4, C3 and C4D, and the nested virtualization page points to the machine series table for the full list.
  • An organization policy that doesn't enforce "Disable VM nested virtualization", which would block it.

Step 1: Create a cluster with a regular node pool

GKE needs a regular node pool for its own system workloads, so create the cluster first and add the sandbox pool second.

gcloud container clusters create <cluster-name> \
    --location=<control-plane-location>

Run kubectl get nodes and check the VERSION column. Anything older than 1.37.0-gke.4713000 won't offer the microVM sandbox.

Step 2: Create the GKE microVM node pool

This is the command from the GKE Sandbox how-to:

gcloud container node-pools create <node-pool-name> \
    --cluster=<cluster-name> \
    --location=<control-plane-location> \
    --sandbox=type=microvm \
    --enable-nested-virtualization \
    --machine-type=n4-standard-8

--sandbox=type=microvm picks the sandbox type and --enable-nested-virtualization turns on the VM-in-VM support. n4-standard-8 comes from the nested virtualization docs' own example, so swap in whatever your quota allows.

Nested virtualization can't be switched on for an existing node pool, so replace any pool created without it. To let GKE create nodes for you, the docs also describe a ComputeClass with type: microvm under nodePoolConfig.sandbox and enableNestedVirtualization: true, on the same GKE version.

Step 3: Schedule Pods with runtimeClassName microvm

The only change to your workload is the runtimeClassName field.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sandbox-pod
  labels:
    environment: sandbox
spec:
  replicas: 2
  selector:
    matchLabels:
      environment: sandbox
  template:
    metadata:
      labels:
        environment: sandbox
    spec:
      runtimeClassName: microvm
      containers:
      - name: nginx
        image: nginx
kubectl apply -f sandbox-deployment.yaml

On Standard clusters, GKE adds the node affinity and toleration for you when a Pod sets runtimeClassName. Sandbox nodes carry the label sandbox.gke.io/runtime: microvm and the taint sandbox.gke.io/runtime=microvm:NoSchedule, so ordinary Pods stay off them unless you add a toleration yourself.

Step 4: Verify the Pods are really in a microVM

Start with the field that GKE controls, not anything reported from inside the Pod:

kubectl get pods -l=environment=sandbox \
    -o jsonpath=$'{range .items[*]}{.metadata.name}: {.spec.runtimeClassName}\n{end}'

Every line should end in microvm. Then compare kernels and look for the virtual hardware:

kubectl exec <pod-name> -- uname -r
kubectl get node <node-name> -o jsonpath='{.status.nodeInfo.kernelVersion}'
kubectl exec <pod-name> -- cat /sys/class/dmi/id/product_name

The two kernel versions should differ, and the last command should print cloud-hypervisor.

Step 5: Block the metadata server from sandbox Pods

Unlike gVisor, microVM Pods can reach cluster metadata by default. The concepts page says to deny egress to 169.254.169.252/32 on port 988, or to 169.254.169.254/32 on port 80 in clusters using GKE Dataplane V2. It links to NetworkPolicy docs instead of showing YAML, so this standard policy is built from those addresses and blocks both on all ports, which is stricter than the docs:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: block-metadata-egress
  namespace: <your-namespace>
spec:
  podSelector:
    matchLabels:
      environment: sandbox
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 169.254.169.252/32
        - 169.254.169.254/32

This allows all other egress, so tighten it for agents that shouldn't reach your internal network. The cluster must enforce NetworkPolicy (Dataplane V2 does), and you should test from inside a Pod that the metadata address no longer answers.

Gotchas

  • Overhead is fixed. Each Pod costs about 250 mCPU and 130 MiB of memory on top of what its containers request. A hundred Pods is 25 vCPU and roughly 12.7 GiB gone before your code runs.
  • Starts are slower than gVisor. Expect about one to two seconds per Pod, against under 200 ms for gVisor.
  • No GPU or TPU. Sandboxed Pods can't use accelerators.
  • No hostNetwork: true. Those Pods can't reach anything outside the Pod.
  • Some security contexts are unsupported. That includes seccomp, AppArmor, SELinux, No New Privileges and procMount.
  • Metrics are Pod-level only. Container-level memory usage isn't supported.
  • Linux nodes only, on Container-Optimized OS or Ubuntu images.

For production agents on GKE, one node pool and one manifest field put the code they generate behind a hypervisor boundary instead of a container boundary alone.

← More in Infrastructure