動的ストレージの遅延バインディングを使用すると、要求時に実行中の事前ウォームアップされた Google Kubernetes Engine(GKE)Agent Sandbox Pod に Filestore エージェント ボリュームを直接挿入できます。標準の Kubernetes ボリューム アタッチメント ライフサイクルをバイパスすることで、動的遅延バインディングは Pod の再起動を必要とせずに 100 ミリ秒未満のストレージ アタッチメント レイテンシを実現します。
このアーキテクチャにより、高密度で低レイテンシのエージェント プラットフォームは次のことが可能になります。
- Pod のコールド スタートとコンテナの初期化の遅延を解消します。
- 永続ワークスペースをオンデマンドで動的にアタッチおよびデタッチします。
- アイドル状態のエージェント セッションを一時停止または休止し、ファイル システムの状態を維持しながら、使用可能な事前ウォーミングされたサンドボックス Pod で再開します。
始める前に
- Filestore エージェント ボリュームの GKE 環境を設定するで初期設定を完了します。
- GKE クラスタがバージョン
1.36.0-gke.3302001以降を実行していることを確認します。このバージョンでは、gVisoremptyDirマウント伝播に必要なforce-sharedアノテーションがサポートされています。 volume-pool-scStorageClassがvolumeBindingMode: ImmediateとreclaimPolicy: Deleteを指定していることを確認します。
アーキテクチャの概要
遅延バインディング アーキテクチャは、次の 4 つのコンポーネントで構成されています。
- プラットフォーム オーケストレーターまたはカスタム コントローラ: セッションのライフサイクルを管理するコントロール プレーン サービスまたは Kubernetes コントローラ。
SandboxClaimイベントを監視し、テナント ボリュームのメタデータを解決し、ストレージ ノード デーモン API を呼び出してストレージをバインドまたはバインド解除し、削除ファイナライザーを管理します。 - ストレージ ノード デーモン: マウント API を公開する各 gVisor ノードで実行される特権
DaemonSet。 force-sharedを使用したSandboxTemplate: ホストマウントを gVisor サンドボックス コンテナに動的に伝播できる gVisor サンドボックス Pod テンプレート。SandboxWarmPool: 事前にウォームアップされた実行中のサンドボックス Pod のプール。クレーム時にマウント リクエストをすぐに受信できます。
ストレージ ノード デーモンをデプロイする
特権 DaemonSet を含む storage-node-daemon.yaml という名前のマニフェストを作成します。
次のようにマニフェストを適用します。
kubectl apply -f storage-node-daemon.yaml
SandboxTemplate と SandboxWarmPool をデプロイする
テンプレートとウォームプールを含むマニフェストを sandbox-latebind.yaml という名前で作成します。
次のようにマニフェストを適用します。
kubectl apply -f sandbox-latebind.yaml
サンドボックスを要求してストレージを動的にバインドする
削除ファイナライザー(
agent.sandbox/storage-cleanup)を含む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請求を適用します。
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: 1GiPVC を適用します。
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出力は、Volume が正常にマウントされたことを示しています。
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 コントローラまたはプラットフォーム オーケストレーターを開発する必要があります。
本番環境のコントローラとノード デーモンを設計する際は、次のアーキテクチャ パターンを実装します。
調整とファイナライザーのライフサイクルを自動化する
ストレージ ノード デーモンは、標準の Kubernetes Container Storage Interface(CSI)ライフサイクル管理の外部でホストレベルのマウントを実行するため、Kubelet は Pod の emptyDir 内のアクティブなマウントを認識しません。マウントがアクティブな状態で Pod が削除されると、Kubelet は emptyDir ディレクトリを削除できず、Device or resource busy エラーが発生し、Pod が Terminating 状態のままになります。
カスタム コントローラは、ファイナライザー(agent.sandbox/storage-cleanup など)を使用して厳格な状態マシンを自動化する必要があります。
- クレームの作成とバインディング:
- 作成時にすべての
SandboxClaimに静的ファイナライザーをアタッチします。 - Kubernetes API で
SandboxClaimのステータス更新を監視します。クレームがウォームプール Pod にバインドされたら、割り当てられたpod_uid、nodeName、バッキング Filestore ボリューム属性を抽出します。 - 認証済みの
bindリクエストを、ターゲット ノードで実行されているストレージ ノード デーモンに送信します。 - 実行中の
Podオブジェクトに動的ファイナライザーを追加するために、直ちにパッチを適用します。SandboxTemplate仕様では静的 Pod ファイナライザーがサポートされていないため、Pod が削除されてもSandboxClaimがアクティブな状態が続くノードのドレイン イベントやリスケジューリング イベント中に Pod を保護するには、Pod を動的にパッチ適用する必要があります。
- 作成時にすべての
- 正常終了と削除の処理:
SandboxClaimリソースとPodリソースの両方でdeletionTimestampを監視します。- 削除または強制排除が検出されたら、ノード デーモンの
unbindエンドポイントを呼び出して、ホスト ディレクトリ(umount -l)をクリーンにマウント解除します。 - アンマウントが成功し、保留中の書き込みがすべてフラッシュされたことを確認してから、
PodとSandboxClaimにパッチを適用してファイナライザーを削除します。これにより、グレースフルな削除、GKE ノードのアップグレード、Spot VM のプリエンプション、メモリ不足(OOM)による強制終了など、クリーンな削除を実現できます。
ストレージ ノード デーモンを保護して強化する
kubectl execを認証済み API に置き換えます。本番環境では、kubectl execを使用したり、デーモンをlocalhostにバインドしたりしないでください。相互 TLS(mTLS)または KubernetesServiceAccountトークン認証で保護されたクラスタ ネットワークを介して専用の gRPC または HTTPS エンドポイントを公開するように、ストレージ ノード デーモンを構成します。- デーモン Namespace とネットワーク アクセスを分離する: 制限付きの管理 Namespace(
defaultやテナント Namespace ではなく、sandbox-storage-systemなど)に特権storage-node-daemonDaemonSetをデプロイします。カスタム コントローラ Pod からのみデーモン API への上り(内向き)を許可し、サンドボックス化されたエージェント Pod からのすべてのトラフィックをブロックする KubernetesNetworkPolicyルールを適用します。 - 事前作成されたコンテナ イメージを使用する:
initContainerで実行時にnfs-commonなどのパッケージをインストールしないようにします。必要なマウント ユーティリティがすべてプリインストールされた、ビルド済みの不変コンテナ イメージを使用して、ノードの起動遅延と外部リポジトリの依存関係を解消します。
ストレージ割り当てとマルチテナント分離を適用する
- エージェントごとのストレージ使用量をモニタリングする: 共有
ReadWriteMany(RWX)Filestore ボリュームのサブディレクトリをemptyDirに動的にバインド マウントする場合、標準の KubernetesemptyDir.sizeLimit設定では、マウントされた NFS パスにエージェントごとのストレージ割り当てを適用できません。単一の暴走エージェントが共有ボリュームを使い果たしてサービス拒否(DoS)を引き起こすのを防ぐには、オーケストレーターでディレクトリ割り当てのモニタリングを実装するか、ボリューム プールを使用して専用ボリュームをプロビジョニングします。 - さまざまなワークスペース アクセスモードに合わせてマウント ペイロードを調整する: コントローラは、ノード デーモンに送信されるパラメータを変更することで、複数のエージェント ストレージ トポロジをサポートできます。
- プライベート分離ワークスペース: 一意のテナント サブディレクトリまたは専用 PVC を、読み取り / 書き込み権限を持つ単一のサンドボックス Pod にバインドします。
- コラボレーション ワークスペース: リアルタイムのファイル共有のために、複数の連携エージェント Pod 間で同じ共有 RWX サブディレクトリを同時にバインドします。
- 探索ブランチ ワークスペース: ベース テンプレート ディレクトリを読み取り専用(
ro)としてマウントし、エージェントがゴールデン コピーを変更せずに共有アセットを読み取れるようにします。同時に、新しい書き込みを別の書き込み可能なスクラッチ パスまたは復元時のコピー ディレクトリに転送します。
ポイントインタイム スナップショットとクリーンアップを調整する
- スナップショットの前に書き込みの静止を実現する: データの破損なしに一貫性のある特定の時点のワークスペース スナップショットを取得するには、ワークスペース ディレクトリをアーカイブする前、または Filestore スナップショットをトリガーする前に、アンバインド ワークフローを開始(またはファイル システム バッファをフラッシュ)して、アクティブな書き込みを一時停止する必要があります。
- テナントのプロビジョニング解除を自動化する: ユーザー セッションまたはワークスペースが完全に期限切れになったら、コントローラがまずすべてのノードでアクティブなマウントをバインド解除してから、非同期バックグラウンド タスクを実行して、テナントの永続ディレクトリをバッキング ボリュームから削除するようにします。
動的ファイナライザー管理、マルチテナント分離、スナップショット復元ワークフローを示す完全なリファレンス実装については、GitHub の GKE Sandbox の遅延バインディング ストレージの例をご覧ください。
次のステップ
- 静的 Agent Sandbox 統合について説明します。
- セルフマネージド GKE ワークロードをデプロイします。
- ボリューム プールの作成と管理の方法を学習する。