Il binding tardivo dell'archiviazione dinamica consente di inserire volumi dell'agente Filestore direttamente nei pod della sandbox dell'agente Google Kubernetes Engine (GKE) pre-riscaldati al momento della richiesta. Evitando il ciclo di vita standard di collegamento dei volumi di Kubernetes, il collegamento dinamico tardivo raggiunge una latenza di collegamento dello spazio di archiviazione inferiore a 100 ms senza richiedere riavvii dei pod.
Questa architettura consente alle piattaforme di agenti a bassa latenza e ad alta densità di:
- Elimina i ritardi di avvio a freddo del pod e di inizializzazione del container.
- Collega e scollega dinamicamente gli spazi di lavoro persistenti on demand.
- Metti in pausa o iberna le sessioni degli agenti inattivi e riprendile in qualsiasi pod sandbox pre-riscaldato disponibile, mantenendo lo stato del file system.
Prima di iniziare
- Completa la configurazione iniziale in Configura l'ambiente GKE per i volumi dell'agente Filestore.
- Verifica che il cluster GKE esegua la versione
1.36.0-gke.3302001o successive. Questa versione supporta l'annotazioneforce-sharedrichiesta per la propagazione del montaggio di gVisoremptyDir. - Verifica che
volume-pool-scStorageClassspecifichivolumeBindingMode: ImmediateereclaimPolicy: Delete.
Panoramica dell'architettura
L'architettura late-binding è composta da quattro componenti:
- Orchestratore della piattaforma o controller personalizzato:un servizio del control plane o un controller Kubernetes che gestisce i cicli di vita delle sessioni. Monitora gli eventi
SandboxClaim, risolve i metadati del volume del tenant, chiama l'API del daemon del nodo di archiviazione per associare o dissociare l'archiviazione e gestisce i finalizzatori di eliminazione. - Daemon del nodo di archiviazione:un
DaemonSetcon privilegi in esecuzione su ogni nodo gVisor che espone un'API di montaggio. SandboxTemplateconforce-shared: un modello di pod sandbox gVisor che consente di propagare dinamicamente i montaggi dell'host nel container sandbox gVisor.SandboxWarmPool: un pool di pod sandbox preinizializzati in esecuzione pronti a ricevere richieste di montaggio immediatamente dopo la rivendicazione.
Esegui il deployment del daemon del nodo di archiviazione
Crea un manifest denominato storage-node-daemon.yaml contenente il privilegio
DaemonSet:
Applica il manifest:
kubectl apply -f storage-node-daemon.yaml
Esegui il deployment di SandboxTemplate e SandboxWarmPool
Crea un manifest denominato sandbox-latebind.yaml contenente il modello e
il pool caldo:
Applica il manifest:
kubectl apply -f sandbox-latebind.yaml
Richiedere una sandbox e associare dinamicamente lo spazio di archiviazione
Crea un manifest di rivendicazione denominato
late-bind-claim.yamlche includa un finalizzatore di eliminazione (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-templateApplica la rivendicazione:
kubectl apply -f late-bind-claim.yaml
Esegui il provisioning dinamico di un volume utilizzando un manifest PVC denominato
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: 1GiApplica il PVC:
kubectl apply -f agent-volume-pvc.yaml
Recupera i dettagli di esportazione di UID, nodo e PV di backup del pod assegnato:
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}')"Invia il segnale di montaggio al daemon del nodo sull'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()) "Applica un finalizzatore di eliminazione al pod rivendicato per impedire la pulizia prematura mentre il montaggio dell'host è attivo:
kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":["agent.sandbox/storage-cleanup"]}}'Verifica il montaggio del volume all'interno del pod in esecuzione:
kubectl logs "${POD_NAME}" -c agentL'output conferma che il volume è stato montato correttamente:
Waiting for late-bind signal... Filestore volume mounted successfully! drwxr-xr-x 2 1000 1000 4096 ... user_data
Mettere in pausa e riprendere una sessione
Quando una sessione dell'agente termina o entra in ibernazione, l'orchestratore deve smontare lo spazio di archiviazione host prima di consentire a Kubernetes di terminare o riciclare il pod.
Avvia l'eliminazione di
SandboxClaimin background. A causa del finalizzatore, Kubernetes contrassegna la richiesta di eliminazione, ma mette in pausa la terminazione del pod:kubectl delete sandboxclaim late-bind-session-1 --wait=false
Smonta la condivisione NFS sul 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()) "Rimuovi i finalizer sia da
SandboxClaimsia dal pod per completare la terminazione:kubectl patch sandboxclaim late-bind-session-1 --type=merge \ -p '{"metadata":{"finalizers":[]}}' kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":[]}}'
Per riprendere la sessione in un secondo momento, richiedi un nuovo pod pre-riscaldato e invia la richiesta di binding utilizzando la PVC esistente (session-1-pvc). Il nuovo pod sandbox ottiene immediatamente l'accesso allo stato dello spazio di lavoro conservato.
Considerazioni sulla produzione e progettazione di controller personalizzati
I comandi manuali in questo documento mostrano i meccanismi di basso livello del dynamic late-binding. Per eseguire questa architettura in modo affidabile in produzione, devi sviluppare un controller Kubernetes personalizzato o un orchestratore di piattaforme personalizzato in base al ciclo di vita della sessione della tua applicazione.
Quando progetti il controller di produzione e il daemon del nodo, implementa i seguenti pattern architetturali:
Automatizzare il ciclo di vita della riconciliazione e del finalizzatore
Poiché il daemon del nodo di archiviazione esegue i montaggi a livello di host al di fuori della gestione del ciclo di vita standard di Container Storage Interface (CSI) di Kubernetes, Kubelet non è a conoscenza dei montaggi attivi all'interno di emptyDir del pod. Se un pod viene eliminato mentre
il montaggio è attivo, Kubelet non riesce a rimuovere la directory emptyDir e genera
un errore Device or resource busy, lasciando il pod bloccato nello stato Terminating.
Il controller personalizzato deve automatizzare una macchina a stati rigorosa utilizzando i finalizzatori
(ad esempio agent.sandbox/storage-cleanup):
- Creazione e associazione di rivendicazioni:
- Al momento della creazione, collega un finalizzatore statico a ogni
SandboxClaim. - Monitora l'API Kubernetes per gli aggiornamenti di stato di
SandboxClaim. Quando una richiesta si lega a un pod del pool caldo, estrai gli attributipod_uid,nodeNamee del volume Filestore di backup assegnati. - Invia una richiesta
bindautenticata al daemon del nodo di archiviazione in esecuzione sul nodo di destinazione. - Applica immediatamente la patch all'oggetto
Podin esecuzione per aggiungere il finalizzatore dinamico. Poiché le specifiche diSandboxTemplatenon supportano i finalizzatori statici dei pod, è necessario applicare patch al pod in modo dinamico per proteggerlo durante gli svuotamenti dei nodi o gli eventi di riprogrammazione in cui il pod viene rimosso, maSandboxClaimrimane attivo.
- Al momento della creazione, collega un finalizzatore statico a ogni
- Gestione dell'arresto controllato e dell'espulsione:
- Cerca
deletionTimestampnelle risorseSandboxClaimePod. - Quando viene rilevata un'eliminazione o un'espulsione, chiama l'endpoint
unbinddel daemon del nodo per smontare correttamente la directory host (umount -l). - Verifica che l'unmount sia riuscito e che tutte le scritture in attesa siano state
scaricate prima di applicare la patch a
PodeSandboxClaimper rimuovere i relativi finalizzatori. Questo può aiutarti a ottenere una rimozione pulita in caso di eliminazioni di rivendicazioni controllate, upgrade dei nodi GKE, interruzioni delle VM spot e terminazioni per esaurimento della memoria (OOM).
- Cerca
Proteggere e rafforzare il daemon del nodo di archiviazione
- Sostituisci
kubectl execcon API autenticate: in produzione, non utilizzarekubectl execo associa il daemon alocalhost. Configura il daemon del nodo di archiviazione in modo che esponga un endpoint gRPC o HTTPS dedicato sulla rete del cluster protetta con autenticazione mutual TLS (mTLS) o token KubernetesServiceAccount. - Isola gli spazi dei nomi dei daemon e l'accesso alla rete:esegui il deployment di
storage-node-daemonDaemonSetcon privilegi in uno spazio dei nomi amministrativo con limitazioni (ad esempiosandbox-storage-system) anziché negli spazi dei nomidefaulto tenant. Applica regoleNetworkPolicydi Kubernetes che consentono l'accesso in entrata all'API del daemon esclusivamente dai pod del controller personalizzato e blocca tutto il traffico dai pod dell'agente in sandbox. - Utilizza immagini container precompilate:evita di installare pacchetti come
nfs-commonin fase di runtime in uninitContainer. Utilizza un'immagine container predefinita e immutabile con tutte le utilità di montaggio richieste preinstallate per eliminare i ritardi di avvio dei nodi e le dipendenze del repository esterno.
Applica quote di archiviazione e isolamento multi-tenant
- Monitora l'utilizzo dello spazio di archiviazione per agente:quando esegui il bind-mount dinamico di una sottodirectory da un volume Filestore
ReadWriteMany(RWX) condiviso in unemptyDir, le impostazioniemptyDir.sizeLimitstandard di Kubernetes non possono applicare quote di spazio di archiviazione per agente al percorso NFS montato. Per impedire a un singolo agente non controllato di esaurire il volume condiviso e causare un attacco Denial of Service (DoS), implementa il monitoraggio della quota di directory nell'orchestratore o esegui il provisioning di volumi dedicati utilizzando i pool di volumi. - Adatta i payload di montaggio per diverse modalità di accesso allo spazio di lavoro: il
controller può supportare più topologie di archiviazione degli agenti variando i
parametri inviati al daemon del nodo:
- Workspace isolati privati:associa una sottodirectory tenant univoca o un PVC dedicato a un singolo pod sandbox con autorizzazioni di lettura/scrittura.
- Spazi di lavoro collaborativi: associa contemporaneamente la stessa sottodirectory RWX condivisa a più pod di agenti coordinati per la condivisione di file in tempo reale.
- Workspace di ramificazione dell'esplorazione: monta una directory di modelli di base
come di sola lettura (
ro) in modo che gli agenti possano leggere le risorse condivise senza modificare la copia di riferimento, mentre indirizzano le nuove scritture a un percorso scratch scrivibile separato o a una directory di copia al ripristino.
Coordinare gli snapshot point-in-time e la pulizia
- Raggiungi la quiescenza di scrittura prima degli snapshot:per acquisire snapshot dello spazio di lavoro coerenti in un determinato momento senza danneggiare i dati, l'orchestratore deve mettere in pausa le scritture attive avviando il flusso di lavoro di annullamento del binding (o svuotando i buffer del file system) prima di archiviare la directory dello spazio di lavoro o attivare uno snapshot Filestore.
- Automatizza il deprovisioning del tenant:quando una sessione utente o uno spazio di lavoro scade in modo permanente, assicurati che il controller annulli prima l'associazione di tutti i montaggi attivi su tutti i nodi prima di eseguire attività asincrone in background per eliminare le directory permanenti del tenant dal volume di archiviazione.
Per un'implementazione di riferimento completa che mostri la gestione dinamica dei finalizzatori, l'isolamento multi-tenant e i workflow di ripristino degli snapshot, consulta l'esempio di archiviazione con binding tardivo di GKE Sandbox su GitHub.
Passaggi successivi
- Esplora l'integrazione statica della sandbox dell'agente.
- Esegui il deployment di carichi di lavoro GKE autogestiti.
- Scopri come creare e gestire i pool di volumi.