GKE Agent Sandbox の動的ストレージの遅延バインディングで Filestore エージェント ボリュームを使用する

動的ストレージの遅延バインディングを使用すると、クレーム時に、実行中のウォームアップ済みの Google Kubernetes Engine(GKE)Agent Sandbox Pod に Filestore エージェント ボリュームを直接挿入できます。動的遅延バインディングは、標準の Kubernetes ボリューム アタッチメント ライフサイクルをバイパスすることで、Pod の再起動を必要とせずに 100 ミリ秒未満のストレージ アタッチメント レイテンシを実現します。

このアーキテクチャにより、高密度で低レイテンシのエージェント プラットフォームは次のタスクを実行できます。

  • Pod のコールド スタートとコンテナの初期化の遅延を排除します。
  • 永続ワークスペースをオンデマンドで動的にアタッチ、デタッチする。
  • アイドル状態のエージェント セッションを一時停止または休止状態にし、ファイル システムの状態を保持しながら、利用可能な事前ウォームアップされたサンドボックス Pod で再開します。

始める前に

  1. Filestore エージェント ボリューム用に GKE 環境を設定するの初期設定を完了します。
  2. GKE クラスタでバージョン 1.36.0-gke.3302001 以降が実行されていることを確認します。
  3. volume-pool-sc StorageClass で volumeBindingMode: Immediate と reclaimPolicy: Delete が指定されていることを確認します。

アーキテクチャの概要

遅延バインディング アーキテクチャは、次の 4 つのコンポーネントで構成されています。

  • プラットフォーム オーケストレーターまたはカスタム コントローラ: セッションのライフサイクルを管理するコントロール プレーン サービスまたは Kubernetes コントローラ。SandboxClaim イベントを監視し、テナント ボリュームのメタデータを解決し、ストレージ ノード デーモン API を呼び出してストレージをバインドまたはバインド解除し、削除ファイナライザを管理します。
  • ストレージ ノード デーモン: 各 gVisor ノードで実行される特権付き DaemonSet。Filestore NFS エクスポートをホスト Pod ディレクトリにバインドするマウント API を公開します。
  • SandboxTemplate と force-shared: ホスト マウントを gVisor サンドボックス コンテナに動的に伝播できるようにする gVisor サンドボックス Pod テンプレート。
  • SandboxWarmPool: 事前にウォームアップされた実行中のサンドボックス Pod のプール。クレーム時にマウント リクエストを即座に受信できます。

動的遅延バインディングの仕組み

動的遅延バインディングでは、Pod の作成時に PersistentVolumeClaim(PVC)をアタッチするのではなく、次のライフサイクルを使用します。

  1. サンドボックスをプリウォーミングする: SandboxWarmPool は、emptyDir ボリューム、HostToContainer マウント伝播、dev.gvisor.empty-dir.VOLUME_NAME.force-shared: "true" アノテーション(<var>VOLUME_NAME</var> は emptyDir ボリュームの名前)で構成された実行中の gVisor Pod を維持します。
  2. クレームとプロビジョニング: エージェント セッションが開始されると、オーケストレーターは SandboxClaim(削除ファイナライザ付き)を作成してウォーム Pod をクレームし、バッキング Filestore PVC をプロビジョニングまたは選択します。
  3. ホスト上のストレージをバインドする: オーケストレーターは、要求された Pod の UID、ノード、Filestore NFS エクスポートの詳細を解決し、削除ファイナライザを Pod に追加して、そのノードのストレージ ノード デーモンに、ホスト上の Pod のバッキング emptyDir パスに NFS ボリュームをマウントするように指示します。マウントの伝播を有効にすると、マウントされたワークスペースが実行中の gVisor コンテナ内にすぐに表示されます。
  4. 破棄前にバインドを解除する: セッションが一時停止または終了すると、オーケストレーターはノードデーモンに、Pod と SandboxClaim からファイナライザを削除する前に、ホスト ディレクトリから NFS ボリュームをアンマウントするように指示します。

カスタム コントローラと認証済みノードデーモンを使用してこれらの手順を自動化する本番環境リファレンス実装については、GitHub の GKE Sandbox の遅延バインディング ストレージの例をご覧ください。

ストレージ ノード デーモンをデプロイする

遅延バインディング ワークフローをテストするには、ホストに NFS クライアント ユーティリティをインストールし、ホスト マウント コマンドの実行環境を提供する特権 DaemonSet を gVisor ノードにデプロイします。

  1. 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
    
  2. 次のようにマニフェストを適用します。

    kubectl apply -f storage-node-daemon.yaml
    

SandboxTemplate と SandboxWarmPool を構成する

実行中の gVisor サンドボックスに動的ホストマウントを伝播できるようにするには、SandboxTemplate に dev.gvisor.empty-dir.workspace-volume.force-shared: "true" アノテーションを含め、emptyDir ボリューム マウントに mountPropagation: HostToContainer を設定する必要があります。

  1. テンプレートとウォームプールを含む 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
    
  2. 次のようにマニフェストを適用します。

    kubectl apply -f sandbox-latebind.yaml
    

サンドボックスを要求してストレージを動的にバインドする

  1. 削除ファイナライザ(agent.sandbox/storage-cleanup)を使用してウォーム サンドボックスを要求し、Filestore エージェント ボリューム PVC をプロビジョニングする late-bind-claim.yaml という名前のマニフェストを作成します。

    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
    
  2. 次のようにマニフェストを適用します。

    kubectl apply -f late-bind-claim.yaml
    
  3. 割り当てられた 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}')"
    
  4. ホスト マウントがアクティブなときに Kubelet が Pod を削除しないように、要求された Pod に削除ファイナライザを追加します。

    kubectl patch pod "${POD_NAME}" --type=merge \
      -p '{"metadata":{"finalizers":["agent.sandbox/storage-cleanup"]}}'
    
  5. 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
    "
    
  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. ホストノードで Filestore NFS ボリュームをマウント解除します。

    kubectl exec "${DAEMON_POD}" -- nsenter -t 1 -m -- \
      umount -l "${HOST_DIR}/user_data"
    
  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":[]}}'
    

後でセッションを再開するには、新しい SandboxClaim を作成し、既存の PVC(session-1-pvc)を新しく要求されたウォーム Pod にマウントします。

次のステップ