Dynamic storage late-binding lets you inject Filestore agent volumes directly into running, pre-warmed Google Kubernetes Engine (GKE) Agent Sandbox pods upon claim. By bypassing the standard Kubernetes volume attachment lifecycle, dynamic late-binding achieves under 100 ms storage attachment latency without requiring pod restarts.
This architecture lets high-density, low-latency agent platforms perform the following tasks:
- Eliminate pod cold-start and container initialization delays.
- Dynamically attach and detach persistent workspaces on demand.
- Pause or hibernate idle agent sessions and resume them on any available pre-warmed sandbox pod while preserving file system state.
Before you begin
- Complete the initial setup in Set up GKE environment for Filestore agent volumes.
- Verify that your GKE cluster runs version
1.36.0-gke.3302001or later. - Verify that your
volume-pool-scStorageClassspecifiesvolumeBindingMode: ImmediateandreclaimPolicy: Delete.
Architecture overview
The late-binding architecture consists of four components:
- Platform orchestrator or custom controller: A control-plane service or
Kubernetes controller that manages session lifecycles. It watches for
SandboxClaimevents, resolves tenant volume metadata, calls the storage node daemon API to bind or unbind storage, and manages deletion finalizers. - Storage node daemon: A privileged
DaemonSetrunning on each gVisor node that exposes a mount API to bind Filestore NFS exports into host pod directories. SandboxTemplatewithforce-shared: A gVisor sandbox pod template that lets host mounts propagate dynamically into the gVisor sandbox container.SandboxWarmPool: A pool of pre-warmed running sandbox pods ready to receive mount requests instantly upon claim.
How dynamic late-binding works
Instead of attaching a PersistentVolumeClaim (PVC) at pod creation time,
dynamic late-binding uses the following lifecycle:
- Pre-warm sandboxes: A
SandboxWarmPoolmaintains running gVisor pods configured with anemptyDirvolume,HostToContainermount propagation, and thedev.gvisor.empty-dir.VOLUME_NAME.force-shared: "true"annotation, where<var>VOLUME_NAME</var>is the name of theemptyDirvolume. - Claim and provision: When an agent session starts, your orchestrator
creates a
SandboxClaim(with a deletion finalizer) to claim a warm pod and provisions or selects a backing Filestore PVC. - Bind storage on the host: Your orchestrator resolves the claimed pod's
UID, node, and Filestore NFS export details, adds a deletion
finalizer to the pod, and instructs the storage node daemon on that node to
mount the NFS volume into the pod's backing
emptyDirpath on the host. With mount propagation enabled, the mounted workspace immediately appears inside the running gVisor container. Unbind before teardown: When the session pauses or ends, your orchestrator instructs the node daemon to unmount the NFS volume from the host directory before removing the finalizers from the
PodandSandboxClaim.
For a production reference implementation that automates these steps with a custom controller and authenticated node daemon, see the GKE Sandbox late-binding storage example on GitHub.
Deploy the storage node daemon
To test the late-binding workflow, deploy a privileged DaemonSet on your
gVisor nodes that installs the NFS client utilities on the host and provides an
execution environment for host mount commands.
Create a manifest named
storage-node-daemon.yaml:apiVersion: apps/v1 kind: DaemonSet metadata: name: storage-node-daemon namespace: default spec: selector: matchLabels: app: storage-node-daemon template: metadata: labels: app: storage-node-daemon spec: hostPID: true nodeSelector: sandbox.gke.io/runtime: gvisor tolerations: - key: "sandbox.gke.io/runtime" operator: "Equal" value: "gvisor" effect: "NoSchedule" containers: - name: daemon image: debian:stable-slim securityContext: privileged: true command: ["/bin/sh", "-c"] args: - | nsenter -t 1 -m -u -i -n -- apt-get update && \ nsenter -t 1 -m -u -i -n -- apt-get install -y nfs-common || true sleep infinityApply the manifest:
kubectl apply -f storage-node-daemon.yaml
Configure the SandboxTemplate and SandboxWarmPool
To enable dynamic host mounts to propagate into a running gVisor sandbox, your
SandboxTemplate must include the
dev.gvisor.empty-dir.workspace-volume.force-shared: "true" annotation and set
mountPropagation: HostToContainer on the emptyDir volume mount.
Create a manifest named
sandbox-latebind.yamlcontaining the template and warm pool:apiVersion: extensions.agents.x-k8s.io/v1alpha1 kind: SandboxTemplate metadata: name: late-bind-template namespace: default spec: networkPolicyManagement: Unmanaged podTemplate: metadata: annotations: dev.gvisor.empty-dir.workspace-volume.force-shared: "true" spec: runtimeClassName: gvisor automountServiceAccountToken: false nodeSelector: sandbox.gke.io/runtime: gvisor tolerations: - key: "sandbox.gke.io/runtime" value: "gvisor" effect: "NoSchedule" securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 containers: - name: agent image: busybox:latest securityContext: capabilities: drop: ["ALL"] allowPrivilegeEscalation: false volumeMounts: - name: workspace-volume mountPath: /workspace mountPropagation: HostToContainer env: - name: MOUNT_PATH value: "/workspace" - name: SUB_DIR value: "user_data" command: ["/bin/sh", "-c"] args: - | echo "Waiting for late-bind signal..." while [ ! -f $MOUNT_PATH/.ready ]; do sleep 0.05; done echo "Filestore volume mounted successfully!" ls -la $MOUNT_PATH/$SUB_DIR date >> $MOUNT_PATH/$SUB_DIR/session.txt sleep infinity volumes: - name: workspace-volume emptyDir: {} --- apiVersion: extensions.agents.x-k8s.io/v1alpha1 kind: SandboxWarmPool metadata: name: late-bind-warmpool namespace: default spec: replicas: 2 sandboxTemplateRef: name: late-bind-templateApply the manifest:
kubectl apply -f sandbox-latebind.yaml
Claim a sandbox and dynamically bind storage
Create a manifest named
late-bind-claim.yamlthat claims a warm sandbox with a deletion finalizer (agent.sandbox/storage-cleanup) and provisions a Filestore agent volume PVC:apiVersion: extensions.agents.x-k8s.io/v1alpha1 kind: SandboxClaim metadata: name: late-bind-session-1 namespace: default finalizers: - agent.sandbox/storage-cleanup spec: sandboxTemplateRef: name: late-bind-template --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: session-1-pvc namespace: default spec: accessModes: [ReadWriteMany] storageClassName: volume-pool-sc resources: requests: storage: 1GiApply the manifest:
kubectl apply -f late-bind-claim.yaml
Retrieve the assigned pod details and backing Filestore NFS export attributes:
POD_NAME=$(kubectl get pods \ -l extensions.agents.x-k8s.io/claimed-by=late-bind-session-1 \ -o jsonpath='{.items[0].metadata.name}') POD_UID=$(kubectl get pod "${POD_NAME}" -o jsonpath='{.metadata.uid}') NODE_NAME=$(kubectl get pod "${POD_NAME}" -o jsonpath='{.spec.nodeName}') PV_NAME=$(kubectl get pvc session-1-pvc -o jsonpath='{.spec.volumeName}') NFS_IP=$(kubectl get pv "${PV_NAME}" \ -o jsonpath='{.spec.csi.volumeAttributes.ip}') NFS_PATH="/$(kubectl get pv "${PV_NAME}" \ -o jsonpath='{.spec.csi.volumeAttributes.volume}')"Add a deletion finalizer to the claimed pod to prevent Kubelet from deleting the pod while the host mount is active:
kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":["agent.sandbox/storage-cleanup"]}}'Mount the Filestore NFS volume into the pod's host
emptyDirdirectory and signal readiness:DAEMON_POD=$(kubectl get pods -l app=storage-node-daemon \ --field-selector spec.nodeName="${NODE_NAME}" \ -o jsonpath='{.items[0].metadata.name}') HOST_DIR="/var/lib/kubelet/pods/${POD_UID}/volumes/kubernetes.io~empty-dir/workspace-volume" kubectl exec "${DAEMON_POD}" -- nsenter -t 1 -m -- sh -c " mkdir -p ${HOST_DIR}/user_data && mount -t nfs ${NFS_IP}:${NFS_PATH} ${HOST_DIR}/user_data && chown -R 1000:1000 ${HOST_DIR}/user_data && echo ready > ${HOST_DIR}/.ready "Verify that the volume is mounted inside the running sandbox pod:
kubectl logs "${POD_NAME}" -c agentThe output confirms that the volume was mounted:
Waiting for late-bind signal... Filestore volume mounted successfully! drwxr-xr-x 2 1000 1000 4096 ... user_data
Unbind storage and release the sandbox
When an agent session pauses or ends, unmount the host storage before allowing Kubernetes to delete the pod.
Initiate deletion of the
SandboxClaimin the background. Because of the finalizer, Kubernetes marks the claim for deletion but pauses pod termination:kubectl delete sandboxclaim late-bind-session-1 --wait=false
Unmount the Filestore NFS volume on the host node:
kubectl exec "${DAEMON_POD}" -- nsenter -t 1 -m -- \ umount -l "${HOST_DIR}/user_data"Remove the finalizers from both the
SandboxClaimand the pod to complete termination:kubectl patch sandboxclaim late-bind-session-1 --type=merge \ -p '{"metadata":{"finalizers":[]}}' kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":[]}}'
To resume the session later, create a new SandboxClaim and mount the existing
PVC (session-1-pvc) into the newly claimed warm pod.
What's next
- Explore static Agent Sandbox integration.
- Deploy self-managed GKE workloads.
- Learn how to create and manage volume pools.