動的ストレージの遅延バインディングを使用すると、クレーム時に、実行中のウォームアップ済みの Google Kubernetes Engine(GKE)Agent Sandbox Pod に Filestore エージェント ボリュームを直接挿入できます。動的遅延バインディングは、標準の Kubernetes ボリューム アタッチメント ライフサイクルをバイパスすることで、Pod の再起動を必要とせずに 100 ミリ秒未満のストレージ アタッチメント レイテンシを実現します。
このアーキテクチャにより、高密度で低レイテンシのエージェント プラットフォームは次のタスクを実行できます。
- Pod のコールド スタートとコンテナの初期化の遅延を排除します。
- 永続ワークスペースをオンデマンドで動的にアタッチ、デタッチする。
- アイドル状態のエージェント セッションを一時停止または休止状態にし、ファイル システムの状態を保持しながら、利用可能な事前ウォームアップされたサンドボックス Pod で再開します。
始める前に
- Filestore エージェント ボリューム用に GKE 環境を設定するの初期設定を完了します。
- GKE クラスタでバージョン
1.36.0-gke.3302001以降が実行されていることを確認します。 volume-pool-scStorageClassで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)をアタッチするのではなく、次のライフサイクルを使用します。
- サンドボックスをプリウォーミングする:
SandboxWarmPoolは、emptyDirボリューム、HostToContainerマウント伝播、dev.gvisor.empty-dir.VOLUME_NAME.force-shared: "true"アノテーション(<var>VOLUME_NAME</var>はemptyDirボリュームの名前)で構成された実行中の gVisor Pod を維持します。 - クレームとプロビジョニング: エージェント セッションが開始されると、オーケストレーターは
SandboxClaim(削除ファイナライザ付き)を作成してウォーム Pod をクレームし、バッキング Filestore PVC をプロビジョニングまたは選択します。 - ホスト上のストレージをバインドする: オーケストレーターは、要求された Pod の UID、ノード、Filestore NFS エクスポートの詳細を解決し、削除ファイナライザを Pod に追加して、そのノードのストレージ ノード デーモンに、ホスト上の Pod のバッキング
emptyDirパスに NFS ボリュームをマウントするように指示します。マウントの伝播を有効にすると、マウントされたワークスペースが実行中の gVisor コンテナ内にすぐに表示されます。 破棄前にバインドを解除する: セッションが一時停止または終了すると、オーケストレーターはノードデーモンに、
PodとSandboxClaimからファイナライザを削除する前に、ホスト ディレクトリから NFS ボリュームをアンマウントするように指示します。
カスタム コントローラと認証済みノードデーモンを使用してこれらの手順を自動化する本番環境リファレンス実装については、GitHub の GKE Sandbox の遅延バインディング ストレージの例をご覧ください。
ストレージ ノード デーモンをデプロイする
遅延バインディング ワークフローをテストするには、ホストに NFS クライアント ユーティリティをインストールし、ホスト マウント コマンドの実行環境を提供する特権 DaemonSet を gVisor ノードにデプロイします。
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
サンドボックスを要求してストレージを動的にバインドする
削除ファイナライザ(
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次のようにマニフェストを適用します。
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}')"ホスト マウントがアクティブなときに Kubelet が Pod を削除しないように、要求された 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 ワークロードをデプロイする。
- ボリューム プールを作成して管理する方法を学習する。