Mit der dynamischen späten Bindung von Speicher können Sie Filestore-Agent-Volumes direkt in laufende, vorab aufgewärmte Google Kubernetes Engine (GKE)-Agent-Sandbox-Pods einfügen, wenn sie angefordert werden. Durch Umgehen des standardmäßigen Kubernetes-Lebenszyklus für die Volume-Einbindung wird mit dynamischer Late-Binding eine Latenz für die Speichereinbindung von unter 100 ms erreicht, ohne dass Pods neu gestartet werden müssen.
Diese Architektur ermöglicht es Agent-Plattformen mit hoher Dichte und niedriger Latenz,
- Verzögerungen durch Pod-Kaltstarts und Containerinitialisierung vermeiden
- Persistente Arbeitsbereiche lassen sich dynamisch nach Bedarf anhängen und trennen.
- Inaktive Agentsitzungen pausieren oder in den Ruhezustand versetzen und sie in einem beliebigen vorab aufgewärmten Sandbox-Pod fortsetzen, wobei der Dateisystemstatus beibehalten wird.
Hinweis
- Führen Sie die Ersteinrichtung in GKE-Umgebung für Filestore-Agenten-Volumes einrichten aus.
- Prüfen Sie, ob auf Ihrem GKE-Cluster die Version
1.36.0-gke.3302001oder höher ausgeführt wird. Diese Version unterstützt dieforce-sharedAnnotation, die für die Mount-Weitergabe von gVisoremptyDirerforderlich ist. - Prüfen Sie, ob in Ihrem
volume-pool-scStorageClassdie WertevolumeBindingMode: ImmediateundreclaimPolicy: Deleteangegeben sind.
Architektur
Die Late-Binding-Architektur besteht aus vier Komponenten:
- Plattform-Orchestrator oder benutzerdefinierter Controller:Ein Steuerungsebenendienst oder Kubernetes-Controller, der den Lebenszyklus von Sitzungen verwaltet. Es überwacht
SandboxClaim-Ereignisse, löst Metadaten zum Mandantenvolumen auf, ruft die Storage-Knoten-Daemon-API auf, um Speicher zu binden oder zu entbinden, und verwaltet Lösch-Finalizer. - Storage-Knoten-Daemon:Ein privilegierter
DaemonSet, der auf jedem gVisor-Knoten ausgeführt wird und eine Mount-API zur Verfügung stellt. SandboxTemplatemitforce-shared:Eine gVisor-Sandbox-Pod-Vorlage, mit der Host-Mounts dynamisch in den gVisor-Sandbox-Container übertragen werden können.SandboxWarmPool:Ein Pool mit vorgewärmten Sandbox-Pods, die sofort nach der Inanspruchnahme Mount-Anfragen empfangen können.
Speicherknoten-Daemon bereitstellen
Erstellen Sie ein Manifest mit dem Namen storage-node-daemon.yaml, das die privilegierten DaemonSet enthält:
Wenden Sie das Manifest an:
kubectl apply -f storage-node-daemon.yaml
SandboxTemplate und SandboxWarmPool bereitstellen
Erstellen Sie ein Manifest mit dem Namen sandbox-latebind.yaml, das die Vorlage und den Warm Pool enthält:
Wenden Sie das Manifest an:
kubectl apply -f sandbox-latebind.yaml
Sandbox beanspruchen und Speicher dynamisch binden
Erstellen Sie ein Anspruchsmanifest mit dem Namen
late-bind-claim.yaml, das einen Finalizer für das Löschen (agent.sandbox/storage-cleanup) enthält: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-templateWende den Anspruch an:
kubectl apply -f late-bind-claim.yaml
Stellen Sie ein Volume dynamisch mit einem PVC-Manifest mit dem Namen
agent-volume-pvc.yamlbereit:apiVersion: v1 kind: PersistentVolumeClaim metadata: name: session-1-pvc namespace: default spec: accessModes: [ReadWriteMany] storageClassName: volume-pool-sc resources: requests: storage: 1GiWenden Sie das PVC an:
kubectl apply -f agent-volume-pvc.yaml
Rufen Sie die zugewiesene Pod-UID, den Knoten und die Details zum Exportieren des Backing-PV ab:
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}')"Senden Sie das Bereitstellungssignal an den Knotendaemon auf dem Pod-Host:
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()) "Wenden Sie einen Lösch-Finalizer auf den beanspruchten Pod an, um eine vorzeitige Bereinigung zu verhindern, während die Host-Einbindung aktiv ist:
kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":["agent.sandbox/storage-cleanup"]}}'Prüfen Sie die Volume-Bereitstellung im ausgeführten Pod:
kubectl logs "${POD_NAME}" -c agentDie Ausgabe bestätigt, dass das Volume erfolgreich bereitgestellt wurde:
Waiting for late-bind signal... Filestore volume mounted successfully! drwxr-xr-x 2 1000 1000 4096 ... user_data
Sitzung pausieren und fortsetzen
Wenn eine Agent-Sitzung beendet wird oder in den Ruhezustand wechselt, muss Ihr Orchestrator den Hostspeicher trennen, bevor Kubernetes den Pod beenden oder wiederverwenden kann.
Löschen von
SandboxClaimim Hintergrund initiieren. Aufgrund des Finalizers markiert Kubernetes den Anspruch zum Löschen, pausiert aber die Pod-Beendigung:kubectl delete sandboxclaim late-bind-session-1 --wait=false
Heben Sie die Bereitstellung der NFS-Freigabe auf dem Hostknoten auf:
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()) "Entfernen Sie die Finalizer sowohl aus dem
SandboxClaimals auch aus dem Pod, um die Beendigung abzuschließen:kubectl patch sandboxclaim late-bind-session-1 --type=merge \ -p '{"metadata":{"finalizers":[]}}' kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":[]}}'
Wenn Sie die Sitzung später fortsetzen möchten, fordern Sie einen neuen vorgewärmten Pod an und senden Sie die Bindungsanfrage mit dem vorhandenen PVC (session-1-pvc). Der neue Sandbox-Pod erhält sofort Zugriff auf den beibehaltenen Arbeitsbereichsstatus.
Überlegungen zur Produktion und Design benutzerdefinierter Controller
Die manuellen Befehle in diesem Dokument veranschaulichen die grundlegenden Mechanismen der dynamischen späten Bindung. Damit diese Architektur zuverlässig in der Produktion ausgeführt werden kann, müssen Sie einen benutzerdefinierten Kubernetes-Controller oder Plattform-Orchestrator entwickeln, der auf den Sitzungslebenszyklus Ihrer Anwendung zugeschnitten ist.
Implementieren Sie beim Entwerfen des Produktionscontrollers und des Knotendaemons die folgenden Architekturmuster:
Abgleich und Finalizer-Lebenszyklus automatisieren
Da der Speicherknoten-Daemon Bereitstellungen auf Hostebene außerhalb des standardmäßigen Kubernetes Container Storage Interface (CSI)-Lebenszyklusmanagements ausführt, ist Kubelet nicht über aktive Bereitstellungen im emptyDir des Pods informiert. Wenn ein Pod gelöscht wird, während die Bereitstellung aktiv ist, kann Kubelet das Verzeichnis emptyDir nicht entfernen und gibt einen Device or resource busy-Fehler aus. Der Pod bleibt dann im Status Terminating.
Ihr benutzerdefinierter Controller muss eine strikte Statusmaschine mit Finalizern (z. B. agent.sandbox/storage-cleanup) automatisieren:
- Claim-Erstellung und ‑Bindung:
- Hängen Sie beim Erstellen jeder
SandboxClaimeinen statischen Finalizer an. - Beobachten Sie die Kubernetes API auf Statusaktualisierungen für
SandboxClaim. Wenn ein Anspruch an einen Warm-Pool-Pod gebunden ist, extrahieren Sie die zugewiesenen Attributepod_uid,nodeNameund das zugrunde liegende Filestore-Volume. - Senden Sie eine authentifizierte
bind-Anfrage an den Speicherknoten-Daemon, der auf dem Zielknoten ausgeführt wird. - Patchen Sie das laufende
Pod-Objekt sofort, um den dynamischen Finalizer hinzuzufügen. DaSandboxTemplate-Spezifikationen keine Finalizer für statische Pods unterstützen, muss der Pod dynamisch gepatcht werden, um ihn bei Node-Drains oder Rescheduling-Ereignissen zu schützen, bei denen der Pod entfernt wird, dieSandboxClaimjedoch aktiv bleibt.
- Hängen Sie beim Erstellen jeder
- Ordnungsgemäße Beendigung und Umgang mit Räumungen:
- Achten Sie auf
deletionTimestampin den RessourcenSandboxClaimundPod. - Wenn ein Löschvorgang oder eine Entfernung erkannt wird, rufen Sie den
unbind-Endpunkt des Knotendemons auf, um das Hostverzeichnis (umount -l) sauber zu unmounten. - Prüfen Sie, ob das Unmounten erfolgreich war und alle ausstehenden Schreibvorgänge abgeschlossen wurden, bevor Sie die
PodundSandboxClaimpatchen, um ihre Finalizer zu entfernen. So können Sie einen sauberen Abbau bei ordnungsgemäßen Löschungen von Ansprüchen, GKE-Knotenupgrades, Preemptions von Spot-VM und OOM-Kills (Out-of-Memory) erreichen.
- Achten Sie auf
Storage-Node-Daemon schützen und härten
kubectl execdurch authentifizierte APIs ersetzen:Verwenden Sie in der Produktion nichtkubectl execund binden Sie den Daemon nicht anlocalhost. Konfigurieren Sie den Storage-Knoten-Daemon so, dass er einen dedizierten gRPC- oder HTTPS-Endpunkt über das Clusternetzwerk bereitstellt, der mit gegenseitigem TLS (mTLS) oder der Kubernetes-ServiceAccount-Tokenauthentifizierung gesichert ist.- Daemon-Namespaces und Netzwerkzugriff isolieren:Stellen Sie den privilegierten
storage-node-daemonDaemonSetin einem eingeschränkten administrativen Namespace (z. B.sandbox-storage-system) anstelle der Namespacesdefaultoder des Mandanten bereit. Wenden Sie Kubernetes-NetworkPolicy-Regeln an, die den Ingress zur Daemon-API ausschließlich von Ihren benutzerdefinierten Controller-Pods zulassen und den gesamten Traffic von Sandbox-Agent-Pods blockieren. - Vorgefertigte Container-Images verwenden:Vermeiden Sie die Installation von Paketen wie
nfs-commonzur Laufzeit in eineminitContainer. Verwenden Sie ein vordefiniertes, unveränderliches Container-Image mit allen erforderlichen Mount-Dienstprogrammen, um Verzögerungen beim Starten von Knoten und Abhängigkeiten von externen Repositorys zu vermeiden.
Speicherkontingente und Mandantenisolation erzwingen
- Speicherplatznutzung pro Agent überwachen:Wenn Sie ein Unterverzeichnis aus einem freigegebenen
ReadWriteMany(RWX) Filestore-Volume dynamisch in einemptyDireinbinden, können mit den Standardeinstellungen füremptyDir.sizeLimitin Kubernetes keine Speicherplatzkontingente pro Agent für den eingebundenen NFS-Pfad erzwungen werden. Um zu verhindern, dass ein einzelner, außer Kontrolle geratener Agent das freigegebene Volume erschöpft und einen DoS-Angriff (Denial-of-Service) verursacht, implementieren Sie die Überwachung von Verzeichniskontingenten in Ihrem Orchestrator oder stellen Sie dedizierte Volumes mit Volume-Pools bereit. - Bereitstellungsnutzlasten für verschiedene Arbeitsbereich-Zugriffsmodi anpassen:Ihr Controller kann mehrere Agent-Speichertopologien unterstützen, indem er die an den Knotendaemon gesendeten Parameter variiert:
- Private, isolierte Arbeitsbereiche:Binden Sie ein eindeutiges Mandantenunterverzeichnis oder einen dedizierten PVC an einen einzelnen Sandbox-Pod mit Lese-/Schreibberechtigungen.
- Gemeinsame Arbeitsbereiche:Binden Sie dasselbe freigegebene RWX-Unterverzeichnis gleichzeitig in mehreren koordinierenden Agent-Pods ein, um Dateien in Echtzeit freizugeben.
- Arbeitsbereiche für die Exploration von Verzweigungen:Hängen Sie ein Basisvorlagenverzeichnis schreibgeschützt (
ro) ein, damit Agents freigegebene Assets lesen können, ohne die Golden Copy zu ändern. Neue Schreibvorgänge werden an einen separaten beschreibbaren Scratch-Pfad oder ein Copy-on-Restore-Verzeichnis weitergeleitet.
Snapshots zu einem bestimmten Zeitpunkt und Bereinigung koordinieren
- Schreibvorgänge vor Snapshots in den Ruhezustand versetzen: Damit konsistente Workspace-Snapshots zu einem bestimmten Zeitpunkt ohne Datenbeschädigung erstellt werden können, sollte Ihr Orchestrator aktive Schreibvorgänge pausieren, indem er den Unbind-Workflow initiiert (oder Dateisystempuffer leert), bevor er das Workspace-Verzeichnis archiviert oder einen Filestore-Snapshot auslöst.
- Automatisches Aufheben der Bereitstellung von Mandanten: Wenn eine Nutzersitzung oder ein Arbeitsbereich dauerhaft abläuft, muss Ihr Controller zuerst alle aktiven Bereitstellungen auf allen Knoten aufheben, bevor asynchrone Hintergrundaufgaben zum Löschen der persistenten Verzeichnisse des Mandanten vom Sicherungs-Volume ausgeführt werden.
Eine vollständige Referenzimplementierung mit dynamischer Finalizer-Verwaltung, Mandantenisolation und Workflows zur Wiederherstellung von Snapshots finden Sie im GKE Sandbox-Beispiel für die späte Bindung von Speicher auf GitHub.
Nächste Schritte
- Statische Agent Sandbox-Integration
- Stellen Sie selbstverwaltete GKE-Arbeitslasten bereit.
- Informationen zum Erstellen und Verwalten von Volumenpools