Cara Men-deploy Agen Hermes di instance Cloud Run

1. Pengantar

Ringkasan

Di lab ini, Anda akan men-deploy instance Hermes Agent (oleh Nous Research) yang sepenuhnya persisten dan aman ke Instance Cloud Run. Anda akan berinteraksi dengan agen AI menggunakan Dasbor Web Hermes, dan mendukung ruang kerja persistennya dengan Google Cloud Storage.

Meskipun Hermes mendukung mode Gateway yang dapat berjalan sebagai Layanan Cloud Run dengan penskalaan otomatis, Hermes juga bertindak sebagai agen stateful yang memindai kemampuan saat booting dan menangani eksekusi di latar belakang. Instance Cloud Run menyediakan lingkungan yang dapat diatasi secara individual dan berumur panjang yang sangat cocok untuk workload ini.

Yang akan Anda lakukan

  • Siapkan bucket Cloud Storage untuk mempertahankan status dan konfigurasi penampung.
  • Buat supervisor Python kustom (run_hermes.py) dan skrip startup (start_hermes.sh) untuk menangani inisialisasi booting.
  • Deploy Agen Hermes menggunakan gcloud beta run instances deploy.
  • Akses dan autentikasi ke Dasbor Hermes.

Yang akan Anda pelajari

  • Cara men-deploy Agen Hermes ke Instance Cloud Run.
  • Cara memasang bucket Cloud Storage ke Instance Cloud Run menggunakan GCSFuse.
  • Cara mengonfigurasi SQLite dan cache sementara dengan aman untuk melewati batasan penguncian file GCSFuse.

2. Penyiapan dan Persyaratan

Penyiapan Project GCP

  1. Login ke Konsol Google Cloud.
  2. Buat atau pilih Project Google Cloud.
  3. Pastikan penagihan diaktifkan untuk project Google Cloud Anda.

Buka Cloud Shell

Aktifkan Google Cloud Shell dari toolbar atas Konsol Cloud.

Menetapkan Project & Menginstal gcloud beta

Pertama, tetapkan project dan region Anda sebagai variabel lingkungan.

export PROJECT_ID=<YOUR_PROJECT_ID>
export REGION="us-west2"
export BUCKET_NAME="hermes-state-${PROJECT_ID}"

Lalu, konfigurasi project Anda untuk gcloud.

gcloud config set project $PROJECT_ID

Pastikan komponen beta diinstal untuk gcloud beta run instances:

gcloud components install beta --quiet

Versi gcloud Anda sudah yang terbaru.

gcloud components updates

Mengaktifkan Google Cloud API yang Diperlukan

Di Cloud Shell, aktifkan Cloud Run, Cloud Storage, dan Secret Manager API:

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

3. Buat Akun Layanan Khusus

Untuk mematuhi prinsip hak istimewa terendah, buat akun layanan IAM khusus untuk agen Hermes dan berikan izin yang diperlukan untuk memanggil model 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. Menyimpan Kredensial di Secret Manager

Kita akan menyimpan kredensial sensitif seperti sandi dasbor di Secret Manager Google Cloud sehingga Cloud Run dapat menyuntikkannya secara aman ke dalam container saat waktu booting.

Buat sandi acak yang aman untuk dasbor Anda dan simpan di 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. Siapkan Bucket Cloud Storage & File Konfigurasi

Hermes memerlukan penyimpanan persisten yang di-mount sebagai /opt/data. Kita akan menggunakan bucket Google Cloud Storage (GCS) dan memasangnya menggunakan pemasangan volume Cloud Storage.

1. Membuat 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. Buat config.yaml

Buat file config.yaml. Pastikan untuk menyertakan _config_version: 12 guna memastikan konfigurasi dimuat dengan benar:

_config_version: 12

model:
  default: "google/gemini-3.8-flash"
  provider: "vertex"

dashboard:
  enabled: true

database:
  journal_mode: delete

3. Buat Skrip Supervisor (run_hermes.py)

Cloud Storage tidak mendukung mekanisme penguncian file tertentu yang diperlukan database SQLite agar dapat berjalan dengan aman. Untuk mencegah kerusakan database, kita memerlukan skrip "supervisor" kustom (run_hermes.py). Skrip ini mengonfigurasi Hermes untuk menyimpan kunci database sementara di memori lokal penampung, bukan di Cloud Storage sebelum memulai agen.

Buat run_hermes.py secara lokal:

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. Buat Skrip Startup (start_hermes.sh)

Buat start_hermes.sh secara lokal.

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

5. Mengupload File ke Cloud Storage

Salin file konfigurasi ke root bucket GCS Anda:

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

6. Men-deploy Hermes di Instance Cloud Run

Kita menggunakan gcloud beta run instances deploy untuk men-deploy container. Perintah ini mencakup konfigurasi tertentu untuk mengatasi masalah umum terkait GCSFuse dan batas container.

Pastikan variabel lingkungan Anda (PROJECT_ID, REGION, BUCKET_NAME, SERVICE_ACCOUNT) diekspor dalam sesi terminal aktif Anda.

Deploy instance:

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

Konfigurasi Penting yang Tercantum di Atas:

  • --service-account: Melampirkan akun layanan hermes-sa khusus.
  • Skrip Supervisor: start_hermes.sh memanggil supervisor Python kustom run_hermes.py yang mengalihkan batas penguncian SQLite dan masalah caching dari GCS FUSE ke tmpfs lokal.
  • --set-secrets: Memasukkan kredensial langsung dari Secret Manager ke dalam variabel lingkungan.

7. Berinteraksi Langsung melalui UI Web Hermes

Setelah di-deploy, Anda dapat mengakses dasbor di URL .run.app yang dibuat. Saat diminta untuk melakukan autentikasi, masukkan admin sebagai Nama Pengguna dan ${DASHBOARD_PASSWORD} Anda sebagai Sandi.

Mulai chat dengan agen Anda

Anda dapat mencoba hal-hal seperti echo "hello" untuk mengonfirmasi bahwa agen berfungsi.

Menguji penyimpanan persisten

Anda dapat menguji penyimpanan persisten di Bucket Google Cloud dengan mengajukan pertanyaan kepada agen

Write "hello world" to a file named hello.txt in your workspace.

Kemudian di shell, Anda dapat memverifikasi bahwa file telah ditulis dengan menjalankan

gcloud storage cat gs://$BUCKET_NAME/workspace/hello.txt

Terakhir, untuk memverifikasi bahwa percakapan dan file Anda tetap ada di seluruh instance Cloud Run baru (karena instance Cloud Run memiliki runtime berkelanjutan hingga 7 hari, dengan kebijakan mulai ulang otomatis yang dikonfigurasi secara default), Anda dapat menjalankan kembali perintah gcloud beta run instances deploy persis seperti sebelumnya. Kemudian, Anda akan melihat sesi chat Anda. Anda dapat bertanya kepada agen

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

dan Anda akan melihat "hello world".

8. Pembersihan

Agar tidak menimbulkan biaya pada akun Google Cloud Anda untuk resource yang digunakan dalam codelab ini:

  1. Hapus Instance Cloud Run:
    gcloud beta run instances delete hermes-instance --region ${REGION} --quiet
    
  2. Menghapus Secret Manager Secrets:
    gcloud secrets delete hermes-dashboard-password --quiet
    
  3. Menghapus Bucket Cloud Storage:
    gcloud storage rm -r gs://${BUCKET_NAME}
    
  4. Menghapus Akun Layanan Khusus:
    gcloud iam service-accounts delete ${SERVICE_ACCOUNT} --quiet
    

9. Kesimpulan

Selamat! Anda telah berhasil men-deploy instance Hermes Agent yang aman dan sepenuhnya persisten di Instance Cloud Run yang didukung oleh Cloud Storage.

Yang telah Anda pelajari

  • Cara men-deploy Agen Hermes ke Instance Cloud Run.
  • Cara memasang bucket Cloud Storage ke Instance Cloud Run menggunakan GCSFuse.
  • Cara mengonfigurasi SQLite dan cache sementara dengan aman untuk melewati batasan penguncian file GCSFuse.