搭配 GKE Agent Sandbox 動態儲存空間延遲繫結,使用 Filestore 代理程式磁碟區

動態儲存空間延遲繫結功能可讓您在聲明時,將 Filestore 代理程式磁碟區直接注入執行中的預先暖機 Google Kubernetes Engine (GKE) Agent Sandbox Pod。動態延遲繫結會略過標準 Kubernetes 磁碟區連結生命週期,在不需要重新啟動 Pod 的情況下,將儲存空間連結延遲時間縮短至 100 毫秒以下。

這項架構可讓高密度、低延遲的代理程式平台執行下列操作:

  • 消除 Pod 冷啟動和容器初始化延遲。
  • 視需要動態附加及卸載持續性工作區。
  • 暫停或休眠閒置的代理程式工作階段,並在任何可用的預熱沙箱 Pod 上恢復工作階段,同時保留檔案系統狀態。

事前準備

  1. 完成「為 Filestore 代理程式磁碟區設定 GKE 環境」中的初始設定。
  2. 確認 GKE 叢集執行的是 1.36.0-gke.3302001 以上版本。這個版本支援 gVisor emptyDir 掛接傳播所需的 force-shared 註解。
  3. 確認 volume-pool-sc StorageClass 是否指定 volumeBindingMode: ImmediatereclaimPolicy: Delete

架構總覽

延遲繫結架構包含四個元件:

  • 平台自動調度管理工具或自訂控制器:管理工作階段生命週期的控制層服務或 Kubernetes 控制器。這個控制器會監看 SandboxClaim 事件、解析租戶磁碟區中繼資料、呼叫儲存節點精靈 API 來繫結或取消繫結儲存空間,以及管理刪除終結器。
  • 儲存空間節點 Daemon:在每個 gVisor 節點上執行的具備特殊權限 DaemonSet,可公開掛接 API。
  • SandboxTemplate (含 force-shared):gVisor 沙箱 Pod 範本,可讓主機掛接動態傳播至 gVisor 沙箱容器。
  • SandboxWarmPool預先暖機的執行中沙箱 Pod 集區,可在聲明後立即接收掛接要求。

部署儲存空間節點 Daemon

建立名為 storage-node-daemon.yaml 的資訊清單,其中包含具備權限的 DaemonSet

套用資訊清單:

kubectl apply -f storage-node-daemon.yaml

部署 SandboxTemplateSandboxWarmPool

建立名為 sandbox-latebind.yaml 的資訊清單,其中包含範本和暖集區:

套用資訊清單:

kubectl apply -f sandbox-latebind.yaml

申請沙箱並動態繫結儲存空間

  1. 建立名為 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
    
  2. 使用名為 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
    
  3. 擷取指派的 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}')"
    
  4. 將掛接信號傳送至 Pod 主機上的節點 Daemon:

    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())
    "
    
  5. 將刪除終結器套用至已聲明的 Pod,防止主機掛接處於啟用狀態時過早清除:

    kubectl patch pod "${POD_NAME}" --type=merge \
      -p '{"metadata":{"finalizers":["agent.sandbox/storage-cleanup"]}}'
    
  6. 確認正在執行的 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。

  1. 在背景中啟動 SandboxClaim 的刪除作業。由於有終結器,Kubernetes 會將聲明標示為待刪除,但暫停 Pod 終止作業:

    kubectl delete sandboxclaim late-bind-session-1 --wait=false
    
  2. 在主機節點上解除掛接 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())
    "
    
  3. 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 控制器或平台自動化調度管理工具。

設計生產控制器和節點 Daemon 時,請實作下列架構模式:

自動執行協調和終止程式生命週期

由於儲存空間節點 Daemon 會在標準 Kubernetes Container Storage Interface (CSI) 生命週期管理機制之外執行主機層級的掛接作業,因此 Kubelet 無法感知 Pod emptyDir 內的主動掛接作業。如果掛接作業進行中時刪除 Pod,Kubelet 就無法移除 emptyDir 目錄,並會引發 Device or resource busy 錯誤,導致 Pod 停留在 Terminating 狀態。

自訂控制器必須使用終結器 (例如 agent.sandbox/storage-cleanup) 自動執行嚴格的狀態機:

  1. 建立及繫結著作權聲明:
    • 建立每個 SandboxClaim 時,請附加靜態終結器。
    • 監控 Kubernetes API 的 SandboxClaim 狀態更新。當聲明繫結至暖集區 Pod 時,請擷取指派的 pod_uidnodeName 和支援的 Filestore 磁碟區屬性。
    • 將已驗證的 bind 要求傳送至目標節點上執行的儲存空間節點 Daemon。
    • 立即修補正在執行的 Pod 物件,加入動態終結器。 由於 SandboxTemplate 規格不支援靜態 Pod 終止程式,因此必須動態修補 Pod,才能在節點排空或重新排程事件期間保護 Pod,此時 Pod 會遭到逐出,但 SandboxClaim 仍處於啟用狀態。
  2. 安全終止和驅逐處理:
    • 請注意 deletionTimestamp 資源中的 SandboxClaimPod
    • 偵測到刪除或逐出作業時,請呼叫節點精靈的 unbind 端點,以乾淨地解除掛接主機目錄 (umount -l)。
    • 確認卸載成功,且所有待處理的寫入作業都已排清,再修補 PodSandboxClaim,移除其終結器。這有助於在正常刪除聲明、GKE 節點升級、Spot VM 搶占和記憶體不足 (OOM) 終止時,實現乾淨的終止程序。

保護及強化儲存空間節點精靈

  • kubectl exec 替換為已驗證的 API:在正式環境中,請勿使用 kubectl exec 或將 Daemon 繫結至 localhost。設定儲存空間節點 Daemon,透過以雙向 TLS (mTLS) 或 Kubernetes ServiceAccount 權杖驗證保護的叢集網路,公開專屬的 gRPC 或 HTTPS 端點。
  • 隔離精靈命名空間和網路存取權:在受限的管理命名空間 (例如 sandbox-storage-system) 中部署具備特殊權限的 storage-node-daemon DaemonSet,而非 default 或租戶命名空間。套用 Kubernetes NetworkPolicy 規則,只允許從自訂控制器 Pod 傳入精靈 API,並封鎖來自沙箱代理程式 Pod 的所有流量。
  • 使用預先建構的容器映像檔:避免在 initContainer 中於執行階段安裝 nfs-common 等套件。使用預先建構的不可變動容器映像檔,並預先安裝所有必要的掛接公用程式,即可消除節點啟動延遲和外部存放區依附元件。

強制執行儲存空間配額和多租戶隔離

  • 監控每個代理程式的儲存空間用量:從共用 ReadWriteMany (RWX) Filestore 磁碟區動態繫結掛接子目錄至 emptyDir 時,標準 Kubernetes emptyDir.sizeLimit 設定無法在掛接的 NFS 路徑上強制執行每個代理程式的儲存空間配額。為避免單一失控的代理程式耗盡共用磁碟區,導致阻斷服務 (DoS),請在協調器中實作目錄配額監控,或使用磁碟區集區佈建專用磁碟區。
  • 為不同的工作區存取模式調整掛接酬載:您的控制器可以變更傳送至節點 Daemon 的參數,支援多個代理程式儲存空間拓撲:
    • 私有隔離工作區:將專屬的租戶子目錄或專用 PVC 繫結至具有讀寫權限的單一沙箱 Pod。
    • 協作工作空間:在多個協調代理程式 Pod 中同時繫結相同的共用 RWX 子目錄,即時共用檔案。
    • 探索分支工作區:以唯讀 (ro) 形式掛接基本範本目錄,讓代理程式讀取共用資產,不必修改黃金副本,同時將新的寫入作業路徑導向可寫入的暫存路徑或還原時複製的目錄。

協調時間點快照和清理作業

  • 在快照前達到寫入靜止狀態:如要擷取一致的即時工作區快照,且不會發生資料損毀情形,自動化調度管理工具應先啟動取消繫結工作流程 (或清除檔案系統緩衝區),暫停作用中的寫入作業,再封存工作區目錄或觸發 Filestore 快照。
  • 自動取消佈建租戶:當使用者工作階段或工作區永久過期時,請確保控制器先取消所有節點的任何有效掛接,再執行非同步背景工作,從備份磁碟區刪除租戶的持續性目錄。

如需完整的參考實作,瞭解如何管理動態終結器、進行多租戶隔離,以及還原快照工作流程,請參閱 GitHub 上的 GKE Sandbox 延遲繫結儲存空間範例

後續步驟