הפעלת תמונות מצב של ארגז חול של סוכנים מתוך אשכול

אתם יכולים להשתמש בתמונות מצב של ארגז החול לסוכנים באשכול Google Kubernetes Engine ‏ (GKE) כדי לשמור את המצב של סביבות ארגז החול. הגישה הזו מאפשרת ניפוי באגים עקבי, מאיצה את חזרות הפיתוח ומקלה על שחזור מהיר של הגדרות מורכבות. במדריך הזה, תפרסו ותבדקו את היכולת הזו על ידי הפעלת אפליקציית לקוח שיוצרת, משהה וממשיכה באופן פרוגרמטי סביבות ארגז חול בתוך האשכול.

מידע נוסף על יצירת תמונות מצב של Pods זמין במאמר שחזור מתמונת מצב של Pod.

עלויות

‫Agent Sandbox מוצע ללא תשלום נוסף ב-GKE. התמחור של GKE חל על המשאבים שאתם יוצרים.

לפני שמתחילים

  1. בדף לבחירת הפרויקט במסוף Cloud de Confiance , בוחרים פרויקט ב- Cloud de Confiance או יוצרים אותו.

    תפקידים שנדרשים כדי לבחור או ליצור פרויקט

    • Select a project: כדי לבחור פרויקט לא צריך תפקיד IAM ספציפי – אפשר לבחור כל פרויקט שקיבלתם בו תפקיד.
    • יצירת פרויקט: כדי ליצור פרויקט, צריך את התפקיד Project Creator (יצירת פרויקטים) (roles/resourcemanager.projectCreator), שכולל את ההרשאה resourcemanager.projects.create. איך מקצים תפקידים

    כניסה לדף לבחירת הפרויקט

  2. מוודאים שהחיוב מופעל בפרויקט Cloud de Confiance .

  3. מפעילים את ממשקי ה-API של Artifact Registry,‏ Kubernetes Engine ו-Cloud Build.

    תפקידים שנדרשים להפעלת ממשקי API

    כדי להפעיל ממשקי API, צריך את תפקיד ה-IAM 'אדמין של Service Usage' (roles/serviceusage.serviceUsageAdmin), שכולל את ההרשאה serviceusage.services.enable. איך מקצים תפקידים

    הפעלת ממשקי ה-API

  4. במסוף Cloud de Confiance , מפעילים את Cloud Shell.

    הפעלת Cloud Shell

  5. מוודאים שיש את ההרשאות הנדרשות כדי להשלים את המדריך הזה.

התפקידים הנדרשים

כדי לקבל את ההרשאות שנדרשות ליצירה ולניהול של ארגזי חול, צריך לבקש מהאדמין להקצות לכם את תפקיד ה-IAM‏ Kubernetes Engine Admin (roles/container.admin) בפרויקט. כדי לקרוא הסבר על מתן תפקידים, ראו איך מנהלים את הגישה ברמת הפרויקט, התיקייה והארגון.

יכול להיות שאפשר לקבל את ההרשאות הנדרשות גם באמצעות תפקידים בהתאמה אישית או תפקידים מוגדרים מראש.

מגבלות

באשכול אזורי, יכול להיות שלצמתים באזורים שונים יש מיקרו-ארכיטקטורות שונות של מעבדים. מכיוון שתמונות המצב מתעדות את מצב ה-CPU, שחזור של תמונת מצב בצומת שחסרות בו תכונות של ה-CPU נכשל (לדוגמה, עם השגיאה OCI runtime restore failed: incompatible FeatureSet).

כדי להימנע מהבעיה הזו, צריך להשתמש בהגדרה המתאימה לסביבה שלכם:

  • סביבת ייצור: כדי לשמור על זמינות גבוהה בכל האשכול, לא מצמידים עומסי עבודה לאזור ספציפי. במקום זאת, כדי להבטיח עקביות של תכונות ה-CPU בכל האזורים, מציינים פלטפורמת CPU מינימלית. למידע נוסף, אפשר לעיין במאמר בנושא בחירת פלטפורמת CPU מינימלית.
  • בדיקה: כדי לפשט את ההגדרה ולמנוע שגיאות של חוסר התאמה ראשוני במעבד, אפשר להשתמש בשדה nodeSelector במניפסט SandboxTemplate כדי להצמיד את ה-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 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 . הגדרת המשתנה הזה עוזרת לוודא שכל המשאבים נוצרים בפרויקט הנכון.
  • PROJECT_NUMBER: מספר הפרויקט הנוכחי ב- Cloud de Confiance by S3NS .
  • CLUSTER_NAME: השם של אשכול GKE, לדוגמה test-snapshot.
  • LOCATION: Cloud de Confiance by S3NS האזור שבו נמצאים אשכול GKE ומאגר Artifact Registry, לדוגמה, us-central1.
  • BUCKET_LOCATION: המיקום של דלי Cloud Storage, לדוגמה us.
  • BUCKET_NAME: השם של קטגוריה של Cloud Storage שמשמשת לקובצי snapshot.
  • CLOUDBUILD_BUCKET_NAME: השם של קטגוריית Cloud Storage שמשמשת ליומנים של Cloud Build.
  • MACHINE_TYPE: סוג המכונה שבה יש להשתמש לצמתי האשכול, לדוגמה e2-standard-8.
  • REPOSITORY_NAME: השם של מאגר Artifact Registry, לדוגמה agent-sandbox.

סקירה כללית של שלבי ההגדרה

כדי להפעיל ולבדוק תמונות מצב של Pod בסביבות ארגז חול של סוכנים מתוך האשכול, צריך לבצע כמה שלבי הגדרה. כדי להבין את השלבים האלה, כדאי קודם להבין את הרכיבים שמשתתפים בתהליך העבודה הכולל.

רכיבים מרכזיים

במדריך הזה משתמשים בשתי אפליקציות Python הבאות כדי לבדוק את תהליך יצירת התמונה:

  • אפליקציית לקוח: סקריפט Python שפועל ב-Pod רגיל באשכול. האפליקציה הזו מנהלת את מחזור החיים של ארגז החול: היא יוצרת את ארגז החול באופן פרוגרמטי, משהה אותו כדי להפעיל צילום מצב, מפעילה מחדש את ארגז החול ומוודאת שהמצב נשמר. במדריך הזה, יוצרים חשבון שירות ב-Kubernetes בשם agent-sandbox-client-sa ומעניקים לו הרשאות RBAC כדי שה-Pod של אפליקציית הלקוח יוכל לנהל משאבים בהתאמה אישית של ארגז חול ואובייקטים של הפעלת תמונת מצב באמצעות Kubernetes API.
  • אפליקציה בסביבת ארגז חול: סקריפט Python שמגדיל ומדפיס מונה כל שנייה. האפליקציה הזו פועלת בצורה מאובטחת בסביבת ארגז החול המבודדת כדי ליצור מצב משתנה שאפליקציית הלקוח יכולה לאמת. במדריך הזה אתם יוצרים חשבון שירות ייעודי ב-Kubernetes בשם snapshot-sa ומגדירים Workload Identity כדי לתת לקבוצת ה-Pod בארגז החול הרשאה לקרוא ולכתוב אובייקטים של תמונות מצב ב-Cloud Storage בצורה מאובטחת.

תהליך ההגדרה והבדיקה

הרשימה הבאה מסכמת את השלבים שצריך לבצע כדי להגדיר את הסביבה ולהריץ את הבדיקה:

  1. יצירת אשכול: יצירת אשכול במצב Autopilot או אשכול רגיל עם תמונות מצב של Pod ותכונת Agent Sandbox מופעלת.
  2. יוצרים מאגר ב-Artifact Registry: יוצרים מאגר Docker לאחסון קובץ האימג' של קונטיינר לאפליקציית הלקוח.
  3. Install Agent Sandbox: התקנת רכיבי הליבה ותוספים של Agent Sandbox באשכול.
  4. הגדרת אחסון והרשאות: יצירת קטגוריה של Cloud Storage והגדרת הרשאות Workload Identity כדי לאפשר שמירת קובצי snapshot בצורה מאובטחת.
  5. הגדרת snapshots של Pod: יצירה והחלה של הגדרת אחסון ה-snapshot, מדיניות ה-snapshot ותבנית ארגז החול.
  6. יצירת גרסת build של אפליקציית הלקוח: יוצרים את קובץ אימג' של קונטיינר של אפליקציית הלקוח ומעבירים אותו בדחיפה למאגר Artifact Registry.
  7. מריצים את הבדיקה: פורסים את ה-Pod של אפליקציית הלקוח, שיוצר את ארגז החול, משהה אותו כדי לצלם תמונת מצב, מפעיל אותו מחדש ומוודא שהמצב של המונה שוחזר בהצלחה.

יצירת אשכול

יוצרים אשכול GKE חדש עם הפעלת תמונות מצב של Pod. כדי ליהנות מתאימות מלאה של התכונות, צריך לציין את ערוץ ההפצה המהירה.

טייס אוטומטי

יוצרים אשכול Autopilot עם התכונות הנדרשות:

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

רגילה

יוצרים אשכול רגיל עם התכונות הנדרשות:

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}

יוצרים מאגר צמתים עם gVisor מופעל:

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

יצירת מאגר Artifact Registry

יוצרים מאגר Docker ב-Artifact Registry כדי לאחסן את קובץ האימג' של הקונטיינר של אפליקציית הלקוח (האפליקציה שיוצרת את ארגז החול ומנהלת אותו):

gcloud artifacts repositories create ${REPOSITORY_NAME} \
   --repository-format=docker \
   --location=${LOCATION} \
   --description="Docker repository for 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

הגדרת האחסון וההרשאות

מגדירים קטגוריה ב-Cloud Storage לאחסון קובצי snapshot של Pod ונותנים את ההרשאות הנדרשות של Workload Identity לחשבון השירות snapshot-sa ולסוכן השירות של GKE. כך עומסי העבודה בסביבת ארגז החול יכולים לשמור ולאחזר אובייקטים של קובצי snapshot בצורה מאובטחת:

  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. יוצרים חשבון שירות של Kubernetes במרחב השמות default. האפליקציה שלכם בארגז חול (סקריפט מונה Python) משתמשת בזהות הזו כדי לבצע אימות לממשקי API חיצוניים ולגשת באופן מאובטח לאובייקטים של תמונות מצב שמאוחסנים ב-Cloud Storage:

    kubectl create serviceaccount "snapshot-sa" \
        --namespace "default"
    
  3. מקשרים את התפקיד 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}.s3ns.svc.id.goog/subject/ns/default/sa/snapshot-sa" \
        --role="roles/storage.bucketViewer"
    
  4. משייכים את התפקיד storage.objectUser לחשבון השירות באמצעות Workload Identity. התפקיד הזה מספק הרשאה לקרוא, לשמור ולמחוק אובייקטים בינאריים של תמונת מצב בקטגוריה:

    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. נותנים לסוכן השירות של GKE הרשאות לנהל (ליצור, לראות את הרשימה, לקרוא ולמחוק) אובייקטים של snapshot בקטגוריה:

    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"
    

הגדרת תמונות מצב של Pod

יוצרים ומחילים את קובצי ההגדרות כדי להתקין את המשאבים המותאמים אישית הנדרשים של Kubernetes. המשאבים האלה מגדירים איך האשכול מאחסן ומנהל תמונות מצב של Pod:

  • PodSnapshotStorageConfig: מציין את קטגוריית Cloud Storage שמיועדת לאחסון אובייקטים בינאריים של תמונת מצב.
  • PodSnapshotPolicy: מגדיר איך מפעילים צילום תמונת מצב באופן ידני, באיזו תדירות הם מקובצים ומהי מדיניות שמירת הנתונים שלהם.
  • SandboxTemplate: מגדיר את הקונטיינר הבסיסי, את הבוחרים של הצמתים ואת חשבונות השירות להרצת עומס העבודה המבודד בארגז החול.
  1. יוצרים ספרייה חדשה לקבצים של לקוח הבדיקה:

    mkdir test_client
    
  2. מנווטים לספרייה החדשה:

    cd test_client
    
  3. יוצרים קובץ בשם snapshot_storage_config.yaml. ההגדרה הזו מציינת את קטגוריית היעד ב-Cloud Storage שבה האשכול שומר את מצב ה-snapshot הבינארי של ה-Pod:

    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 הזה, האפליקציה ב-Sandbox (סקריפט 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)
    
  10. החלת מניפסט של תבנית ארגז חול:

    kubectl apply -f python-counter-template.yaml
    

בניית אפליקציית הלקוח

יוצרים את קובץ אימג' של קונטיינר של אפליקציית הלקוח ומעלים אותו ל-Artifact Registry.

  1. יוצרים קובץ בשם 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. יוצרים קובץ בשם 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 כדי ליצור את קובץ האימג' ולהעביר אותו בדחיפה מרחוק:

    Docker

    1. הגדרת אימות Docker ל-Artifact Registry:

      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. מחליפים את ה-placeholders של משתני הסביבה בקובץ התצורה:

      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 Storage ליומנים של Cloud Build:

      gcloud storage buckets create "gs://${CLOUDBUILD_BUCKET_NAME}" \
          --location="${BUCKET_LOCATION}"
      
    4. נותנים את ההרשאות הנדרשות לחשבון השירות ב-Cloud Build.

      הפקודות הבאות משתמשות בחשבון השירות שמוגדר כברירת מחדל ב-Compute Engine‏ ($PROJECT_NUMBER-compute@developer.s3ns-system.iam.gserviceaccount.com), שהוא ברירת המחדל ברוב הפרויקטים. אם הפרויקט שלכם משתמש בחשבון השירות מדור קודם של Cloud Build, מחליפים את כתובת האימייל של החבר בפקודות הבאות ב-$PROJECT_NUMBER@cloudbuild.s3ns-system.iam.gserviceaccount.com.

      מכיוון שהוספתם קישור IAM מותנה לפרויקט בשלב מוקדם יותר במדריך הזה, אתם צריכים לכלול את הדגל --condition=None כשאתם מוסיפים את הקישורים החדשים האלה. כך לא תהיה הפסקה של gcloud כדי לבקש אישור אינטראקטיבי:

      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. מריצים את ה-build באמצעות Cloud Build:

      gcloud builds submit --config cloudbuild.yaml
      

הרצת הבדיקה

פורסים את אפליקציית הלקוח כדי ליצור את ארגז החול, מפעילים snapshot ומוודאים שהמונה הפנימי ממשיך לפעול מהמצב השמור שלו.

  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. מחילים את מניפסט ה-Pod של אפליקציית הלקוח:

    kubectl apply -f 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 ${CLUSTER_NAME} --location="${LOCATION}" --quiet
    
  2. כדי להסיר את מאגר Docker שיצרתם עבור תמונת הבדיקה, צריך למחוק את מאגר Artifact Registry:

    gcloud artifacts repositories delete ${REPOSITORY_NAME} --location="${LOCATION}" --quiet
    
  3. מחיקת קטגוריה של Cloud Storage וכל קובצי ה-snapshot שבתוכה. הפעולה הזו מסירה באופן אוטומטי את הקישורים של Workload Identity ל-IAM ברמת הקטגוריה שחלים עליה:

    gcloud storage rm --recursive "gs://${BUCKET_NAME}"
    
  4. מסירים את קישור ה-IAM ברמת הפרויקט לסוכן השירות של 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. אם השתמשתם ב-Cloud Build במקום ב-Docker כדי ליצור את קובץ האימג' של הקונטיינר ולהעביר אותו בדחיפה, צריך למחוק את קטגוריית היומנים ולהסיר את ההרשאות של חשבון השירות:

    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"
    

המאמרים הבאים