نحوه استقرار Hermes Agent در نمونه‌های Cloud Run

۱. مقدمه

نمای کلی

در این آزمایش، شما یک نمونه کاملاً پایدار و امن از Hermes Agent (ساخته Nous Research) را در Cloud Run Instances مستقر خواهید کرد. شما با استفاده از Hermes Web Dashboard با عامل هوش مصنوعی خود تعامل خواهید داشت و فضای کاری پایدار آن را با Google Cloud Storage پشتیبانی خواهید کرد.

در حالی که Hermes از حالت Gateway پشتیبانی می‌کند که می‌تواند به عنوان یک سرویس Cloud Run با مقیاس‌پذیری خودکار اجرا شود، همچنین به عنوان یک عامل stateful عمل می‌کند که مهارت‌ها را در هنگام بوت اسکن کرده و اجرای پس‌زمینه را مدیریت می‌کند. Cloud Run Instances یک محیط با طول عمر بالا و آدرس‌دهی جداگانه را فراهم می‌کند که برای این حجم کاری کاملاً مناسب است.

کاری که انجام خواهید داد

  • یک مخزن ذخیره‌سازی ابری (Cloud Storage Bucket) برای حفظ وضعیت و پیکربندی‌های کانتینر آماده کنید.
  • یک ناظر پایتون سفارشی ( run_hermes.py ) و یک اسکریپت راه‌اندازی ( start_hermes.sh ) برای مدیریت مقداردهی اولیه بوت ایجاد کنید.
  • با استفاده از gcloud beta run instances deploy کنید.
  • به داشبورد هرمس دسترسی پیدا کنید و احراز هویت شوید.

آنچه یاد خواهید گرفت

  • نحوه‌ی استقرار Hermes Agent در نمونه‌های Cloud Run.
  • نحوه‌ی مانت کردن باکت‌های ذخیره‌سازی ابری روی نمونه‌های ابری با استفاده از GCSFuse.
  • چگونه می‌توان با خیال راحت SQLite و حافظه‌های موقت موقت را برای دور زدن محدودیت‌های قفل فایل GCSFuse پیکربندی کرد.

۲. تنظیمات و الزامات

راه‌اندازی پروژه GCP

  1. وارد کنسول ابری گوگل شوید.
  2. یک پروژه Google Cloud ایجاد یا انتخاب کنید.
  3. مطمئن شوید که پرداخت برای پروژه Google Cloud شما فعال است.

پوسته ابری را باز کنید

Google Cloud Shell را از نوار ابزار بالای کنسول ابری فعال کنید.

تنظیم پروژه و نصب نسخه بتای جی‌کلود

ابتدا، پروژه و منطقه خود را به عنوان متغیرهای محیطی تنظیم کنید.

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

فعال کردن API های مورد نیاز Google Cloud

در Cloud Shell، رابط‌های برنامه‌نویسی کاربردی Cloud Run، Cloud Storage و Secret Manager را فعال کنید:

gcloud services enable \
  run.googleapis.com \
  secretmanager.googleapis.com \
  storage.googleapis.com \
  compute.googleapis.com \
  aiplatform.googleapis.com

۳. ایجاد حساب کاربری اختصاصی برای سرویس

برای پایبندی به اصل حداقل امتیاز، یک حساب کاربری سرویس IAM اختصاصی برای عامل Hermes ایجاد کنید و مجوزهای لازم برای فراخوانی مدل‌های هوش مصنوعی Vertex را به آن اعطا کنید:

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"

۴. ذخیره اطلاعات کاربری در 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"

۵. آماده‌سازی سطل ذخیره‌سازی ابری و فایل‌های پیکربندی

هرمس به فضای ذخیره‌سازی پایدار (persistent storage) که به صورت /opt/data نصب شده است، نیاز دارد. ما از یک باکت (bact) فضای ذخیره‌سازی ابری گوگل (GCS) استفاده خواهیم کرد و آن را با استفاده از نصب‌کننده‌های فضای ذخیره‌سازی ابری (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"

۲. ایجاد 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

۳. ایجاد اسکریپت ناظر ( run_hermes.py )

فضای ذخیره‌سازی ابری از مکانیزم‌های قفل‌گذاری فایل خاصی که پایگاه‌های داده SQLite برای اجرای ایمن به آنها نیاز دارند، پشتیبانی نمی‌کند. برای جلوگیری از خرابی پایگاه داده، به یک اسکریپت "supervisor" سفارشی ( 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)

۴. ایجاد اسکریپت راه‌اندازی ( start_hermes.sh )

start_hermes.sh به صورت محلی ایجاد کنید.

#!/bin/sh
set -e
export PYTHONUNBUFFERED=1
exec python3 /opt/data/run_hermes.py

۵. فایل‌ها را در فضای ذخیره‌سازی ابری آپلود کنید

فایل‌های پیکربندی را در ریشه‌ی باکت GCS خود کپی کنید:

gcloud storage cp config.yaml run_hermes.py start_hermes.sh gs://${BUCKET_NAME}/

۶. هرمس را روی نمونه‌های ابری اجرا کنید

ما از 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 را ضمیمه می‌کند.
  • اسکریپت‌های ناظر : start_hermes.sh ناظر سفارشی پایتون run_hermes.py را فراخوانی می‌کند که محدودیت‌های قفل SQLite و مشکلات ذخیره‌سازی را از GCS FUSE به tmpfهای محلی هدایت می‌کند.
  • --set-secrets : اعتبارنامه‌ها را مستقیماً از Secret Manager به متغیرهای محیطی تزریق می‌کند.

۷. مستقیماً از طریق رابط کاربری وب هرمس تعامل داشته باشید

پس از استقرار، می‌توانید از طریق آدرس اینترنتی .run.app تولید شده به داشبورد خود دسترسی پیدا کنید. در صورت درخواست احراز هویت، admin به عنوان نام کاربری و ${DASHBOARD_PASSWORD} خود را به عنوان رمز عبور وارد کنید.

با نماینده خود چت کنید

می‌توانید چیزهایی مانند echo "hello" را برای تأیید کارکرد عامل امتحان کنید.

تست فضای ذخیره‌سازی پایدار

شما می‌توانید با پرسیدن از نماینده، ذخیره‌سازی پایدار را در Google Cloud Bucket خود آزمایش کنید.

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 حداکثر ۷ روز زمان اجرای مداوم دارد و سیاست راه‌اندازی مجدد خودکار به طور پیش‌فرض پیکربندی شده است)، می‌توانید دستور gcloud beta run instances deploy را دقیقاً مانند قبل اجرا کنید. سپس می‌توانید جلسات چت خود را مشاهده کنید. و می‌توانید از نماینده خود بپرسید

Read the contents of the file hello.txt in your workspace.

و شما عبارت "سلام دنیا" را خواهید دید.

۸. تمیز کردن

برای جلوگیری از تحمیل هزینه به حساب Google Cloud خود برای منابع مورد استفاده در این آزمایشگاه کد:

  1. نمونه Cloud Run را حذف کنید :
    gcloud beta run instances delete hermes-instance --region ${REGION} --quiet
    
  2. حذف اسرار مدیر مخفی :
    gcloud secrets delete hermes-dashboard-password --quiet
    
  3. حذف سطل ذخیره‌سازی ابری :
    gcloud storage rm -r gs://${BUCKET_NAME}
    
  4. حذف حساب کاربری سرویس اختصاصی :
    gcloud iam service-accounts delete ${SERVICE_ACCOUNT} --quiet
    

۹. نتیجه‌گیری

تبریک! شما با موفقیت یک نمونه امن و کاملاً پایدار از Hermes Agent را روی Cloud Run Instances که توسط Cloud Storage پشتیبانی می‌شود، مستقر کردید!

آنچه آموختید

  • نحوه‌ی استقرار Hermes Agent در نمونه‌های Cloud Run.
  • نحوه‌ی مانت کردن باکت‌های ذخیره‌سازی ابری روی نمونه‌های ابری با استفاده از GCSFuse.
  • چگونه می‌توان با خیال راحت SQLite و حافظه‌های موقت موقت را برای دور زدن محدودیت‌های قفل فایل GCSFuse پیکربندی کرد.