借助动态存储后期绑定,您可以在声明时将 Filestore 代理卷直接注入到正在运行的预热 Google Kubernetes Engine (GKE) Agent Sandbox pod 中。通过绕过标准的 Kubernetes 卷挂接生命周期,动态后期绑定可实现低于 100 毫秒的存储挂接延迟,而无需重启 Pod。
此架构可让高密度、低延迟的代理平台:
- 消除 pod 冷启动和容器初始化延迟。
- 按需动态附加和分离持久性工作区。
- 暂停或休眠空闲的代理会话,并在任何可用的预热沙盒 pod 上恢复这些会话,同时保留文件系统状态。
准备工作
- 完成为 Filestore 代理卷设置 GKE 环境中的初始设置。
- 验证您的 GKE 集群是否运行的是
1.36.0-gke.3302001或更高版本。此版本支持 gVisoremptyDir挂载传播所需的force-shared注解。 - 验证您的
volume-pool-scStorageClass是否指定了volumeBindingMode: Immediate和reclaimPolicy: Delete。
架构概览
后期绑定架构包含以下四个组件:
- 平台编排工具或自定义控制器:管理会话生命周期的控制平面服务或 Kubernetes 控制器。它会监控
SandboxClaim事件,解析租户卷元数据,调用存储节点守护程序 API 来绑定或取消绑定存储,并管理删除终结器。 - 存储节点守护程序:在每个 gVisor 节点上运行的特权
DaemonSet,用于公开装载 API。 SandboxTemplate(含force-shared):一种 gVisor 沙盒 pod 模板,可让主机挂载动态传播到 gVisor 沙盒容器中。SandboxWarmPool:预热的正在运行的沙盒 Pod 池,在声明时可立即接收装载请求。
部署存储节点守护程序
创建一个名为 storage-node-daemon.yaml 的清单,其中包含特权 DaemonSet:
应用清单:
kubectl apply -f storage-node-daemon.yaml
部署 SandboxTemplate 和 SandboxWarmPool
创建一个名为 sandbox-latebind.yaml 的清单,其中包含模板和暖池:
应用清单:
kubectl apply -f sandbox-latebind.yaml
声明沙盒并动态绑定存储空间
创建一个名为
late-bind-claim.yaml的声明清单,其中包含删除终结器 (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-template应用声明:
kubectl apply -f late-bind-claim.yaml
使用名为
agent-volume-pvc.yaml的 PVC 清单动态预配卷:apiVersion: v1 kind: PersistentVolumeClaim metadata: name: session-1-pvc namespace: default spec: accessModes: [ReadWriteMany] storageClassName: volume-pool-sc resources: requests: storage: 1Gi应用 PVC:
kubectl apply -f agent-volume-pvc.yaml
检索分配的 pod UID、节点和后备 PV 导出详细信息:
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}')"将装载信号发送到 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()) "向已声明的 pod 应用删除终结器,以防止在宿主装载处于活动状态时过早清理:
kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":["agent.sandbox/storage-cleanup"]}}'验证正在运行的 pod 内的卷装载:
kubectl logs "${POD_NAME}" -c agent输出结果确认卷已成功装载:
Waiting for late-bind signal... Filestore volume mounted successfully! drwxr-xr-x 2 1000 1000 4096 ... user_data
暂停和恢复会话
当代理会话结束或进入休眠状态时,在允许 Kubernetes 终止或回收 pod 之前,编排器必须卸载宿主存储空间。
在后台启动
SandboxClaim的删除操作。由于存在终结器,Kubernetes 会将声明标记为待删除,但会暂停 pod 终止:kubectl delete sandboxclaim late-bind-session-1 --wait=false
在宿主节点上卸载 NFS 共享:
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()) "从
SandboxClaim和 pod 中移除终结器,以完成终止:kubectl patch sandboxclaim late-bind-session-1 --type=merge \ -p '{"metadata":{"finalizers":[]}}' kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":[]}}'
如需稍后恢复会话,请声明一个新的预热 Pod,并使用现有 PVC (session-1-pvc) 发送绑定请求。新的沙盒 Pod 会立即获得对已保留的工作区状态的访问权限。
生产环境注意事项和自定义控制器设计
本文档中的手动命令演示了动态后期绑定的底层机制。为了在生产环境中可靠地运行此架构,您必须开发一个自定义 Kubernetes 控制器或平台编排器,以适应应用的会话生命周期。
在设计生产控制器和节点守护程序时,请实现以下架构模式:
自动执行协调和 Finalizer 生命周期
由于存储节点守护程序在标准 Kubernetes 容器存储接口 (CSI) 生命周期管理之外执行主机级装载,因此 Kubelet 不知道 pod 的 emptyDir 内是否存在有效装载。如果 Pod 在挂载处于活跃状态时被删除,Kubelet 将无法移除 emptyDir 目录并引发 Device or resource busy 错误,导致 Pod 停留在 Terminating 状态。
您的自定义控制器必须使用终结器(例如 agent.sandbox/storage-cleanup)自动执行严格的状态机:
- 声明创建和绑定:
- 在创建时,将静态 finalizer 附加到每个
SandboxClaim。 - 监控 Kubernetes API,以获取
SandboxClaim状态更新。当声明绑定到暖池 pod 时,提取分配的pod_uid、nodeName和后备 Filestore 卷属性。 - 向目标节点上运行的存储节点守护程序发送经过身份验证的
bind请求。 - 立即修补正在运行的
Pod对象,以添加动态 finalizer。 由于SandboxTemplate规范不支持静态 pod 终结器,因此需要动态修补 pod,以便在节点排空或重新调度事件(其中 pod 被逐出但SandboxClaim保持活跃状态)期间保护 pod。
- 在创建时,将静态 finalizer 附加到每个
- 正常终止和逐出处理:
- 在
SandboxClaim和Pod资源中查找deletionTimestamp。 - 检测到删除或逐出操作时,调用节点守护程序的
unbind端点以干净地卸载宿主目录 (umount -l)。 - 在修补
Pod和SandboxClaim以移除其终结器之前,请验证卸载是否成功,以及所有待处理的写入操作是否已刷新。这有助于您在正常声明删除、GKE 节点升级、Spot 虚拟机抢占和内存不足 (OOM) 终止时实现干净的拆解。
- 在
保护和强化存储节点守护程序
- 将
kubectl exec替换为经过身份验证的 API:在生产环境中,请勿使用kubectl exec或将守护程序绑定到localhost。配置存储节点守护程序,以通过集群网络公开专用的 gRPC 或 HTTPS 端点,该端点通过双向 TLS (mTLS) 或 KubernetesServiceAccount令牌身份验证进行保护。 - 隔离守护程序命名空间和网络访问权限:在受限的管理命名空间(例如
sandbox-storage-system)中部署特权storage-node-daemonDaemonSet,而不是在default或租户命名空间中部署。应用 KubernetesNetworkPolicy规则,以仅允许从自定义控制器 Pod 向守护程序 API 入站,并阻止来自沙盒代理 Pod 的所有流量。 - 使用预先构建的容器映像:避免在
initContainer中运行时安装nfs-common等软件包。使用预构建的不可变容器映像,其中预安装了所有必需的装载实用程序,以消除节点启动延迟和外部代码库依赖项。
强制执行存储空间配额和多租户隔离
- 监控每个代理的存储空间用量:当您将共享
ReadWriteMany(RWX) Filestore 卷中的子目录动态绑定装载到emptyDir中时,标准 KubernetesemptyDir.sizeLimit设置无法对装载的 NFS 路径强制执行每个代理的存储空间配额。为防止单个失控代理耗尽共享卷并导致拒绝服务 (DoS),请在编排器中实现目录配额监控,或使用卷池配置专用卷。 - 针对不同的工作区访问模式调整装载载荷:您的控制器可以通过改变发送到节点守护程序的参数来支持多种代理存储拓扑:
- 私有隔离工作区:将唯一的租户子目录或专用 PVC 绑定到具有读写权限的单个沙盒 pod。
- 协作式工作区:在多个协调的代理 Pod 中同时绑定同一共享 RWX 子目录,以实现实时文件共享。
- 探索分支工作区:以只读 (
ro) 方式装载基本模板目录,以便代理可以读取共享资源,而无需修改黄金副本,同时将新的写入操作路由到单独的可写暂存路径或恢复时复制目录。
协调时间点快照和清理
- 在创建快照之前实现写入静止:为了捕获一致的时间点工作区快照而不损坏数据,您的编排器应在归档工作区目录或触发 Filestore 快照之前,通过启动取消绑定工作流(或刷新文件系统缓冲区)来暂停活跃的写入操作。
- 自动取消配置租户:当用户会话或工作区永久过期时,请确保控制器先取消绑定所有节点上的任何有效装载,然后再执行异步后台任务以从后备卷中删除租户的持久性目录。
如需查看完整的参考实现,了解动态终结器管理、多租户隔离和快照恢复工作流,请参阅 GitHub 上的 GKE Sandbox 延迟绑定存储示例。
后续步骤
- 探索静态 Agent Sandbox 集成。
- 部署自管理的 GKE 工作负载。
- 了解如何创建和管理卷池。