La vinculación tardía del almacenamiento dinámico te permite insertar volúmenes del agente de Filestore directamente en los Pods de Google Kubernetes Engine (GKE) Agent Sandbox en ejecución y precalentados cuando se reclaman. Al omitir el ciclo de vida estándar de la conexión de volúmenes de Kubernetes, la vinculación tardía dinámica logra una latencia de conexión de almacenamiento inferior a 100 ms sin necesidad de reiniciar los pods.
Esta arquitectura permite que las plataformas de agentes de alta densidad y baja latencia hagan lo siguiente:
- Elimina los retrasos en el inicio en frío del Pod y la inicialización del contenedor.
- Conectar y desconectar dinámicamente espacios de trabajo persistentes según demanda
- Pausar o hibernar las sesiones de agentes inactivas y reanudarlas en cualquier pod de zona de pruebas precalentado disponible, a la vez que se conserva el estado del sistema de archivos
Antes de comenzar
- Completa la configuración inicial en Configura el entorno de GKE para los volúmenes del agente de Filestore.
- Verifica que tu clúster de GKE ejecute la versión
1.36.0-gke.3302001o posterior. Esta versión admite la anotaciónforce-sharednecesaria para la propagación del montaje deemptyDirde gVisor. - Verifica que tu
volume-pool-scStorageClassespecifiquevolumeBindingMode: ImmediateyreclaimPolicy: Delete.
Descripción general de la arquitectura
La arquitectura de vinculación tardía consta de cuatro componentes:
- Organizador de la plataforma o controlador personalizado: Es un servicio del plano de control o un controlador de Kubernetes que administra los ciclos de vida de las sesiones. Supervisa los eventos de
SandboxClaim, resuelve los metadatos de volumen del inquilino, llama a la API del daemon del nodo de almacenamiento para vincular o desvincular el almacenamiento y administra los finalizadores de eliminación. - Daemon del nodo de almacenamiento: Un
DaemonSetcon privilegios que se ejecuta en cada nodo de gVisor y expone una API de montaje. SandboxTemplateconforce-shared: Es una plantilla de pod de zona de pruebas de gVisor que permite que las vinculaciones del host se propaguen de forma dinámica en el contenedor de zona de pruebas de gVisor.SandboxWarmPool: Es un grupo de pods de zona de pruebas en ejecución preparados previamente y listos para recibir solicitudes de vinculación de inmediato tras la reclamación.
Implementa el daemon del nodo de almacenamiento
Crea un manifiesto llamado storage-node-daemon.yaml que contenga el DaemonSet con privilegios:
Aplica el manifiesto
kubectl apply -f storage-node-daemon.yaml
Implementa SandboxTemplate y SandboxWarmPool
Crea un manifiesto llamado sandbox-latebind.yaml que contenga la plantilla y el grupo de instancias en espera:
Aplica el manifiesto
kubectl apply -f sandbox-latebind.yaml
Cómo reclamar una zona de pruebas y vincular almacenamiento de forma dinámica
Crea un manifiesto de reclamo llamado
late-bind-claim.yamlque incluya un finalizador de eliminación (agent.sandbox/storage-cleanup):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-templateAplica el reclamo:
kubectl apply -f late-bind-claim.yaml
Aprovisiona un volumen de forma dinámica con un manifiesto de PVC llamado
agent-volume-pvc.yaml:apiVersion: v1 kind: PersistentVolumeClaim metadata: name: session-1-pvc namespace: default spec: accessModes: [ReadWriteMany] storageClassName: volume-pool-sc resources: requests: storage: 1GiAplica el PVC:
kubectl apply -f agent-volume-pvc.yaml
Recupera el UID del Pod, el nodo y los detalles de exportación del PV de respaldo asignados:
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}')"Envía el indicador de activación al daemon del nodo en el host del pod:
DAEMON_POD=$(kubectl get pods -l app=storage-node-daemon \ --field-selector spec.nodeName="${NODE_NAME}" \ -o jsonpath='{.items[0].metadata.name}') kubectl exec "${DAEMON_POD}" -c daemon -- python3 -c " import urllib.request, json payload = json.dumps({ 'action': 'bind_nfs', 'pod_uid': '${POD_UID}', 'volume_name': 'workspace-volume', 'sub_dir': 'user_data', 'nfs_server': '${NFS_IP}', 'nfs_path': '${NFS_PATH}' }).encode() req = urllib.request.Request( 'http://localhost:9090', data=payload, headers={'Content-Type': 'application/json'}) print(urllib.request.urlopen(req).read().decode()) "Aplica un finalizador de eliminación al Pod reclamado para evitar la limpieza prematura mientras la conexión del host esté activa:
kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":["agent.sandbox/storage-cleanup"]}}'Verifica la activación del volumen dentro del pod en ejecución:
kubectl logs "${POD_NAME}" -c agentEl resultado confirma que el volumen se activó correctamente:
Waiting for late-bind signal... Filestore volume mounted successfully! drwxr-xr-x 2 1000 1000 4096 ... user_data
Cómo pausar y reanudar una sesión
Cuando una sesión del agente finaliza o entra en hibernación, tu orquestador debe desmontar el almacenamiento del host antes de permitir que Kubernetes finalice o recicle el pod.
Inicia la eliminación de
SandboxClaimen segundo plano. Debido al finalizador, Kubernetes marca el reclamo para su eliminación, pero detiene la finalización del Pod:kubectl delete sandboxclaim late-bind-session-1 --wait=false
Desactiva el recurso compartido de NFS en el nodo host:
kubectl exec "${DAEMON_POD}" -c daemon -- python3 -c " import urllib.request, json payload = json.dumps({ 'action': 'unbind', 'pod_uid': '${POD_UID}', 'volume_name': 'workspace-volume', 'sub_dir': 'user_data' }).encode() req = urllib.request.Request( 'http://localhost:9090', data=payload, headers={'Content-Type': 'application/json'}) print(urllib.request.urlopen(req).read().decode()) "Quita los finalizadores del
SandboxClaimy del pod para completar la finalización:kubectl patch sandboxclaim late-bind-session-1 --type=merge \ -p '{"metadata":{"finalizers":[]}}' kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":[]}}'
Para reanudar la sesión más tarde, solicita un nuevo pod preparado previamente y envía la solicitud de vinculación con el PVC existente (session-1-pvc). El nuevo pod de zona de pruebas obtiene acceso de inmediato al estado conservado del espacio de trabajo.
Consideraciones de producción y diseño de controles personalizados
Los comandos manuales de este documento demuestran los mecanismos de bajo nivel de la vinculación tardía dinámica. Para ejecutar esta arquitectura de forma confiable en producción, debes desarrollar un controlador de Kubernetes personalizado o un organizador de plataformas adaptado al ciclo de vida de la sesión de tu aplicación.
Cuando diseñes el controlador de producción y el daemon de nodos, implementa los siguientes patrones arquitectónicos:
Automatiza el ciclo de vida de la conciliación y el finalizador
Debido a que el daemon del nodo de almacenamiento realiza activaciones a nivel del host fuera de la administración del ciclo de vida estándar de la interfaz de almacenamiento de contenedores (CSI) de Kubernetes, Kubelet no conoce las activaciones activas dentro del emptyDir del pod. Si se borra un pod mientras el montaje está activo, Kubelet no podrá quitar el directorio emptyDir y generará un error Device or resource busy, lo que dejará el pod atascado en un estado Terminating.
Tu controlador personalizado debe automatizar una máquina de estados estricta con finalizadores (como agent.sandbox/storage-cleanup):
- Creación y vinculación de reclamos:
- Adjunta un finalizador estático a cada
SandboxClaimen el momento de la creación. - Observa la API de Kubernetes para ver las actualizaciones de estado de
SandboxClaim. Cuando un reclamo se vincula a un pod de grupo de recursos activo, extrae los atributos asignados depod_uid,nodeNamey el volumen de Filestore de respaldo. - Envía una solicitud
bindautenticada al daemon del nodo de almacenamiento que se ejecuta en el nodo de destino. - Aplica un parche de inmediato al objeto
Poden ejecución para agregar el finalizador dinámico. Dado que las especificaciones deSandboxTemplateno admiten finalizadores de Pod estáticos, se requiere aplicar parches al Pod de forma dinámica para protegerlo durante los eventos de drenaje o reprogramación de nodos en los que se expulsa el Pod, peroSandboxClaimpermanece activo.
- Adjunta un finalizador estático a cada
- Control de desalojo y finalización correcta:
- Observa
deletionTimestampen los recursosSandboxClaimyPod. - Cuando se detecta una eliminación o desalojo, llama al extremo
unbinddel daemon del nodo para desmontar correctamente el directorio del host (umount -l). - Verifica que el desmontaje se haya realizado correctamente y que todas las escrituras pendientes se hayan vaciado antes de aplicar parches a
PodySandboxClaimpara quitar sus finalizadores. Esto puede ayudarte a lograr una limpieza en la eliminación de reclamos correctos, las actualizaciones de nodos de GKE, las interrupciones de VM Spot y las finalizaciones por falta de memoria (OOM).
- Observa
Protege y endurece el daemon del nodo de almacenamiento
- Reemplaza
kubectl execpor APIs autenticadas: En producción, no useskubectl execni vincules el daemon alocalhost. Configura el daemon del nodo de almacenamiento para exponer un extremo dedicado de gRPC o HTTPS a través de la red del clúster protegida con TLS mutua (mTLS) o autenticación de token deServiceAccountde Kubernetes. - Aísla los espacios de nombres de los daemons y el acceso a la red: Implementa el
storage-node-daemonDaemonSetcon privilegios en un espacio de nombres administrativo restringido (por ejemplo,sandbox-storage-system) en lugar de los espacios de nombresdefaulto de inquilino. Aplica reglas deNetworkPolicyde Kubernetes que permitan el ingreso a la API del daemon exclusivamente desde los Pods del controlador personalizado y bloqueen todo el tráfico de los Pods del agente en zona de pruebas. - Usa imágenes de contenedor precompiladas: Evita instalar paquetes como
nfs-commonen el tiempo de ejecución en uninitContainer. Usa una imagen de contenedor inmutable compilada previamente con todas las utilidades de montaje necesarias preinstaladas para eliminar las demoras en el inicio de los nodos y las dependencias de repositorios externos.
Aplica cuotas de almacenamiento y aislamiento multiusuario
- Supervisa el uso de almacenamiento por agente: Cuando vinculas de forma dinámica un subdirectorio desde un volumen compartido
ReadWriteMany(RWX) de Filestore a unemptyDir, la configuración estándar deemptyDir.sizeLimitde Kubernetes no puede aplicar cuotas de almacenamiento por agente en la ruta de acceso NFS montada. Para evitar que un solo agente descontrolado agote el volumen compartido y provoque una denegación de servicio (DoS), implementa la supervisión de cuotas de directorio en tu orquestador o aprovisiona volúmenes dedicados con grupos de volúmenes. - Adapta las cargas útiles de la vinculación para los diferentes modos de acceso al espacio de trabajo: Tu controlador puede admitir varias topologías de almacenamiento de agentes variando los parámetros que se envían al daemon del nodo:
- Espacios de trabajo privados aislados: Vincula un subdirectorio único del usuario o un PVC dedicado a un solo pod de zona de pruebas con permisos de lectura y escritura.
- Espacios de trabajo colaborativos: Vincula de forma simultánea el mismo subdirectorio RWX compartido en varios pods de agentes coordinadores para compartir archivos en tiempo real.
- Espacios de trabajo de ramificación de exploración: Monta un directorio de plantillas base como de solo lectura (
ro) para que los agentes puedan leer los recursos compartidos sin modificar la copia dorada, mientras enruta las escrituras nuevas a una ruta de acceso de borrador grabable independiente o a un directorio de copia en restauración.
Coordina las instantáneas de un momento determinado y la limpieza
- Logra la quiescencia de escritura antes de las instantáneas: Para capturar instantáneas coherentes del espacio de trabajo en un momento determinado sin corrupción de datos, tu orquestador debe pausar las escrituras activas iniciando el flujo de trabajo de desvinculación (o vaciando los búferes del sistema de archivos) antes de archivar el directorio del espacio de trabajo o activar una instantánea de Filestore.
- Automatiza la anulación del aprovisionamiento del arrendatario: Cuando una sesión de usuario o un espacio de trabajo vencen de forma permanente, asegúrate de que tu controlador primero desvincule cualquier montaje activo en todos los nodos antes de ejecutar tareas asíncronas en segundo plano para borrar los directorios persistentes del arrendatario del volumen de respaldo.
Para ver una implementación de referencia completa que muestre la administración dinámica de finalizadores, el aislamiento de múltiples arrendatarios y los flujos de trabajo de restauración de instantáneas, consulta el ejemplo de almacenamiento de vinculación tardía de GKE Sandbox en GitHub.
¿Qué sigue?
- Explora la integración de Agent Sandbox estática.
- Implementa cargas de trabajo de GKE autoadministradas.
- Obtén más información para crear y administrar grupos de volumen.