クラスタ内から Agent Sandbox スナップショットをトリガーする

このチュートリアルでは、Google Kubernetes Engine(GKE)クラスタ内から Agent Sandbox のスナップショット機能をデプロイしてテストする方法について説明します。 クラスタ内でクライアント アプリケーションを実行して、サンドボックス環境をプログラムで作成、一時停止、再開する方法を学習します。

Pod のスナップショットの作成の詳細については、 Pod スナップショットから復元するをご覧ください。

費用

Agent Sandbox は GKE で追加料金なしで提供されます。 GKE の料金 は、作成したリソースに適用されます。

始める前に

  1. コンソールのプロジェクト セレクタページで、プロジェクトを選択または作成します。 Cloud de Confiance Cloud de Confiance

    プロジェクトを選択または作成するために必要なロール

    • プロジェクトを選択する: プロジェクトの選択には特定の IAM ロールは必要ありません。ロールが付与されているプロジェクトを選択できます。
    • プロジェクトを作成する: プロジェクトを作成するには、プロジェクト作成者ロール (roles/resourcemanager.projectCreator)が必要です。これには resourcemanager.projects.create 権限が含まれています。詳しくは、ロールを付与する方法をご覧ください。

    プロジェクト セレクタに移動

  2. プロジェクトで課金が有効になっていることを確認します Cloud de Confiance 。

  3. Artifact Registry、Kubernetes Engine API を有効にします。

    API を有効にするために必要なロール

    API を有効にするには、serviceusage.services.enable 権限を含む Service Usage 管理者 IAM ロール(roles/serviceusage.serviceUsageAdmin)が必要です。詳しくは、ロールを付与する方法をご覧ください。

    API を有効にする

  4. コンソールで Cloud Shell をアクティブにします。 Cloud de Confiance

    Cloud Shell をアクティブにする

  5. このチュートリアルを完了するために必要な権限があることを確認します

必要なロール

サンドボックスの作成と管理に必要な権限を取得するには、プロジェクトに対するKubernetes Engine 管理者 roles/container.admin)IAM ロールを付与するよう管理者に依頼してください。ロールの付与については、プロジェクト、フォルダ、組織に対するアクセス権の管理をご覧ください。

必要な権限は、カスタムロールや他の事前定義ロールから取得することもできます。

制限事項

リージョン クラスタでは、異なるゾーンのノードで CPU マイクロアーキテクチャが異なる場合があります。スナップショットは CPU の状態をキャプチャするため、CPU 機能が不足しているノードでスナップショットを復元すると失敗します(たとえば、エラー OCI runtime restore failed: incompatible FeatureSetが発生します)。

この問題を回避するには、環境に適した構成を使用します。

  • 本番環境: クラスタ全体で高可用性を維持するには、ワークロードを特定のゾーンに固定しないでください。代わりに、最小 CPU プラットフォームを指定して、すべてのゾーンで CPU 機能の整合性を確保します。詳細については、 最小 CPU プラットフォームを選択するをご覧ください。
  • テスト: 設定を簡素化し、初期の CPU 不一致エラーを回避するには、 マニフェストで nodeSelector フィールドを使用して、Pod を特定のゾーン(us-central1-a など)に固定します。SandboxTemplateこのチュートリアルの例では、このテスト構成を使用しています。

環境変数を定義する

このチュートリアルで実行するコマンドを簡略化するために、Cloud Shell で環境変数を設定できます。Cloud Shell で、次のコマンドを実行して環境変数を定義します。

export PROJECT_ID=$(gcloud config get project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
export CLUSTER_NAME="test-snapshot"
export LOCATION="us-central1"
export BUCKET_LOCATION="us"
export MACHINE_TYPE="n2-standard-2"
export REPOSITORY_NAME="agent-sandbox"
export BUCKET_NAME="${PROJECT_ID}_snapshots"
export CLOUDBUILD_BUCKET_NAME="${PROJECT_ID}_cloudbuild"

これらの環境変数の説明は次のとおりです。

  • PROJECT_ID: 現在の Cloud de Confiance by S3NS プロジェクトの ID。この変数を定義すると、すべてのリソースが正しいプロジェクトに作成されます。
  • PROJECT_NUMBER: 現在の Cloud de Confiance by S3NS プロジェクトのプロジェクト番号。
  • CLUSTER_NAME: GKE クラスタの名前(例: test-snapshot)。
  • LOCATION: GKE クラスタと Artifact Registry リポジトリがある Cloud de Confiance by S3NS リージョン(例: us-central1)。
  • BUCKET_LOCATION: Cloud Storage バケットのロケーション(例: us)。
  • BUCKET_NAME: スナップショットに使用される Cloud Storage バケットの名前。
  • CLOUDBUILD_BUCKET_NAME: Cloud Build ログに使用される Cloud Storage バケットの名前。
  • MACHINE_TYPE: クラスタノードに使用するマシンタイプ(例: e2-standard-8)。
  • REPOSITORY_NAME: Artifact Registry リポジトリの名前(例: agent-sandbox)。

構成手順の概要

クラスタ内から Agent Sandbox 環境の Pod スナップショットを有効にしてテストするには、いくつかの構成手順を行う必要があります。これらの手順を理解するには、まずワークフロー全体に関わるコンポーネントを理解すると役立ちます。

主要コンポーネント

このチュートリアルでは、次の 2 つの Python アプリケーションを使用してスナップショット プロセスをテストします。

  • クライアント アプリケーション: クラスタ内の標準 Pod で実行される Python スクリプト。このアプリケーションは、サンドボックスのライフサイクルを管理します。サンドボックスをプログラムで作成し、一時停止してスナップショットをトリガーし、サンドボックスを再開して、状態が保持されていることを確認します。このチュートリアルでは、agent-sandbox-client-sa という名前の Kubernetes サービス アカウントを作成し、RBAC 権限を付与して、クライアント アプリケーション Pod が Kubernetes API を使用してサンドボックス カスタム リソースとスナップショット トリガー オブジェクトを管理できるようにします。
  • サンドボックス化されたアプリケーション: 1 秒ごとに カウンタをインクリメントして出力する Python スクリプト。このアプリケーションは、隔離されたサンドボックス環境内で安全に実行され、クライアント アプリケーションが検証できる変更状態を生成します。このチュートリアルでは、snapshot-sa という名前の専用 Kubernetes サービス アカウントを作成し、Workload Identity を構成して、サンドボックス化された Pod が Cloud Storage 内のスナップショット オブジェクトを安全に読み書きできるようにします。

構成とテストのプロセス

次のリストに、環境を設定してテストを実行するために必要な手順をまとめます。

  1. クラスタを作成する: Pod スナップショットと Agent Sandbox 機能が有効になっている Autopilot クラスタまたは Standard クラスタを作成します。
  2. Artifact Registry リポジトリを作成する: クライアント アプリケーションのコンテナ イメージを保存する Docker リポジトリを作成します。
  3. Agent Sandbox をインストールする: クラスタにコアの agent-sandbox コンポーネントと拡張機能をインストールします。
  4. ストレージと権限を構成する: Cloud Storage バケットを作成し、スナップショットを安全に保存できるように Workload Identity 権限を構成します。
  5. Pod スナップショットを構成する: スナップショット ストレージ構成、スナップショット ポリシー、サンドボックス テンプレートを作成して適用します。
  6. クライアント アプリケーションをビルドする: クライアント アプリケーションの コンテナ イメージをビルドして、Artifact Registry リポジトリに push します。
  7. テストを実行する: クライアント アプリケーション Pod をデプロイします。これにより、 サンドボックスが作成され、一時停止してスナップショットがキャプチャされ、再開され、 カウンタの状態が正常に復元されたことが確認されます。

クラスタの作成

Pod スナップショットが有効な新しい GKE クラスタを作成します。完全な機能互換性を確保するには、即時リリース チャンネルを指定します。

Autopilot

必要な機能を備えた Autopilot クラスタを作成します。

gcloud beta container clusters create-auto ${CLUSTER_NAME} \
   --enable-pod-snapshots \
   --release-channel=rapid \
   --location=${LOCATION}

標準

必要な機能を備えた Standard クラスタを作成します。

gcloud beta container clusters create ${CLUSTER_NAME} \
   --enable-pod-snapshots \
   --release-channel=rapid \
   --machine-type=${MACHINE_TYPE} \
   --workload-pool=${PROJECT_ID}.svc.id.goog \
   --workload-metadata=GKE_METADATA \
   --num-nodes=1 \
   --location=${LOCATION}

gVisor を有効にしてノードプールを作成します。

gcloud container node-pools create gvisor-pool \
   --cluster ${CLUSTER_NAME} \
   --num-nodes=1 \
   --location=${LOCATION} \
   --project=${PROJECT_ID} \
   --sandbox type=gvisor

Artifact Registry リポジトリを作成する

Artifact Registry に Docker リポジトリを作成して、クライアント アプリケーション(サンドボックスを作成して管理するアプリケーション)のコンテナ イメージを保存します。

gcloud artifacts repositories create ${REPOSITORY_NAME} \
   --repository-format=docker \
   --location=${LOCATION} \
   --description="Docker repository for Agent Sandbox"

Agent Sandbox をインストールする

クラスタに Agent Sandbox コア コンポーネントと拡張機能をインストールします(バージョン v0.4.6 を例として使用)。

# Install the core agent-sandbox components
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v0.4.6/manifest.yaml

# Install the extensions (e.g., Warm Pools, Claims)
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v0.4.6/extensions.yaml

ストレージと権限を構成する

Pod スナップショットを保存する Cloud Storage バケットを構成し、必要な Workload Identity 権限を snapshot-sa サービス アカウントと GKE サービス エージェントに付与します。これにより、サンドボックス化されたワークロードでスナップショット オブジェクトを安全に保存して取得できます。

  1. 新しい Cloud Storage バケットを作成します。

    gcloud storage buckets create "gs://${BUCKET_NAME}" \
        --uniform-bucket-level-access \
        --enable-hierarchical-namespace \
        --soft-delete-duration=0d \
        --location="${BUCKET_LOCATION}"
    
  2. default Namespace に Kubernetes サービス アカウントを作成します。サンドボックス化されたアプリケーション(Python カウンタ スクリプト)は、この ID を使用して外部 API に対する認証を行い、Cloud Storage に保存されているスナップショット オブジェクトに安全にアクセスします。

    kubectl create serviceaccount "snapshot-sa" \
        --namespace "default"
    
  3. Workload Identity を使用して、storage.bucketViewer ロールをサービス アカウントにバインドします。このロールにより、サンドボックス化されたワークロードはバケットの内容を一覧表示し、特定のスナップショットを見つけることができます。

    gcloud storage buckets add-iam-policy-binding "gs://${BUCKET_NAME}" \
        --member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT_ID}.svc.id.goog/subject/ns/default/sa/snapshot-sa" \
        --role="roles/storage.bucketViewer"
    
  4. Workload Identity を使用して、storage.objectUser ロールをサービス アカウントにバインドします。このロールは、バケット内のスナップショット バイナリ オブジェクトの読み取り、保存、削除を行う権限を付与します。

    gcloud storage buckets add-iam-policy-binding "gs://${BUCKET_NAME}" \
        --member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT_ID}.svc.id.goog/subject/ns/default/sa/snapshot-sa" \
        --role="roles/storage.objectUser"
    
  5. バケット内のスナップショット オブジェクトを管理(作成、一覧表示、読み取り、削除)する権限を GKE サービス エージェントに付与します。

    gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
        --member="serviceAccount:service-${PROJECT_NUMBER}@container-engine-robot.iam.gserviceaccount.com" \
        --role="roles/storage.objectUser" \
        --condition="expression=resource.name.startsWith(\"projects/_/buckets/${BUCKET_NAME}\"),title=restrict_to_bucket,description=Restricts access to one bucket only"
    

Pod スナップショットを構成する

構成ファイルを作成して適用し、必要な Kubernetes カスタム リソースをインストールします。これらのリソースは、クラスタが Pod スナップショットを保存して管理する方法を定義します。

  • PodSnapshotStorageConfig: スナップショット バイナリ オブジェクトの保存用に指定された Cloud Storage バケットを指定します。
  • PodSnapshotPolicy: スナップショットを手動でトリガーする方法、グループ化の頻度 、保持ポリシーを定義します。
  • SandboxTemplate: 隔離されたサンドボックス化されたワークロードを実行するための基盤となるコンテナ、ノードセレクタ、および サービス アカウントを定義します。
  1. test_client/snapshot_storage_config.yaml という名前のファイルを作成します。この構成では、クラスタがバイナリ Pod スナップショットの状態を保存するターゲット Cloud Storage バケットを指定します。

    apiVersion: podsnapshot.gke.io/v1
    kind: PodSnapshotStorageConfig
    metadata:
      name: example-pod-snapshot-storage-config
    spec:
      snapshotStorageConfig:
        gcs:
          bucket: "$BUCKET_NAME"
    
  2. 構成ファイル内の環境変数のプレースホルダを置き換えます。

    sed -i "s/\$BUCKET_NAME/$BUCKET_NAME/g" test_client/snapshot_storage_config.yaml
    
  3. ストレージ構成マニフェストを適用します。

    kubectl apply -f test_client/snapshot_storage_config.yaml
    
  4. ストレージ構成の準備ができるまで待ちます。

    kubectl wait --for=condition=Ready podsnapshotstorageconfig/example-pod-snapshot-storage-config --timeout=60s
    
  5. test_client/snapshot_policy.yaml という名前のファイルを作成します。この構成では、サンドボックス化されたワークロードのスナップショットを最大 2 つ保持する保持ルールを確立します。トリガータイプは manual に設定されています。これにより、クライアント アプリケーションはオンデマンドでスナップショットを制御できます。

    apiVersion: podsnapshot.gke.io/v1
    kind: PodSnapshotPolicy
    metadata:
      name: example-pod-snapshot-policy
      namespace: default
    spec:
      storageConfigName: example-pod-snapshot-storage-config
      selector:
        matchLabels:
          app: agent-sandbox-workload
      triggerConfig:
        type: manual
        postCheckpoint: resume
      snapshotGroupingRules:
        groupByLabelValue:
          labels: ["agents.x-k8s.io/sandbox-name-hash", "tenant-id", "user-id"]
          groupRetentionPolicy:
            maxSnapshotCountPerGroup: 2
    
  6. スナップショット ポリシー マニフェストを適用します。

    kubectl apply -f test_client/snapshot_policy.yaml
    
  7. test_client/python-counter-template.yaml という名前のファイルを作成します。この構成では、サンドボックス Pod を定義し、snapshot-sa サービス アカウント ID を割り当てます。この割り当てにより、サンドボックスが安全に実行されます。その Pod 内で、サンドボックス化されたアプリケーション(Python スクリプト)は、インクリメント カウンタをコンテナログに継続的に出力します。

    apiVersion: extensions.agents.x-k8s.io/v1alpha1
    kind: SandboxTemplate
    metadata:
      name: python-counter-template
      namespace: default
    spec:
      podTemplate:
        metadata:
          labels:
            app: agent-sandbox-workload
        spec:
          serviceAccountName: snapshot-sa
          runtimeClassName: gvisor
          nodeSelector:
            topology.kubernetes.io/zone: us-central1-a # Pin to a zone to avoid CPU mismatch during restore
          containers:
          - name: python-counter
            image: python:3.13-slim
            command: ["python3", "-c"]
            args:
              - |
                import time
                i = 0
                while True:
                  print(f"Count: {i}", flush=True)
                  i += 1
                  time.sleep(1)
    
  8. サンドボックス テンプレート マニフェストを適用します。

    kubectl apply -f test_client/python-counter-template.yaml
    

クライアント アプリケーションをビルドする

クライアント アプリケーションのコンテナ イメージを作成して Artifact Registry にアップロードします。

  1. test_client/Dockerfile.client という名前のファイルを作成します。このファイルは、クライアント アプリケーションの Python ランタイム環境と依存関係を定義します。

    FROM python:3.13-slim
    
    WORKDIR /app
    
    RUN pip install "k8s-agent-sandbox[tracing]==0.4.6"
    
    # Copy test script
    COPY client_test.py /app/client_test.py
    
    CMD ["python", "/app/client_test.py"]
    
  2. test_client/client_test.py という名前のファイルを作成します。このスクリプトは、サンドボックスのライフサイクルを管理し、スナップショットの作成後に状態が正常に再開されることを確認します。

    import time
    import logging
    import re
    from kubernetes import config, client
    from k8s_agent_sandbox.gke_extensions.snapshots import PodSnapshotSandboxClient
    
    logging.basicConfig(level=logging.INFO)
    
    def get_last_count(pod_name, namespace):
        v1 = client.CoreV1Api()
        try:
            logs = v1.read_namespaced_pod_log(name=pod_name, namespace=namespace)
            counts = re.findall(r"Count: (\d+)", logs)
            if counts:
                return int(counts[-1])
            return None
        except Exception as e:
            logging.error(f"Failed to read logs for pod {pod_name}: {e}")
            return None
    
    def get_current_pod_name(sandbox_id, namespace):
        custom_api = client.CustomObjectsApi()
        try:
            sandbox_cr = custom_api.get_namespaced_custom_object(
                group="agents.x-k8s.io",
                version="v1alpha1",
                namespace=namespace,
                plural="sandboxes",
                name=sandbox_id
            )
            metadata = sandbox_cr.get("metadata", {})
            annotations = metadata.get("annotations", {})
            return annotations.get("agents.x-k8s.io/pod-name")
        except Exception as e:
            logging.error(f"Failed to get sandbox CR: {e}")
            return None
    
    def get_current_count(sandbox_id, namespace="default"):
        pod_name = get_current_pod_name(sandbox_id, namespace)
        if not pod_name:
            logging.error(f"Could not determine pod name for sandbox {sandbox_id}")
            return None
        return get_last_count(pod_name, namespace)
    
    def suspend_sandbox(sandbox):
        logging.info("Pausing sandbox (using snapshots)...")
        try:
            suspend_resp = sandbox.suspend(snapshot_before_suspend=True)
            if suspend_resp.success:
                logging.info("Sandbox paused successfully.")
                if suspend_resp.snapshot_response:
                    logging.info(f"Snapshot created: {suspend_resp.snapshot_response.snapshot_uid}")
                return suspend_resp
            else:
                logging.error(f"Failed to pause: {suspend_resp.error_reason}")
                exit(1)
        except Exception as e:
            logging.error(f"Failed to pause sandbox: {e}")
            exit(1)
    
    def resume_sandbox(sandbox):
        logging.info("Resuming sandbox (using snapshots)...")
        try:
            resume_resp = sandbox.resume()
            if resume_resp.success:
                logging.info("Sandbox resumed successfully.")
                if resume_resp.restored_from_snapshot:
                    logging.info(f"Restored from snapshot: {resume_resp.snapshot_uid}")
                return resume_resp
            else:
                logging.error(f"Failed to resume: {resume_resp.error_reason}")
                exit(1)
        except Exception as e:
            logging.error(f"Failed to resume sandbox: {e}")
            exit(1)
    
    def verify_continuity(count_before, count_after):
        if count_before is not None and count_after is not None:
            logging.info(f"Verification: Count before={count_before}, Count after={count_after}")
            if count_after >= count_before:
                logging.info("SUCCESS: Sandbox resumed from where it left off (or later).")
            else:
                logging.error("FAIL: Sandbox counter reset or went backwards!")
        else:
            logging.warning("Could not verify counter continuity.")
    
    def main():
        try:
            config.load_incluster_config()
        except config.ConfigException:
            config.load_kube_config()
    
        client_reg = PodSnapshotSandboxClient()
    
        logging.info("Creating sandbox...")
        sandbox = client_reg.create_sandbox(template="python-counter-template", namespace="default")
        logging.info(f"Sandbox created with ID: {sandbox.sandbox_id}")
    
        logging.info("Waiting for sandbox to run...")
        time.sleep(10)
    
        count_before = get_current_count(sandbox.sandbox_id)
        logging.info(f"Count before suspend: {count_before}")
    
        suspend_sandbox(sandbox)
    
        logging.info("Waiting 10 seconds...")
        time.sleep(10)
    
        resume_sandbox(sandbox)
    
        logging.info("Waiting for sandbox to be ready again...")
        time.sleep(10)
    
        count_after = get_current_count(sandbox.sandbox_id)
        logging.info(f"Count after resume: {count_after}")
    
        verify_continuity(count_before, count_after)
    
        logging.info("Snapshot test completed successfully.")
    
    if __name__ == "__main__":
        main()
    
  3. クライアント コンテナ イメージをビルドして Artifact Registry にアップロードします。環境(Cloud Shell など)に Docker がインストールされている場合は、Docker を使用してイメージをローカルでビルドできます。Docker がない環境で作業している場合は、Cloud Build を使用してイメージをリモートでビルドして push できます。

    Docker

    1. Artifact Registry の Docker 認証を構成します。

      gcloud auth configure-docker "${LOCATION}-docker.pkg.dev"
      
    2. クライアント コンテナ イメージをローカルでビルドして push します。

      docker build -t "${LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/sandbox-client:latest" -f test_client/Dockerfile.client test_client
      docker push "${LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/sandbox-client:latest"
      

    Cloud Build

    1. test_client/cloudbuild.yaml という名前のファイルを作成します。

      steps:
      - name: 'gcr.io/cloud-builders/docker'
        args: ['build', '-t', '$LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY_NAME/sandbox-client:latest', '-f', 'test_client/Dockerfile.client', 'test_client']
      images:
      - '$LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY_NAME/sandbox-client:latest'
      
    2. 構成ファイル内の環境変数のプレースホルダを置き換えます。

      sed -i "s/\$REPOSITORY_NAME/$REPOSITORY_NAME/g" test_client/cloudbuild.yaml
      sed -i "s/\$LOCATION/$LOCATION/g" test_client/cloudbuild.yaml
      sed -i "s/\$PROJECT_ID/$PROJECT_ID/g" test_client/cloudbuild.yaml
      
    3. Cloud Build サービス アカウントに必要な権限を付与します。

      gcloud projects add-iam-policy-binding $PROJECT_ID \
         --member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
         --role="roles/artifactregistry.writer"
      
      gcloud projects add-iam-policy-binding $PROJECT_ID \
         --member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
         --role="roles/logging.logWriter"
      
      gcloud storage buckets add-iam-policy-binding "gs://$CLOUDBUILD_BUCKET_NAME" \
         --member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
         --role="roles/storage.objectAdmin"
      
    4. Cloud Build を使用してビルドを実行します。

      gcloud builds submit --config test_client/cloudbuild.yaml
      

テストを実行する

クライアント アプリケーションをデプロイしてサンドボックスを作成し、スナップショットをトリガーして、内部カウンタが保存された状態から正常に再開されることを確認します。

  1. test_client/client_sa.yaml という名前のファイルを作成します。このマニフェストは、agent-sandbox-client-sa サービス アカウントと、サンドボックス カスタム リソースの管理に必要な RBAC 権限を定義します。

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: agent-sandbox-client-sa
      namespace: default
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: agent-sandbox-client-role
      namespace: default
    rules:
    - apiGroups: ["agents.x-k8s.io"]
      resources: ["sandboxes"]
      verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
    - apiGroups: ["extensions.agents.x-k8s.io"]
      resources: ["sandboxclaims"]
      verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
    - apiGroups: ["podsnapshot.gke.io"]
      resources: ["podsnapshotmanualtriggers", "podsnapshots"]
      verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
    - apiGroups: [""]
      resources: ["pods", "pods/log"]
      verbs: ["get", "list", "watch"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: agent-sandbox-client-rolebinding
      namespace: default
    subjects:
    - kind: ServiceAccount
      name: agent-sandbox-client-sa
      namespace: default
    roleRef:
      kind: Role
      name: agent-sandbox-client-role
      apiGroup: rbac.authorization.k8s.io
    
  2. クライアント サービス アカウントと RBAC マニフェストを適用します。

    kubectl apply -f test_client/client_sa.yaml
    
  3. test_client/client_pod.yaml という名前のファイルを作成します。このマニフェストは、事前ビルドされたコンテナ イメージを使用してクライアント アプリケーション Pod を作成します。

    apiVersion: v1
    kind: Pod
    metadata:
      name: agent-sandbox-client-pod
      namespace: default
    spec:
      serviceAccountName: agent-sandbox-client-sa
      containers:
      - name: client
        image: $LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY_NAME/sandbox-client:latest
        imagePullPolicy: Always
      restartPolicy: Never
    
  4. マニフェスト内の環境変数のプレースホルダを置き換えます。

    sed -i "s/\$REPOSITORY_NAME/$REPOSITORY_NAME/g" test_client/client_pod.yaml
    sed -i "s/\$LOCATION/$LOCATION/g" test_client/client_pod.yaml
    sed -i "s/\$PROJECT_ID/$PROJECT_ID/g" test_client/client_pod.yaml
    
  5. クライアント アプリケーション Pod マニフェストを適用します。

    kubectl apply -f test_client/client_pod.yaml
    
  6. Pod ログをストリーミングして、実行フローを確認します。

    kubectl logs -f agent-sandbox-client-pod
    

テストが正常に実行されると、出力は次のようになります(読みやすくするために短縮しています)。

2026-04-21 23:02:39,030 - INFO - Creating sandbox...
...
2026-04-21 23:02:51,755 - INFO - Count before suspend: 23
2026-04-21 23:02:51,755 - INFO - Pausing sandbox (using snapshots)...
...
2026-04-21 23:03:07,115 - INFO - Resuming sandbox (using snapshots)...
...
2026-04-21 23:03:21,329 - INFO - Count after resume: 38
2026-04-21 23:03:21,329 - INFO - Verification: Count before=23, Count after=38
2026-04-21 23:03:21,329 - INFO - SUCCESS: Sandbox resumed from where it left off (or later).

出力は、サンドボックスが一時停止して再開されたときに状態が正常に保持されることを示しています。サンドボックスが一時停止(一時停止してゼロにスケーリング)されている間、カウンタは停止し、サンドボックスが復元されるとカウンタが再開されます。一時停止しない場合、カウンタは一時停止期間中も進み続け、カウントは大幅に増加します。

リソースのクリーンアップ

アカウントに課金されないようにするには、作成したリソース を削除します。 Cloud de Confiance by S3NS

  1. GKE クラスタを削除します。これにより、ノードプールとその内部のすべての Kubernetes サービス アカウントも削除されます。

    gcloud beta container clusters delete test-snapshot --location="${LOCATION}" --quiet
    
  2. Artifact Registry リポジトリを削除して、テストイメージ用に作成した Docker リポジトリを削除します。

    gcloud artifacts repositories delete ${REPOSITORY_NAME} --location="${LOCATION}" --quiet
    
  3. Cloud Storage バケットとその内部のすべてのスナップショットを削除します。 これにより、バケットレベルの Workload Identity IAM バインディングが自動的に削除されます。

    gcloud storage rm --recursive "gs://${BUCKET_NAME}"
    
  4. GKE サービス エージェントのプロジェクト レベルの IAM バインディングを削除します。

    gcloud projects remove-iam-policy-binding "${PROJECT_ID}" \
        --member="serviceAccount:service-${PROJECT_NUMBER}@container-engine-robot.iam.gserviceaccount.com" \
        --role="roles/storage.objectUser" \
        --condition="expression=resource.name.startsWith(\"projects/_/buckets/${BUCKET_NAME}\"),title=restrict_to_bucket,description=Restricts access to one bucket only"
    
  5. Docker の代わりに Cloud Build を使用してコンテナ イメージをビルドして push した場合は、ログバケットを削除してサービス アカウントの権限を削除します。

    gcloud storage rm --recursive "gs://${CLOUDBUILD_BUCKET_NAME}"
    
    gcloud projects remove-iam-policy-binding $PROJECT_ID \
        --member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
        --role="roles/artifactregistry.writer"
    
    gcloud projects remove-iam-policy-binding $PROJECT_ID \
        --member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
        --role="roles/logging.logWriter"
    

次のステップ