借助动态存储后期绑定,您可以在声明时将 Filestore 智能体存储卷直接注入正在运行且已预热的 Google Kubernetes Engine (GKE) Agent Sandbox Pod 中。通过绕过标准 Kubernetes 卷挂接生命周期,动态延迟绑定可实现低于 100 毫秒的存储挂接延迟时间,而无需重启 Pod。
此架构可让高密度、低延迟的代理平台执行以下任务:
- 消除 Pod 冷启动和容器初始化延迟。
- 根据需要动态挂接和分离永久性工作区。
- 暂停或休眠空闲代理会话,并在任何可用的预热沙盒 Pod 上恢复这些会话,同时保留文件系统状态。
准备工作
- 完成为 Filestore 智能体存储卷设置 GKE 环境中的初始设置。
- 验证您的 GKE 集群是否运行
1.36.0-gke.3302001版或更高版本。 - 验证您的
volume-pool-scStorageClass是否指定了volumeBindingMode: Immediate和reclaimPolicy: Delete。
架构概览
后期绑定架构由四个组件组成:
- 平台编排器或自定义控制器:管理会话生命周期的控制平面服务或 Kubernetes 控制器。它会监视
SandboxClaim事件、解析租户卷元数据、调用存储节点守护程序 API 来绑定或解除绑定存储,以及管理删除终结器。 - 存储节点守护进程:在每个 gVisor 节点上运行的特权
DaemonSet,用于公开装载 API,以将 Filestore NFS 导出绑定到主机 Pod 目录。 SandboxTemplate与force-shared:gVisor 沙盒 Pod 模板,可让主机装载动态传播到 gVisor 沙盒容器中。SandboxWarmPool:一个由预热的运行中沙盒 Pod 组成的池,可在声明后立即接收装载请求。
动态后期绑定的运作方式
动态延迟绑定不使用在 Pod 创建时附加 PersistentVolumeClaim (PVC) 的方式,而是使用以下生命周期:
- 预热沙盒:
SandboxWarmPool维护运行中的 gVisor Pod,这些 Pod 配置了emptyDir卷、HostToContainer装载传播和dev.gvisor.empty-dir.VOLUME_NAME.force-shared: "true"注解,其中<var>VOLUME_NAME</var>是emptyDir卷的名称。 - 认领和预配:智能体对话开始后,编排器会创建一个
SandboxClaim(带有删除终结器)来认领一个温启动 Pod,并预配或选择一个后备 Filestore PVC。 - 在主机上绑定存储:编排器会解析声明的 Pod 的 UID、节点和 Filestore NFS 导出详细信息,向 Pod 添加删除终结器,并指示该节点上的存储节点守护程序将 NFS 卷装载到主机上 Pod 的后备
emptyDir路径中。启用装载传播后,装载的工作区会立即显示在正在运行的 gVisor 容器中。 在拆解前解除绑定:当会话暂停或结束时,您的编排器会指示节点守护程序从主机目录中卸载 NFS 卷,然后再从
Pod和SandboxClaim中移除终结器。
如需查看使用自定义控制器和经过身份验证的节点守护程序自动执行这些步骤的生产参考实现,请参阅 GitHub 上的 GKE Sandbox 延迟绑定存储示例。
部署存储节点守护程序
如需测试后期绑定工作流,请在 gVisor 节点上部署特权 DaemonSet,以便在主机上安装 NFS 客户端实用程序,并为主机装载命令提供执行环境。
创建名为
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 infinity应用清单:
kubectl apply -f storage-node-daemon.yaml
配置 SandboxTemplate 和 SandboxWarmPool
如需使动态主机装载传播到正在运行的 gVisor 沙盒中,您的 SandboxTemplate 必须包含 dev.gvisor.empty-dir.workspace-volume.force-shared: "true" 注解,并在 emptyDir 卷装载上设置 mountPropagation: HostToContainer。
创建一个名为
sandbox-latebind.yaml的清单,其中包含模板和温池: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-template应用清单:
kubectl apply -f sandbox-latebind.yaml
申领沙盒并动态绑定存储空间
创建一个名为
late-bind-claim.yaml的清单,该清单声明一个具有删除终结器 (agent.sandbox/storage-cleanup) 的温沙盒,并预配一个 Filestore 智能体存储卷 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: 1Gi应用清单:
kubectl apply -f late-bind-claim.yaml
检索分配的 Pod 详细信息和后备 Filestore NFS 导出属性:
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 添加删除终结器,以防止 Kubelet 在主机装载处于活跃状态时删除 Pod:
kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":["agent.sandbox/storage-cleanup"]}}'将 Filestore NFS 卷装载到 Pod 的主机
emptyDir目录中,并发出就绪信号: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 "验证卷是否已装载到正在运行的沙盒 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
在主机节点上卸载 Filestore NFS 卷:
kubectl exec "${DAEMON_POD}" -- nsenter -t 1 -m -- \ umount -l "${HOST_DIR}/user_data"从
SandboxClaim和 Pod 中移除终结器,以完成终止:kubectl patch sandboxclaim late-bind-session-1 --type=merge \ -p '{"metadata":{"finalizers":[]}}' kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":[]}}'
如需稍后恢复会话,请创建一个新的 SandboxClaim,并将现有 PVC (session-1-pvc) 装载到新声明的热 Pod 中。
后续步骤
- 探索静态 Agent Sandbox 集成。
- 部署自行管理的 GKE 工作负载。
- 了解如何创建和管理卷池。