如何使用 Terraform 在 Cloud Run 執行個體上部署 OpenClaw

1. 簡介

總覽

在本實驗室中,您將使用宣告式基礎架構即程式碼 (Terraform),將完全永久性的正式版 OpenClaw (開放原始碼 AI 代理框架) 執行個體部署至 Cloud Run 執行個體。您將使用 google 供應商的 google_cloud_run_v2_instance 資源佈建 Cloud Run 執行個體、透過 Cloud Storage FUSE 掛接 Google Cloud Storage 來保留代理工作區狀態、使用 Google Cloud Secret Manager 安全管理 API 憑證,並透過 OpenClaw 的內建網頁 UI 與 AI 代理互動,使用 Cloud Run 執行個體原生 SSH 進行安全裝置配對。

開始之前,您可以先參閱 OpenClaw 說明文件,熟悉 OpenClaw 的架構、工具和 Agentic Workflows。

學習內容

  • 在 Terraform (main.tf) 中以宣告方式定義整個基礎架構,包括 API、服務帳戶、IAM 繫結、密鑰、儲存空間 bucket 和 Cloud Run 執行個體。
  • 使用 google_cloud_run_v2_instance 部署 OpenClaw 2.0 以上版本 (ghcr.io/openclaw/openclaw:latest)。
  • 在 Secret Manager 中自動產生及儲存安全閘道憑證。
  • 透過 Cloud Storage FUSE 掛接 Cloud Storage bucket,並同步處理 /home/node/.openclaw,以確保狀態、工作區和 SQLite 持續存在。
  • 使用 gcloud beta run instances ssh 驗證及核准瀏覽器裝置配對要求,以便進入 OpenClaw Control 網頁版使用者介面。
  • (選用) 設定訊息管道 (Telegram 或 WhatsApp)。
  • (選用) 將自訂技能新增至 Cloud Storage,擴充代理的功能。

課程內容

  • 如何使用 Terraform 宣告及部署 Cloud Run 執行個體。
  • 如何在 Terraform 中設定 Cloud Storage FUSE 磁碟區掛接。
  • 如何將 Secret Manager 密鑰,以環境變數的形式安全地插入 Cloud Run 執行個體。
  • 如何使用 Cloud Run 執行個體原生 SSH 管理 OpenClaw 2.0 裝置驗證。
  • 如何在 Cloud Run 執行個體上,執行長時間運作的永久代理工作負載。

2. 設定和需求

設定 GCP 專案

  1. 登入 Google Cloud 控制台。
  2. 建立或選取 Google Cloud 專案。
  3. 確認 Google Cloud 專案已啟用計費功能。

開啟 Cloud Shell

從 Cloud 控制台頂端的工具列啟用 Google Cloud Shell。Cloud Shell 已預先安裝 Terraform 和 Google Cloud SDK。

設定專案和區域

將專案 ID 和區域設為環境變數,並設定 gcloud:

export PROJECT_ID=<YOUR_PROJECT_ID>
export REGION=<YOUR_REGION>

gcloud config set project $PROJECT_ID
gcloud config get project

安裝 gcloud Beta 版元件

確認已安裝 beta 元件 (Cloud Run 執行個體 SSH 和記錄串流功能需要):

gcloud components install beta --quiet
gcloud components update

驗證 Terraform

確認 Terraform 已安裝且符合版本需求:

terraform version

您應該會看到 Terraform 1.5.0 以上版本。

3. (選用) 設定訊息整合功能 (Telegram 或 WhatsApp)

您可以將 OpenClaw 連結至 Telegram 或 WhatsApp。請選擇下方的選項 A 或選項 B。

選項 A:設定 Telegram 機器人

  1. 開啟 Telegram 並搜尋 @BotFather。
  2. 傳送 /newbot 指令,然後按照提示指定機器人名稱和使用者名稱。
  3. 複製產生的 HTTP API 權杖 (例如 123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ)。
  4. 在 Telegram 搜尋 @userinfobot,傳送 /start,然後複製數字使用者 ID (例如 8035936176)。

您將在 terraform.tfvars 檔案中,以 Terraform 輸入變數的形式提供這些值。

選項 B:WhatsApp 設定

  1. 取得國際電話號碼格式的個人 WhatsApp 電話號碼,不含空格或符號 (例如 +15551234567)。
  2. OpenClaw 透過 WhatsApp 管道許可清單政策連線。

4. 在 Terraform 中定義基礎架構

我們會在 Terraform 中宣告整個基礎架構。包括啟用必要的 Google Cloud API、建立專屬服務帳戶和 IAM 角色、在 Secret Manager 產生及儲存密鑰、佈建 Cloud Storage 狀態 bucket、產生 openclaw.json 設定,以及部署 Cloud Run 執行個體。

1. 建立 Terraform 目錄

在 Cloud Shell 建立 Terraform 檔案的目錄,並前往該目錄:

mkdir openclaw-terraform && cd openclaw-terraform

2. 建立「main.tf」

建立名為 main.tf 的檔案。這項設定使用 Google 供應商定義所有基礎架構元件:

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = ">= 8.8.0"
    }
    random = {
      source  = "hashicorp/random"
      version = ">= 3.5.0"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
}

# 1. Enable Required APIs
resource "google_project_service" "services" {
  for_each = toset([
    "run.googleapis.com",
    "secretmanager.googleapis.com",
    "storage.googleapis.com",
    "generativelanguage.googleapis.com",
    "compute.googleapis.com"
  ])
  project            = var.project_id
  service            = each.key
  disable_on_destroy = false
}

# 2. Dedicated Service Account
resource "google_service_account" "openclaw_sa" {
  account_id   = "openclaw-sa"
  display_name = "OpenClaw Service Account"
  depends_on   = [google_project_service.services]
}

# 3. Secret Manager: Gateway Password
resource "random_password" "gateway_password" {
  length  = 32
  special = false
}

resource "google_secret_manager_secret" "gateway_password" {
  secret_id = "openclaw-gateway-password"
  replication {
    auto {}
  }
  depends_on = [google_project_service.services]
}

resource "google_secret_manager_secret_version" "gateway_password" {
  secret      = google_secret_manager_secret.gateway_password.id
  secret_data = random_password.gateway_password.result
}

resource "google_secret_manager_secret_iam_member" "sa_gateway_password" {
  secret_id = google_secret_manager_secret.gateway_password.id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${google_service_account.openclaw_sa.email}"
}

# 4. Secret Manager: Gemini API Key
resource "google_secret_manager_secret" "gemini_api_key" {
  secret_id = "gemini-api-key"
  replication {
    auto {}
  }
  depends_on = [google_project_service.services]
}

resource "google_secret_manager_secret_version" "gemini_api_key" {
  secret      = google_secret_manager_secret.gemini_api_key.id
  secret_data = var.gemini_api_key
}

resource "google_secret_manager_secret_iam_member" "sa_gemini_api_key" {
  secret_id = google_secret_manager_secret.gemini_api_key.id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${google_service_account.openclaw_sa.email}"
}

# 5. Optional Secret Manager: Telegram Bot Token
resource "google_secret_manager_secret" "telegram_bot_token" {
  count     = var.telegram_bot_token != "" ? 1 : 0
  secret_id = "telegram-bot-token"
  replication {
    auto {}
  }
  depends_on = [google_project_service.services]
}

resource "google_secret_manager_secret_version" "telegram_bot_token" {
  count       = var.telegram_bot_token != "" ? 1 : 0
  secret      = google_secret_manager_secret.telegram_bot_token[0].id
  secret_data = var.telegram_bot_token
}

resource "google_secret_manager_secret_iam_member" "sa_telegram_bot_token" {
  count     = var.telegram_bot_token != "" ? 1 : 0
  secret_id = google_secret_manager_secret.telegram_bot_token[0].id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${google_service_account.openclaw_sa.email}"
}

# 6. Cloud Storage Bucket for State Persistence
resource "google_storage_bucket" "openclaw_state" {
  name                        = "openclaw-state-${var.project_id}"
  location                    = var.region
  uniform_bucket_level_access = true
  force_destroy               = true
  depends_on                  = [google_project_service.services]
}

resource "google_storage_bucket_iam_member" "sa_storage" {
  bucket = google_storage_bucket.openclaw_state.name
  role   = "roles/storage.objectUser"
  member = "serviceAccount:${google_service_account.openclaw_sa.email}"
}

# 7. Upload openclaw.json to Cloud Storage Root
resource "google_storage_bucket_object" "openclaw_config" {
  name   = "openclaw.json"
  bucket = google_storage_bucket.openclaw_state.name
  content = jsonencode({
    gateway = {
      mode           = "local"
      port           = 18789
      trustedProxies = ["127.0.0.1", "::1", "169.254.0.0/16", "10.0.0.0/8"]
      bind           = "lan"
      auth = {
        password = "$${OPENCLAW_GATEWAY_PASSWORD}"
      }
      controlUi = {
        allowedOrigins = ["*"]
        enabled        = true
      }
    }
    memory = {
      search = {
        provider = "gemini"
      }
    }
    agents = {
      defaults = {
        model = {
          primary = "google/gemini-3.1-pro-preview"
        }
        sandbox = {
          mode = "off"
        }
      }
    }
    skills = {
      load = {
        extraDirs = ["/mnt/openclaw/skills"]
      }
    }
    channels = {
      telegram = {
        enabled        = var.telegram_bot_token != ""
        defaultAccount = "default"
        accounts = {
          default = {
            enabled   = var.telegram_bot_token != ""
            dmPolicy  = "allowlist"
            allowFrom = var.telegram_user_id != "" ? [var.telegram_user_id] : []
          }
        }
      }
    }
    plugins = {
      entries = {
        google   = { enabled = true }
        telegram = { enabled = var.telegram_bot_token != "" }
      }
    }
  })
}

# 8. Cloud Run Instance
resource "google_cloud_run_v2_instance" "openclaw" {
  name     = "openclaw-instance"
  location = var.region

  ingress              = "INGRESS_TRAFFIC_ALL"
  invoker_iam_disabled = true
  restart_policy       = "ON_FAILURE"
  service_account      = google_service_account.openclaw_sa.email

  containers {
    image = "ghcr.io/openclaw/openclaw:latest"
    args = [
      "/bin/sh",
      "-c",
      "cp /mnt/openclaw/openclaw.json /home/node/.openclaw/ && exec node /app/openclaw.mjs gateway"
    ]

    ports {
      container_port = 18789
    }

    resources {
      limits = {
        cpu    = "4"
        memory = "4Gi"
      }
    }

    env {
      name = "GEMINI_API_KEY"
      value_source {
        secret_key_ref {
          secret  = google_secret_manager_secret.gemini_api_key.secret_id
          version = "latest"
        }
      }
    }

    env {
      name = "OPENCLAW_GATEWAY_PASSWORD"
      value_source {
        secret_key_ref {
          secret  = google_secret_manager_secret.gateway_password.secret_id
          version = "latest"
        }
      }
    }

    dynamic "env" {
      for_each = toset(var.telegram_bot_token != "" ? ["telegram"] : [])
      content {
        name = "TELEGRAM_BOT_TOKEN"
        value_source {
          secret_key_ref {
            secret  = google_secret_manager_secret.telegram_bot_token[0].secret_id
            version = "latest"
          }
        }
      }
    }

    volume_mounts {
      name       = "openclaw-storage"
      mount_path = "/mnt/openclaw"
    }
  }

  volumes {
    name = "openclaw-storage"
    gcs {
      bucket        = google_storage_bucket.openclaw_state.name
      read_only     = false
      mount_options = ["uid=1000", "gid=1000", "file-mode=0700", "dir-mode=0700"]
    }
  }

  depends_on = [
    google_project_service.services,
    google_storage_bucket_object.openclaw_config,
    google_secret_manager_secret_version.gateway_password,
    google_secret_manager_secret_version.gemini_api_key,
    google_secret_manager_secret_iam_member.sa_gateway_password,
    google_secret_manager_secret_iam_member.sa_gemini_api_key,
    google_storage_bucket_iam_member.sa_storage
  ]
}

3. 建立「variables.tf」

建立名為 variables.tf 的檔案,定義輸入變數:

variable "project_id" {
  type        = string
  description = "Google Cloud Project ID"
}

variable "region" {
  type        = string
  default     = "us-west1"
  description = "Google Cloud Region for Cloud Run and Storage"
}

variable "gemini_api_key" {
  type        = string
  sensitive   = true
  description = "Gemini API Key from Google AI Studio"
}

variable "telegram_bot_token" {
  type        = string
  default     = ""
  sensitive   = true
  description = "(Optional) Telegram Bot HTTP API Token"
}

variable "telegram_user_id" {
  type        = string
  default     = ""
  description = "(Optional) Telegram User ID for allowlist policy"
}

4. 建立「outputs.tf」

建立名為 outputs.tf 的檔案,公開已部署的執行個體網址、產生的閘道密碼和儲存空間 bucket 名稱:

output "instance_url" {
  description = "Public URL for OpenClaw Cloud Run Instance"
  value       = google_cloud_run_v2_instance.openclaw.urls[0]
}

output "gateway_password" {
  description = "Generated OpenClaw Gateway Password"
  value       = random_password.gateway_password.result
  sensitive   = true
}

output "storage_bucket" {
  description = "Cloud Storage bucket persisting OpenClaw state"
  value       = google_storage_bucket.openclaw_state.name
}

5. 建立「terraform.tfvars」

取得 Gemini API 金鑰的方法如下:

  1. 前往 Google AI Studio,使用 Google 帳戶登入。
  2. 按一下「建立 API 金鑰」,然後選取 Google Cloud 專案。
  3. 複製產生的 API 金鑰。

建立名為 terraform.tfvars 的檔案,並加入專案座標:

project_id     = "YOUR_PROJECT_ID"
region         = "YOUR_REGION"
gemini_api_key = "YOUR_GEMINI_API_KEY"

# Optional: Uncomment if configuring Telegram
# telegram_bot_token = "YOUR_TELEGRAM_BOT_TOKEN"
# telegram_user_id   = "YOUR_TELEGRAM_USER_ID"

5. 使用 Terraform 部署基礎架構

1. 初始化並套用

初始化 Terraform 目前使用的目錄,並下載供應商外掛程式:

terraform init

部署基礎架構:

terraform apply

系統提示時,請查看預計使用的資源,並輸入 yes 確認。

2. 擷取部署作業輸出內容

從 Terraform 狀態匯出執行個體網址和產生的閘道密碼:

export INSTANCE_URL=$(terraform output -raw instance_url)
export GATEWAY_PASSWORD=$(terraform output -raw gateway_password)

echo "Instance URL: ${INSTANCE_URL}"
echo "Gateway Password: ${GATEWAY_PASSWORD}"

6. 透過網頁 UI 互動,並透過 Cloud Run SSH 驗證

OpenClaw 2.0 以上版本會強制執行加密裝置驗證。從新瀏覽器連線時,閘道會產生配對要求,您可以直接透過 Cloud Run 執行個體原生 SSH 核准。

1. 開啟 OpenClaw 控制項 UI

  1. 在網路瀏覽器中開啟 $INSTANCE_URL。
  2. 系統提示驗證時,請輸入閘道密碼 ($GATEWAY_PASSWORD)。
  3. 由於這是新的瀏覽器工作階段,OpenClaw 控制項 UI 會顯示待處理的裝置配對提示,並附上要求 ID (例如:b56143fc-1654-49da-a1d8-5d40479ddc66)。請複製這個要求 ID。

2. 透過 SSH 連至 Cloud Run 執行個體

在 Cloud Shell 中,啟動安全的互動式殼層,連線至執行中的執行個體:

gcloud beta run instances ssh openclaw-instance --region=${REGION}

3. 核准裝置配對

在 SSH 工作階段中,核准瀏覽器要求:

openclaw devices approve <REQUEST_ID> --password ${GATEWAY_PASSWORD}

4. 提示代理 (實際應用實例)

核准後,瀏覽器會立即連線至 OpenClaw 控制資訊主頁!

  1. 在對話 UI 中傳送提示 (例如 "Hello"),測試 OpenClaw 代理。
  2. 如要測試 Cloud Run 執行個體的長期生命週期和永久儲存空間,請提示 OpenClaw 代理執行排定的自主工作。舉例來說,您可以讓它監控 GitHub 存放區的版本,並草擬摘要:
    Check the GitHub repository openclaw/openclaw every hour for the next 24 hours. Track any new releases, merged pull requests, and breaking changes. Save a structured markdown digest titled `daily-release-digest.md` to your workspace, and notify me in this chat with a summary whenever a new release is detected.
    
  3. OpenClaw 狀態、工作區檔案和 SQLite 資料庫會透過 Cloud Storage FUSE 直接保存至 Cloud Storage,因此排定的工作、對話記錄和生成的摘要檔案,在執行個體重新啟動和容器升級後仍會保留。

7. (選用) 驗證訊息管道整合

如果您在上述選用設定步驟中設定了 Telegram 或 WhatsApp,可以驗證訊息傳送:

  1. 在 Cloud Shell 中查看執行個體記錄:
    gcloud beta run instances logs read openclaw-instance --region ${REGION} --limit 20
    
  2. 開啟 Telegram (或 WhatsApp) 並傳送訊息 (例如 /start 或 Hello OpenClaw!)。
  3. 機器人會根據 openclaw.json 中的許可清單驗證使用者 ID,並使用 Gemini 回覆!

8. 監控及檢查執行個體記錄

Cloud Run 執行個體會擷取容器的 stdout 和 stderr,並直接串流至 Google Cloud Logging。您可以使用 Cloud Shell 或 Cloud 控制台,即時監控閘道流量及檢查代理工具執行情形。

1. 讀取近期執行個體記錄

如要在 Cloud Shell 中擷取執行個體的近期記錄項目,請按照下列步驟操作:

gcloud beta run instances logs read openclaw-instance \
  --region ${REGION} \
  --limit 50

2. 要觀察的重要記錄特徵

觀察執行中的代理程式時,請留意下列重要的執行階段記錄事件:

  • [gateway] ready:確認 OpenClaw HTTP 和 WebSocket 閘道已初始化,並在通訊埠 18789 監聽。
  • [gateway] agent model: ...:顯示使用中的 Gemini 模型 (例如 google/gemini-3.1-pro-preview 或 google/gemini-3.5-flash)。
  • [agents/tool-policy]:顯示政策允許或限制的工具。
  • [agent/embedded]:追蹤代理執行的作業和叫用的工具。

3. 在 Google Cloud 控制台查看記錄

您也可以使用 Cloud 控制台中的 Logs Explorer查看及篩選記錄:

resource.type="cloud_run_instance"
resource.labels.instance_name="openclaw-instance"

9. (選用) 使用技能擴充代理

OpenClaw 支援技能,也就是模組化功能套件,可教導代理特定工作流程、專用 CLI 工具和特定領域指令。

技能運作方式

每項技能都是一個目錄,內含 SKILL.md 檔案。首先是指定中繼資料 (name 和 description) 的 YAML frontmatter,接著是 Markdown 指令:

---
name: summarize-logs
description: Summarize Cloud Run error logs into actionable bullet points.
---

# Log Summarizer Skill

When asked to analyze or summarize logs:
1. Parse error stack traces and group similar errors by frequency.
2. Identify root causes such as memory limits, timeouts, or permission errors.
3. Propose concrete remediation steps.
  • name:技能 ID (也可以直接做為斜線指令呼叫,例如 /summarize-logs)。
  • description:指示 OpenClaw 模型何時在自然對話中自動叫用這項技能。

在 Cloud Storage bucket 中新增自訂技能

Cloud Storage bucket 會掛接在 /mnt/openclaw,並在 skills.load.extraDirs 中設定,因此放置在 gs://${BUCKET_NAME}/skills/ 下方的技能會自動載入,並在執行個體重新啟動後保留:

  1. 擷取 bucket 名稱:
    export BUCKET_NAME=$(terraform output -raw storage_bucket)
    
  2. 建立本機技能目錄:
    mkdir -p my-skill
    cat << 'EOF' > my-skill/SKILL.md
    ---
    name: summarize-logs
    description: Summarize Cloud Run error logs into actionable bullet points.
    ---
    
    # Log Summarizer Skill
    
    When asked to analyze or summarize logs, group errors by frequency and suggest actionable fixes.
    EOF
    
  3. 將技能上傳至 Cloud Storage:
    gcloud storage cp -r my-skill gs://${BUCKET_NAME}/skills/
    
  4. 在 Chat 中使用技能:
    • 返回 OpenClaw 控制網頁 UI。
    • 現在可以使用 /summarize-logs 提示代理,或提出符合技能說明的自然語言問題!

10. 清除

本程式碼實驗室會使用資源,如要避免系統持續向您的 Google Cloud 帳戶收取相關費用,您可以使用 Terraform,透過單一步驟刪除所有佈建的基礎架構:

  1. 確認您位於 openclaw-terraform 目錄:
    cd openclaw-terraform
    
  2. 刪除所有資源:
    terraform destroy
    
  3. 出現提示時,請查看要移除的資源,然後輸入 yes 確認。Terraform 會徹底刪除 Cloud Run 執行個體、Secret Manager 密鑰、IAM 政策繫結、服務帳戶和 Cloud Storage bucket。

11. 結語

恭喜!您已成功使用宣告式 Terraform,在 Cloud Run 執行個體上部署安全、完全永久的 OpenClaw 執行個體,並由 Cloud Storage 狀態永久性、Secret Manager 憑證安全性,以及 Cloud Run 執行個體原生 SSH 提供支援,確保裝置驗證安全無虞!