GKE 上の Agent Substrate のインフラストラクチャを設計する

Google Kubernetes Engine(GKE)を使用すると、GKE 上の Agent Substrate クラスタをスケーリングして、アイドル状態のコンピューティング費用を最小限に抑えながら、高同時実行の AI エージェント ワークロードをサポートできます。専用のシステム インフラストラクチャ ノードプールをカスタム ComputeClass と容量バッファと組み合わせることで、重要なシステム コンポーネントを分離し、Compute Engine マシンシリーズ全体でハードウェア フォールバックを自動化し、スタンバイ ワーカー容量をウォーム状態に保つことができます。

このドキュメントでは、GKE クラスタ コンポーネントの適切なサイズ設定、システム インフラストラクチャとデータベース ノードプールの分離、WorkerPool リソースの自動ノード プロビジョニングの構成、クラスタのメンテナンスとアップグレードの計画について説明します。このドキュメントは、GKE インフラストラクチャで Agent Substrate をプロビジョニングして管理するプラットフォーム管理者を対象としています。

始める前に

スケール用にクラスタ インフラストラクチャを構成する前に、環境が次の要件を満たしていることを確認してください。

  • GKE クラスタのモードとバージョン: コントロール プレーンとノードプールの両方でバージョン 1.37 以降を実行する GKE Standard クラスタを使用する必要があります。GKE Autopilot クラスタは、GKE の Agent Substrate ではサポートされていません。
  • GKE への Agent Substrate のインストール: GKE に Agent Substrate がすでにインストールされているか、システム インフラストラクチャ ノードの分離を有効にして GKE に Agent Substrate をインストールする予定がある必要があります。インストール手順については、GKE に Agent Substrate をインストールするをご覧ください。

Agent Substrate クラスタのサイズを適正化する

クラスタのサイズは、平均ではなく、ピーク時の同時実行数とピーク時の再開率に合わせて設定します。ワーカーノードは、同時に実行されるアクターの最大数を保持する必要があります。また、システム インフラストラクチャ コンポーネントとストレージパスは、アクターの再開と一時停止の最速レートに追いつく必要があります。

ワークロードのピークを見積もる

本番環境の測定値がない場合は、登録されているアクターの合計数(total_actors)、同時に実行されているアクターの平均割合(average_active_fraction)、トラフィック バースト乗数(burst_factor)、アクターがターンごとにアクティブな状態を維持する平均時間(average_seconds_active_per_resume)から、ピーク時の同時実行数と再開率を推定します。

peak_running_actors = total_actors × average_active_fraction × burst_factor
peak_resume_rate    = peak_running_actors / average_seconds_active_per_resume

たとえば、合計 100,000 個のアクターがあり、平均 10% がアクティブ(10,000 個のアクター)で、バースト係数が 2 の場合、クラスタはピーク時に 20,000 個のアクターをサポートする必要があります。各 Actor が一時停止する前に平均 100 秒間実行される場合、そのピークを維持するには、1 秒あたり 200 回の再開と 200 回の一時停止が必要です。

GKE の Agent Substrate は、アイドル状態のアクターを自動的に一時停止しません。アプリケーションまたはオーケストレーターが一時停止をトリガーする必要があります。

データプレーンのサイズを決定する

このセクションでは、ワーカー Pod、GKE ノード、スナップショット帯域幅、atelet ノード エージェントなど、データプレーンのサイズ設定に関するベスト プラクティスについて説明します。

ワーカー Pod と GKE ノードを計算する

WorkerPool の各ワーカー Pod は、複数のアクターをホストします。ワーカーは、ateworker コンテナの --max-actors フラグで設定された上限(デフォルトは 1000)よりも少ないアクターをホストし、ActorTemplate の上限に適合する十分な CPU とメモリ容量が残っている場合に、新しいアクターを受け入れることができます。ワーカーの上限とアクターの上限の相互作用の詳細については、オープンソースの Agent Substrate API ガイドのリソースの適切なサイズ設定をご覧ください。

20% の使用可能なスケジューリング容量(0.8 係数)を持つワーカー Pod の必要な数を計算します。

slots_per_worker = min(max_actors, worker_memory / actor_memory, worker_cpu / actor_cpu)
total_workers    = peak_running_actors / (slots_per_worker × 0.8)

この式では、max_actors 変数のデフォルトは 1000 です(--max-actors フラグで構成)。worker_memory の値から、ワーカーが独自のプロセス用に予約するメモリを差し引きます。microVM WorkerPool リソースの場合、ワーカーによって報告されたアクターごとの仮想マシンモニター(VMM)メモリ オーバーヘッドを actor_memory 値に追加します。これは、プレースメントがそのオーバーヘッドをワーカー容量に対してカウントするためです。

ノードごとに 1 つのワーカー Pod がある場合、ワーカーの数は GKE ワーカーノードの数と同じです。ノードの割り当て可能な CPU とメモリの 100% をワーカー Pod に割り当てないでください。各ノードに atelet DaemonSet(ate-system Namespace 内)と GKE システム デーモン用の容量を残してください。GKE Standard は、クラスタあたり最大 65,000 ノード、ノードあたり最大 512 Pod をサポートしています。デフォルトの最大値はノードあたり 110 Pod です(--max-pods-per-node フラグで構成可能)。大規模なクラスタには追加の要件があります。

スナップショットの帯域幅とノードディスクのサイズを調整する

各アクターの停止により、/var/lib/ate ディレクトリ パスの下のローカルノード ディスクにスナップショットが書き込まれます。SYNCHRONOUS モードでは、atelet エージェントは、一時停止オペレーションが戻る前にスナップショットを Cloud Storage にアップロードします。ASYNCHRONOUS モードでは、atelet エージェントは、スナップショットが取得されてから --snapshot-upload-delay フラグ値(デフォルトは 5m)の遅延後に Cloud Storage へのアップロードをスケジュールします。Agent Substrate は、--snapshot-detach-delay フラグで構成された期間(デフォルト値は 30m)、ノードに最新のスナップショットをキャッシュに保存します。この期間内に再開されたアクターは、同じノードにスケジュールされ、Cloud Storage からスナップショットをダウンロードするのではなく、ローカル ディスクから復元します。

非圧縮スナップショット サイズ(snapshot_size)は、ActorTemplate のメモリ上限に、書き込み可能なルート ファイル システムまたは永続ディレクトリに書き込まれたデータを加えたものとして見積もります。スパース ファイル処理と zstd 圧縮により、Cloud Storage との間で転送される圧縮スナップショット サイズ(compressed_snapshot_size)が削減されます。Cloud Storage では、Agent Substrate がアクターごとに最大 2 つのスナップショット(前のスナップショットと進行中のスナップショット)を保存します。

  • Cloud Storage のピーク帯域幅(各方向): peak_resume_rate × compressed_snapshot_size
  • ノードあたりのピーク時のノードディスク書き込みスループット: 2 × peak_resume_rate × snapshot_size / total_nodes(再開ダウンロードと一時停止チェックポイントの両方がノードディスクに書き込まれます)。

GKE のデフォルトの 100 GiB hyperdisk-balanced ブートディスクは、3,600 IOPS と 290 MiB/秒のスループットを提供します(デフォルトのパフォーマンスはディスクサイズに応じてスケーリングされます)。これにより、ゲスト I/O が重い場合にスナップショット オペレーションが制限される可能性があります。ワーカー ノードプールを作成するときは、/var/lib/ate/snapshots ディレクトリ パスに専用ディスクをマウントするか、gcloud container node-pools create コマンドで --boot-disk-provisioned-iops フラグと --boot-disk-provisioned-throughput フラグを使用してブートディスクのパフォーマンスを向上させます。

Security Token Service の割り当てを確認する

Cloud Storage との間でスナップショットを転送するたびに、アクターをスコープとするトークンが必要です。このトークンは、ate-api-server コンポーネントが Security Token Service(STS)API にリクエストします。ate-api-server コンポーネントは、20 ~ 40 分ごとにアクターごとに最大 1 つの STS リクエストを送信し(通常はそのウィンドウの最初の再開時)、有効期限の 20 分前までトークンを再利用します。

STS Exchange token global requests の割り当ては、デフォルトでプロジェクトあたり 1 分あたり 6,000 リクエストで、Workload Identity Federation for GKE と共有されます。

  • 必要な STS 割り当て(1 分あたりのリクエスト数): 最後のスナップショット転送が 20 分以上前のアクターの再開のピークレートの最大 60 倍に、Workload Identity Federation for GKE の容量を加えたものとして見積もります。このレートがわからない場合は、フォールバックとして 60 × peak_resume_rate 式を使用します。たとえば、1 秒あたり 200 件の再開には、1 分あたり最大 12,000 件のリクエストが必要です。
  • 割り当てを増やす方法: Google Cloud コンソールの [割り当てとシステム上限] ページで割り当ての増加をリクエストします。スナップショット転送は、プロジェクトで Security Token Service API が有効になっていなくても機能しますが、[割り当てとシステム上限] ページに割り当てを表示するには、API を有効にする必要があります。詳細については、Workload Identity Federation for GKE のドキュメントの Security Token Service の Exchange Token API の割り当てをご覧ください。

atelet ノード エージェントを調整する

atelet DaemonSet は、ワーカーノードごとに 1 つの Pod を実行して、Actor の復元とチェックポイントを行います。各 atelet Pod で、objectstore-plugin コンテナはスナップショットを圧縮し、Cloud Storage との間で転送します。

次の表に、atelet コンテナと objectstore-plugin コンテナの主なサイズ設定を示します。

設定 デフォルト サイズ設定のガイダンス
atelet と objectstore-plugin のコンテナの CPU とメモリ リクエスト: 50 mCPU、128 MiB メモリ、上限なし 両方のコンテナでメモリ上限を省略して、メモリ上限なしで Burstable QoS を構成します。メモリ上限に達すると、ノード エージェントが停止し、ノード上のすべての再開が停止するためです。CPU リクエストとメモリ リクエストを増やして、ピーク時の再開率でフルノードで測定されたピーク時の使用量に一致させ、ワーカー Pod が atelet エージェントに必要なリソースを消費しないようにします。
--upload-memory-budget フラグ(objectstore-plugin コンテナ)と --snapshot-upload-workers フラグ(atelet コンテナ) 512 MiB の予算、2 つのアップロード ワーカー

一時停止率の高いマルチアクター ノードで同時スナップショット アップロードがキューに登録されないようにするには、次の操作を行います。

  • --upload-memory-budget フラグの値を増やします(objectstore-plugin コンテナのメモリ リクエストをこの予算より大きく設定します)。
  • --snapshot-upload-workers フラグの値を増やします。
--drain-timeout フラグと terminationGracePeriodSeconds フィールド 5 分(--drain-timeout フラグ)、330 秒(terminationGracePeriodSeconds フィールド) ノードのドレインまたはアップグレード中に Pod が停止したときに、atelet エージェントが処理中の復元とスナップショットのアップロードを完了するまでの時間を制御します。大規模なスナップショットの --drain-timeout フラグの値を増やす場合は、atelet DaemonSet の terminationGracePeriodSeconds フィールドの値も増やして、アップロードが完了する前に kubelet エージェントが atelet エージェントを停止しないようにします。

システム インフラストラクチャ コンポーネントとデータベースのサイズを決定する

水平方向にスケーリングする ate-system Namespace のステートレス システム コンポーネントの Deployment の場合、35% の使用可能な容量(0.65 係数)と 1 つのスペア レプリカ(+ 1)を使用して、ノードの中断を許容するために必要なレプリカ数を推定します。

replicas = ceil(peak_load / (capacity_per_replica × 0.65)) + 1

共有依存関係(PostgreSQL の CPU や接続の上限など)が容量に達すると、水平スケーリングによるスループットの向上は停止します。システム インフラストラクチャ コンポーネントとデータベースのサイズを一緒に設定します。コンポーネント フラグの詳細については、オープンソースのオペレーター インストール ガイドをご覧ください。

次の表に、各システム インフラストラクチャ コンポーネントのリソースとスケーリング ルールを示します。

コンポーネント QoS とリソースのルール スケーリングと接続のチューニング
上り(内向き)ルーター(atenet-router) Guaranteed QoS を構成する: 一致する CPU とメモリのリクエストと上限を設定して、Go ランタイムがスレッド数をコンテナの CPU 予算に合わせるようにします。 1 秒あたりのピーク時の上り(内向き)リクエスト数、mTLS オーバーヘッド、リクエスト ペイロード サイズに基づいて、レプリカを水平方向にスケーリングします。
API サーバー(ate-api-server) Guaranteed QoS を構成する: 一致する CPU とメモリのリクエストと上限を設定して、Go ランタイムがスレッド数をコンテナの CPU 予算に合わせるようにします。 1 秒あたりのピーク時の再開サイクルと一時停止サイクル(再開サイクルと一時停止サイクルあたり約 4 ミリ秒の CPU)と、上り(内向き)リクエストのルックアップ負荷に基づいてレプリカをスケーリングします。ateapi.postgres.poolMaxConns 設定を明示的に設定します。各レプリカは最大 poolMaxConns + 6 個のデータベース接続を開きます。すべてのレプリカの合計は、PostgreSQL の max_connections 設定より小さくする必要があります。
データベース(PostgreSQL) メモリを保証して Burstable QoS を構成する: メモリ リクエストを上限と同じに設定して、shared_buffers キャッシュをメモリにロックし、メモリ不足(OOM)による強制排除を防ぎます。専用の ate-postgres ノードで実行する場合は、CPU の上限を省略します。これにより、カーネル CPU スロットリングによってチェックポイントと自動バキュームのバーストが遅くなることがなくなります。 この単一インスタンスの StatefulSet を垂直方向にスケーリングします。ピーク負荷時に使用率が 80% 未満になるように十分な CPU をプロビジョニングし、Actor テーブルをキャッシュするのに十分な RAM(shared_buffers パラメータをメモリの約 25% に設定)をプロビジョニングします。また、インストール時に ateapi.postgres.storageClass 設定で SSD ベースの StorageClass を設定します(たとえば、C4 または N4 の dynamic-rwo StorageClass、C3 または N2 の premium-rwo StorageClass)。postgres-config ConfigMap(postgresql.conf ファイル)の max_connections パラメータを増やして、すべての ate-api-server レプリカに適合させます。
下り(外向き)プロキシ(atenet-egress) リソースルール: 512 mCPU の CPU リクエスト(上限 8 vCPU)と 512 MiB のメモリ リクエスト(上限 1 GiB)を設定します。アウトバウンド Actor トラフィックのレイテンシが増加した場合は、リクエストと上限を増やします。 Actor のアウトバウンド リクエスト トラフィックに基づいてレプリカを水平方向にスケーリングします。
ワーカー コントローラ(ate-controller) リソースルール: 1 つのレプリカを保持します(リーダー選出なし)。大規模な WorkerPool スケールアウト イベント中のピーク時の使用量に基づいて、CPU リクエストとメモリ リクエストを設定します。 大規模なフリートで新しいワーカー Pod の登録に時間がかかりすぎる場合は、--worker-sync-concurrency フラグの値を増やします(デフォルトは 2)。
証明書コントローラ(podcertificate-controller) リソースルール: podcertificate-controller-system Namespace で実行され、新しいワーカー Pod がアクターを受け入れる前に、mTLS 証明書に署名します。 数千のワーカーのフリートの場合は、podcert.workersPerSigner 設定(デフォルトは 0。署名者ごとに 1 ワーカーのマニフェストのデフォルトを保持)、--kube-api-qps フラグ(デフォルトは 5)、--kube-api-burst フラグ(デフォルトは 10)の値を増やします。
ID プロバイダ(ate-idp-server) リソースルール: OpenID Connect(OIDC)ディスカバリ ドキュメントと JSON Web Key Set(JWKS)を ate-system Namespace で提供し、Actor JSON Web Token(JWT)を検証します。JWKS クエリのピーク量に基づいて CPU とメモリのリクエストを設定します。 証明書利用者(RP)が鍵をキャッシュせずに高いリクエスト率で JWKS エンドポイントにクエリを実行する場合は、レプリカを水平方向にスケーリングします。
認証情報プロバイダ(k8s-credential-provider) リソースルール: Kubernetes Secret 認証情報の挿入が有効になっている場合、ate-system 名前空間で実行されます。アウトバウンド認証情報の挿入量に基づいて CPU とメモリのリクエストを設定します。 atenet-egress プロキシで Kubernetes Secret 認証情報の挿入を必要とするアウトバウンド Actor リクエストのレートに基づいて、レプリカを水平方向にスケーリングします。

システム インフラストラクチャとワーカー ノードプールを分離する

本番環境クラスタでは、ワーカーノードでの高チャーンのアクター スケジューリングとスナップショット トラフィックが、CPU、メモリ、ディスク I/O をめぐってシステム インフラストラクチャ コンポーネントと競合する可能性があります。GKE クラスタを 3 つの異なるノードプール階層に分割します。

  • データベース ノードプール(ate-postgres ラベル): PostgreSQL StatefulSet 専用のノードプール。すべてのアクターとワーカーのメタデータを保存します。
  • システム インフラストラクチャ ノードプール(ate-system-infra ラベル): 残りのシステム インフラストラクチャ サービス(atenet-router、ate-api-server、atenet-egress、ate-controller、ate-idp-server、k8s-credential-provider コンポーネント)専用のノードプール。
  • ワーカー ノードプール(WorkerPool): GKE カスタム ComputeClass と GKE 容量バッファによって管理される、動的にスケーリングされるノードプール。アクティブな Actor サンドボックスをホストします。

GKE に Agent Substrate をインストールするときに、cordonSystemInfraComponents 設定(または --cordon-system-infra-components フラグ)を使用して、データベースとシステム インフラストラクチャ コンポーネントを専用ノードに分離できます。インストールする前に、必要なラベルと NoSchedule taint を使用して両方のノードプールを作成する必要があります。

# 1. Create the dedicated PostgreSQL node pool
gcloud container node-pools create ate-postgres-pool \
    --cluster=CLUSTER_NAME \
    --location=LOCATION \
    --node-locations=POSTGRES_ZONE \
    --machine-type=POSTGRES_MACHINE_TYPE \
    --num-nodes=1 \
    --node-labels=ate.dev/workloadType=ate-postgres \
    --node-taints=ate.dev/workloadType=ate-postgres:NoSchedule

# 2. Create the dedicated system infrastructure node pool
gcloud container node-pools create ate-system-infra-pool \
    --cluster=CLUSTER_NAME \
    --location=LOCATION \
    --machine-type=SYSTEM_INFRA_MACHINE_TYPE \
    --num-nodes=2 \
    --node-labels=ate.dev/workloadType=ate-system-infra \
    --node-taints=ate.dev/workloadType=ate-system-infra:NoSchedule

次のように置き換えます。

  • CLUSTER_NAME: GKE Standard クラスタの名前。
  • LOCATION: クラスタのコントロール プレーンの Compute Engine ゾーンまたはリージョン。
  • POSTGRES_ZONE: 単一レプリカの PostgreSQL StatefulSet の単一の Compute Engine ゾーン。リージョン クラスタの場合は、クラスタのリージョン内の 1 つのゾーンを指定して、StatefulSet の永続ディスクが作成されるゾーンにノードプールが単一のノードをプロビジョニングするようにします。ゾーンクラスタの場合は、クラスタのゾーンを指定します。
  • POSTGRES_MACHINE_TYPE: PostgreSQL インスタンスのサイズに合わせた Compute Engine マシンタイプ。
  • SYSTEM_INFRA_MACHINE_TYPE: システム インフラストラクチャ コンポーネントに合わせてサイズ設定された Compute Engine マシンタイプ(c3-standard-8 マシンタイプなど)。

リージョン クラスタでは、--num-nodes フラグはゾーンごとに適用されます(たとえば、3 つのゾーンに --num-nodes=2 フラグを設定すると、合計 6 つのノードが作成されます)。ate-system-infra-pool ノードプールを特定のゾーンに配置するには、--node-locations フラグを追加します。

インストール中に cordonSystemInfraComponents 設定を有効にし、いずれかのノードプールがない場合、対応するシステム Pod は Pending 状態のままになります。

カスタム ComputeClass を使用してハードウェア ターゲティングを自動化する

GKE のカスタム ComputeClass を使用すると、WorkerPool ノードの Compute Engine マシンシリーズの優先順位リストを定義し、ゾーンでリソースが利用できない場合にセカンダリ マシンシリーズに自動的にフォールバックできます。

ランタイム別に Compute Engine マシンシリーズを選択する

サンドボックス ランタイムごとに個別の ComputeClass を構成します。

  • MicroVM ランタイム(Cloud Hypervisor): ComputeClass の優先度で enableNestedVirtualization: true フィールドを設定し、ネストされた仮想化をサポートする x86 Compute Engine マシンシリーズ(N4、C3、C4、N2 など)をターゲットにします。または、KVM をネイティブに実行し、enableNestedVirtualization フィールドを必要としない x86 ベアメタル マシンタイプを使用します。ネストされた仮想化の制限に記載されている Arm ベースの VM とマシンシリーズはサポートされていません。
  • gVisor ランタイム: ネストされた仮想化が有効になっていない、汎用またはコンピューティング最適化の x86 Compute Engine マシンシリーズ(N4、C3、C4、N2 など)をターゲットにします。混合 CPU マイクロアーキテクチャ(E2 など)で実行されるマシンシリーズと Arm ベースの VM は、GKE の Agent Substrate でサポートされていません。ActorTemplate が選択するすべての WorkerPool リソースは、同じ CPU アーキテクチャを共有する必要があります。

詳細な比較については、サンドボックス ランタイムを選択するをご覧ください。

Agent Substrate に必要な ComputeClass ルール

GKE ワーカーノードで Agent Substrate の ComputeClass を作成する場合は、次のルールに従ってください。

  • ノードベースの ComputeClass を使用する: ネストされた仮想化をサポートしていない Pod ベースの課金(podFamily フィールド)は使用しないでください。
  • nodePoolConfig.sandbox.type: microvm フィールドを設定しないでください。この設定により、Pod ベースのワークロードで GKE Sandbox の Kata ランタイムが有効になり、GKE ワーカーの Agent Substrate がスケジューリングされないようにする sandbox.gke.io/runtime=microvm taint が適用されます。GKE の Agent Substrate は独自のマイクロ VM ランタイムを管理し、標準の Container-Optimized OS(cos_containerd イメージタイプ)ノードでの Compute Engine のネストされた仮想化のみを必要とします。
  • サンドボックスの密度が高い場合に Linux カーネル sysctl 設定を構成する: ワーカーノードが約 800 個を超える同時サンドボックスを実行する場合は、ファイル システム モニタリング(fs.inotify.* パラメータ)と Address Resolution Protocol(ARP)ネイバー テーブル(net.ipv4.neigh.default.gc_thresh* パラメータ)の Linux カーネルの上限を増やす必要があります。インストーラは、作成したクラスタにのみこれらの設定を適用します。カスタム ComputeClass を構成する場合(priorityDefaults.nodeSystemConfig.linuxNodeConfig.sysctls フィールドを使用)や、ワーカー ノードプールを事前に作成する場合(--system-config-from-file フラグを使用)は、次の sysctl 値を自分で適用します。

    • fs.inotify.max_user_instances: 65536
    • fs.inotify.max_user_watches: 1048576
    • net.ipv4.neigh.default.gc_thresh1: 4096
    • net.ipv4.neigh.default.gc_thresh2: 16384
    • net.ipv4.neigh.default.gc_thresh3: 32768

    ノード sysctl パラメータの構成の詳細については、Sysctl 構成オプションをご覧ください。

microVM WorkerPool リソースのスケールアップ パターンを選択する

MicroVM ワーカー Pod は ate.dev/kvm: 1 拡張リソースをリクエストします。このリソースは、ノードが起動して atelet エージェントが起動した後にのみ、atelet デバイス プラグインによってアドバタイズされます。GKE ノードの自動プロビジョニングでは、プロビジョニングされていないノード テンプレートの ate.dev/kvm 拡張リソースが認識されないため、保留中の microVM ワーカー Pod は、ゼロレプリカから新しいノードプールを自動的に作成するように GKE をトリガーできません。ただし、ノードプールにすでに 1 つ以上の実行中のノードがある場合、GKE はそのノードプールをスケールアップできます。

次の表は、GKE で microVM WorkerPool リソースをスケーリングするためにサポートされているパターンを比較したものです。

パターン 仕組み 0 ノードへのスケーリングが可能か リソースが利用できない場合のマルチプライオリティ フォールバック 次の場合に使用することをおすすめします。
minimumCapacity フィールドを含む自動作成プール nodePoolAutoCreation.enabled: true フィールドと minimumCapacity.targetNodeCount: 1 フィールドを明示的な machineType 優先度で設定します。 いいえ(1 つのウォームノードを実行し続ける) クラスレベルの minimumCapacity フィールドの初期プロビジョニング時のみ。バースト中のフォールバックでは、spec.priorities[] フィールドの各フォールバック エントリに minimumCapacity.targetNodeCount: 1 フィールドを設定する必要があります(優先度ごとに 1 つのウォームノードを保持)。 ノードプールの作成を自動化し、常に少なくとも 1 つのワーカーノードを実行し続ける。
容量バッファを使用して自動作成されたプール nodePoolAutoCreation.enabled: true フィールドを、PodTemplate で ate.dev/kvm 拡張リソースが省略されているアクティブまたはスタンバイの CapacityBuffer と組み合わせます。 スタンバイに一時停止します(たとえば、デフォルトの 100 GiB ブートディスクを備えた c3-standard-4 ノードで実行中のコンピューティング費用の約 8%)。 はい(バッファ プレースホルダは、優先度全体でノード自動プロビジョニングのフォールバックをトリガーします) アイドル費用を抑えながら、高速なバースト スケーリング(1 ~ 4 秒のアクティブ、または 20 ~ 35 秒のスタンバイ ノードの再開、50 ~ 85 秒の新しいワーカーの準備)を実現したい。
ノード テンプレート ラベル付きの事前作成された 0 ノードプール clusterautoscaler-nodetemplate-resources.ate.dev/kvm=4096 ラベルを使用して自動スケーリングされた 0 ノードプールを事前に作成し、ComputeClass の spec.priorities[].nodepools フィールドで参照します。 はい(真の 0 ノード、アイドル状態のコンピューティング費用なし) はい(事前に作成されたノードプール全体) スタンバイ容量バッファを維持せずに、真のゼロへのスケーリングを実現したい。

例: minimumCapacity フィールドを含む ComputeClass

minimumCapacity フィールドを使用する場合、spec.priorities フィールドのすべてのエントリで、machineFamily フィールドのみを指定するのではなく、明示的な machineType フィールドを指定する必要があります。

apiVersion: cloud.google.com/v1
kind: ComputeClass
metadata:
  name: substrate-microvm
spec:
  priorityDefaults:
    enableNestedVirtualization: true
    nodeSystemConfig:
      linuxNodeConfig:
        sysctls:
          fs.inotify.max_user_instances: 65536
          fs.inotify.max_user_watches: 1048576
          net.ipv4.neigh.default.gc_thresh1: 4096
          net.ipv4.neigh.default.gc_thresh2: 16384
          net.ipv4.neigh.default.gc_thresh3: 32768
  priorities:
  - machineType: n4-standard-4
  - machineType: c3-standard-4
  - machineType: n2-standard-4
  nodePoolAutoCreation:
    enabled: true
  minimumCapacity:
    targetNodeCount: 1
  nodePoolConfig:
    imageType: cos_containerd
  whenUnsatisfiable: DoNotScaleUp

この ComputeClass を WorkerPool のターゲットにするには、WorkerPool Pod テンプレートに nodeSelector: {cloud.google.com/compute-class: substrate-microvm} フィールドを追加します。

ComputeClass が異なる x86 マシンシリーズ(N4 から C3 や N2 など)にフォールバックする場合、メモリ忠実度(SNAPSHOT_FIDELITY_MEMORY モード)で永続スナップショットを復元するには、CPU 機能がスナップショットの CPU 機能のスーパーセットであるワーカーが必要です。互換性のあるワーカーに容量がない場合、Agent Substrate は復元をルート ファイル システムの忠実度(SNAPSHOT_FIDELITY_ROOTFS モード)にダウングレードします。これにより、ルート ファイル システムと永続ボリュームのデータを保持しながら、アクターのプロセスを再起動します。

例: 事前に作成された 0 ノードプールを使用してゼロからスケーリングする

  1. 高密度 Linux カーネルの sysctl 設定を system-config.yaml ファイルとして保存します。

    linuxConfig:
      sysctl:
        fs.inotify.max_user_instances: '65536'
        fs.inotify.max_user_watches: '1048576'
        net.ipv4.neigh.default.gc_thresh1: '4096'
        net.ipv4.neigh.default.gc_thresh2: '16384'
        net.ipv4.neigh.default.gc_thresh3: '32768'
    
  2. --system-config-from-file=system-config.yaml フラグと clusterautoscaler-nodetemplate-resources.ate.dev/kvm=4096 ラベルを使用して、プライマリとフォールバックのネストされた仮想化ノードプールを 0 ノードで事前に作成します。これにより、GKE クラスタ オートスケーラーは、空のプールが ate.dev/kvm 拡張リソースを提供することを認識します。

    # Primary N4 pool (0 nodes)
    gcloud container node-pools create microvm-n4-pool \
        --cluster=CLUSTER_NAME \
        --location=LOCATION \
        --machine-type=n4-standard-4 \
        --enable-nested-virtualization \
        --system-config-from-file=system-config.yaml \
        --num-nodes=0 \
        --enable-autoscaling --total-min-nodes=0 --total-max-nodes=10 \
        --node-labels=cloud.google.com/compute-class=substrate-microvm-zero,clusterautoscaler-nodetemplate-resources.ate.dev/kvm=4096 \
        --node-taints=cloud.google.com/compute-class=substrate-microvm-zero:NoSchedule
    
    # Fallback C3 pool (0 nodes)
    gcloud container node-pools create microvm-c3-pool \
        --cluster=CLUSTER_NAME \
        --location=LOCATION \
        --machine-type=c3-standard-4 \
        --enable-nested-virtualization \
        --system-config-from-file=system-config.yaml \
        --num-nodes=0 \
        --enable-autoscaling --total-min-nodes=0 --total-max-nodes=10 \
        --node-labels=cloud.google.com/compute-class=substrate-microvm-zero,clusterautoscaler-nodetemplate-resources.ate.dev/kvm=4096 \
        --node-taints=cloud.google.com/compute-class=substrate-microvm-zero:NoSchedule
    

    次のように置き換えます。

    • CLUSTER_NAME: GKE Standard クラスタの名前。
    • LOCATION: クラスタの Compute Engine のゾーンまたはリージョン。--total-min-nodes フラグと --total-max-nodes フラグを使用すると、ゾーンごとではなく、ノードプール全体に自動スケーリングの境界が設定されます。
  3. ComputeClass で、事前に作成されたプールを優先順位で参照します。

    apiVersion: cloud.google.com/v1
    kind: ComputeClass
    metadata:
      name: substrate-microvm-zero
    spec:
      nodePoolAutoCreation:
        enabled: false
      priorities:
      - nodepools:
        - microvm-n4-pool
      - nodepools:
        - microvm-c3-pool
      whenUnsatisfiable: DoNotScaleUp
    

    priorityDefaults フィールドは、nodepools フィールドを使用する優先度には適用されません。ノード設定は、事前に作成されたノードプールから取得されるためです。自動作成されたフォールバック優先度と同様に、CPU 機能がスナップショットの CPU 機能のスーパーセットではないフォールバック マシンシリーズでアクターを再開すると、復元が SNAPSHOT_FIDELITY_ROOTFS モードにダウングレードされ、アクターのプロセスが再起動されます。

ComputeClass の構成の詳細については、カスタム ComputeClass を使用して自動スケーリングされたノード属性を制御するをご覧ください。

WorkerPool のバースト スケーリング用に容量バッファを構成する

Actor トラフィックの急増が、実行中の WorkerPool Pod の容量を超えると、新しい Worker Pod は、Compute Engine ノードのプロビジョニング、atelet エージェントの初期化、サンドボックス アセットのプリウォーミングが完了するまで待機してから、Actor を処理する必要があります。GKE 容量バッファは、ノードのプロビジョニングと atelet アセットのプリウォーミングをクリティカル パスから外します。

  • アクティブな容量バッファ(buffer.x-k8s.io/active-capacity 戦略): 事前にプロビジョニングされたノードを実行して準備を整えておくことで、新しいワーカー Pod が 1 ~ 4 秒でスケジュールされます。この場合、Compute Engine VM の実行コストがすべて発生します。
  • スタンバイ容量バッファ(buffer.gke.io/standby-capacity 戦略): ノードをプロビジョニングし、GKE が Compute Engine VM を一時停止する前に、atelet エージェントがスタンバイ初期化ウィンドウ中にディスク上のサンドボックス アセット(runsc バイナリまたは Cloud Hypervisor と Kata イメージ)を初期化してプリウォームできるようにします。このウィンドウのデフォルトは 5 分(5m)で、CapacityBuffer の buffer.gke.io/standby-capacity-init-time アノテーションによって構成されます。アセットのプリウォーミングに 5 分以上かかる場合は、このアノテーションの値を増やします。バーストが発生すると、GKE は約 20 ~ 35 秒で一時停止中のノードを再開し、エンドツーエンドの WorkerPool の準備(Worker Pod のスケジューリング、Pod 証明書の発行、ate-api-server コンポーネントへの容量の登録)が約 50 ~ 85 秒で完了します。一時停止中は、一時停止された VM のメモリとブートディスク ストレージに対してのみ課金されます。たとえば、デフォルトの 100 GiB hyperdisk-balanced ブートディスクを備えた c3-standard-4 ノードでは、一時停止されたストレージの費用は、実行中のコンピューティング費用の約 8% になります。正確な割合は、マシンシェイプ、ブートディスク サイズ、プロビジョニングされた IOPS またはスループットによって異なります。

バッファ戦略の詳細については、起動レイテンシを短縮する戦略をご覧ください。

容量バッファの制限事項とルール

GKE で Agent Substrate の容量バッファを構成する場合は、次のルールを適用します。

  • スタンバイ バッファの Compute Engine マシンシェイプの制限: Compute Engine インスタンスの一時停止は、ローカル SSD がアタッチされている VM や、208 GB を超えるメモリを搭載したマシンシェイプ(c3-highmem-88 マシンタイプやベアメタル シェイプなど)ではサポートされていません。大規模なワーカーノード シェイプまたはローカル SSD を搭載したノードの場合は、スタンバイ バッファではなくアクティブ容量バッファを使用します。
  • scalableRef フィールドが 0 レプリカの WorkerPool または microVM Deployment を指さないようにする:
    • プールが 0 個のレプリカにスケーリングされている場合、GKE CapacityBuffer コントローラはカスタム リソース(kind: WorkerPool)を解決できません。
    • microVM WorkerPool リソースの場合も、scalableRef フィールドを基盤となる Deployment に向けないでください。Deployment Pod テンプレートには ate.dev/kvm: 1 拡張リソースが含まれているため、ノードの自動プロビジョニングで空のプールをスケールアウトできません。代わりに、ate.dev/kvm 拡張リソースをリクエストせずに ComputeClass を選択する PodTemplate(podTemplateRef フィールド)を参照してバッファを構成します。
    • gVisor WorkerPool リソース(ate.dev/kvm 拡張リソースをリクエストしない)の場合、scalableRef フィールドを WorkerPool の基盤となる Deployment に直接指定するか、PodTemplate を使用します。

例: PodTemplate を使用したスタンバイ CapacityBuffer

次のマニフェストでは、microVM または gVisor WorkerPool リソースで動作する PodTemplate とスタンバイ CapacityBuffer を定義します。

apiVersion: v1
kind: PodTemplate
metadata:
  name: substrate-worker-buffer-template
  namespace: WORKERPOOL_NAMESPACE
template:
  spec:
    nodeSelector:
      cloud.google.com/compute-class: COMPUTE_CLASS_NAME
    containers:
    - name: buffer-placeholder
      image: registry.k8s.io/pause:3.9
      resources:
        requests:
          cpu: "WORKER_CPU_REQUEST"
          memory: "WORKER_MEMORY_REQUEST"
---
apiVersion: autoscaling.x-k8s.io/v1beta1
kind: CapacityBuffer
metadata:
  name: substrate-standby-buffer
  namespace: WORKERPOOL_NAMESPACE
spec:
  podTemplateRef:
    name: substrate-worker-buffer-template
  replicas: BUFFER_REPLICAS
  provisioningStrategy: buffer.gke.io/standby-capacity

次のように置き換えます。

  • WORKERPOOL_NAMESPACE: WorkerPool の Kubernetes Namespace(例: my-workerpool)。
  • COMPUTE_CLASS_NAME: WorkerPool の対象となるカスタム ComputeClass の名前(例: substrate-microvm)。
  • WORKER_CPU_REQUEST と WORKER_MEMORY_REQUEST: WorkerPool 内の単一のワーカー Pod の CPU とメモリのリクエスト(4 vCPU ノード上の 2 CPU と 4Gi メモリなど)。atelet エージェントと GKE システム デーモンのノード容量を残します。これらの値を 1 つのワーカー Pod のリクエストに一致させると、各バッファ レプリカが 1 つのノードに複数のバッファ プレースホルダをパッキングするのではなく、1 つのスタンバイ ワーカー ノードをプロビジョニングするようになります。
  • BUFFER_REPLICAS: 事前ウォーミングを維持するスタンバイ ワーカーノードの数(例: 4 ノード)。

スタンバイ容量バッファを使用する場合は、buffer.gke.io/standby-capacity-node-suspended taint(operator: "Exists" フィールドを含む)の toleration を atelet DaemonSet に追加して、置換 atelet Pod が一時停止中のスタンバイ ノードにスケジュールされたままになるようにします(DaemonSet のローリング アップデート中など)。DaemonSet 構成の例については、スタンバイ バッファにイメージをプリロードするをご覧ください。

複数のワークロードが同じ ComputeClass を共有している場合は、一致するワークロード分離 toleration と nodeSelector ラベルを WorkerPool Pod テンプレートとバッファ PodTemplate の両方に追加します。これにより、GKE は他のワークロードが要求しない専用の taint 適用ノードをプロビジョニングします。また、atelet エージェントがこれらの専用ノードでスケジュールできるように、一致する toleration を atelet DaemonSet に追加します。手順については、容量バッファを構成するをご覧ください。

アップグレードとオートスケーラーの相互作用を管理する

このセクションでは、アクティブな Actor を中断することなく、atelet DaemonSet の更新、クラスタ オートスケーラーのスケールダウン イベント、ノードのアップグレードを管理する方法について説明します。

スタンバイ バッファを使用して atelet DaemonSet を更新する

一時停止中のスタンバイ バッファノードには、一時停止中に buffer.gke.io/standby-capacity-node-suspended taint と到達不能 taint が適用されるため、一時停止中のノードの kubelet エージェントは Pod の削除を認識できません。atelet DaemonSet が buffer.gke.io/standby-capacity-node-suspended taint を許容していることを確認します(operator: "Exists" フィールドを使用)。この toleration を使用しても、atelet DaemonSet の標準のローリング アップデート(maxUnavailable: 1 フィールドを使用)中に、一時停止されたノード上の Pod が Terminating 状態になり、実行中のノードでアップデートが進行できなくなる可能性があります。

スタンバイ ノードが一時停止しているクラスタで atelet DaemonSet を更新する前に、次のいずれかを行います。

  • 到達不能なスタンバイ ノードが更新予算を使い果たさないように、atelet DaemonSet の spec.updateStrategy.rollingUpdate.maxUnavailable フィールドを、一時停止中のスタンバイ ノードの最大数より大きい値に設定します。
  • atelet DaemonSet を更新する前に、スタンバイ CapacityBuffer レプリカを一時的に 0 にスケーリングし、クラスタ オートスケーラーがすべての一時停止中のスタンバイ バッファノードを削除するまで待ちます(通常は 10 ~ 15 分)。Suspended 条件ステータスが True であるノードがまだ存在するかどうかを確認するには、次のコマンドを実行します。

    kubectl get nodes -o custom-columns='NAME:.metadata.name,SUSPENDED:.status.conditions[?(@.type=="Suspended")].status'
    

スケールダウンとアップグレード中にアクティブなアクターを保護する

アクティブな Actor は、個々の Kubernetes Pod としてではなく、atelet エージェントによって管理されるサンドボックス内で実行されるため、ノードのワーカー Pod がアクティブな Actor をホストしている場合でも、ノードの CPU リクエストとメモリ リクエストが GKE クラスタ オートスケーラーに低く表示されることがあります。ノードがドレインされると、kubelet エージェントは SIGTERM シグナルをワーカー Pod に送信し、ワーカーは SIGTERM シグナルを、ホストする各アクターの各コンテナのメインプロセスに転送します。各アクティブなアクターは、ワーカーが停止する前に最大 30 分間一時停止できます。すべてのステートフル アクターに、シグナルで終了せず、DURABILITY_MODE_SYNCHRONOUS モードで SuspendActor メソッドを呼び出す SIGTERM ハンドラを付与します。メインプロセスが SIGTERM シグナルで終了するアクターは、保存されていない状態をすぐに失います。アクティブな Actor をホストしている間にワーカー Pod が停止すると、Actor は ACTOR_STATE_CRASHED 状態になります。

スケールダウン イベントと GKE クラスタのアップグレード中の停止を最小限に抑えるには、次のプラクティスを適用します。

  • ステートフル WorkerPool ノードに Spot VM を使用しないでください。Spot VM のプリエンプションでは、ノードを停止する 15 秒から 2 分前に終了通知が提供されるためです。
  • オーケストレーターでドレインを調整して、ワーカー Pod が停止する前にアクティブなアクターがチェックポイントされ、一時停止されるようにします。または、クラスタ オートスケーラーがアクティブなワーカー Pod をエビクションしないようにします(たとえば、cluster-autoscaler.kubernetes.io/safe-to-evict: "false" Pod アノテーションを使用します)。WorkerPool Pod テンプレートで safe-to-evict: "false" アノテーションを静的に設定すると、Worker Pod がノードにスケジュールされている間、クラスタ オートスケーラーはノードをスケールダウンしません。これは、その Worker がアクティブな Actor をホストしていない場合でも同様です。アイドル状態のノードを解放するには、WorkerPool レプリカをスケールダウンするか、アノテーションを動的に管理する必要があります。ワーカーの強制排除のライフサイクルと、RevertActor メソッドを使用してクラッシュしたアクターを復元する方法については、Agent Substrate API ガイドの強制排除をご覧ください。
  • GKE サージ アップグレードを構成し、リソースに制約のある環境でのアップグレードを確認して、既存のワーカーノードが切断およびドレインされる前に新しいワーカーノードがプロビジョニングされるようにします。

次のステップ