วิธีติดตั้งใช้งาน Hermes Agent ในอินสแตนซ์ Cloud Run

1. บทนำ

ภาพรวม

ในแล็บนี้ คุณจะได้ติดตั้งใช้งานอินสแตนซ์ที่ปลอดภัยและคงอยู่ตลอดเวลาของ Hermes Agent (จาก Nous Research) ในอินสแตนซ์ Cloud Run คุณจะโต้ตอบกับ AI Agent โดยใช้แดชบอร์ดเว็บของ Hermes และสำรองข้อมูลพื้นที่ทำงานถาวรด้วย Google Cloud Storage

แม้ว่า Hermes จะรองรับโหมด Gateway ที่สามารถเรียกใช้เป็นบริการ Cloud Run ที่ปรับขนาดอัตโนมัติได้ แต่ก็ยังทำหน้าที่เป็นเอเจนต์แบบมีสถานะที่สแกนทักษะเมื่อบูตและจัดการการดำเนินการในเบื้องหลังด้วย อินสแตนซ์ Cloud Run มีสภาพแวดล้อมที่ใช้งานได้นานและกำหนดที่อยู่ได้ทีละรายการ ซึ่งเหมาะกับภาระงานนี้

สิ่งที่คุณต้องทำ

  • เตรียม Bucket ของ Cloud Storage เพื่อคงสถานะและการกำหนดค่าของคอนเทนเนอร์
  • สร้างโปรแกรมควบคุม Python ที่กำหนดเอง (run_hermes.py) และสคริปต์เริ่มต้น (start_hermes.sh) เพื่อจัดการการเริ่มต้นระบบ
  • ติดตั้งใช้งาน Hermes Agent โดยใช้ gcloud beta run instances deploy
  • เข้าถึงและตรวจสอบสิทธิ์แดชบอร์ด Hermes

สิ่งที่คุณจะได้เรียนรู้

  • วิธีติดตั้งใช้งาน Hermes Agent ในอินสแตนซ์ Cloud Run
  • วิธีติดตั้งที่เก็บข้อมูล Cloud Storage กับอินสแตนซ์ Cloud Run โดยใช้ GCSFuse
  • วิธีกำหนดค่า SQLite และแคชชั่วคราวอย่างปลอดภัยเพื่อหลีกเลี่ยงข้อจำกัดในการล็อกไฟล์ของ GCSFuse

2. การตั้งค่าและข้อกำหนด

การตั้งค่าโปรเจ็กต์ GCP

  1. ลงชื่อเข้าใช้ คอนโซล Google Cloud
  2. สร้างหรือเลือกโปรเจ็กต์ Google Cloud
  3. ตรวจสอบว่าได้เปิดใช้การเรียกเก็บเงินสำหรับโปรเจ็กต์ Google Cloud แล้ว

เปิด Cloud Shell

เปิดใช้งาน Google Cloud Shell จากแถบเครื่องมือด้านบนของ Cloud Console

ตั้งค่าโปรเจ็กต์และติดตั้ง gcloud รุ่นเบต้า

ก่อนอื่น ให้ตั้งค่าโปรเจ็กต์และภูมิภาคเป็นตัวแปรสภาพแวดล้อม

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 APIs ที่จำเป็น

ใน Cloud Shell ให้เปิดใช้ Cloud Run, Cloud Storage และ Secret Manager API โดยทำดังนี้

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. เตรียม Bucket ของ Cloud Storage และไฟล์การกำหนดค่า

Hermes ต้องมีพื้นที่เก็บข้อมูลถาวรที่ติดตั้งเป็น /opt/data เราจะใช้ที่เก็บข้อมูล Google Cloud Storage (GCS) และติดตั้งโดยใช้การติดตั้งโวลุ่ม Cloud Storage

1. สร้าง Bucket ของ 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 ต้องใช้เพื่อเรียกใช้อย่างปลอดภัย เราต้องมีสคริปต์ "Supervisor" ที่กำหนดเอง (run_hermes.py) เพื่อป้องกันไม่ให้ฐานข้อมูลเสียหาย สคริปต์นี้จะกำหนดค่า Hermes ให้จัดเก็บการล็อกฐานข้อมูลชั่วคราวไว้ในหน่วยความจำภายในของคอนเทนเนอร์แทนที่จะจัดเก็บไว้ใน Cloud Storage ก่อนที่จะเริ่ม Agent

สร้าง 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

คัดลอกไฟล์การกำหนดค่าไปยังรูทของ Bucket ของ 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: start_hermes.sh เรียกใช้ Supervisor ของ Python ที่กำหนดเองrun_hermes.pyซึ่งกำหนดเส้นทางข้อจำกัดการล็อก SQLite และปัญหาการแคชออกจาก GCS FUSE ไปยัง tmpfs ในเครื่อง
  • --set-secrets: แทรกข้อมูลเข้าสู่ระบบจาก Secret Manager ลงในตัวแปรสภาพแวดล้อมโดยตรง

7. โต้ตอบโดยตรงผ่าน UI บนเว็บของ Hermes

เมื่อติดตั้งใช้งานแล้ว คุณจะเข้าถึงแดชบอร์ดได้ที่ .run.app URL ที่สร้างขึ้น เมื่อระบบแจ้งให้ตรวจสอบสิทธิ์ ให้ป้อน admin เป็นชื่อผู้ใช้และ ${DASHBOARD_PASSWORD} เป็นรหัสผ่าน

แชทกับตัวแทน

คุณลองใช้คำสั่งอย่าง echo "hello" เพื่อยืนยันว่าเอเจนต์ทำงานได้

ทดสอบพื้นที่เก็บข้อมูลถาวร

คุณทดสอบพื้นที่เก็บข้อมูลถาวรใน Bucket ของ 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 นี้

  1. ลบอินสแตนซ์ Cloud Run
    gcloud beta run instances delete hermes-instance --region ${REGION} --quiet
    
  2. ลบข้อมูลลับใน Secret Manager:
    gcloud secrets delete hermes-dashboard-password --quiet
    
  3. ลบที่เก็บข้อมูล Cloud Storage:
    gcloud storage rm -r gs://${BUCKET_NAME}
    
  4. ลบบัญชีบริการเฉพาะ
    gcloud iam service-accounts delete ${SERVICE_ACCOUNT} --quiet
    

9. บทสรุป

ยินดีด้วย คุณได้ติดตั้งใช้งานอินสแตนซ์ของ Hermes Agent ที่ปลอดภัยและคงอยู่ตลอดเวลาบนอินสแตนซ์ Cloud Run ที่ได้รับการสนับสนุนโดย Cloud Storage เรียบร้อยแล้ว

สิ่งที่คุณได้เรียนรู้

  • วิธีติดตั้งใช้งาน Hermes Agent ในอินสแตนซ์ Cloud Run
  • วิธีติดตั้งที่เก็บข้อมูล Cloud Storage กับอินสแตนซ์ Cloud Run โดยใช้ GCSFuse
  • วิธีกำหนดค่า SQLite และแคชชั่วคราวอย่างปลอดภัยเพื่อหลีกเลี่ยงข้อจำกัดในการล็อกไฟล์ของ GCSFuse