はじめに
これまで、AWSとAzureに関して最新を追求かつベストプラクティスを意識したTerraformコード生成スキルをIBM Bobで作成してみた際のスキル作成手順及び使用感を纏めた記事を投稿してきました。
今回は横展開の最後として、Google Cloud版のTerraformコード生成スキルをIBM Bobで作成した際の手順、及び、使用感を纏めました。
MCPサーバーの登録
今回のスキル作成においては、TerraformのMCPサーバーと共に、Google CloudのDeveloper Knowledge MCPサーバーを活用することにしました。
Google Developer Knowledge MCPサーバー
Google Cloudのドキュメントにアクセスして、各々のGoogle Cloudサービスに関する情報を取得したり、Google Cloudのベストプラクティスやサービスガイドを提供してくれるMCPサーバーです。
当該MCPサーバーを使用する前提として、以下が必要です。
- Google Cloudのアカウントを保有している事 (Gmailアドレス等で作成可能です。)
- gcloud CLI(Google CloudのCLI)をインストールしていること
- Google Cloudのプロジェクトを作成していること (※)
AWSであればコンソールにログオン後は即座に各種サービスのリソースをプロビジョニングすると思いますが、Google Cloudの場合はまずプロジェクトと呼ばれる大きな器を作成し、そこに必要なリソースを作成していきます。Azureでいうところのリソースグループのようなものです。
APIキーの作成
上記の「Developer Knowledge MCPサーバーに接続する」に記載の「APIキーを使用して認証する」以降を参考にして下記の処理を実施していきます。
1.「APIキーを作成する」を参照し、APIの有効化とAPIキーを作成する。
AWS KiroやIBM Bobでは、リンク先に記載の「OAuthを使用して認証する」という方法では当該MCPサーバーには接続できませんので、ご注意ください。
Terraform MCPサーバー
現行のTerraformプロバイダーが提供しているTerraformレジストリー(今回はAzure用)から、現行のドキュメント・モジュール・ポリシーをリアルタイムでアクセスし、最新情報をAIに提供してくれるMCPサーバーです。
両者のMCPサーバーを、IBM Bobのmcp.jsonファイルに下記のように登録します。
{
"mcpServers": {
"google-developer-knowledge": {
"url": "https://developerknowledge.googleapis.com/mcp",
"headers": {
"X-Goog-Api-Key": "YOUR_API_KEY"
},
"disabled": false
},
"terraform": {
"command": "C:/Users/userid/terraform-mcp-server.exe",
"transport": "stdio"
}
}
}
AWSやAzureのMCPサーバーはローカル上でコマンドを実行して起動するタイプですが、Google Developer Knowledge MCPサーバーは、HTTP経由でリモート接続するStreamableタイプのMCPサーバーになります。
Google CloudのDeveloper Knowledge MCPサーバーのX-Goog-Api-Keyパラメータ値に「YOUR_API_KEY」とありますが、ここでの「YOUR_API_KEY」はAPIキーの名前ではなく、APIキーの中身であることに注意してください。
その後、IBM BobのMCP設定パネルで、Google CloudのDevloper Knowledge MCPサーバー(google-developer-knowledge)とTerraformのMCPサーバー(terraform)のステータスが「接続済み」になっていることを確認してください。
IBM Bobを活用したスキル作成
基本的にAWS、AzureのTerraformコード生成スキル作成とやり方は同じです。
スキルはグローバル設定とし、スキル名は「terraform-gcp」としました。ファイル構造は下記の通りです。
C:.bob
└─skills
├─terraform-gcp
│ SKILL.md
ある程度AIに実施させたい作業方針を自分なりに整理した上で、今回はIBM Bobに下記のようなプロンプトを入力して、スキルを作成してもらいました。
TerraformでGoogle Cloudの環境プロビジョニングを行う際、Google Cloudのベストプラクティスに則ったコーディングができるように、以下のスキルを作成してください。
・スキル名:terraform-gcp
・既存の「terraform」MCPサーバーにアクセスし、TerraformのGoogle Cloudプロバイダーの最新バージョンのモジュールを確認して、コーディングを行う。
・既存の「google-developer-knowledge」MCPサーバーにアクセスし、Google Cloudのベストプラクティスに則ったコーディングを意識する。
・パーツ化を意識し、コードの構成はmodule構造とする。
・可読性を意識し、適宜コメント文を記述する。
下記のGoogle Cloudの公式ドキュメントにも記載されていますが、昔と違い、最近ではGoogle Cloudの環境をIaCツールを用いてプロビジョニングする場合、選択肢の候補としてはTerraformが最上位に来ています。その為、AWS/Azureの時とは異なり、「Google Cloud純正ツールとの違いを意識したコードとする」といったようなプロンプトを明示指定はしませんでした。
スキルの稼働確認、生成されたTerraformコードの稼働確認完了後の最終的なSKILL.mdファイルは下記のGitHubリポジトリに公開していますので、ご参照ください。
今回、私のPC内の「.bob/skills」フォルダ内には、他に「terraform-aws」「terraform-azure」という名前のスキルが格納されています。興味深い動きだったのが、Bob君は他の既存のSKILL.mdの内容を読み取って、同じようなフォーマットで構成してくれたため、Google Cloudの純正のIaCツール(Deployment Manager)との差異に関する考慮点までSKILL.mdに追記してくれました。
スキルの稼働検証
IBM Bobのチャットウィンドウ上で「/」コマンドを入力し、「terraform-gcp」スキルを選択して、今回は下記のようなGoogle Cloud構成をプロビジョニングするTerraformコードを作成してもらいました。

- 東京リージョンにGoogle Cloudストレージを1個作成し、静的ウェブサイトを構築する。
- Cloud CDNのオリジンを静的ウェブサイトとしたコンテンツデリバリーネットワークを構成する。
Cloud StorageはAWSのS3、AzureのStorage Account(blob storage)に相当します。
Cloud CDNはAWSのCloudFront、AzureのAzure Frontに相当します。
AWSやAzureと大きく異なるのが、Google Cloudの場合Cloud CDN単体ではCloud Storageをバックエンド(オリジン)として設定することができず、HTTP(S)のCloud Loadbalancerとセットで構成する必要がある点です。
Loadbalancer上でCloud Storageをバックエンドとして設定し、そこでCloud CDNを有効化してコンテンツデリバリーネットワークを構築することができるようになります。
Bob君のチャットウィンドウで下記のようなプロンプトを入力し実行します。
下記の構成をもつGoogle Cloud環境をプロビジョニングするTerraformコードを作成してください。
・東京リージョンにGoogle Cloudストレージを1個作成し、静的ウェブサイトを構築する。
・Cloud CDNのオリジンを静的ウェブサイトとしたコンテンツデリバリーネットワークを構成する。
Bob君はGoogle Developer Knowledge MCPサーバーとTerraform MCPサーバーにアクセスし、Google CloudのTerraformモジュールの最新バージョンの取得、ベストプラクティスの取得に成功しました。

※本記事執筆時点でのプロバイダーモジュールの最新版は8.0.0です。

最終的にBob君が生成したTerraformコードのファイル構成は下記の通りです。

ファイルの役割と設計ポイントを記載してくれています。


Teraformコード実行後のGoogle Cloud環境構成確認
下記サイトの「Running Terraform on your workstation.」節に記載されているように、PC上でGoogle CloudをプロビジョニングするためのTerraformを実行するためには、「gcloud auth application-default login」コマンドを実行して、Google Cloudへの認証を終えておく必要があります。
今回はさほど大きなトラブルシューティングは発生せず、最後まで「terraform apply」コマンドで全リソースのプロビジョニングを完了できました。
Apply complete! Resources: 10 added, 0 changed, 0 destroyed.
Outputs:
backend_bucket_name = "static-website-lb-backend"
bucket_name = "graceful-medley-308610-static-website"
bucket_url = "gs://graceful-medley-308610-static-website"
http_url = "http://8.233.181.11"
https_url = "HTTPS は無効です。enable_https = true に設定してください。"
load_balancer_ip = "8.233.181.11"
稼働確認が完了したBob君作成terraformコードの主なサンプルファイルを紹介します。いずれもDeployment Managerとの機能差異であったり、ベストプラクティスを取得して情報記載してくれています。
- ルート配下のmain.tf
##############################################################################
# モジュール名: root
# 概要 : Cloud Storage 静的ウェブサイト + Cloud CDN 構成のエントリポイント
# 依存モジュール: modules/storage, modules/cdn
# 最終更新 : 2025-07-09
#
# 構成概要:
# 1. modules/storage — 東京リージョン(ASIA マルチリージョン)の GCS バケット作成
# 2. modules/cdn — Cloud CDN + External Application Load Balancer 設定
#
# デプロイ手順:
# 1. terraform.tfvars に project_id 等を設定する
# 2. terraform init
# 3. terraform plan
# 4. terraform apply
##############################################################################
# -----------------------------------------------------------------------
# 共通ラベル(ローカル変数)
# [ベストプラクティス] 全リソースに統一ラベルを付与する。
# ラベルキー・値は小文字英数字・アンダースコア・ハイフンのみ使用可能。
# -----------------------------------------------------------------------
locals {
common_labels = {
managed_by = "terraform"
environment = var.environment
project = var.project_name
team = var.team
}
}
# -----------------------------------------------------------------------
# module: storage
# 東京リージョン(Asia マルチリージョン)の Cloud Storage バケットを作成し、
# 静的ウェブサイトホスティングを設定する。
#
# ・bucket_name はグローバル一意である必要があるため、
# プロジェクト ID + サフィックスの組み合わせを推奨する。
# -----------------------------------------------------------------------
module "storage" {
source = "./modules/storage"
project_id = var.project_id
bucket_name = "${var.project_id}-${var.bucket_name_suffix}"
# Asia マルチリージョン: 東京を含む Asia 圏の複数リージョンにレプリケート
# [ベストプラクティス] Cloud CDN オリジンにはマルチリージョンが推奨
# 単一リージョン(東京)に限定する場合は "ASIA-NORTHEAST1" に変更する
location = "ASIA"
index_page = "index.html"
not_found_page = "404.html"
force_destroy = var.force_destroy
# CORS: 許可オリジンは本番環境では自ドメインに限定すること
cors_origins = ["*"]
labels = local.common_labels
}
# -----------------------------------------------------------------------
# module: cdn
# Cloud CDN + External Application Load Balancer を構成する。
# storage モジュールが作成したバケット名を backend bucket として渡す。
#
# [DM 差異] Deployment Manager ではリソース間の依存が参照変数で暗黙解決されるが、
# Terraform では depends_on を使って module 間の依存関係を明示的に記述する。
# bucket_name は module.storage の出力を参照しているため Terraform は依存関係を
# 推論できるが、storage モジュール全体の完了を cdn モジュール開始の前提とすることを
# 意図として明示するために depends_on を設定する。
# -----------------------------------------------------------------------
module "cdn" {
source = "./modules/cdn"
project_id = var.project_id
bucket_name = module.storage.bucket_name
lb_name = var.lb_name
# Cloud CDN ポリシー設定
cdn_cache_mode = var.cdn_cache_mode
cdn_default_ttl = var.cdn_default_ttl
cdn_max_ttl = var.cdn_max_ttl
cdn_client_ttl = var.cdn_client_ttl
# HTTPS 設定(カスタムドメインがある場合は true + ssl_domain を設定)
enable_https = var.enable_https
ssl_domain = var.ssl_domain
labels = local.common_labels
# storage モジュール全体(バケット・IAM・サンプルオブジェクト)の
# 作成完了後に cdn モジュールを開始することを明示する。
depends_on = [module.storage]
}
- ルート配下のversions.tf
##############################################################################
# バージョン制約
# Google Cloud プロバイダーは terraform MCP で確認した最新バージョンに固定する。
# 確認バージョン: hashicorp/google 8.0.0 (2024年確認済)
##############################################################################
terraform {
required_version = ">= 1.6.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 8.0"
}
}
# リモートバックエンド(本番環境では必須)
# [DM 差異] Deployment Manager は Google 側で状態管理するが、
# Terraform は tfstate を明示的に管理する必要がある。
# backend "gcs" {
# bucket = "<tfstate-bucket-name>"
# prefix = "static-website/prod"
# }
}
provider "google" {
project = var.project_id
region = var.region
}
- storageモジュール配下のmain.tf
##############################################################################
# モジュール名: storage
# 概要 : 静的ウェブサイトホスティング用 Cloud Storage バケットの作成
# 依存モジュール: なし(ルートモジュールから呼び出し)
# 最終更新 : 2025-07-09
#
# アーキテクチャ概要:
# Cloud Storage バケットに静的コンテンツ(HTML/CSS/JS)を配置し、
# Cloud CDN の backend bucket として公開する。
# HTTPS は Cloud Load Balancer 側で終端するため、バケット自体は
# public_access_prevention を enforced にせず objectViewer を付与する。
##############################################################################
# -----------------------------------------------------------------------
# 必要 API の有効化
# [DM 差異] Deployment Manager は既存の有効 API を前提とするが、
# Terraform では google_project_service で必要な API を明示的に有効化する。
# disable_on_destroy = false: モジュール破棄時に他サービスへの影響を防ぐ。
# -----------------------------------------------------------------------
resource "google_project_service" "storage" {
project = var.project_id
service = "storage.googleapis.com"
disable_on_destroy = false
}
# -----------------------------------------------------------------------
# Cloud Storage バケット(静的ウェブサイト用)
#
# ・location: asia(東京リージョンを含む Asia マルチリージョン)
# Cloud CDN のオリジンとして使用する場合、
# Google 公式ドキュメントではマルチリージョンバケットが推奨される
# (可用性向上・フォールトトレランス改善のため)。
# 単一リージョン限定にしたい場合は "ASIA-NORTHEAST1" に変更する。
#
# ・uniform_bucket_level_access: オブジェクト単位 ACL を無効化し
# IAM のみで制御する(Google ベストプラクティス準拠)。
#
# ・public_access_prevention: Cloud CDN 経由で公開するため "inherited" を
# 使用し、allUsers への objectViewer 付与で公開アクセスを実現する。
# 完全にプライベートにしたい場合は "enforced" に変更し、
# Cloud CDN の signed URL を利用すること。
# -----------------------------------------------------------------------
resource "google_storage_bucket" "main" {
project = var.project_id
name = var.bucket_name
location = var.location
# ストレージクラス: 静的コンテンツの頻繁アクセスに適した STANDARD
storage_class = "STANDARD"
# オブジェクト単位 ACL を無効化し IAM で一元管理(推奨設定)
uniform_bucket_level_access = true
# Cloud CDN 経由公開のため public_access_prevention は inherited
# (独自ドメインを使わずバケット直接公開しない構成でも同様)
public_access_prevention = "inherited"
# 静的ウェブサイト用のインデックス・エラーページ設定
website {
main_page_suffix = var.index_page
not_found_page = var.not_found_page
}
# CORS 設定: ブラウザから直接アセットを取得する場合に必要
cors {
origin = var.cors_origins
method = ["GET", "HEAD", "OPTIONS"]
response_header = ["Content-Type", "Cache-Control"]
max_age_seconds = 3600
}
# [ベストプラクティス] terraform destroy 時の安全装置
# dev 環境は force_destroy = true にしてコンテンツを一括削除可能にする
force_destroy = var.force_destroy
# ラベルを全リソースに統一付与(キー・値は小文字英数字・アンダースコア・ハイフンのみ)
labels = merge(var.labels, { component = "storage" })
depends_on = [google_project_service.storage]
}
# -----------------------------------------------------------------------
# バケットを公開読み取り可能に設定
#
# [IAM ベストプラクティス]
# google_storage_bucket_iam_member (additive) を使用し、
# 既存の IAM バインディングを上書きしない。
# allUsers への roles/storage.objectViewer 付与は静的ウェブサイトの
# 公開に必要な最小権限である。
# -----------------------------------------------------------------------
resource "google_storage_bucket_iam_member" "public_read" {
bucket = google_storage_bucket.main.name
role = "roles/storage.objectViewer"
member = "allUsers"
}
# -----------------------------------------------------------------------
# サンプル index.html のアップロード
# 実運用では CI/CD パイプライン等で別途アップロードすること。
# -----------------------------------------------------------------------
resource "google_storage_bucket_object" "index" {
name = var.index_page
bucket = google_storage_bucket.main.name
content = <<-EOT
<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8"><title>Static Website</title></head>
<body>
<h1>静的ウェブサイト - Cloud Storage + Cloud CDN</h1>
<p>Terraform でプロビジョニングされたサンプルページです。</p>
</body>
</html>
EOT
content_type = "text/html; charset=utf-8"
# ブラウザキャッシュ制御ヘッダー(Cloud CDN の TTL と合わせて設定)
cache_control = "public, max-age=3600"
}
# -----------------------------------------------------------------------
# サンプル 404.html のアップロード
# -----------------------------------------------------------------------
resource "google_storage_bucket_object" "not_found" {
name = var.not_found_page
bucket = google_storage_bucket.main.name
content = <<-EOT
<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8"><title>404 Not Found</title></head>
<body>
<h1>404 - ページが見つかりません</h1>
</body>
</html>
EOT
content_type = "text/html; charset=utf-8"
cache_control = "public, max-age=300"
}
静的ウェブサイトを構成するためのサンプルWebファイル(index.html/404.html)も併せて作成するようにしてくれています。また、不必要な権限を与えることなく、IAMの最小権限設定もしてくれています。
- cdnモジュール配下のmain.tf
##############################################################################
# モジュール名: cdn
# 概要 : Cloud CDN + External Application Load Balancer の構成
# Cloud Storage バケットを backend bucket とした静的配信
# 依存モジュール: storage モジュール(bucket_name 出力を受け取る)
# 最終更新 : 2025-07-09
#
# アーキテクチャ:
# [クライアント]
# ↓ HTTP(80) または HTTPS(443)
# [グローバル外部 IP アドレス]
# ↓
# [Forwarding Rule] → [Target HTTP(S) Proxy]
# ↓
# [URL Map]
# ↓
# [Backend Bucket (Cloud CDN 有効)]
# ↓
# [Cloud Storage バケット]
#
# Cloud CDN はロードバランサーに統合されており、
# backend_bucket に enable_cdn = true を設定するだけで有効化される。
# Cloud CDN のエッジノードは自動的に世界中(東京含む)に配置される。
#
# [DM 差異] Deployment Manager では Cloud CDN の設定は個別に行うが、
# Terraform では google_compute_backend_bucket の cdn_policy ブロックで
# CDN ポリシーをコードとして一元管理できる。
##############################################################################
# -----------------------------------------------------------------------
# 必要 API の有効化
# [DM 差異] Deployment Manager は既存の有効 API を前提とするが、
# Terraform では google_project_service で必要な API を明示的に有効化する。
# -----------------------------------------------------------------------
resource "google_project_service" "compute" {
project = var.project_id
service = "compute.googleapis.com"
disable_on_destroy = false
}
# -----------------------------------------------------------------------
# グローバル静的 IP アドレスの予約
# Cloud CDN / ロードバランサーのフロントエンド IP として使用する。
# グローバル外部 IP アドレスは Cloud Load Balancer でのみ使用可能。
# -----------------------------------------------------------------------
resource "google_compute_global_address" "main" {
project = var.project_id
name = "${var.lb_name}-ip"
depends_on = [google_project_service.compute]
}
# -----------------------------------------------------------------------
# Backend Bucket — Cloud CDN の設定
#
# google_compute_backend_bucket に enable_cdn = true を設定することで
# Cloud CDN が有効になる。cdn_policy で TTL・キャッシュモードを制御する。
#
# [CDN ベストプラクティス]
# - cache_mode = CACHE_ALL_STATIC: 静的コンテンツを自動的にキャッシュ
# - negative_caching = true: 404/410 等のエラーレスポンスもキャッシュ
# してオリジンへの負荷を軽減する
# - serve_while_stale: キャッシュ有効期限切れ後もバックグラウンドで
# 再検証しながら古いキャッシュを配信(可用性向上)
# -----------------------------------------------------------------------
resource "google_compute_backend_bucket" "main" {
project = var.project_id
name = "${var.lb_name}-backend"
description = "Cloud Storage バケットをオリジンとした CDN バックエンド"
bucket_name = var.bucket_name
enable_cdn = true
cdn_policy {
# 静的コンテンツを自動検出してキャッシュ(Google 推奨デフォルト設定)
cache_mode = var.cdn_cache_mode
# キャッシュ TTL 設定
client_ttl = var.cdn_client_ttl # ブラウザキャッシュ有効期間
default_ttl = var.cdn_default_ttl # デフォルトキャッシュ有効期間
max_ttl = var.cdn_max_ttl # 最大キャッシュ有効期間
# エラーレスポンスのキャッシュ(オリジン負荷軽減)
negative_caching = true
# キャッシュ失効後もバックグラウンド再検証中は古いコンテンツを返す(秒)
serve_while_stale = 86400
}
}
# -----------------------------------------------------------------------
# URL Map — リクエストルーティング設定
# すべてのパスをデフォルトの backend bucket に転送するシンプルな構成。
# パスベースのルーティングが必要な場合は host_rule / path_matcher を追加する。
# -----------------------------------------------------------------------
resource "google_compute_url_map" "main" {
project = var.project_id
name = var.lb_name
default_service = google_compute_backend_bucket.main.id
}
# -----------------------------------------------------------------------
# HTTP フロントエンド設定
# -----------------------------------------------------------------------
# HTTP Target Proxy(ポート 80 用)
resource "google_compute_target_http_proxy" "main" {
project = var.project_id
name = "${var.lb_name}-http-proxy"
url_map = google_compute_url_map.main.id
}
# HTTP Forwarding Rule(グローバル外部 LB)
resource "google_compute_global_forwarding_rule" "http" {
project = var.project_id
name = "${var.lb_name}-http"
ip_protocol = "TCP"
# EXTERNAL: クラシック Application Load Balancer(バックエンドバケット対応)
# EXTERNAL_MANAGED: 高度なトラフィック管理が必要な場合に使用
load_balancing_scheme = "EXTERNAL"
port_range = "80"
target = google_compute_target_http_proxy.main.id
ip_address = google_compute_global_address.main.id
}
# -----------------------------------------------------------------------
# HTTPS フロントエンド設定(enable_https = true の場合のみ作成)
#
# [DM 差異] Deployment Manager では証明書管理は別途行うが、
# Terraform では google_compute_managed_ssl_certificate で
# Google マネージド証明書をコードで宣言的に管理できる。
#
# 注意: SSL 証明書のプロビジョニングにはドメイン所有確認と
# DNS/IP の伝播が必要なため、最大 60 分程度かかる場合がある。
# -----------------------------------------------------------------------
# Google マネージド SSL 証明書(HTTPS 有効時のみ作成)
resource "google_compute_managed_ssl_certificate" "main" {
count = var.enable_https ? 1 : 0
project = var.project_id
name = "${var.lb_name}-cert"
managed {
# [DM 差異] Terraform では count = var.enabled ? 1 : 0 で条件付きリソースを管理する
domains = [var.ssl_domain]
}
}
# HTTPS Target Proxy(ポート 443 用、HTTPS 有効時のみ作成)
resource "google_compute_target_https_proxy" "main" {
count = var.enable_https ? 1 : 0
project = var.project_id
name = "${var.lb_name}-https-proxy"
url_map = google_compute_url_map.main.id
ssl_certificates = [google_compute_managed_ssl_certificate.main[0].id]
}
# HTTPS Forwarding Rule(グローバル外部 LB、HTTPS 有効時のみ作成)
resource "google_compute_global_forwarding_rule" "https" {
count = var.enable_https ? 1 : 0
project = var.project_id
name = "${var.lb_name}-https"
ip_protocol = "TCP"
load_balancing_scheme = "EXTERNAL"
port_range = "443"
target = google_compute_target_https_proxy.main[0].id
ip_address = google_compute_global_address.main.id
}
Cloud CDNのオリジンへの負荷軽減策についても反映してくれています。
Cloud LoadBalancerの起動完了確認
Cloud Storageの静的ウェブサイトをバックエンドとするCloud Loadbalancerは、Terraformによるプロビジョニング直後はトラフィックを受け付けていない為、3分~5分程度待ってから下記のPowerShellコマンドを実行します。ステータスコードに200が返ってきたらOKです。
PS C:\WINDOWS\system32> do { >> $status = (Invoke-WebRequest -Uri "http://8.233.181.11" -UseBasicParsing -ErrorAction SilentlyContinue).StatusCode
>> Write-Host "Status: $status $(Get-Date -Format 'HH:mm:ss')"
>> Start-Sleep -Seconds 15
>> } until ($status -eq 200)
Status: 200 18:37:35
静的ウェブサイトへのアクセス確認
「terraform apply」のoutputに出力されている「http_url」が、静的ウェブサイトへのHTTPアクセス用のURL(Cloud CDNのディストリビューションのIPアドレス)になります。これを使って、「curl -v http://8.233.181.11」コマンドを実行します。
curl -v http://8.233.181.11
* Trying 8.233.181.11:80...
* Established connection to 8.233.181.11 (8.233.181.11 port 80) from 192.168.2.103 port 57626
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 8.233.181.11
> User-Agent: curl/8.21.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< X-GUploader-UploadID: AJjja9aXhoV36_BxM8R6rFwZJBeTYGkDZ-4J9INJTtck2_QclL0qwsWKUK2_at8H8gNefzMp
< x-goog-generation: 1787995800725877
< x-goog-metageneration: 1
< x-goog-stored-content-encoding: identity
< x-goog-stored-content-length: 271
< x-goog-hash: crc32c=PUf24Q==
< x-goog-hash: md5=2xZWRowQd+HLcfAjzi+71w==
< x-goog-storage-class: STANDARD
< Accept-Ranges: bytes
< Content-Length: 271
< Access-Control-Allow-Origin: *
< Access-Control-Expose-Headers: Content-Type
< Access-Control-Expose-Headers: Cache-Control
< Server: UploadServer
< Date: Sat, 29 Aug 2026 09:37:35 GMT
< Last-Modified: Sat, 29 Aug 2026 09:30:00 GMT
< ETag: "db1656468c1077e1cb71f023ce2fbbd7"
< Content-Type: text/html; charset=utf-8
< Age: 40
< Cache-Control: public,max-age=3600
<
<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8"><title>Static Website</title></head>
<body>
<h1>静的ウェブサイト - Cloud Storage + Cloud CDN</h1>
<p>Terraform でプロビジョニングされたサンプルページです。</p>
</body>
</html>
* Connection #0 to host 8.233.181.11:80 left intact
Cloud Storageに保管されている静的ウェブサイトのindex.htmlの中身が返ってきているので、接続確認OKです。
Cloud CDNのキャッシュヒット率確認
「curl -sI http://8.233.181.11」コマンドを実行し、レスポンスヘッダーのAgeとCache-Controlの値を確認します。
curl -sI http://8.233.181.11
HTTP/1.1 200 OK
X-GUploader-UploadID: AJjja9aXhoV36_BxM8R6rFwZJBeTYGkDZ-4J9INJTtck2_QclL0qwsWKUK2_at8H8gNefzMp
x-goog-generation: 1787995800725877
x-goog-metageneration: 1
x-goog-stored-content-encoding: identity
x-goog-stored-content-length: 271
x-goog-hash: crc32c=PUf24Q==
x-goog-hash: md5=2xZWRowQd+HLcfAjzi+71w==
x-goog-storage-class: STANDARD
Accept-Ranges: bytes
Content-Length: 271
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Type
Access-Control-Expose-Headers: Cache-Control
Server: UploadServer
Date: Sat, 29 Aug 2026 09:37:35 GMT
Last-Modified: Sat, 29 Aug 2026 09:30:00 GMT
ETag: "db1656468c1077e1cb71f023ce2fbbd7"
Content-Type: text/html; charset=utf-8
Age: 70
Cache-Control: public,max-age=3600
Ageの値は「70」となっており、70秒前にキャッシュされたコンテンツがCDNから配信されたことを示しています。また、Cache-Controlの値が、storageモジュールのmain.tfファイル内の「resource "google_storage_bucket_object" "index"」ブロック内に指定されている「cache_control」値と一致しているので、キャッシュポリシーも正しく反映されていることが確認できます。
存在しないパスにアクセスした際の404ページの確認
「curl -v http://8.233.181.11/nonexistent-page」コマンドを実行して、404.htmlファイルの中身が戻されることを確認します。
curl -v http://8.233.181.11/nonexistent-page
* Trying 8.233.181.11:80...
* Established connection to 8.233.181.11 (8.233.181.11 port 80) from 192.168.2.103 port 57645
* using HTTP/1.x
> GET /nonexistent-page HTTP/1.1
> Host: 8.233.181.11
> User-Agent: curl/8.21.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 404 Not Found
< Content-Type: text/html; charset=utf-8
< X-GUploader-UploadID: AJjja9a4qpVJrYJiIWXqTaY7H-lKGeJUaA0ILrHaZXkwxPmteoMQOrT9ip9QIWT6VbAzzIMjgkzvXW8
< Date: Sat, 29 Aug 2026 09:39:51 GMT
< Cache-Control: public, max-age=300
< Expires: Sat, 29 Aug 2026 09:44:51 GMT
< Last-Modified: Sat, 29 Aug 2026 09:30:00 GMT
< ETag: "68898cd9ef212612acc197cdf4bbe691"
< x-goog-generation: 1787995800536424
< x-goog-metageneration: 1
< x-goog-stored-content-encoding: identity
< x-goog-stored-content-length: 171
< x-goog-hash: crc32c=/czSWg==
< x-goog-hash: md5=aImM2e8hJhKswZfN9LvmkQ==
< x-goog-storage-class: STANDARD
< Accept-Ranges: bytes
< Content-Length: 171
< Access-Control-Allow-Origin: *
< Access-Control-Expose-Headers: Content-Type
< Access-Control-Expose-Headers: Cache-Control
< Server: UploadServer
<
<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8"><title>404 Not Found</title></head>
<body>
<h1>404 - ページが見つかりません</h1>
</body>
</html>
* Connection #0 to host 8.233.181.11:80 left intact
正常に404.htmlファイルの中身が戻されていることが確認できました。
Terraformコードの稼働確認で得られた気づき
今回、「terraform apply」コマンド実行時に1件だけ下記のエラーが発生しました。
module.storage.google_storage_bucket.main: Creating...
╷
│ Error: googleapi: Error 400: Invalid user label: project with value My First Project. Reason: Invalid field "user_labels_list.project"; value "My First Project" does not conform to regular expression "[\p{Ll}\p{Lo}\p{N}_-]{0,63}"; character "M" at position 0 is not a non-uppercased letter (Unicode character class Ll or Lo), digit, hyphen, or underscore., invalid
│
│ with module.storage.google_storage_bucket.main,
│ on modules\storage\main.tf line 43, in resource "google_storage_bucket" "main":
│ 43: resource "google_storage_bucket" "main" {
私は普段Google Cloudで「My First Project」という名前のプロジェクトを使用しており、その中にリソースを作成するようにしていました。今回、Bob君が作成してくれたterraform.tfvarsファイルの「project_name」パラメータ値に「My First Project」と指定して、Terraformのルート配下のmain.tfファイルに指定されているローカル変数のCommomラベルの「project = var.project_name」で、私のプロジェクト名を引き渡す仕組みになっていたのですが、TerraformのGoogle Cloudモジュールのラベル制約として、スペースが含まれてはいけないというのを初めて知りました。(小文字・数字・アンダースコア・ハイフンのみ指定可能)
過去にGoogle CloudのTerraformコードは作成していましたが、当時はラベルの指定はしていなかったので、プロジェクト名にスペースが入っていても影響は無かったものと思います。
実際、下記リンク先の記載を見ても、プロジェクト名にスペースを含むことは問題ない旨記載されています。
Bob君はこの時の対応として、ルート配下のvariables.tfファイルに「project_name」変数と「team」変数にvalidationステップを追加して、スペースが含まれている場合には「terraform apply」ではなく、「terraform plan」実行時にエラーに気づくようにしてくれました。下記はvariables.tfファイルの抜粋です。
variable "project_name" {
description = "ラベル付けに使用するプロジェクト略称(小文字英数字・ハイフン・アンダースコアのみ / 大文字・スペース不可)"
type = string
# [既知の落とし穴] Google Cloud ラベル値は小文字英数字・アンダースコア・ハイフンのみ。
# 大文字・スペース・全角文字を含む値を渡すと apply 時に Error 400 になるため、
# terraform plan の段階で検出できるよう validation を設定する。
validation {
condition = can(regex("^[a-z0-9_-]+$", var.project_name))
error_message = "project_name はラベル値として使用されるため、小文字英数字・アンダースコア・ハイフンのみ使用できます(大文字・スペース・全角文字は不可)。例: my-first-project"
}
}
variable "team" {
description = "ラベル付けに使用するチーム名(小文字英数字・ハイフン・アンダースコアのみ)"
type = string
default = "platform"
# [既知の落とし穴] project_name と同様にラベル値の文字制約が適用される。
validation {
condition = can(regex("^[a-z0-9_-]+$", var.team))
error_message = "team はラベル値として使用されるため、小文字英数字・アンダースコア・ハイフンのみ使用できます。"
}
}
今回の件をきっかけに、私は自分のGoogle Cloud環境のプロジェクト名を見直し、「my-first-project」のようにスペースを含まないプロジェクト名に変更することにしました。プロジェクト名の変更はいつでも可能です。
併せて、Bob君にこの気づきについても次回に向けてSKILL.mdに反映してもらいました。
おわりに
今回は、Google CloudのDeveloper Knowledge MCPサーバーとTerraformのMCPサーバーを活用して、Google Cloudの最新を追求しつつ、ベストプラクティスも意識したTerraformコードを生成するスキルをIBM Bobで作成してみた際の手順と使用感について紹介しました。
スキルは1度作成したら終わりではありません。Bob君に作成してもらったTerraformコードを使ってみて、不具合等が発生してトラブル対応の上解決できたものについては、該当スキルのSKILL.mdファイルに経験情報を蓄積していくプロセスを回し、より価値のあるスキルに成長させていこうと思います。
