1. مقدمة
نظرة عامة
في هذا التمرين العملي، ستنشر مثيلاً آمنًا ومستمرًا بالكامل من Hermes Agent (من Nous Research) إلى مثيلات Cloud Run. يمكنك التفاعل مع وكيل الذكاء الاصطناعي باستخدام لوحة بيانات الويب في Hermes، والاستفادة من مساحة العمل الدائمة من خلال Google Cloud Storage.
على الرغم من أنّ Hermes يتيح "وضع البوابة" الذي يمكن تشغيله كخدمة Cloud Run قابلة للتوسّع التلقائي، إلا أنّه يعمل أيضًا كبرنامج وكيل ذي حالة يفحص المهارات عند بدء التشغيل ويتعامل مع التنفيذ في الخلفية. توفّر "مثيلات Cloud Run" بيئة طويلة الأمد يمكن الوصول إليها بشكل فردي، وهي مناسبة تمامًا لعبء العمل هذا.
الإجراءات التي ستنفذّها
- جهِّز حزمة Cloud Storage للاحتفاظ بحالة الحاوية وإعداداتها.
- أنشئ مشرف Python مخصّصًا (
run_hermes.py) ونصًا برمجيًا للتشغيل (start_hermes.sh) للتعامل مع عملية بدء التشغيل. - يمكنك نشر Hermes Agent باستخدام
gcloud beta run instances deploy. - الوصول إلى لوحة بيانات Hermes والمصادقة عليها
أهداف الدورة التعليمية
- كيفية نشر وكيل Hermes على مثيلات Cloud Run
- كيفية ربط حِزم Cloud Storage بمثيلات Cloud Run باستخدام GCSFuse
- كيفية ضبط SQLite وذاكرات التخزين المؤقت المؤقتة بأمان لتجاوز القيود المفروضة على قفل الملفات في GCSFuse
2. الإعداد والمتطلبات
إعداد مشروع Google Cloud Platform
- سجِّل الدخول إلى Google Cloud Console.
- أنشئ مشروعًا على Google Cloud أو اختَر مشروعًا حاليًا.
- تأكَّد من تفعيل الفوترة لمشروعك على 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:
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المخصّص- برامج الإشراف:
start_hermes.shتستدعي برنامج إشراف مخصّصًا بلغة Pythonrun_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.
بعد ذلك، يمكنك في shell التأكّد من أنّه تمّت كتابة الملفّ من خلال تنفيذ
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 مقابل الموارد المستخدَمة في هذا الدرس العملي:
- حذف مثيل 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 Agent على مثيلات Cloud Run التي تستند إلى Cloud Storage.
ما تعلّمته
- كيفية نشر وكيل Hermes على مثيلات Cloud Run
- كيفية ربط حِزم Cloud Storage بمثيلات Cloud Run باستخدام GCSFuse
- كيفية ضبط SQLite وذاكرات التخزين المؤقت المؤقتة بأمان لتجاوز القيود المفروضة على قفل الملفات في GCSFuse