1. はじめに
概要
このラボでは、宣言型の Infrastructure as Code(Terraform)を使用して、オープンソースの AI エージェント フレームワークである OpenClaw の完全に永続的な本番環境グレードのインスタンスを Cloud Run インスタンスにデプロイします。google プロバイダの google_cloud_run_v2_instance リソースを使用して Cloud Run インスタンスをプロビジョニングし、Cloud Storage FUSE を介して Google Cloud Storage をマウントしてエージェント ワークスペースの状態を保持します。また、Google Cloud Secret Manager を使用して API 認証情報を安全に管理し、Cloud Run インスタンスのネイティブ SSH を使用して OpenClaw の組み込みウェブ UI を介して AI エージェントとやり取りし、安全なデバイスのペア設定を行います。
始める前に、OpenClaw のドキュメントを読んで、OpenClaw のアーキテクチャ、ツール、Agentic Workflows について理解を深めてください。
演習内容
- API、サービス アカウント、IAM バインディング、シークレット、ストレージ バケット、Cloud Run インスタンスなど、インフラストラクチャ全体を Terraform(
main.tf)で宣言的に定義します。 google_cloud_run_v2_instanceを使用して OpenClaw 2.0+(ghcr.io/openclaw/openclaw:latest)をデプロイします。- セキュア ゲートウェイの認証情報を自動的に生成して Secret Manager に保存します。
- Cloud Storage FUSE を介して Cloud Storage バケットをマウントし、永続的な状態、ワークスペース、SQLite の永続性のために
/home/node/.openclawを同期します。 gcloud beta run instances sshを使用して、OpenClaw Control ウェブ UI へのブラウザ デバイスのペア設定リクエストを認証して承認します。- (省略可)メッセージ チャネル(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 プロジェクトの設定
- Google Cloud コンソールにログインします。
- Google Cloud プロジェクトを作成または選択します。
- 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 コンポーネントがインストールされていることを確認します(Cloud Run インスタンスの SSH とログ ストリーミングに必要)。
gcloud components install beta --quiet
gcloud components update
Terraform を確認する
Terraform がインストールされ、バージョンの要件を満たしていることを確認します。
terraform version
Terraform v1.5.0 以降が表示されます。
3. (省略可)メッセージング統合(Telegram または WhatsApp)を設定する
OpenClaw は Telegram または WhatsApp に接続できます。以下のオプション A またはオプション B を選択します。
オプション A: Telegram ボットの設定
- Telegram を開き、
@BotFatherを検索します。 /newbotコマンドを送信し、プロンプトに沿って bot 名とユーザー名を指定します。- 生成された HTTP API トークン(例:
123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ)をコピーします。 - Telegram で
@userinfobotを検索し、/startを送信して、数字のユーザー ID(例:8035936176)をコピーします。
これらの値は、terraform.tfvars ファイルで Terraform 入力変数として指定します。
オプション B: WhatsApp の設定
- 個人の WhatsApp の電話番号を国際電話形式で取得します。スペースや記号は含めないでください(例:
+15551234567)。 - OpenClaw は、WhatsApp チャネルの許可リスト ポリシーを介して接続します。
4. Terraform でインフラストラクチャを定義する
インフラストラクチャ全体を Terraform で宣言します。これには、必要な Google Cloud APIs の有効化、専用のサービス アカウントと IAM ロールの作成、Secret Manager でのシークレットの生成と保存、Cloud Storage 状態バケットのプロビジョニング、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 という名前のファイルを作成して、デプロイされたインスタンスの URL、生成されたゲートウェイ パスワード、ストレージ バケット名を公開します。
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 キーを取得するには:
- Google AI Studio にアクセスし、Google アカウントでログインします。
- [API キーを作成] をクリックし、Google Cloud プロジェクトを選択します。
- 生成された 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 の状態からインスタンス URL と生成されたゲートウェイ パスワードをエクスポートします。
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 Control UI を開く
- ウェブブラウザで
$INSTANCE_URLを開きます。 - 認証を求められたら、ゲートウェイのパスワード(
$GATEWAY_PASSWORD)を入力します。 - これは新しいブラウザ セッションであるため、OpenClaw Control UI には、リクエスト ID(例:
b56143fc-1654-49da-a1d8-5d40479ddc66)を含む保留中のデバイスのペア設定プロンプトが表示されます。このリクエスト ID をコピーします。
2. Cloud Run インスタンスに SSH 接続する
Cloud Shell で、実行中のインスタンスへのセキュアなインタラクティブ シェルを起動します。
gcloud beta run instances ssh openclaw-instance --region=${REGION}
3. デバイスのペア設定を承認する
SSH セッション内で、ブラウザ リクエストを承認します。
openclaw devices approve <REQUEST_ID> --password ${GATEWAY_PASSWORD}
4. エージェントにプロンプトを入力する(インスタンスの動作)
承認されると、ブラウザが OpenClaw Control Dashboard にすぐに接続されます。
- チャット UI でプロンプト(
"Hello"など)を送信して、OpenClaw エージェントをテストします。 - 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. - OpenClaw の状態、ワークスペース ファイル、SQLite データベースは、Cloud Storage FUSE を介して Cloud Storage に直接永続化されるため、スケジュール設定されたタスク、会話履歴、生成されたダイジェスト ファイルは、インスタンスの再起動やコンテナのアップグレード後も保持されます。
7. (省略可)メッセージ チャネルの統合を確認する
上記のオプションの設定手順で Telegram または WhatsApp を構成した場合は、メッセージの配信を確認できます。
- Cloud Shell でインスタンス ログを確認します。
gcloud beta run instances logs read openclaw-instance --region ${REGION} --limit 20 - Telegram(または WhatsApp)を開き、メッセージ(
/startやHello OpenClaw!など)を送信します。 - bot は
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 コンソールのログ エクスプローラを使用して、ログを表示してフィルタすることもできます。
resource.type="cloud_run_instance"
resource.labels.instance_name="openclaw-instance"
9. (省略可)スキルを使用してエージェントを拡張する
OpenClaw は、エージェントに特定のワークフロー、専用の CLI ツール、ドメイン固有の指示を教えるモジュラー機能パッケージであるスキルをサポートしています。
スキルの仕組み
すべてのスキルは、SKILL.md ファイルを含むディレクトリです。最初に、メタデータ(name と description)を指定する YAML フロントマターがあり、その後に 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 バケットにカスタムスキルを追加する
Cloud Storage バケットは /mnt/openclaw にマウントされ、skills.load.extraDirs で構成されているため、gs://${BUCKET_NAME}/skills/ に配置されたスキルは自動的に読み込まれ、インスタンスの再起動後も保持されます。
- バケット名を取得する:
export BUCKET_NAME=$(terraform output -raw storage_bucket) - ローカル スキル ディレクトリを作成する:
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 - スキルを Cloud Storage にアップロードする:
gcloud storage cp -r my-skill gs://${BUCKET_NAME}/skills/ - チャットでスキルを使用する:
- OpenClaw Control のウェブ UI に戻ります。
/summarize-logsを使用してエージェントにプロンプトを送信したり、スキルの説明に一致する自然言語の質問をしたりできるようになりました。
10. クリーンアップ
この Codelab で使用したリソースについて、Google Cloud アカウントに継続的に課金されないようにするには、Terraform を使用して、プロビジョニングされたすべてのインフラストラクチャを 1 つの手順で破棄します。
openclaw-terraformディレクトリにいることを確認します。cd openclaw-terraform- すべてのリソースを破棄します。
terraform destroy - プロンプトが表示されたら、削除するリソースを確認し、「
yes」と入力して確定します。Terraform は、Cloud Run インスタンス、Secret Manager のシークレット、IAM ポリシー バインディング、サービス アカウント、Cloud Storage バケットを完全に削除します。
11. まとめ
おめでとうございます!宣言型の Terraform を使用して、Cloud Run インスタンスに OpenClaw の安全で完全に永続的なインスタンスをデプロイできました。このインスタンスは、Cloud Storage の状態の永続性、Secret Manager の認証情報のセキュリティ、Cloud Run インスタンスのネイティブ SSH による安全なデバイス認証によって支えられています。