Activa instantáneas de Agent Sandbox en GKE desde un clúster

Usa instantáneas de Agent Sandbox en Google Kubernetes Engine (GKE) dentro de un clúster para conservar el estado de tus entornos de zona de pruebas. Este enfoque proporciona una depuración coherente, acelera la iteración del desarrollo y facilita la recuperación rápida de configuraciones complejas. En este instructivo, implementarás y probarás esta capacidad ejecutando una aplicación cliente que crea, pausa y reanuda entornos de zona de pruebas dentro del clúster de manera programática.

Para obtener más información sobre cómo tomar instantáneas de Pods, consulta Restablece a partir de una instantánea de Pod.

Costos

Agent Sandbox en GKE es un software de código abierto que puedes usar sin cargo adicional. Los precios de GKE se aplican a los recursos que creas, incluidos los recursos de procesamiento que usa el controlador de Agent Sandbox.

Antes de comenzar

  1. En la consola de Google Cloud , en la página del selector de proyectos, selecciona o crea un proyecto de Google Cloud .

    Roles necesarios para seleccionar o crear un proyecto

    • Selecciona un proyecto: Para seleccionar un proyecto, no se requiere un rol de IAM específico. Puedes seleccionar cualquier proyecto en el que se te haya otorgado un rol.
    • Crea un proyecto: Para crear un proyecto, necesitas el rol de creador de proyectos (roles/resourcemanager.projectCreator), que contiene el permiso resourcemanager.projects.create. Obtén más información para otorgar roles.

    Ir al selector de proyectos

  2. Verifica que la facturación esté habilitada para tu proyecto de Google Cloud .

  3. Habilita las APIs de Artifact Registry, Kubernetes Engine y Cloud Build, si aún no están habilitadas.

    Roles necesarios para habilitar las APIs

    Para habilitar las APIs, necesitas el permiso serviceusage.services.enable. Si creaste el proyecto, es probable que ya tengas este permiso a través del rol de propietario (roles/owner). De lo contrario, puedes obtenerlo a través del rol de administrador de Service Usage (roles/serviceusage.serviceUsageAdmin). Obtén más información para otorgar roles.

    Habilitar las API

  4. En la consola de Google Cloud , activa Cloud Shell.

    Activa Cloud Shell

  5. Verifica que tengas los permisos necesarios para completar este instructivo.

Roles obligatorios

Para obtener los permisos que necesitas para crear y administrar entornos de pruebas, pídele a tu administrador que te otorgue el rol de IAM de administrador de Kubernetes Engine (roles/container.admin) en tu proyecto. Para obtener más información sobre cómo otorgar roles, consulta Administra el acceso a proyectos, carpetas y organizaciones.

También puedes obtener los permisos necesarios mediante roles personalizados o cualquier otro rol predefinido.

Limitaciones

Las instantáneas de Pods requieren gVisor y no funcionan con sandboxes de microVM.

En un clúster regional, los nodos de diferentes zonas pueden tener distintas microarquitecturas de CPU. Debido a que las instantáneas capturan el estado de la CPU, el restablecimiento de una instantánea en un nodo con funciones de CPU faltantes falla (por ejemplo, con el error OCI runtime restore failed: incompatible FeatureSet).

Para evitar este problema, usa la configuración adecuada para tu entorno:

  • Producción: Para preservar la alta disponibilidad en todo el clúster, no fijes las cargas de trabajo en una zona específica. En su lugar, ayuda a garantizar la coherencia de las funciones de CPU en todas las zonas especificando una plataforma de CPU mínima. Para obtener más información, consulta Elige una plataforma de CPU mínima.
  • Pruebas: Para simplificar la configuración y evitar errores iniciales de discrepancia de CPU, puedes usar un campo nodeSelector en tu manifiesto SandboxTemplate para fijar el Pod a una zona específica, como us-central1-a. En el ejemplo de este instructivo, se usa esta configuración de prueba.

Define las variables de entorno

Para simplificar los comandos que ejecutas en este instructivo, puedes configurar variables de entorno en Cloud Shell. En Cloud Shell, define las siguientes variables de entorno útiles ejecutando los siguientes comandos:

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"

A continuación, se explica cada una de estas variables de entorno:

  • PROJECT_ID: Es el ID de tu proyecto de Google Cloud actual. Los comandos de este documento usan este ID para nombrar recursos y otorgar permisos de IAM.
  • PROJECT_NUMBER: Es el número de tu proyecto de Google Cloud actual.
  • CLUSTER_NAME: Es el nombre de tu clúster de GKE, por ejemplo, test-snapshot.
  • LOCATION: Es la región de Google Cloud para tu repositorio de Artifact Registry (por ejemplo, us-central1). Si creas un clúster de Autopilot, el clúster también usa esta región.
  • ZONE: Es la zona del clúster. Si creas un clúster de Standard, establece este valor en una zona de la región LOCATION (por ejemplo, us-central1-a). La plantilla de zona de pruebas que crees más adelante en este instructivo ejecuta zonas de pruebas en us-central1-a. Si usas una zona diferente, también cámbiala en la plantilla.
  • BUCKET_LOCATION: Es la ubicación de tus buckets de Cloud Storage, por ejemplo, us.
  • BUCKET_NAME: Es el nombre del bucket de Cloud Storage que se usa para las instantáneas, por ejemplo, PROJECT_ID_snapshots. Los nombres de los buckets deben ser únicos a nivel global.
  • CLOUDBUILD_BUCKET_NAME: Es el nombre del bucket de Cloud Storage que se usa para los registros de Cloud Build. Este valor es el nombre predeterminado del bucket de Cloud Build, así que no lo cambies.
  • MACHINE_TYPE: Es el tipo de máquina que se usará para los nodos del clúster y el grupo de nodos de gVisor, por ejemplo, n2-standard-2. Las instantáneas de Pods no admiten tipos de máquinas E2.
  • REPOSITORY_NAME: Es el nombre del repositorio de Artifact Registry, por ejemplo, agent-sandbox.

Descripción general de los pasos de configuración

Para habilitar y probar las instantáneas de Pods de Agent Sandbox en entornos de GKE desde tu clúster, debes realizar varios pasos de configuración. Para comprender estos pasos, primero es útil entender los componentes involucrados en el flujo de trabajo general.

Componentes clave

En este instructivo, se usan las siguientes dos aplicaciones de Python para probar el proceso de instantánea:

  • Aplicación cliente: Es una secuencia de comandos de Python que se ejecuta en un Pod estándar en tu clúster. Esta aplicación administra el ciclo de vida de la zona de pruebas: crea la zona de pruebas de manera programática, la pausa para activar una instantánea, la reanuda y verifica que se haya conservado el estado. En este instructivo, crearás una cuenta de servicio de Kubernetes llamada agent-sandbox-client-sa y le otorgarás permisos de RBAC para que el Pod de la aplicación cliente pueda administrar recursos personalizados de la zona de pruebas y objetos activadores de instantáneas con la API de Kubernetes.
  • Aplicación de zona de pruebas: Es una secuencia de comandos de Python que incrementa y, luego, imprime un contador cada segundo. Esta aplicación se ejecuta de forma segura dentro del entorno de zona de pruebas aislado para generar un estado cambiante que la aplicación cliente pueda verificar. En este instructivo, crearás una cuenta de servicio de Kubernetes dedicada llamada snapshot-sa y configurarás Workload Identity para autorizar al Pod de zona de pruebas a leer y escribir objetos de instantáneas de forma segura en Cloud Storage.

Proceso de configuración y prueba

En la siguiente lista, se resumen los pasos que debes seguir para configurar tu entorno y ejecutar la prueba:

  1. Crea un clúster: Crea un clúster de Autopilot o Standard con las instantáneas de Pods habilitadas.
  2. Crea un repositorio de Artifact Registry: Crea un repositorio de Docker para almacenar la imagen de contenedor de tu aplicación cliente.
  3. Instala Agent Sandbox: Instala los componentes y las extensiones principales de agent-sandbox en tu clúster.
  4. Configura el almacenamiento y los permisos: Crea un bucket de Cloud Storage y configura los permisos de Workload Identity para permitir que las instantáneas se guarden de forma segura.
  5. Configura instantáneas de Pods: crea y aplica la configuración de almacenamiento de instantáneas, la política de instantáneas, la plantilla de la zona de pruebas y el grupo de preparación.
  6. Compila la aplicación cliente: Compila la imagen de contenedor para la aplicación cliente y envíala a tu repositorio de Artifact Registry.
  7. Ejecuta la prueba: Implementa el Pod de la aplicación cliente, que crea la zona de pruebas, la pausa para capturar una instantánea, la reanuda y verifica que el estado del contador se haya restablecido correctamente.

Crea un clúster

Crea un clúster de GKE nuevo con las instantáneas de Pods habilitadas. Para obtener compatibilidad total con las funciones, especifica el canal de versiones rápido.

Autopilot

Crea un clúster de Autopilot con las funciones requeridas:

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

Estándar

Crea un clúster de Standard con las funciones requeridas. Para reducir los costos, el siguiente comando crea un clúster zonal, que ejecuta su plano de control y sus nodos en una zona:

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}

Crea un grupo de nodos con gVisor habilitado:

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

Crea un repositorio de Artifact Registry

Crea un repositorio de Docker en Artifact Registry para almacenar la imagen de contenedor de tu aplicación cliente (la aplicación que crea y administra la zona de pruebas):

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

Instala Agent Sandbox

Instala los componentes principales y las extensiones de Agent Sandbox en tu clúster. En este instructivo, se usa la versión 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

Para obtener más información sobre cómo instalar Agent Sandbox, consulta Instala Agent Sandbox en GKE.

Configura el almacenamiento y los permisos

Configura un bucket de Cloud Storage para almacenar instantáneas de Pods y otorga los permisos de Workload Identity necesarios a la cuenta de servicio de snapshot-sa y al agente de servicio de GKE. Esto permite que tus cargas de trabajo de zona de pruebas guarden y recuperen objetos de instantáneas de forma segura:

  1. Crea un bucket de Cloud Storage nuevo:

    gcloud storage buckets create "gs://${BUCKET_NAME}" \
        --uniform-bucket-level-access \
        --enable-hierarchical-namespace \
        --soft-delete-duration=0d \
        --location="${BUCKET_LOCATION}"
    
  2. Crea una cuenta de servicio de Kubernetes en el espacio de nombres default. Tu aplicación de zona de pruebas (la secuencia de comandos del contador de Python) usa esta identidad para autenticarse en las APIs externas y acceder de forma segura a los objetos de instantánea almacenados en Cloud Storage:

    kubectl create serviceaccount "snapshot-sa" \
        --namespace "default"
    
  3. Vincula el rol storage.bucketViewer a tu cuenta de servicio con Workload Identity. Este rol permite que la carga de trabajo de la zona de pruebas enumere el contenido del bucket y ubique instantáneas específicas:

    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. Vincula el rol storage.objectUser a tu cuenta de servicio con Workload Identity. Este rol proporciona el permiso para leer, guardar y borrar objetos binarios de instantáneas dentro del 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. Otorga permisos al agente de servicio de GKE para administrar (crear, enumerar, leer y borrar) objetos de instantáneas dentro del 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"
    

Configura instantáneas de Pods

Crea y aplica los archivos de configuración para instalar los recursos personalizados de Kubernetes necesarios. Estos recursos definen cómo el clúster almacena y administra las instantáneas de Pods:

  • PodSnapshotStorageConfig: Especifica el bucket de Cloud Storage designado para almacenar objetos binarios de instantáneas.
  • PodSnapshotPolicy: Define cómo se activan las instantáneas de forma manual, con qué frecuencia se agrupan y sus políticas de retención.
  • SandboxTemplate: Define el contenedor subyacente, los selectores de nodos y las cuentas de servicio para ejecutar la carga de trabajo aislada de la zona de pruebas.
  • SandboxWarmPool: Mantiene una zona de pruebas precalentada, creada a partir del recurso SandboxTemplate, lista para que la aplicación cliente la reclame.
  1. Crea un directorio nuevo para los archivos del cliente de prueba:

    mkdir test_client
    
  2. Navega al directorio nuevo:

    cd test_client
    
  3. Crea un archivo llamado snapshot_storage_config.yaml. Esta configuración especifica el bucket de Cloud Storage de destino en el que el clúster guarda el estado de la instantánea binaria del Pod:

    apiVersion: podsnapshot.gke.io/v1
    kind: PodSnapshotStorageConfig
    metadata:
      name: example-pod-snapshot-storage-config
    spec:
      snapshotStorageConfig:
        gcs:
          bucket: "$BUCKET_NAME"
    
  4. Sustituye el marcador de posición de la variable de entorno en el archivo de configuración:

    sed -i "s/\$BUCKET_NAME/${BUCKET_NAME}/g" snapshot_storage_config.yaml
    
  5. Aplica el manifiesto de configuración de almacenamiento:

    kubectl apply -f snapshot_storage_config.yaml
    
  6. Espera a que la configuración de almacenamiento esté lista:

    kubectl wait --for=condition=Ready podsnapshotstorageconfig/example-pod-snapshot-storage-config --timeout=60s
    
  7. Crea un archivo llamado snapshot_policy.yaml. Esta configuración establece una regla de retención que conserva un máximo de dos instantáneas para tu carga de trabajo de zona de pruebas. El tipo de activador se establece en manual: Esto permite que la aplicación cliente controle las instantáneas a pedido:

    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. Aplica el manifiesto de la política de instantáneas:

    kubectl apply -f snapshot_policy.yaml
    
  9. Crea un archivo llamado python-counter-template.yaml. Esta configuración define el Pod de la zona de pruebas y le asigna la identidad de la cuenta de servicio snapshot-sa. Esta asignación ayuda a garantizar que la zona de pruebas se ejecute de forma segura. Dentro de ese Pod, la aplicación de zona de pruebas (una secuencia de comandos de Python) imprime de forma continua un contador incremental en los registros del contenedor:

    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. Aplica el manifiesto de la plantilla de la zona de pruebas:

    kubectl apply -f python-counter-template.yaml
    
  11. Crea un archivo llamado python-counter-pool.yaml. Esta configuración crea un grupo de preparación que mantiene una zona de pruebas preparada previamente, creada a partir de la plantilla python-counter-template, lista para que la aplicación cliente la reclame:

    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. Aplica el manifiesto del grupo de preparación:

    kubectl apply -f python-counter-pool.yaml
    

Crear la aplicación cliente

Crea la imagen de contenedor para la aplicación cliente y súbela a Artifact Registry.

  1. Crea un archivo llamado Dockerfile.client. En este archivo, se definen el entorno de ejecución de Python y las dependencias de la aplicación cliente:

    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. Crea un archivo llamado client_test.py. Esta secuencia de comandos administra el ciclo de vida de la zona de pruebas y verifica que el estado se reanude correctamente después de tomar una instantánea:

    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. Compila la imagen de contenedor del cliente y súbela a Artifact Registry. Si tu entorno (como Cloud Shell) tiene Docker instalado, puedes usar Docker para compilar la imagen de forma local. Si trabajas en un entorno sin Docker, puedes usar Cloud Build para compilar y enviar la imagen de forma remota:

    Docker

    1. Configura la autenticación de Docker para Artifact Registry:

      gcloud auth configure-docker "${LOCATION}-docker.pkg.dev"
      
    2. Compila y envía la imagen de contenedor del cliente de forma local:

      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. Crea un archivo llamado 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. Sustituye los marcadores de posición de las variable de entorno en el archivo de configuración:

      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. Crea el bucket de Cloud Storage para los registros de Cloud Build. Si tu proyecto ya usó Cloud Build, es posible que este bucket ya exista y que el comando falle con un error que indica que ya eres propietario del bucket. Puedes ignorar este error sin problemas:

      gcloud storage buckets create "gs://${CLOUDBUILD_BUCKET_NAME}" \
          --location="${BUCKET_LOCATION}"
      
    4. Otorga los permisos necesarios a la cuenta de servicio de Cloud Build.

      En los siguientes comandos, se usa la cuenta de servicio predeterminada de Compute Engine ($PROJECT_NUMBER-compute@developer.gserviceaccount.com), que es la predeterminada para la mayoría de los proyectos. Si tu proyecto usa la cuenta de servicio heredada de Cloud Build, reemplaza la dirección de correo electrónico del miembro en los siguientes comandos por $PROJECT_NUMBER@cloudbuild.gserviceaccount.com.

      Como agregaste una vinculación de IAM condicional a tu proyecto antes en este instructivo, debes incluir la marca --condition=None cuando agregues estas vinculaciones nuevas. Esto evita que gcloud se detenga para solicitar una confirmación interactiva:

      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. Ejecuta la compilación con Cloud Build:

      gcloud builds submit --config cloudbuild.yaml
      

Ejecuta la prueba

Implementa la aplicación cliente para crear la zona de pruebas, activar una instantánea y verificar que el contador interno se reanude correctamente desde su estado guardado.

  1. Crea un archivo llamado client_sa.yaml. En este manifiesto, se define la cuenta de servicio agent-sandbox-client-sa y sus permisos de RBAC necesarios para administrar los recursos personalizados de la zona de pruebas:

    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. Aplica la cuenta de servicio del cliente y el manifiesto de RBAC:

    kubectl apply -f client_sa.yaml
    
  3. Crea un archivo llamado client_pod.yaml. Este manifiesto crea el Pod de la aplicación cliente con la imagen de contenedor compilada previamente:

    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. Sustituye los marcadores de posición de las variable de entorno en el manifiesto:

    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. Espera hasta que la zona de pruebas del grupo de preparación esté lista:

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

    En el siguiente paso, la aplicación cliente crea un recurso SandboxClaim. Si la aplicación crea la reclamación antes de que la zona de pruebas del grupo esté lista, el controlador podría crear una zona de pruebas nueva para la reclamación en lugar de asignar la del grupo. En un clúster de Autopilot, la zona de pruebas puede tardar varios minutos en estar lista, ya que es posible que GKE necesite crear un nodo para ella.

  6. Aplica el manifiesto del Pod de la aplicación cliente:

    kubectl apply -f client_pod.yaml
    
  7. Transmite los registros del Pod para verificar el flujo de ejecución:

    kubectl logs -f agent-sandbox-client-pod
    

Cuando la prueba se ejecuta correctamente, el resultado es similar al siguiente (se acortó para facilitar la lectura):

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).

El resultado muestra que la zona de pruebas conserva correctamente su estado cuando se suspende y se reanuda. El contador deja de avanzar mientras la zona de pruebas está suspendida, ya que el controlador de Agent Sandbox borra el Pod de la zona de pruebas. Cuando se reanuda la zona de pruebas, el controlador vuelve a crear el Pod, que se restablece a partir de la instantánea, y el contador continúa. Sin la suspensión, el contador habría seguido avanzando durante el período de suspensión y el recuento sería significativamente mayor.

Limpia los recursos

Para evitar que se apliquen cargos a tu cuenta de Google Cloud , borra los recursos que creaste:

  1. Borra el clúster de GKE. Esto también borra el grupo de nodos y todas las cuentas de servicio de Kubernetes que contiene:

    Autopilot

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

    Estándar

    gcloud container clusters delete ${CLUSTER_NAME} \
        --location="${ZONE}" --quiet
    
  2. Borra el repositorio de Artifact Registry para quitar el repositorio de Docker que creaste para la imagen de prueba:

    gcloud artifacts repositories delete ${REPOSITORY_NAME} --location="${LOCATION}" --quiet
    
  3. Borra el bucket de Cloud Storage y todas las instantáneas que contiene. Esto quita automáticamente las vinculaciones de IAM de Workload Identity a nivel de bucket que se le aplicaron:

    gcloud storage rm --recursive "gs://${BUCKET_NAME}"
    
  4. Quita la vinculación de IAM a nivel de proyecto para el agente de servicio de GKE:

    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. Si usaste Cloud Build en lugar de Docker para compilar y enviar la imagen de contenedor, limpia los recursos de Cloud Build. Borra solo los recursos que creaste para este instructivo, ya que otras cargas de trabajo de tu proyecto podrían depender de recursos que existían antes:

    • Si el bucket de Cloud Build no existía antes de comenzar este instructivo, bórralo:

      gcloud storage rm --recursive "gs://${CLOUDBUILD_BUCKET_NAME}"
      
    • Si la cuenta de servicio predeterminada de Compute Engine no tenía los roles roles/artifactregistry.writer y roles/logging.logWriter antes de comenzar este instructivo, quítalos:

      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
      

¿Qué sigue?