1. מבוא
סקירה כללית
בשיעור ה-Lab הזה תפרסו מופע מאובטח של Hermes Agent (של Nous Research) עם נתונים שנשמרים באופן מלא במופעים של Cloud Run. תקיימו אינטראקציה עם סוכן ה-AI באמצעות לוח הבקרה באינטרנט של Hermes, ותגבו את סביבת העבודה הקבועה שלו באמצעות Google Cloud Storage.
Hermes תומך במצב Gateway שיכול לפעול כשירות Cloud Run עם התאמה אוטומטית לעומס, אבל הוא גם פועל כסוכן עם שמירת מצב שסורק מיומנויות בעת האתחול ומטפל בהרצה ברקע. מופעי Cloud Run מספקים סביבה ארוכת טווח עם כתובת ייחודית, שמתאימה באופן מושלם לעומס העבודה הזה.
הפעולות שתבצעו:
- מכינים קטגוריה של Cloud Storage כדי לשמור את המצב וההגדרות של הקונטיינר.
- יוצרים סופרווייזר מותאם אישית של Python (
run_hermes.py) וסקריפט לטעינה בזמן ההפעלה (start_hermes.sh) כדי לטפל באתחול של האתחול. - פורסים את סוכן Hermes באמצעות
gcloud beta run instances deploy. - ניגשים ללוח הבקרה של Hermes ומבצעים אימות.
מה תלמדו
- איך פורסים את סוכן Hermes למכונות Cloud Run.
- איך לטעון קטגוריות של Cloud Storage למופעים של Cloud Run באמצעות GCSFuse.
- איך מגדירים בבטחה SQLite ומטמון זמני כדי לעקוף את המגבלות של נעילת קבצים ב-GCSFuse.
2. הגדרה ודרישות
הגדרת פרויקט GCP
- נכנסים למסוף Google Cloud.
- יוצרים פרויקט חדש ב-Google Cloud או בוחרים פרויקט קיים.
- מוודאים שהחיוב מופעל בפרויקט בענן שלכם ב-Google Cloud.
פתיחת Cloud Shell
מפעילים את Google Cloud Shell מסרגל הכלים העליון של Cloud Console.
הגדרת פרויקט והתקנת gcloud beta
קודם כל, מגדירים את הפרויקט והאזור כמשתני סביבה.
export PROJECT_ID=<YOUR_PROJECT_ID>
export REGION="us-west2"
export BUCKET_NAME="hermes-state-${PROJECT_ID}"
מגדירים את הפרויקט לשימוש ב-gcloud.
gcloud config set project $PROJECT_ID
מוודאים שהרכיב beta מותקן עבור gcloud beta run instances:
gcloud components install beta --quiet
וגרסת gcloud שלכם עדכנית.
gcloud components updates
הפעלה של ממשקי Google Cloud API הנדרשים
ב-Cloud Shell, מפעילים את Cloud Run, Cloud Storage ו-Secret Manager APIs:
gcloud services enable \
run.googleapis.com \
secretmanager.googleapis.com \
storage.googleapis.com \
compute.googleapis.com \
aiplatform.googleapis.com
3. יצירת חשבון שירות ייעודי
כדי לפעול לפי העיקרון של הרשאות מינימליות, צריך ליצור חשבון שירות ייעודי ב-IAM לסוכן Hermes ולהעניק לו את ההרשאות הנדרשות להפעלת מודלים של Vertex AI:
export SERVICE_ACCOUNT_NAME="hermes-sa"
gcloud iam service-accounts create ${SERVICE_ACCOUNT_NAME} \
--display-name="Hermes Service Account"
export SERVICE_ACCOUNT="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member="serviceAccount:${SERVICE_ACCOUNT}" \
--role="roles/aiplatform.user"
4. אחסון פרטי כניסה ב-Secret Manager
אנחנו נאחסן פרטי כניסה רגישים כמו הסיסמה של לוח הבקרה ב-Google Cloud Secret Manager, כדי ש-Cloud Run יוכל להוסיף אותם באופן מאובטח למאגר בזמן האתחול.
יוצרים סיסמה אקראית מאובטחת ללוח הבקרה ומאחסנים אותה ב-Secret Manager:
export DASHBOARD_PASSWORD=$(openssl rand -hex 16)
echo "Generated Hermes Dashboard Password: ${DASHBOARD_PASSWORD}"
echo -n "${DASHBOARD_PASSWORD}" | gcloud secrets create hermes-dashboard-password \
--data-file=- \
--replication-policy="automatic"
gcloud secrets add-iam-policy-binding hermes-dashboard-password \
--member="serviceAccount:${SERVICE_ACCOUNT}" \
--role="roles/secretmanager.secretAccessor"
5. הכנת קטגוריה של Cloud Storage וקבצי תצורה
ל-Hermes נדרש אחסון מתמיד שמוטמע כ-/opt/data. נשתמש בקטגוריה של Google Cloud Storage (GCS) ונטען אותה באמצעות טעינות של נפח אחסון ב-Cloud Storage.
1. יצירת קטגוריה של Cloud Storage
gcloud storage buckets create gs://${BUCKET_NAME} --location=${REGION}
# Grant the service account permissions to mount the bucket
gcloud storage buckets add-iam-policy-binding gs://${BUCKET_NAME} \
--member="serviceAccount:${SERVICE_ACCOUNT}" \
--role="roles/storage.objectAdmin"
2. יצירה של config.yaml
יוצרים קובץ config.yaml. חשוב לכלול את _config_version: 12 כדי לוודא שההגדרה נטענת בצורה נכונה:
_config_version: 12
model:
default: "google/gemini-3.8-flash"
provider: "vertex"
dashboard:
enabled: true
database:
journal_mode: delete
3. יצירת סקריפט של מפקח (run_hermes.py)
Cloud Storage לא תומך במנגנוני נעילת הקבצים הספציפיים שנדרשים למסדי נתונים של SQLite כדי לפעול בצורה בטוחה. כדי למנוע פגיעה במסד הנתונים, אנחנו צריכים סקריפט מותאם אישית של 'מפקח' (run_hermes.py). הסקריפט הזה מגדיר את Hermes לאחסן את נעילות מסד הנתונים הזמניות שלו בזיכרון המקומי של מאגר התגים במקום ב-Cloud Storage לפני הפעלת הסוכן.
יצירת run_hermes.py באופן מקומי:
import os
import shutil
import subprocess
import sys
import threading
import time
print(
"=== INITIALIZING HERMES SUPERVISOR ===", flush=True
)
# 1. Local Directory Setup
# Creates temporary, local folders (in /tmp) for the agent's caches and working directories.
# See more below in comment section NOTE ON CLOUD STORAGE FUSE
home_dir = "/tmp/hermes_home"
hermes_dir = os.path.join(home_dir, ".hermes")
os.makedirs(hermes_dir, exist_ok=True)
os.makedirs("/tmp/logs", exist_ok=True)
os.makedirs("/tmp/skills", exist_ok=True)
os.makedirs("/tmp/uv_cache", exist_ok=True)
os.makedirs("/tmp/cache", exist_ok=True)
os.makedirs("/opt/data/workspace", exist_ok=True)
os.makedirs("/opt/data/.hermes", exist_ok=True)
# 2. State Restoration & Database Config
# Copies your existing configurations and chat history (state.db) from Cloud Storage into the local folders.
# It also forces the SQLite database into TRUNCATE mode, a crucial step to prevent database corruption
# when eventually saving back to Cloud Storage. See more in section 3. Note on Cloud Storage Fuse below
if os.path.exists("/opt/data/config.yaml"):
shutil.copy("/opt/data/config.yaml", os.path.join(hermes_dir, "config.yaml"))
print(f"Synced config.yaml -> {hermes_dir}/config.yaml", flush=True)
elif os.path.exists("/opt/data/.hermes/config.yaml"):
shutil.copy("/opt/data/.hermes/config.yaml", os.path.join(hermes_dir, "config.yaml"))
print(f"Synced config.yaml from .hermes -> {hermes_dir}/config.yaml", flush=True)
if os.path.exists("/opt/data/.env"):
shutil.copy("/opt/data/.env", os.path.join(hermes_dir, ".env"))
print(f"Synced .env -> {hermes_dir}/.env", flush=True)
elif os.path.exists("/opt/data/.hermes/.env"):
shutil.copy("/opt/data/.hermes/.env", os.path.join(hermes_dir, ".env"))
print(f"Synced .env from .hermes -> {hermes_dir}/.env", flush=True)
if os.path.exists("/opt/data/.hermes/state.db"):
shutil.copy("/opt/data/.hermes/state.db", os.path.join(hermes_dir, "state.db"))
print(f"Synced state.db -> {hermes_dir}/state.db (restored previous chats!)", flush=True)
# 3. Note on Cloud Storage Fuse
# Cloud Storage FUSE is optimized for object storage, but is not fully POSIX compliant.
# This means GCS lacks the byte-range file locking required by active caches and default SQLite (WAL mode)
# which SQLite depends on to prevent data collisions.
# Without these locks, SQLite experiences database corruption and blocked I/O operations.
# To ensure stability, we route these active I/O processes to local container memory (/tmp).
# See section 5 Enable Autosave below on how /tmp is uploaded to Cloud Storage.
# Read more: https://cloud.google.com/storage/docs/cloud-storage-fuse/overview#differences-and-limitations
db_path = os.path.join(hermes_dir, "state.db")
try:
import sqlite3
conn = sqlite3.connect(db_path)
conn.execute("PRAGMA journal_mode=TRUNCATE;")
conn.close()
print("Configured SQLite database to TRUNCATE mode for direct single-file persistence", flush=True)
except Exception as e:
print(f"Warning: Failed to configure TRUNCATE mode: {e}", flush=True)
subprocess.run(["chmod", "-R", "777", "/tmp"], check=False)
# 4. Update system environment variables
# Hermes needs to know to look at the new local /tmp folders rather than defaulting to the mounted bucket.
env = dict(os.environ)
env["HOME"] = home_dir
env["HERMES_HOME"] = hermes_dir
env["PATH"] = "/opt/hermes/.venv/bin:/opt/hermes/bin:" + env.get("PATH", "")
env["PYTHONUNBUFFERED"] = "1"
env["HERMES_STATE_PATH"] = hermes_dir
env["HERMES_SKILLS_PATH"] = "/tmp/skills"
env["UV_CACHE_DIR"] = "/tmp/uv_cache"
env["XDG_CACHE_HOME"] = "/tmp/cache"
env["SQLITE_BUSY_TIMEOUT"] = "30000"
env["HERMES_ALLOW_ROOT_GATEWAY"] = "1"
env["HERMES_WORKSPACE"] = "/opt/data/workspace"
env["HERMES_WRITE_SAFE_ROOT"] = "/opt/data"
python_bin = "/opt/hermes/.venv/bin/python3"
# 5. Enable Autosave
# Spawn a background worker thread to watch your local database and config files every 5 seconds.
# As you chat with your agent, this worker thread automatically copies the updated database content
# back to Cloud Storage to persist it.
def sync_to_gcs_loop():
files_to_sync = ["state.db", "config.yaml", ".env"]
last_mtimes = {}
# Initialize last_mtimes
for f in files_to_sync:
path = os.path.join(hermes_dir, f)
if os.path.exists(path):
last_mtimes[f] = os.path.getmtime(path)
else:
last_mtimes[f] = 0
while True:
time.sleep(5)
for f in files_to_sync:
src_path = os.path.join(hermes_dir, f)
if os.path.exists(src_path):
try:
mtime = os.path.getmtime(src_path)
if mtime > last_mtimes.get(f, 0):
dst_path = os.path.join("/opt/data/.hermes", f)
shutil.copy2(src_path, dst_path)
last_mtimes[f] = mtime
print(f"Auto-saved {f} to GCS volume mount", flush=True)
except Exception as e:
print(f"Error auto-saving {f} to GCS: {e}", flush=True)
threading.Thread(target=sync_to_gcs_loop, daemon=True).start()
# 6. Launch the Hermes Gateway (the AI backend) and the Web Dashboard (the UI)
# These are launched as parallel processes, sending logs to Cloud Run via stdout & stderr
print("=== STARTING GATEWAY IN BACKGROUND ===", flush=True)
gw = subprocess.Popen(
[python_bin, "-m", "hermes_cli.main", "gateway", "run"],
env=env,
cwd="/opt/data/workspace",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
)
def stream_gw():
for line in iter(gw.stdout.readline, ""):
if line:
print(f"[GATEWAY] {line.rstrip()}", flush=True)
threading.Thread(target=stream_gw, daemon=True).start()
print("=== STARTING DASHBOARD ON 0.0.0.0:8080 ===", flush=True)
sys.stdout.flush()
dash = subprocess.Popen(
[
python_bin,
"-m",
"hermes_cli.main",
"dashboard",
"--host",
"0.0.0.0",
"--port",
"8080",
"--skip-build",
],
env=env,
cwd="/opt/data/workspace",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
)
for line in iter(dash.stdout.readline, ""):
if line:
print(f"[DASHBOARD] {line.rstrip()}", flush=True)
rc = dash.wait()
print(f"DASHBOARD EXITED WITH RETURN CODE: {rc}", flush=True)
while True:
time.sleep(10)
4. יצירת סקריפט הפעלה (start_hermes.sh)
ליצור start_hermes.sh באופן מקומי.
#!/bin/sh
set -e
export PYTHONUNBUFFERED=1
exec python3 /opt/data/run_hermes.py
5. העלאת קבצים ל-Cloud Storage
מעתיקים את קובצי ההגדרות לשורש של קטגוריית ה-GCS:
gcloud storage cp config.yaml run_hermes.py start_hermes.sh gs://${BUCKET_NAME}/
6. פריסת Hermes במכונות של Cloud Run
אנחנו משתמשים ב-gcloud beta run instances deploy כדי לפרוס את הקונטיינר. הפקודה הזו כוללת הגדרות ספציפיות לטיפול בבעיות מוכרות ב-GCSFuse ובמגבלות של קונטיינרים.
מוודאים שמשתני הסביבה (PROJECT_ID, REGION, BUCKET_NAME, SERVICE_ACCOUNT) מיוצאים בסשן הטרמינל הפעיל.
פורסים את המכונה:
gcloud beta run instances deploy hermes-instance \
--image nousresearch/hermes-agent:latest \
--service-account ${SERVICE_ACCOUNT} \
--command "/bin/sh" \
--args "/opt/data/start_hermes.sh" \
--port 8080 \
--cpu 2 \
--memory 4Gi \
--ingress all \
--no-invoker-iam-check \
--add-volume name=hermes-storage,mount-path=/opt/data,type=cloud-storage,mount-options="uid=2000;gid=2000;file-mode=0777;dir-mode=0777;implicit-dirs",bucket=$BUCKET_NAME \
--set-secrets "HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=hermes-dashboard-password:latest" \
--set-env-vars "PYTHONUNBUFFERED=1,VERTEX_PROJECT_ID=$PROJECT_ID,VERTEX_LOCATION=global,HERMES_DASHBOARD_BASIC_AUTH_USERNAME=admin,HERMES_ALLOW_ROOT_GATEWAY=1,HERMES_WORKSPACE=/opt/data/workspace,HERMES_WRITE_SAFE_ROOT=/opt/data" \
--region $REGION \
--project $PROJECT_ID
הגדרות חשובות שמופיעות למעלה:
-
--service-account: צירוף חשבון השירות הייעודיhermes-sa. - Supervisor Scripts:
start_hermes.shקורא ל-Python supervisor בהתאמה אישיתrun_hermes.pyשמנתב את מגבלות הנעילה של SQLite ובעיות שמירה במטמון מ-GCS FUSE ל-tmpfs מקומי. -
--set-secrets: מחדיר פרטי כניסה ישירות מ-Secret Manager למשתני סביבה.
7. אינטראקציה ישירה דרך ממשק המשתמש האינטרנטי של Hermes
אחרי הפריסה, אפשר לגשת למרכז הבקרה בכתובת ה-URL שנוצרה .run.app. כשמוצגת בקשה לאימות, מזינים admin בתור שם המשתמש ואת ${DASHBOARD_PASSWORD} בתור הסיסמה.
צ'אט עם הסוכן
אפשר לנסות דברים כמו echo "hello" כדי לוודא שהנציג פועל.
בדיקת אחסון מתמיד
אתם יכולים לבדוק את האחסון המתמיד בקטגוריה שלכם ב-Google Cloud על ידי פנייה לנציג
Write "hello world" to a file named hello.txt in your workspace.
אחר כך, במעטפת, אפשר להריץ את הפקודה הבאה כדי לוודא שהקובץ נכתב:
gcloud storage cat gs://$BUCKET_NAME/workspace/hello.txt
לבסוף, כדי לוודא שהצ'אטים והקבצים שלכם נשמרים במופעים חדשים של Cloud Run (למופע של Cloud Run יש זמן ריצה רציף של עד 7 ימים, עם מדיניות הפעלה מחדש אוטומטית שמוגדרת כברירת מחדל), אתם יכולים להריץ מחדש את הפקודה gcloud beta run instances deploy בדיוק כמו קודם. אחרי זה יוצגו לכם סשנים של צ'אטים. ואפשר לשאול את הסוכן
Read the contents of the file hello.txt in your workspace.
יופיע hello world.
8. מחיקה
כדי להימנע מחיובים בחשבון Google Cloud על המשאבים שבהם השתמשתם ב-codelab הזה:
- מחיקת מכונת Cloud Run:
gcloud beta run instances delete hermes-instance --region ${REGION} --quiet - מחיקת סודות ב-Secret Manager:
gcloud secrets delete hermes-dashboard-password --quiet - מחיקת קטגוריה של Cloud Storage:
gcloud storage rm -r gs://${BUCKET_NAME} - מחיקת חשבון שירות ייעודי:
gcloud iam service-accounts delete ${SERVICE_ACCOUNT} --quiet
9. סיכום
מעולה! פרסתם בהצלחה מופע מאובטח ומתמשך לחלוטין של סוכן Hermes במופעי Cloud Run שמגובים על ידי Cloud Storage.
מה למדתם
- איך פורסים את סוכן Hermes למכונות Cloud Run.
- איך לטעון קטגוריות של Cloud Storage למופעים של Cloud Run באמצעות GCSFuse.
- איך מגדירים בבטחה SQLite ומטמון זמני כדי לעקוף את המגבלות של נעילת קבצים ב-GCSFuse.