Hermes aracısını Cloud Run örneklerine dağıtma

1. Giriş

Genel Bakış

Bu laboratuvarda, Hermes Agent'ın (Nous Research tarafından) tamamen kalıcı ve güvenli bir örneğini Cloud Run örneklerine dağıtacaksınız. Hermes Web Kontrol Paneli'ni kullanarak yapay zeka aracınızla etkileşim kuracak ve kalıcı çalışma alanını Google Cloud Storage ile destekleyeceksiniz.

Hermes, otomatik ölçeklendirme yapan bir Cloud Run hizmeti olarak çalışabilecek bir Ağ Geçidi modunu desteklese de önyükleme sırasında becerileri tarayan ve arka planda yürütmeyi işleyen durum bilgili bir ajan olarak da işlev görür. Cloud Run örnekleri, bu iş yükü için mükemmel bir uyum sağlayan, uzun ömürlü ve ayrı ayrı adreslenebilir bir ortam sunar.

Yapacaklarınız

  • Kapsayıcı durumunu ve yapılandırmalarını kalıcı hale getirmek için bir Cloud Storage paketi hazırlayın.
  • Önyükleme başlatma işlemini yönetmek için özel bir Python denetçisi (run_hermes.py) ve bir başlangıç komut dosyası (start_hermes.sh) oluşturun.
  • gcloud beta run instances deploy kullanarak Hermes aracısını dağıtın.
  • Hermes kontrol paneline erişin ve kimliğinizi doğrulayın.

Neler öğreneceksiniz?

  • Hermes aracısını Cloud Run örneklerine dağıtma
  • GCSFuse kullanarak Cloud Storage paketlerini Cloud Run örneklerine bağlama
  • GCSFuse dosya kilitleme sınırlamalarını atlamak için SQLite ve geçici önbellekleri güvenli bir şekilde yapılandırma

2. Kurulum ve Gereksinimler

GCP Projesi Kurulumu

  1. Google Cloud Console'da oturum açın.
  2. Google Cloud projesi oluşturun veya seçin.
  3. Google Cloud projeniz için faturalandırmanın etkinleştirildiğinden emin olun.

Cloud Shell'i açın

Cloud Console'un üst araç çubuğundan Google Cloud Shell'i etkinleştirin.

Projeyi ayarlama ve gcloud beta'yı yükleme

Öncelikle projenizi ve bölgenizi ortam değişkenleri olarak ayarlayın.

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

Ayrıca projenizi gcloud için yapılandırın.

gcloud config set project $PROJECT_ID

gcloud beta run instances için beta bileşeninin yüklendiğinden emin olun:

gcloud components install beta --quiet

gcloud sürümünüz güncel olmalıdır.

gcloud components updates

Gerekli Google Cloud API'lerini etkinleştirme

Cloud Shell'de Cloud Run, Cloud Storage ve Secret Manager API'lerini etkinleştirin:

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

3. Özel Hizmet Hesabı Oluşturma

En az ayrıcalık ilkesine uymak için Hermes aracısı için özel bir IAM hizmet hesabı oluşturun ve Vertex AI modellerini çağırmak için gerekli izinleri verin:

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. Kimlik bilgilerini Secret Manager'da saklama

Kontrol paneli şifresi gibi hassas kimlik bilgilerini Google Cloud Secret Manager'da depolarız. Böylece Cloud Run, bu bilgileri başlatma sırasında kapsayıcıya güvenli bir şekilde yerleştirebilir.

Kontrol paneliniz için güvenli bir rastgele şifre oluşturun ve bu şifreyi Secret Manager'da saklayın:

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 paketi ve yapılandırma dosyalarını hazırlama

Hermes'in /opt/data olarak monte edilmiş kalıcı depolama alanına ihtiyacı vardır. Google Cloud Storage (GCS) paketi kullanıp Cloud Storage birim bağlamalarını kullanarak paketi bağlayacağız.

1. Cloud Storage paketi oluşturma

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 oluştur

config.yaml dosyası oluşturun. Yapılandırmanın doğru şekilde yüklendiğinden emin olmak için _config_version: 12 eklediğinizden emin olun:

_config_version: 12

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

dashboard:
  enabled: true

database:
  journal_mode: delete

3. Yönetici Komut Dosyası Oluşturma (run_hermes.py)

Cloud Storage, SQLite veritabanlarının güvenli bir şekilde çalışması için gereken belirli dosya kilitleme mekanizmalarını desteklemez. Veritabanı bozulmasını önlemek için özel bir "denetleyici" komut dosyasına (run_hermes.py) ihtiyacımız var. Bu komut dosyası, aracı başlatmadan önce Hermes'i geçici veritabanı kilitlerini Cloud Storage yerine kapsayıcının yerel belleğinde saklayacak şekilde yapılandırır.

run_hermes.py öğesini yerel olarak oluşturma:

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. Başlangıç komut dosyasını oluşturma (start_hermes.sh)

start_hermes.sh öğesini yerel olarak oluşturun.

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

5. Cloud Storage'a dosya yükleme

Yapılandırma dosyalarını GCS paketinize kopyalayın:

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

6. Hermes'i Cloud Run örneklerine dağıtma

Kapsayıcıyı dağıtmak için gcloud beta run instances deploy kullanıyoruz. Bu komut, GCSFuse ve kapsayıcı sınırlarıyla ilgili bilinen sorunları gidermek için belirli yapılandırmalar içerir.

Ortam değişkenlerinizin (PROJECT_ID, REGION, BUCKET_NAME, SERVICE_ACCOUNT) etkin terminal oturumunuza aktarıldığından emin olun.

Örneği dağıtın:

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

Yukarıda Belirtilen Önemli Yapılandırmalar:

  • --service-account: Özel hermes-sa hizmet hesabını ekler.
  • Supervisor komut dosyaları: start_hermes.sh, SQLite kilitleme sınırlarını ve önbelleğe alma sorunlarını GCS FUSE'dan yerel tmpfs'ye yönlendiren özel Python supervisor'ı run_hermes.py çağırır.
  • --set-secrets: Kimlik bilgilerini doğrudan Secret Manager'dan ortam değişkenlerine yerleştirir.

7. Hermes web arayüzü üzerinden doğrudan etkileşim kurma

Dağıtım tamamlandıktan sonra, oluşturulan .run.app URL'sinden kontrol panelinize erişebilirsiniz. Kimlik doğrulama istendiğinde Kullanıcı adı olarak admin, şifre olarak da ${DASHBOARD_PASSWORD} girin.

Temsilcinizle sohbet etme

Temsilcinin çalıştığını doğrulamak için echo "hello" gibi ifadeler deneyebilirsiniz.

Kalıcı depolama alanını test etme

Ajanı sorgulayarak Google Cloud paketinizdeki kalıcı depolama alanını test edebilirsiniz.

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

Ardından kabuğunuzda aşağıdaki komutu çalıştırarak dosyanın yazıldığını doğrulayabilirsiniz:

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

Son olarak, sohbetlerinizin ve dosyalarınızın yeni Cloud Run örneklerinde kalıcı olduğunu doğrulamak için (Cloud Run örneği, varsayılan olarak yapılandırılmış otomatik yeniden başlatma politikasıyla 7 güne kadar kesintisiz çalışma süresine sahiptir) gcloud beta run instances deploy komutunu tam olarak daha önce olduğu gibi yeniden çalıştırabilirsiniz. Ardından sohbet oturumlarınızı görürsünüz. Ayrıca, temsilcinize şunları sorabilirsiniz:

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

ve "hello world" ifadesini görürsünüz.

8. Temizleme

Bu codelab'de kullanılan kaynaklar için Google Cloud hesabınızın ücretlendirilmesini istemiyorsanız:

  1. Cloud Run örneğini silin:
    gcloud beta run instances delete hermes-instance --region ${REGION} --quiet
    
  2. Secret Manager gizli anahtarlarını silme:
    gcloud secrets delete hermes-dashboard-password --quiet
    
  3. Cloud Storage paketini silme:
    gcloud storage rm -r gs://${BUCKET_NAME}
    
  4. Özel hizmet hesabını silme:
    gcloud iam service-accounts delete ${SERVICE_ACCOUNT} --quiet
    

9. Sonuç

Tebrikler! Cloud Storage tarafından desteklenen Cloud Run örneklerinde Hermes aracısının güvenli ve tamamen kalıcı bir örneğini başarıyla dağıttınız.

Öğrendikleriniz

  • Hermes aracısını Cloud Run örneklerine dağıtma
  • GCSFuse kullanarak Cloud Storage paketlerini Cloud Run örneklerine bağlama
  • GCSFuse dosya kilitleme sınırlamalarını atlamak için SQLite ve geçici önbellekleri güvenli bir şekilde yapılandırma