ניהול האחסון של Agent Sandbox ב-GKE

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

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

מידע נוסף על בחירת פתרון אחסון זמין במאמר בחירת אחסון לעומסי עבודה של סוכני AI.

במסמך הבא נעשה שימוש ב-dynamic-rwo StorageClass לצורך בחירה אוטומטית של סוג הדיסק כדי להקצות דיסקים שתואמים לסוגי המכונות של הצמתים שבהם מתוזמנים פודים של ארגז החול לסוכנים ב-GKE. כדי לוודא ש-GKE יקצה נפחי אחסון מסוג Hyperdisk Balanced לאחסון של הסוכנים, צריך לתזמן את ארגז החול של הסוכנים ב-GKE Pods בצמתים של משפחות מכונות תואמות, כמו N4. אחרת, GKE יחזור ל-pd-balanced.

במסמך הזה מוטמע מצב הגישה לנתונים Private Isolated Workspace באמצעות גישת ReadWriteOnce (RWO). במצב הזה, הסוכן מופעל עם ספריית אחסון פרטית ומבודדת, שיש לו גישת קריאה וכתיבה בלעדית אליה.

אלא אם צוין אחרת, הטמעות ההפניה במסמך הזה משתמשות ביצירה ישירה של ארגז חול, שמתאימה לסוכנים שסובלים חביון הפעלה של כמה שניות. כדי להשיג זמן אחזור של פחות משנייה להפעלה של סביבות עבודה עם שמירת מצב או שחזור לנקודת זמן מסוימת, צריך להשתמש ב-Agent Sandbox ב-GKE Warm Pools. כדי לקשר אחסון ל-Warm Pool Pod שנתבע, נדרשים סקריפטים בהתאמה אישית ו-DaemonSet עם הרשאות. דוגמה להטמעה אפשר לראות בדוגמה הזו ב-GitHub.

מצבי גישה חלופיים

כדי לתמוך במצבי גישה חלופיים, אפשר לשנות את הגדרות הנפח ותמונת המצב בהגדרות האישיות:

  • סביבת עבודה שיתופית: משנים את accessModes ל-ReadWriteMany ומשתמשים ב-StorageClass עם יכולת RWX, כמו Filestore Multishares (Enterprise) ‏(enterprise-multishare-rwx).
  • סביבת עבודה של הסתעפות לניתוח נתונים: טעינת תיקיית התבניות במצב קריאה בלבד ומתן פנקס טיוטה נפרד עם הרשאת כתיבה. בתבנית הבסיס, צריך להשתמש באחסון שתומך בכמה קבצים מצורפים לקריאה בלבד, כמו Hyperdisk ML עם מצב גישה ReadOnlyMany (ROX) או Filestore Multishares עם מצב גישה RWX.

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

מפעילים את Agent Sandbox ב-GKE באשכול.

הגדרת סביבת עבודה עם שמירת מצב

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

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

הגישה הזו מתבססת על משאבי GKE PersistentVolumeClaim (PVC) רגילים כדי לקשר בין ארגז חול לבין PVC קיים שמכיל את הנתונים של המשתמש.

רצף האירועים בתבנית של סביבת עבודה עם שמירת מצב:

  1. הקצאה: האדמין או כלי התזמור מקצים באופן ידני PVC פרטי לכל סשן של נציג באמצעות מזהה דטרמיניסטי (לדוגמה, pvc-agent-1).
  2. הפניה: במשאב Sandbox, משתמשים בשדה persistentVolumeClaim בבלוק volumes כדי לציין את ה-claimName המדויק של הנפח הקיים.
  3. זמן האחזור: כשיוצרים את ארגז החול, מערכת GKE צריכה לצרף באופן דינמי את דיסק Compute Engine למכונת ה-VM של הצומת, וזה גורם לעיכוב סטנדרטי של כמה שניות.
  4. עקביות: כשמנתקים את הסשן (מוחקים את ארגז החול), GKE מנתק את הדיסק אבל לא משמיד את ה-PVC, כדי לוודא שהמצב האחרון נשמר לסשן הבא.

כדי להגדיר סביבת עבודה עם שמירת מצב ששומרת נתונים בין סשנים, מבצעים את השלבים שמתוארים בקטעי המשנה הבאים.

הקצאת סביבת עבודה קבועה (PVC)

יוצרים PersistentVolumeClaim‏ (PVC) פרטי שמשתמש במזהה דטרמיניסטי, כמו pvc-agent-1.

  1. שומרים את קובץ המניפסט הבא בשם pvc-agent-1.yaml:

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: pvc-agent-1 # Derived directly from the deterministic assignment ID
      namespace: default
    spec:
      accessModes:
        - ReadWriteOnce
      storageClassName: dynamic-rwo # Selects disk type compatible with the node machine family
      resources:
        requests:
          storage: 10Gi
    
  2. החלת המניפסט:

    kubectl apply -f pvc-agent-1.yaml
    

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

פריסת ארגז החול של הנציג

פורסים את המשאב המותאם אישית של ארגז החול, תוך הפניה ל-PVC הדטרמיניסטי.

  1. שומרים את המניפסט הבא בתור sandbox-agent-1.yaml:

    apiVersion: agents.x-k8s.io/v1alpha1
    kind: Sandbox
    metadata:
      name: sandbox-agent-1 # Traceable sandbox name
      namespace: default
    spec:
      replicas: 1
      podTemplate:
        spec:
          runtimeClassName: gvisor # Required
          automountServiceAccountToken: false # Required
          securityContext:
            runAsNonRoot: true # Required
            runAsUser: 1000
            fsGroup: 1000 # Grant group access to the volume
          nodeSelector:
            sandbox.gke.io/runtime: gvisor # Required
          tolerations:
          - key: "sandbox.gke.io/runtime"
            value: "gvisor"
            effect: "NoSchedule" # Required
          containers:
          - name: agent
            image: registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0
            ports:
            - containerPort: 8888
            volumeMounts:
            - name: workspace-disk
              mountPath: /workspace # Mounts the private disk into the container
            resources:
              limits:
                cpu: "500m"
                memory: "1Gi" # Required
            securityContext:
              capabilities:
                drop: ["ALL"] # Required
          volumes:
          - name: workspace-disk
            persistentVolumeClaim:
              claimName: pvc-agent-1 # Binds this specific Sandbox to Agent 1's PVC
          restartPolicy: OnFailure
    
  2. החלת המניפסט:

    kubectl apply -f sandbox-agent-1.yaml
    

‫GKE מאמת את קיבולת הצומת ומצרף את הדיסק, והתהליך הזה נמשך כמה שניות. המאגר מאותחל בתוך ליבת gVisor של מרחב משתמש.

כתיבת נתונים מהסוכן

כדי לדמות סוכן AI פעיל שמבצע שינויים בקבצים בסביבת העבודה שלו, צריך לכתוב קובץ טקסט לדיסק המותקן.

# Set the active Pod name
POD_NAME=sandbox-agent-1

# Write a state file to the persistent directory
kubectl exec $POD_NAME -- sh -c "echo 'Workspace State Saved - Agent 1' > /workspace/modified_data.txt"

# Confirm the file exists on the disk
kubectl exec $POD_NAME -- cat /workspace/modified_data.txt

סיום הסשן עם הנציג

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

kubectl delete sandbox sandbox-agent-1

‫GKE מבטל את הטעינה של הדיסק ומנתק אותו. ה-PVC של pvc-agent-1 נשאר, והנתונים נשמרים.

הפעלה מחדש של סשן עם נציג

כדי להפעיל מחדש את הסשן, צריך לפרוס מחדש משאב Sandbox חדש שמפנה לאותו PVC.

kubectl apply -f sandbox-agent-1.yaml

הדיסק מחובר מחדש (מה שגורם לעיכוב בחיבור) והקונטיינר מופעל.

אימות שמירת הנתונים

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

# Set the active Pod name of the new session
NEW_POD_NAME=sandbox-agent-1

# Read the file from the newly booted sandbox
kubectl exec -it $NEW_POD_NAME -- cat /workspace/modified_data.txt

הפלט אמור להציג את Workspace State Saved - Agent 1.

מחיקת משאבים

מוחקים את ארגז החול של הסוכן ואת בקשת הנפח הקבוע שמשויכת אליו:

kubectl delete sandbox sandbox-agent-1
kubectl delete pvc pvc-agent-1

הגדרה של שחזור לנקודת זמן והעברת בעלות

התבנית הזו מתאימה לשכפול של מערכי נתונים כדי להריץ ניסויים מקבילים, לניפוי באגים או לעבודה עצמאית. סביבת העבודה של הסוכן מאותחלת מקבוצת נתונים היסטורית (או ממצב משותף), ושומרת שינויים עוקבים בשכבה נפרדת ופרטית שניתן לכתוב בה, בלי לשנות את התבנית הבסיסית.

ההטמעה לדוגמה בקטע הזה משתמשת ביצירה ישירה של ארגז חול, והיא רלוונטית לסוכנים שסובלים חביון הפעלה של כמה שניות. הגישה הזו מסתמכת על התזמור כדי להקצות באופן דינמי PersistentVolumeClaims‏ (PVCs) חדשים מ-VolumeSnapshots היסטוריים לפני הפעלת סשן חדש של ארגז חול.

יצירת VolumeSnapshotClass

יוצרים VolumeSnapshotClass שמציין את מנהל ההתקן של CSI ואת מדיניות המחיקה. ל-Hyperdisk, משתמשים במנהל ההתקן pd.csi.storage.gke.io.

  1. שומרים את קובץ המניפסט הבא בשם 1-snapshot-class.yaml:

    apiVersion: snapshot.storage.k8s.io/v1
    kind: VolumeSnapshotClass
    metadata:
      name: standard-rwo-snapshot
    driver: pd.csi.storage.gke.io
    deletionPolicy: Delete
    
  2. מחילים את המניפסט:

    kubectl apply -f 1-snapshot-class.yaml
    

הקצאת סביבת העבודה הראשונית

יוצרים נפח אחסון שבו הסוכן יבצע את העבודה הראשונית שלו.

  1. שומרים את המניפסט הבא בתור 2-source-pvc.yaml:

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: agent-source-pvc
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: dynamic-rwo
      resources:
        requests:
          storage: 10Gi
    
  2. החלת המניפסט:

    kubectl apply -f 2-source-pvc.yaml
    

יצירת נתוני מדינה

פריסת Pod של ארגז חול כדי לכתוב נתונים לנפח.

  1. שומרים את המניפסט הבא בתור 3-source-sandbox.yaml:

    apiVersion: agents.x-k8s.io/v1alpha1
    kind: Sandbox
    metadata:
      name: agent-session-v1
      namespace: default
    spec:
      replicas: 1
      podTemplate:
        spec:
          runtimeClassName: gvisor
          automountServiceAccountToken: false # Required
          securityContext:
            runAsNonRoot: true # Required
            runAsUser: 1000
            fsGroup: 1000 # Grant group access to the volume
          nodeSelector:
            sandbox.gke.io/runtime: gvisor # Required
          tolerations:
          - key: "sandbox.gke.io/runtime"
            value: "gvisor"
            effect: "NoSchedule" # Required
          containers:
          - name: agent
            image: registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0
            ports:
            - containerPort: 8888
            volumeMounts:
            - name: workspace
              mountPath: /workspace
            resources:
              limits:
                cpu: "500m"
                memory: "1Gi" # Required
            securityContext:
              capabilities:
                drop: ["ALL"] # Required
          volumes:
          - name: workspace
            persistentVolumeClaim:
              claimName: agent-source-pvc
          restartPolicy: OnFailure
    
  2. החלת המניפסט:

    kubectl apply -f 3-source-sandbox.yaml
    
  3. מחכים שה-Pod יפעל ואז כותבים קובץ תמונת מצב:

    POD_NAME=agent-session-v1
    kubectl exec $POD_NAME -- sh -c "echo 'Point-in-Time Snapshot - v1' > /workspace/state.txt"
    

העברת המצב ההיסטורי לארכיון (CSI VolumeSnapshot)

מפעילים CSI VolumeSnapshot כדי להקפיא את המצב הנוכחי. כשמצלמים תמונת מצב, חשוב לפעול לפי השיטות המומלצות לצילום תמונות מצב של דיסקים.

  1. שומרים את המניפסט הבא בתור 4-volume-snapshot.yaml:

    apiVersion: snapshot.storage.k8s.io/v1
    kind: VolumeSnapshot
    metadata:
      name: agent-session-v1-snapshot
    spec:
      volumeSnapshotClassName: standard-rwo-snapshot
      source:
        persistentVolumeClaimName: agent-source-pvc
    
  2. מחילים את המניפסט:

    kubectl apply -f 4-volume-snapshot.yaml
    

שחזור אמצעי האחסון מתמונת המצב

פורסים PVC חדש עם dataSource שמצביע על VolumeSnapshot של ה-CSI.

  1. שומרים את קובץ המניפסט הבא בשם 5-restored-pvc.yaml:

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: agent-restored-pvc
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: dynamic-rwo
      dataSource:
        name: agent-session-v1-snapshot
        kind: VolumeSnapshot
        apiGroup: snapshot.storage.k8s.io
      resources:
        requests:
          storage: 10Gi
    
  2. מחילים את המניפסט:

    kubectl apply -f 5-restored-pvc.yaml
    

הפעלת הסשן ששוחזר ב-Agent Sandbox

מקצים משאב חדש של ארגז חול שמפנה ל-PVC ששוחזר.

  1. שומרים את המניפסט הבא בתור 6-restored-sandbox.yaml:

    apiVersion: agents.x-k8s.io/v1alpha1
    kind: Sandbox
    metadata:
      name: agent-session-v2-restored
      namespace: default
    spec:
      replicas: 1
      podTemplate:
        spec:
          runtimeClassName: gvisor
          automountServiceAccountToken: false # Required
          securityContext:
            runAsNonRoot: true # Required
            runAsUser: 1000
            fsGroup: 1000 # Grant group access to the volume
          nodeSelector:
            sandbox.gke.io/runtime: gvisor # Required
          tolerations:
          - key: "sandbox.gke.io/runtime"
            value: "gvisor"
            effect: "NoSchedule" # Required
          containers:
          - name: agent
            image: registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0
            ports:
            - containerPort: 8888
            volumeMounts:
            - name: workspace
              mountPath: /workspace
            resources:
              limits:
                cpu: "500m"
                memory: "1Gi" # Required
            securityContext:
              capabilities:
                drop: ["ALL"] # Required
          volumes:
          - name: workspace
            persistentVolumeClaim:
              claimName: agent-restored-pvc
          restartPolicy: OnFailure
    
  2. מחילים את המניפסט:

    kubectl apply -f 6-restored-sandbox.yaml
    

אימות של התמדה ושחזור

מוודאים שהסוכן יכול לקרוא את הנתונים ההיסטוריים.

NEW_POD_NAME=agent-session-v2-restored
kubectl exec $NEW_POD_NAME -- cat /workspace/state.txt

הפלט המצופה: Point-in-Time Snapshot - v1

מחיקת משאבים

מוחקים את ארגזי החול של הסוכן, את ה-PVC ואת ה-VolumeSnapshot:

kubectl delete sandbox agent-session-v1
kubectl delete sandbox agent-session-v2-restored
kubectl delete pvc agent-source-pvc
kubectl delete pvc agent-restored-pvc
kubectl delete volumesnapshot agent-session-v1-snapshot
kubectl delete volumesnapshotclass standard-rwo-snapshot

הגדרת סביבת עבודה זמנית

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

הגדרה עם הפעלה תוך פחות משנייה

להשתמש ב-Agent Sandbox במאגרי GKE Warm Pools כדי להקצות מראש נפחים ריקים ברקע.

הגדרת SandboxTemplate

מגדירים את האחסון הזמני בבלוק volumeClaimTemplate.

  1. שומרים את קובץ המניפסט הבא בשם stateless-template.yaml:

    apiVersion: extensions.agents.x-k8s.io/v1alpha1
    kind: SandboxTemplate
    metadata:
      name: stateless-sandbox-template
      namespace: default
    spec:
      podTemplate:
        spec:
          runtimeClassName: gvisor
          automountServiceAccountToken: false
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000
            fsGroup: 1000 # Grant group access to the volume
          nodeSelector:
            sandbox.gke.io/runtime: gvisor
          tolerations:
          - key: "sandbox.gke.io/runtime"
            value: "gvisor"
            effect: "NoSchedule"
          containers:
          - name: agent
            image: registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0
            ports:
            - containerPort: 8888
            volumeMounts:
            - name: ephemeral-disk
              mountPath: /workspace
            resources:
              limits:
                cpu: "500m"
                memory: "1Gi" # Required
            securityContext:
              capabilities:
                drop: ["ALL"] # Required
      volumeClaimTemplates:
      - metadata:
          name: ephemeral-disk
        spec:
          accessModes: ["ReadWriteOnce"]
          storageClassName: dynamic-rwo # Selects disk type compatible with node machine family
          resources:
            requests:
              storage: 10Gi
    

הפעלת מאגר חם של ארגז חול

  1. שומרים את המניפסט הבא בתור stateless-warmpool.yaml:

    apiVersion: extensions.agents.x-k8s.io/v1alpha1
    kind: SandboxWarmPool
    metadata:
      name: stateless-warmpool
      namespace: default
    spec:
      replicas: 5 # Keep five standby Pods with pre-attached empty disks
      sandboxTemplateRef:
        name: stateless-sandbox-template
    
  2. מחילים את שני המניפסטים:

    kubectl apply -f stateless-template.yaml
    kubectl apply -f stateless-warmpool.yaml
    

תביעת בעלות על ארגז החול

מגדירים SandboxClaim שמופעל כשמשתמש מתחיל סשן.

  1. שומרים את המניפסט הבא בתור stateless-sandbox-claim.yaml:

    apiVersion: extensions.agents.x-k8s.io/v1alpha1
    kind: SandboxClaim
    metadata:
      name: agent-1-claim
    spec:
      sandboxTemplateRef:
        name: stateless-sandbox-template
    
  2. מחילים את המניפסט:

    kubectl apply -f stateless-sandbox-claim.yaml
    

אימות ביצוע תוך פחות משנייה

מציינים את שם ה-Pod של ארגז החול של הנציג ומוודאים שהספרייה /workspace מותקנת ומוכנה לשימוש מיידי:

export POD_NAME=$(kubectl get sandboxclaim agent-1-claim -o jsonpath='{.status.sandbox.name}')
kubectl exec $POD_NAME -- ls -la /workspace

לסיים את הסשן עם הסוכן

כדי לסיים את הסשן של הסוכן ולשחרר את ארגז החול שנתבע, מוחקים את המשאב SandboxClaim:

kubectl delete sandboxclaim agent-1-claim

מחיקת משאבים

מוחקים את מאגר המכונות החמות של ארגז החול ואת התבנית של ארגז החול:

kubectl delete sandboxwarmpool stateless-warmpool
kubectl delete sandboxtemplate stateless-sandbox-template

הגדרה עם הפעלה של כמה שניות

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

הגדרת ארגז חול ללא שמירת מצב לסוכן

  1. שומרים את המניפסט הבא בתור sandbox-direct-stateless.yaml:

    apiVersion: agents.x-k8s.io/v1alpha1
    kind: Sandbox
    metadata:
      name: sandbox-direct-stateless
      namespace: default
    spec:
      replicas: 1
      podTemplate:
        spec:
          runtimeClassName: gvisor
          automountServiceAccountToken: false
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000
            fsGroup: 1000 # Grant group access to the volume
          nodeSelector:
            sandbox.gke.io/runtime: gvisor
          tolerations:
          - key: "sandbox.gke.io/runtime"
            value: "gvisor"
            effect: "NoSchedule"
          containers:
          - name: agent
            image: registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0
            ports:
            - containerPort: 8888
            volumeMounts:
            - name: ephemeral-disk
              mountPath: /workspace
            resources:
              limits:
                cpu: "500m"
                memory: "1Gi" # Required
            securityContext:
              capabilities:
                drop: ["ALL"] # Required
          restartPolicy: OnFailure
      volumeClaimTemplates:
      - metadata:
          name: ephemeral-disk
        spec:
          accessModes: ["ReadWriteOnce"]
          storageClassName: dynamic-rwo
          resources:
            requests:
              storage: 10Gi
    

פריסת ארגז החול של הנציג

כדי להקצות את הדיסק באופן דינמי ולצרף אותו לצומת המתוזמן, מחילים את המניפסט:

kubectl apply -f sandbox-direct-stateless.yaml

אימות זמן האחזור של ההפעלה והביצוע

עוקבים אחרי הסטטוס של ה-Pod כדי לראות את העיכוב בצירוף לפני שה-Pod עובר למצב Running:

kubectl get pods -w

אחרי שה-Pod פועל, צריך לתעד את שם ה-Pod ולוודא שהספרייה /workspace זמינה:

POD_NAME=sandbox-direct-stateless
kubectl exec $POD_NAME -- ls -la /workspace

סיום הסשן עם הסוכן

כדי להפסיק את הפוד באופן אוטומטי ולמחוק את האחסון הזמני שלו, צריך למחוק את משאב ארגז החול:

kubectl delete sandbox sandbox-direct-stateless

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