Déclencher des instantanés Agent Sandbox depuis un cluster

Utilisez des instantanés Agent Sandbox dans un cluster Google Kubernetes Engine (GKE) pour préserver l'état de vos environnements sandbox. Cette approche permet un débogage cohérent, accélère l'itération du développement et facilite la récupération rapide des configurations complexes. Dans ce tutoriel, vous allez déployer et tester cette fonctionnalité en exécutant une application cliente qui crée, met en veille et reprend des environnements de bac à sable de manière programmatique à l'intérieur du cluster.

Pour en savoir plus sur la création d'instantanés de pods, consultez Restaurer à partir d'un instantané de pod.

Coûts

Agent Sandbox est proposé sans frais supplémentaires dans GKE. La tarification GKE s'applique aux ressources que vous créez.

Avant de commencer

  1. Dans la console Cloud de Confiance , sur la page de sélection du projet, sélectionnez ou créez un projet Cloud de Confiance .

    Rôles requis pour sélectionner ou créer un projet

    • Sélectionnez un projet : la sélection d'un projet ne nécessite pas de rôle IAM spécifique. Vous pouvez sélectionner n'importe quel projet pour lequel un rôle vous a été attribué.
    • Créer un projet : pour créer un projet, vous devez disposer du rôle Créateur de projet (roles/resourcemanager.projectCreator), qui contient l'autorisation resourcemanager.projects.create. Découvrez comment attribuer des rôles.

    Accéder au sélecteur de projet

  2. Vérifiez que la facturation est activée pour votre projet Cloud de Confiance .

  3. Activez les API Artifact Registry, Kubernetes Engine et Cloud Build, si ce n'est pas déjà fait.

    Rôles requis pour activer les API

    Pour activer les API, vous devez disposer de l'autorisation serviceusage.services.enable. Si vous avez créé le projet, vous disposez probablement déjà de cette autorisation grâce au rôle Propriétaire (roles/owner). Sinon, vous pouvez obtenir cette autorisation grâce au rôle Administrateur Service Usage (roles/serviceusage.serviceUsageAdmin). Découvrez comment attribuer des rôles.

    Activer les API

  4. Dans la console Cloud de Confiance , activez Cloud Shell.

    Activer Cloud Shell

  5. Vérifiez que vous disposez des autorisations requises pour suivre ce tutoriel.

Rôles requis

Pour obtenir les autorisations nécessaires pour créer et gérer des bacs à sable, demandez à votre administrateur de vous accorder le rôle IAM Administrateur Kubernetes Engine (roles/container.admin) sur votre projet. Pour en savoir plus sur l'attribution de rôles, consultez Gérer l'accès aux projets, aux dossiers et aux organisations.

Vous pouvez également obtenir les autorisations requises via des rôles personnalisés ou d'autres rôles prédéfinis.

Limites

Dans un cluster régional, les nœuds de différentes zones peuvent avoir des microarchitectures de processeur différentes. Étant donné que les instantanés capturent l'état du processeur, la restauration d'un instantané sur un nœud dont il manque des fonctionnalités de processeur échoue (par exemple, avec l'erreur OCI runtime restore failed: incompatible FeatureSet).

Pour éviter ce problème, utilisez la configuration appropriée pour votre environnement :

  • Production : pour préserver la haute disponibilité dans votre cluster, n'épinglez pas les charges de travail à une zone spécifique. Spécifiez plutôt une plate-forme de processeur minimale pour garantir la cohérence des fonctionnalités du processeur dans toutes les zones. Pour en savoir plus, consultez Choisir une configuration minimum de plate-forme de processeur.
  • Test : pour simplifier la configuration et éviter les erreurs initiales d'incompatibilité du processeur, vous pouvez utiliser un champ nodeSelector dans votre fichier manifeste SandboxTemplate afin d'épingler le pod à une zone spécifique, telle que us-central1-a. L'exemple de ce tutoriel utilise cette configuration de test.

Définir des variables d'environnement

Pour simplifier les commandes que vous exécutez dans ce tutoriel, vous pouvez définir des variables d'environnement dans Cloud Shell. Dans Cloud Shell, définissez les variables d'environnement utiles suivantes en exécutant les commandes suivantes :

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"

Voici une explication de ces variables d'environnement :

  • PROJECT_ID : ID de votre projet Cloud de Confiance by S3NS actuel. La définition de cette variable permet de s'assurer que toutes les ressources sont créées dans le bon projet.
  • PROJECT_NUMBER : numéro de votre projet actuel Cloud de Confiance by S3NS .
  • CLUSTER_NAME : nom de votre cluster GKE, par exemple test-snapshot.
  • LOCATION : région Cloud de Confiance by S3NS où se trouvent votre cluster GKE et votre dépôt Artifact Registry (par exemple, us-central1).
  • BUCKET_LOCATION : emplacement de vos buckets Cloud Storage (par exemple, us).
  • BUCKET_NAME : nom du bucket Cloud Storage utilisé pour les instantanés.
  • CLOUDBUILD_BUCKET_NAME : nom du bucket Cloud Storage utilisé pour les journaux Cloud Build.
  • MACHINE_TYPE : type de machine à utiliser pour les nœuds du cluster (par exemple, e2-standard-8).
  • REPOSITORY_NAME : nom du dépôt Artifact Registry (par exemple, agent-sandbox).

Présentation des étapes de configuration

Pour activer et tester les snapshots de pods des environnements Agent Sandbox depuis votre cluster, vous devez effectuer plusieurs étapes de configuration. Pour comprendre ces étapes, il est utile de commencer par identifier les composants impliqués dans le workflow global.

Composants clés

Ce tutoriel utilise les deux applications Python suivantes pour tester le processus de création d'instantanés :

  • Application cliente : script Python s'exécutant dans un pod standard de votre cluster. Cette application gère le cycle de vie du bac à sable : elle crée le bac à sable de manière programmatique, le met en veille pour déclencher un instantané, le réactive et vérifie que l'état a été conservé. Dans ce tutoriel, vous allez créer un compte de service Kubernetes nommé agent-sandbox-client-sa et lui accorder des autorisations RBAC afin que le pod de l'application cliente puisse gérer les ressources personnalisées du bac à sable et les objets de déclencheur de snapshot à l'aide de l'API Kubernetes.
  • Application en bac à sable : script Python qui incrémente et affiche un compteur toutes les secondes. Cette application s'exécute de manière sécurisée dans l'environnement de bac à sable isolé pour générer un état changeant que l'application cliente peut vérifier. Dans ce tutoriel, vous allez créer un compte de service Kubernetes dédié nommé snapshot-sa et configurer Workload Identity pour autoriser le pod en bac à sable à lire et écrire des objets instantanés de manière sécurisée dans Cloud Storage.

Processus de configuration et de test

Voici les étapes à suivre pour configurer votre environnement et exécuter le test :

  1. Créer un cluster : créez un cluster Autopilot ou Standard avec les instantanés de pods et la fonctionnalité Agent Sandbox activés.
  2. Créer un dépôt Artifact Registry : créez un dépôt Docker pour stocker l'image de conteneur de votre application cliente.
  3. Installer Agent Sandbox : installez les composants et extensions agent-sandbox principaux sur votre cluster.
  4. Configurer le stockage et les autorisations : créez un bucket Cloud Storage et configurez les autorisations Workload Identity pour permettre l'enregistrement sécurisé des instantanés.
  5. Configurer les instantanés de pods : créez et appliquez la configuration de stockage des instantanés, la stratégie d'instantanés et le modèle de bac à sable.
  6. Créez l'application cliente : créez l'image de conteneur pour l'application cliente et transférez-la vers votre dépôt Artifact Registry.
  7. Exécutez le test : déployez le pod de l'application cliente, qui crée le bac à sable, le met en pause pour capturer un instantané, le reprend et vérifie que l'état du compteur a bien été restauré.

Créer un cluster

Créez un cluster GKE avec les instantanés de pods activés. Pour une compatibilité totale des fonctionnalités, spécifiez le version disponible rapide.

Autopilot

Créez un cluster Autopilot avec les fonctionnalités requises :

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

Standard

Créez un cluster Standard avec les fonctionnalités requises :

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

Créez un pool de nœuds avec gVisor activé :

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

Créer un dépôt Artifact Registry

Créez un dépôt Docker dans Artifact Registry pour stocker l'image de conteneur de votre application cliente (l'application qui crée et gère le bac à sable) :

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

Installer Agent Sandbox

Installez les composants et extensions principaux d'Agent Sandbox sur votre cluster (en utilisant la version v0.4.6 comme exemple) :

# 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

Configurer le stockage et les autorisations

Configurez un bucket Cloud Storage pour stocker les instantanés de pods et accordez les autorisations Workload Identity requises au compte de service snapshot-sa et à l'agent de service GKE. Cela permet à vos charges de travail en bac à sable d'enregistrer et de récupérer des objets d'instantané de manière sécurisée :

  1. Créez un bucket Cloud Storage :

    gcloud storage buckets create "gs://${BUCKET_NAME}" \
        --uniform-bucket-level-access \
        --enable-hierarchical-namespace \
        --soft-delete-duration=0d \
        --location="${BUCKET_LOCATION}"
    
  2. Créez un compte de service Kubernetes dans l'espace de noms default. Votre application en bac à sable (le script de compteur Python) utilise cette identité pour s'authentifier auprès des API externes et accéder de manière sécurisée aux objets instantanés stockés dans Cloud Storage :

    kubectl create serviceaccount "snapshot-sa" \
        --namespace "default"
    
  3. Associez le rôle storage.bucketViewer à votre compte de service à l'aide de Workload Identity. Ce rôle permet à la charge de travail en bac à sable de lister le contenu du bucket et de localiser des instantanés spécifiques :

    gcloud storage buckets add-iam-policy-binding "gs://${BUCKET_NAME}" \
        --member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT_ID}.s3ns.svc.id.goog/subject/ns/default/sa/snapshot-sa" \
        --role="roles/storage.bucketViewer"
    
  4. Associez le rôle storage.objectUser à votre compte de service à l'aide de Workload Identity. Ce rôle fournit l'autorisation de lire, d'enregistrer et de supprimer des objets binaires d'instantané dans le bucket :

    gcloud storage buckets add-iam-policy-binding "gs://${BUCKET_NAME}" \
        --member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT_ID}.s3ns.svc.id.goog/subject/ns/default/sa/snapshot-sa" \
        --role="roles/storage.objectUser"
    
  5. Accordez à l'agent de service GKE les autorisations nécessaires pour gérer (créer, lister, lire et supprimer) les objets instantanés dans le bucket :

    gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
        --member="serviceAccount:service-${PROJECT_NUMBER}@container-engine-robot.s3ns-system.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"
    

Configurer les instantanés de pods

Créez et appliquez les fichiers de configuration pour installer les ressources personnalisées Kubernetes requises. Ces ressources définissent la manière dont le cluster stocke et gère les instantanés de pods :

  • PodSnapshotStorageConfig : spécifie le bucket Cloud Storage désigné pour stocker les objets binaires des instantanés.
  • PodSnapshotPolicy : définit la manière dont les instantanés sont déclenchés manuellement, la fréquence à laquelle ils sont regroupés et leurs règles de conservation.
  • SandboxTemplate : définit le conteneur sous-jacent, les sélecteurs de nœuds et les comptes de service pour l'exécution de la charge de travail isolée dans le bac à sable.
  1. Créez un répertoire pour les fichiers du client de test :

    mkdir test_client
    
  2. Accédez au nouveau répertoire :

    cd test_client
    
  3. Créez un fichier nommé snapshot_storage_config.yaml. Cette configuration spécifie le bucket Cloud Storage cible dans lequel le cluster enregistre l'état binaire de l'instantané de pod :

    apiVersion: podsnapshot.gke.io/v1
    kind: PodSnapshotStorageConfig
    metadata:
      name: example-pod-snapshot-storage-config
    spec:
      snapshotStorageConfig:
        gcs:
          bucket: "$BUCKET_NAME"
    
  4. Remplacez l'espace réservé de la variable d'environnement dans le fichier de configuration :

    sed -i "s/\$BUCKET_NAME/$BUCKET_NAME/g" snapshot_storage_config.yaml
    
  5. Appliquez le fichier manifeste de configuration du stockage :

    kubectl apply -f snapshot_storage_config.yaml
    
  6. Attendez que la configuration du stockage soit prête :

    kubectl wait --for=condition=Ready podsnapshotstorageconfig/example-pod-snapshot-storage-config --timeout=60s
    
  7. Créez un fichier nommé snapshot_policy.yaml. Cette configuration établit une règle de conservation qui conserve un maximum de deux instantanés pour votre charge de travail en bac à sable. Le type de déclencheur est défini sur manual, ce qui permet à l'application cliente de contrôler les instantanés à la demande :

    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. Appliquez le fichier manifeste de la règle d'instantané :

    kubectl apply -f snapshot_policy.yaml
    
  9. Créez un fichier nommé python-counter-template.yaml. Cette configuration définit le pod de bac à sable et lui attribue l'identité du compte de service snapshot-sa. Cette attribution permet de garantir que le bac à sable s'exécute de manière sécurisée. Dans ce pod, l'application en bac à sable (un script Python) imprime en continu un compteur incrémentiel dans les journaux du conteneur :

    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)
    
  10. Appliquez le fichier manifeste du modèle de bac à sable :

    kubectl apply -f python-counter-template.yaml
    

Créer l'application cliente

Créez l'image de conteneur pour l'application cliente et importez-la dans Artifact Registry.

  1. Créez un fichier nommé Dockerfile.client. Ce fichier définit l'environnement d'exécution Python et les dépendances de l'application cliente :

    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. Créez un fichier nommé client_test.py. Ce script gère le cycle de vie du bac à sable et vérifie que l'état reprend correctement après la création d'un instantané :

    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. Créez l'image de conteneur client et importez-la dans Artifact Registry. Si Docker est installé dans votre environnement (comme Cloud Shell), vous pouvez l'utiliser pour créer l'image en local. Si vous travaillez dans un environnement sans Docker, vous pouvez utiliser Cloud Build pour créer et envoyer l'image à distance :

    Docker

    1. Configurez l'authentification Docker pour Artifact Registry :

      gcloud auth configure-docker "${LOCATION}-docker.pkg.dev"
      
    2. Créez et transférez l'image de conteneur client localement :

      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. Créez un fichier nommé 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. Remplacez les espaces réservés des variable d'environnement dans le fichier de configuration :

      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. Créez le bucket Cloud Storage pour les journaux Cloud Build :

      gcloud storage buckets create "gs://${CLOUDBUILD_BUCKET_NAME}" \
          --location="${BUCKET_LOCATION}"
      
    4. Accordez les autorisations nécessaires au compte de service Cloud Build.

      Les commandes suivantes utilisent le compte de service Compute Engine par défaut ($PROJECT_NUMBER-compute@developer.s3ns-system.iam.gserviceaccount.com), qui est celui par défaut pour la plupart des projets. Si votre projet utilise l'ancien compte de service Cloud Build, remplacez l'adresse e-mail du membre dans les commandes suivantes par $PROJECT_NUMBER@cloudbuild.s3ns-system.iam.gserviceaccount.com.

      Étant donné que vous avez ajouté une liaison IAM conditionnelle à votre projet plus tôt dans ce tutoriel, vous devez inclure l'indicateur --condition=None lorsque vous ajoutez ces nouvelles liaisons. Cela empêche gcloud de mettre en pause pour demander une confirmation interactive :

      gcloud projects add-iam-policy-binding $PROJECT_ID \
         --member="serviceAccount:$PROJECT_NUMBER-compute@developer.s3ns-system.iam.gserviceaccount.com" \
         --role="roles/artifactregistry.writer"
      
      gcloud projects add-iam-policy-binding $PROJECT_ID \
         --member="serviceAccount:$PROJECT_NUMBER-compute@developer.s3ns-system.iam.gserviceaccount.com" \
         --role="roles/logging.logWriter"
      
      gcloud storage buckets add-iam-policy-binding "gs://$CLOUDBUILD_BUCKET_NAME" \
         --member="serviceAccount:$PROJECT_NUMBER-compute@developer.s3ns-system.iam.gserviceaccount.com" \
         --role="roles/storage.objectAdmin"
      
    5. Exécutez la compilation à l'aide de Cloud Build :

      gcloud builds submit --config cloudbuild.yaml
      

Exécuter le test

Déployez l'application cliente pour créer le bac à sable, déclencher un instantané et vérifier que le compteur interne reprend correctement à partir de son état enregistré.

  1. Créez un fichier nommé client_sa.yaml. Ce fichier manifeste définit le compte de service agent-sandbox-client-sa et les autorisations RBAC requises pour gérer les ressources personnalisées du bac à sable :

    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. Appliquez le compte de service client et le fichier manifeste RBAC :

    kubectl apply -f client_sa.yaml
    
  3. Créez un fichier nommé client_pod.yaml. Ce fichier manifeste crée le pod d'application cliente à l'aide de l'image de conteneur préconfigurée :

    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. Remplacez les espaces réservés des variable d'environnement dans le fichier manifeste :

    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. Appliquez le fichier manifeste de pod de l'application cliente :

    kubectl apply -f client_pod.yaml
    
  6. Diffusez les journaux du pod pour vérifier le flux d'exécution :

    kubectl logs -f agent-sandbox-client-pod
    

Lorsque le test s'exécute correctement, le résultat ressemble à ceci (raccourci ici pour plus de lisibilité) :

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

Le résultat montre que le bac à sable conserve bien son état lorsqu'il est suspendu et réactivé. Le compteur cesse de progresser lorsque le bac à sable est suspendu (mis en pause et mis à l'échelle zéro), et reprend lorsque le bac à sable est restauré. Sans suspension, le compteur aurait continué à progresser pendant la période de suspension et le nombre aurait été beaucoup plus élevé.

Effectuer un nettoyage des ressources

Pour éviter que des frais ne soient facturés sur votre compte Cloud de Confiance by S3NS , supprimez les ressources que vous avez créées :

  1. Supprimez le cluster GKE. Cette opération supprime également le pool de nœuds et tous les comptes de service Kubernetes qu'il contient :

    gcloud beta container clusters delete ${CLUSTER_NAME} --location="${LOCATION}" --quiet
    
  2. Supprimez le dépôt Artifact Registry pour supprimer le dépôt Docker que vous avez créé pour l'image de test :

    gcloud artifacts repositories delete ${REPOSITORY_NAME} --location="${LOCATION}" --quiet
    
  3. Supprimez le bucket Cloud Storage et tous les instantanés qu'il contient. Cela supprime automatiquement les liaisons IAM Workload Identity au niveau du bucket qui lui sont appliquées :

    gcloud storage rm --recursive "gs://${BUCKET_NAME}"
    
  4. Supprimez la liaison IAM au niveau du projet pour l'agent de service GKE :

    gcloud projects remove-iam-policy-binding "${PROJECT_ID}" \
        --member="serviceAccount:service-${PROJECT_NUMBER}@container-engine-robot.s3ns-system.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 vous avez utilisé Cloud Build au lieu de Docker pour compiler et transmettre l'image de conteneur, supprimez le bucket de journaux et supprimez les autorisations du compte de service :

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

Étapes suivantes