從叢集內部觸發 GKE 快照的 Agent Sandbox

在叢集內使用 Google Kubernetes Engine (GKE) 快照上的 Agent Sandbox,保留沙箱環境的狀態。這種做法可提供一致的偵錯功能、加快開發疊代速度,並快速復原複雜的設定。在本教學課程中,您將執行用戶端應用程式,以程式輔助方式在叢集內建立、暫停及恢復沙箱環境,藉此部署及測試這項功能。

如要進一步瞭解如何建立 Pod 快照,請參閱「從 Pod 快照還原」。

費用

GKE 的 Agent Sandbox 是開放原始碼軟體,無須額外付費即可使用。您建立的資源 (包括 Agent Sandbox 控制器使用的運算資源) 適用 GKE 定價。

事前準備

  1. 在 Google Cloud 控制台的專案選取器頁面中,選取或建立 Google Cloud 專案。

    選取或建立專案所需的角色

    • 選取專案:選取專案時,不需具備特定 IAM 角色,只要您已獲派某個專案的角色,即可選取該專案。
    • 建立專案:如要建立專案,您必須具備專案建立者角色 (roles/resourcemanager.projectCreator),其中包含 resourcemanager.projects.create 權限。瞭解如何授予角色。

    前往專案選取器

  2. 確認 Google Cloud 專案已啟用計費功能。

  3. 啟用 Artifact Registry、Kubernetes Engine、Cloud Build API (如有尚未啟用的 API)。

    啟用 API 所需的角色

    您必須具備 serviceusage.services.enable 權限,才能啟用 API。如果您建立了專案,可能已透過擁有者角色 (roles/owner) 取得這項權限。否則,您可以透過 Service Usage 管理員角色 (roles/serviceusage.serviceUsageAdmin) 取得這項權限。瞭解如何授予角色。

    啟用 API

  4. 在 Google Cloud 控制台啟用 Cloud Shell。

    啟用 Cloud Shell

  5. 確認您具備完成本教學課程所需的權限。

必要的角色

如要取得建立及管理沙箱所需的權限,請要求管理員授予您專案的 Kubernetes Engine 管理員 (roles/container.admin) IAM 角色。如要進一步瞭解如何授予角色,請參閱「管理專案、資料夾和組織的存取權」。

您或許也能透過自訂角色或其他預先定義的角色,取得必要權限。

限制

Pod 快照需要 gVisor,不適用於 microVM 沙箱。

在區域叢集中,不同可用區中的節點可能具有不同的 CPU 微架構。由於快照會擷取 CPU 狀態,因此在缺少 CPU 功能的節點上還原快照會失敗 (例如發生 OCI runtime restore failed: incompatible FeatureSet 錯誤)。

為避免這個問題,請根據您的環境使用適當的設定:

  • 正式環境:為確保整個叢集的高可用性,請勿將工作負載固定在特定可用區。請改為指定最低 CPU 平台,確保所有可用區的 CPU 功能一致。詳情請參閱「選擇最低 CPU 平台」。
  • 測試:為簡化設定並避免初始 CPU 不相符錯誤,您可以在 SandboxTemplate 資訊清單中使用 nodeSelector 欄位,將 Pod 固定至特定可用區,例如 us-central1-a。本教學課程中的範例會使用這項測試設定。

定義環境變數

為簡化本教學課程中執行的指令,您可以在 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 ZONE="us-central1-a"
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:目前 Google Cloud 專案的 ID。本文中的指令會使用這個 ID 命名資源及授予 IAM 權限。
  • PROJECT_NUMBER:目前 Google Cloud 專案的專案編號。
  • CLUSTER_NAME:GKE 叢集的名稱,例如 test-snapshot。
  • LOCATION:Artifact Registry 存放區的 Google Cloud 區域 (例如 us-central1)。如果您建立 Autopilot 叢集,叢集也會使用這個區域。
  • ZONE:叢集的可用區。如果您建立的是標準叢集,請將這個值設為 LOCATION 區域中的可用區 (例如 us-central1-a)。您稍後在本教學課程中建立的沙箱範本會在 us-central1-a 中執行沙箱。如果使用其他可用區,請一併變更範本中的可用區。
  • BUCKET_LOCATION:Cloud Storage bucket 的位置,例如 us。
  • BUCKET_NAME:用於快照的 Cloud Storage bucket 名稱,例如 PROJECT_ID_snapshots。Bucket 名稱不得重複
  • CLOUDBUILD_BUCKET_NAME:用於 Cloud Build 記錄檔的 Cloud Storage bucket 名稱。這個值是預設的 Cloud Build bucket 名稱,請勿變更。
  • MACHINE_TYPE:叢集節點和 gVisor 節點集區要使用的機器類型,例如 n2-standard-2。Pod 快照不支援 E2 機型。
  • REPOSITORY_NAME:Artifact Registry 存放區的名稱,例如 agent-sandbox。

設定步驟總覽

如要在叢集內啟用及測試 GKE 環境中 Agent Sandbox 的 Pod 快照,您需要執行多項設定步驟。如要瞭解這些步驟,建議您先瞭解整體工作流程中涉及的元件。

重要元件

本教學課程使用下列兩個 Python 應用程式測試快照程序:

  • 用戶端應用程式:在叢集標準 Pod 中執行的 Python 指令碼。這個應用程式會管理沙箱生命週期:以程式輔助方式建立沙箱、暫停沙箱以觸發快照、恢復沙箱,並驗證狀態是否保留。在本教學課程中,您將建立名為 agent-sandbox-client-sa 的 Kubernetes 服務帳戶,並授予 RBAC 權限,讓用戶端應用程式 Pod 能使用 Kubernetes API 管理沙箱自訂資源和快照觸發條件物件。
  • 沙箱應用程式:Python 指令碼,每秒遞增並輸出計數器。這個應用程式會在隔離的沙箱環境中安全執行,產生用戶端應用程式可驗證的變更狀態。在本教學課程中,您將建立名為 snapshot-sa 的專屬 Kubernetes 服務帳戶,並設定 Workload Identity,授權沙箱 Pod 安全地讀取及寫入 Cloud Storage 中的快照物件。

設定和測試程序

以下列出設定環境及執行測試的步驟:

  1. 建立叢集:建立已啟用 Pod 快照的 Autopilot 或 Standard 叢集。
  2. 建立 Artifact Registry 存放區:建立 Docker 存放區,用於儲存用戶端應用程式的容器映像檔。
  3. 安裝 Agent Sandbox:在叢集上安裝核心 agent-sandbox 元件和擴充功能。
  4. 設定儲存空間和權限:建立 Cloud Storage bucket 並設定 Workload Identity 權限,以便安全地儲存快照。
  5. 設定 Pod 快照:建立並套用快照儲存空間設定、快照政策、沙箱範本和暖集區。
  6. 建構用戶端應用程式:建構用戶端應用程式的容器映像檔,並推送至 Artifact Registry 存放區。
  7. 執行測試:部署用戶端應用程式 Pod,建立沙箱、暫停沙箱以擷取快照、恢復沙箱,並驗證計數器的狀態是否已成功還原。

建立叢集

建立新的 GKE 叢集,並啟用 Pod 快照功能。如要確保功能完全相容,請指定快速發布管道。

Autopilot

建立 Autopilot 叢集,並啟用必要功能:

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

標準

建立具備所需功能的 Standard 叢集。為降低成本,下列指令會建立可用區叢集,在單一可用區中執行控制層和節點:

gcloud 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=${ZONE}

建立已啟用 gVisor 的節點集區:

gcloud container node-pools create gvisor-pool \
   --cluster ${CLUSTER_NAME} \
   --machine-type=${MACHINE_TYPE} \
   --num-nodes=1 \
   --location=${ZONE} \
   --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 核心元件和擴充功能。本教學課程使用 1.0.6 版:

# Install the core agent-sandbox components and the extensions (e.g., Warm Pools, Claims)
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v1.0.6/sandbox-with-extensions.yaml

如要進一步瞭解如何安裝 Agent Sandbox,請參閱「在 GKE 上安裝 Agent Sandbox」。

設定儲存空間和權限

設定 Cloud Storage bucket 來儲存 Pod 快照,並將必要的 Workload Identity 權限授予 snapshot-sa 服務帳戶和 GKE 服務代理。這樣一來,沙箱化工作負載就能安全地儲存及擷取快照物件:

  1. 建立新的 Cloud Storage bucket:

    gcloud storage buckets create "gs://${BUCKET_NAME}" \
        --uniform-bucket-level-access \
        --enable-hierarchical-namespace \
        --soft-delete-duration=0d \
        --location="${BUCKET_LOCATION}"
    
  2. 在 default 命名空間中建立 Kubernetes 服務帳戶。您的沙箱應用程式 (Python 計數器指令碼) 會使用這個身分向外部 API 驗證,並安全地存取儲存在 Cloud Storage 物件儲存庫中的快照物件:

    kubectl create serviceaccount "snapshot-sa" \
        --namespace "default"
    
  3. 使用 Workload Identity 將 storage.bucketViewer 角色繫結至服務帳戶。這個角色可讓沙箱化工作負載列出 bucket 內容,並找出特定快照:

    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 角色繫結至服務帳戶。這個角色具備讀取、儲存及刪除 bucket 內快照二進位物件的權限:

    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 服務代理權限,以便管理 (建立、列出、讀取及刪除) bucket 中的快照物件:

    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 bucket。
  • PodSnapshotPolicy:定義手動觸發快照的方式、分組頻率和保留政策。
  • SandboxTemplate:定義基礎容器、節點選取器,以及用於執行隔離沙箱工作負載的服務帳戶。
  • SandboxWarmPool:保留從 SandboxTemplate 資源建立的預熱沙箱,供用戶端應用程式宣告。
  1. 為測試用戶端檔案建立新目錄:

    mkdir test_client
    
  2. 前往新目錄:

    cd test_client
    
  3. 建立名為 snapshot_storage_config.yaml 的檔案。這項設定會指定目標 Cloud Storage bucket,叢集會將二進位 Pod 快照狀態儲存至該 bucket:

    apiVersion: podsnapshot.gke.io/v1
    kind: PodSnapshotStorageConfig
    metadata:
      name: example-pod-snapshot-storage-config
    spec:
      snapshotStorageConfig:
        gcs:
          bucket: "$BUCKET_NAME"
    
  4. 替換設定檔中的環境變數預留位置:

    sed -i "s/\$BUCKET_NAME/${BUCKET_NAME}/g" snapshot_storage_config.yaml
    
  5. 套用儲存空間設定資訊清單:

    kubectl apply -f snapshot_storage_config.yaml
    
  6. 等待儲存空間設定就緒:

    kubectl wait --for=condition=Ready podsnapshotstorageconfig/example-pod-snapshot-storage-config --timeout=60s
    
  7. 建立名為 snapshot_policy.yaml 的檔案。這項設定會建立保留規則,最多保留兩個沙箱工作負載的快照。觸發條件類型設為 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
    
  8. 套用快照政策資訊清單:

    kubectl apply -f snapshot_policy.yaml
    
  9. 建立名為 python-counter-template.yaml 的檔案。這項設定會定義沙箱 Pod,並為其指派 snapshot-sa 服務帳戶身分。這項指派作業有助於確保沙箱安全執行。在該 Pod 中,沙箱應用程式 (Python 指令碼) 會持續將遞增計數器輸出至容器記錄:

    apiVersion: extensions.agents.x-k8s.io/v1beta1
    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)
    
  10. 套用沙箱範本資訊清單:

    kubectl apply -f python-counter-template.yaml
    
  11. 建立名為 python-counter-pool.yaml 的檔案。這項設定會建立暖集區,其中包含一個預熱沙箱 (根據 python-counter-template 範本建立),可供用戶端應用程式宣告:

    apiVersion: extensions.agents.x-k8s.io/v1beta1
    kind: SandboxWarmPool
    metadata:
      name: python-counter-pool
      namespace: default
    spec:
      replicas: 1
      sandboxTemplateRef:
        name: python-counter-template
    
  12. 套用暖集區資訊清單:

    kubectl apply -f python-counter-pool.yaml
    

建構用戶端應用程式

建立用戶端應用程式的容器映像檔,並上傳至 Artifact Registry。

  1. 建立名為 Dockerfile.client 的檔案。這個檔案定義了用戶端應用程式的 Python 執行階段環境和依附元件:

    FROM python:3.13-slim
    
    WORKDIR /app
    
    RUN pip install "k8s-agent-sandbox[tracing]==1.0.6"
    
    # Copy test script
    COPY client_test.py /app/client_test.py
    
    CMD ["python", "/app/client_test.py"]
    
  2. 建立名為 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_count(sandbox):
        # The sandbox's Pod has the same name as the Sandbox resource.
        pod_name = sandbox.get_pod_name()
        if not pod_name:
            logging.error(f"Could not determine pod name for sandbox {sandbox.sandbox_id}")
            return None
        return get_last_count(pod_name, sandbox.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(warmpool="python-counter-pool", 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)
        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)
        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 從遠端建構及推送映像檔:

    Docker

    1. 為 Artifact Registry 設定 Docker 驗證:

      gcloud auth configure-docker "${LOCATION}-docker.pkg.dev"
      
    2. 在本機建構並推送用戶端容器映像檔:

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

    Cloud Build

    1. 建立名為 cloudbuild.yaml 的檔案:

      steps:
      - name: 'gcr.io/cloud-builders/docker'
        args: ['build', '-t', '$LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY_NAME/sandbox-client:latest', '-f', 'Dockerfile.client', '.']
      images:
      - '$LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY_NAME/sandbox-client:latest'
      
    2. 替換設定檔中的環境變數預留位置:

      sed -i "s/\$REPOSITORY_NAME/${REPOSITORY_NAME}/g" cloudbuild.yaml
      sed -i "s/\$LOCATION/${LOCATION}/g" cloudbuild.yaml
      sed -i "s/\$PROJECT_ID/${PROJECT_ID}/g" cloudbuild.yaml
      
    3. 建立 Cloud Build 記錄檔的 Cloud Storage bucket。如果專案先前曾使用 Cloud Build,這個 bucket 可能已存在,指令會失敗並顯示錯誤,指出您已擁有該 bucket。您可以放心忽略這項錯誤:

      gcloud storage buckets create "gs://${CLOUDBUILD_BUCKET_NAME}" \
          --location="${BUCKET_LOCATION}"
      
    4. 將必要權限授予 Cloud Build 服務帳戶。

      下列指令會使用 Compute Engine 預設服務帳戶 ($PROJECT_NUMBER-compute@developer.gserviceaccount.com),這是大多數專案的預設帳戶。如果專案使用舊版 Cloud Build 服務帳戶,請將下列指令中的成員電子郵件地址替換為 $PROJECT_NUMBER@cloudbuild.gserviceaccount.com。

      由於您在本教學課程稍早為專案新增了條件式 IAM 繫結,因此在新增這些新繫結時,應加入 --condition=None 旗標。這樣一來,gcloud 就不會暫停並要求互動式確認:

      gcloud projects add-iam-policy-binding ${PROJECT_ID} \
         --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
         --role="roles/artifactregistry.writer" \
         --condition=None
      
      gcloud projects add-iam-policy-binding ${PROJECT_ID} \
         --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
         --role="roles/logging.logWriter" \
         --condition=None
      
      gcloud storage buckets add-iam-policy-binding "gs://${CLOUDBUILD_BUCKET_NAME}" \
         --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
         --role="roles/storage.objectAdmin"
      
    5. 使用 Cloud Build 執行建構作業:

      gcloud builds submit --config cloudbuild.yaml
      

執行測試

部署用戶端應用程式,建立沙箱、觸發快照,並確認內部計數器成功從儲存狀態恢復。

  1. 建立名為 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 client_sa.yaml
    
  3. 建立名為 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" client_pod.yaml
    sed -i "s/\$LOCATION/${LOCATION}/g" client_pod.yaml
    sed -i "s/\$PROJECT_ID/${PROJECT_ID}/g" client_pod.yaml
    
  5. 等待暖集區的沙箱準備就緒:

    kubectl wait sandboxwarmpool python-counter-pool \
        --for=jsonpath='{.status.readyReplicas}'=1 \
        --timeout=600s
    

    在下一個步驟中,用戶端應用程式會建立 SandboxClaim 資源。如果應用程式在集區的沙箱準備就緒前建立宣告,控制器可能會為宣告建立新的沙箱,而不是從集區指派沙箱。在 Autopilot 叢集中,沙箱可能需要幾分鐘才能準備就緒,因為 GKE 可能需要為其建立節點。

  6. 套用用戶端應用程式 Pod 資訊清單:

    kubectl apply -f client_pod.yaml
    
  7. 串流 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).

輸出內容顯示,沙箱在暫停和恢復時成功保留狀態。沙箱暫停時,計數器會停止前進,因為 Agent Sandbox 控制器會刪除沙箱的 Pod。沙箱恢復運作時,控制器會重新建立 Pod,並從快照還原,計數器也會繼續計數。如果不暫停,計數器在暫停期間會繼續增加,計數值會明顯偏高。

清除所用資源

如要避免系統向您的 Google Cloud 帳戶收費,請刪除您建立的資源:

  1. 刪除 GKE 叢集。這也會刪除節點集區和其中的所有 Kubernetes 服務帳戶:

    Autopilot

    gcloud container clusters delete ${CLUSTER_NAME} \
        --location="${LOCATION}" --quiet
    

    標準

    gcloud container clusters delete ${CLUSTER_NAME} \
        --location="${ZONE}" --quiet
    
  2. 刪除 Artifact Registry 存放區,移除為測試映像檔建立的 Docker 存放區:

    gcloud artifacts repositories delete ${REPOSITORY_NAME} --location="${LOCATION}" --quiet
    
  3. 刪除 Cloud Storage bucket 和當中的所有快照。這會自動移除套用至該 bucket 的 bucket 層級 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. 如果您使用 Cloud Build (而非 Docker) 建構及推送容器映像檔,請清除 Cloud Build 資源。請只刪除您為本教學課程建立的資源,因為專案中的其他工作負載可能依賴先前存在的資源:

    • 如果開始本教學課程前,Cloud Build bucket 尚未存在,請刪除該 bucket:

      gcloud storage rm --recursive "gs://${CLOUDBUILD_BUCKET_NAME}"
      
    • 如果 Compute Engine 預設服務帳戶在您開始本教學課程前,沒有 roles/artifactregistry.writer 和 roles/logging.logWriter 角色,請移除這些角色:

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

後續步驟