はじめに
Google Cloudを用いたプロジェクトでAPI Gatewayを用いてバックエンドのCloud Runとやり取りをする構成を作成しました。その際に、API GatewayをTerraformで設計しました。
ここでは、TerraformでAPI Gatewayを扱う際の注意事項について記事にします。
API Gatewayの構成要素
Google CloudのAPI Gatewayは以下のような構成で作られています。
- API:API全体を管理するための論理的な「コンテナ(入れ物)」です。APIの名前などの管理情報を保持し、後述するAPI ConfigとGatewayは、このAPIリソースの中に作成されます
- API Config: APIの具体的な振る舞いを定義する「設計図」にあたる設定情報です。Swagger 2.0形式をサポートしており、YAML形式で表現され、APIのパス、バックエンドへのルーティングルールなどを記述します
- Gateway (ゲートウェイ):API Configをデプロイして作成される、リクエストを処理する「実体」です。このGatewayに固有のURL(エンドポイント)が割り当てられ、紐づけられたAPI Configの定義に従って、外部からのリクエストを受け付けます
Terraformで記述されるAPI Gatewayの構成要素を視覚的に表現すると以下のようになります。
「API Config」にはAPIの振る舞いを定義したYAMLファイルが含まれており、Configをもとに「Gateway」が作られます。
これらのリソースを「API Gateway」としてまとめて機能するというイメージです
Terraformでの記述
API GatewayをTerraformで作成するには以下のように記述します。
「google_api_gateway_api」、「google_api_gateway_api_config」、「google_api_gateway_gateway」という3つのリソースが主になっていることがわかります。
また、API Gatewayに対してAPI Keyで認証を行うために「google_apikeys_key」リソースを作成しています。
# Terraform の main.tf
resource "google_api_gateway_api" "api_gw" {
provider = google-beta
project = test_project
api_id = "api-gw-test"
display_name = "api-gw-test"
}
resource "google_api_gateway_api_config" "api_gw" {
provider = google-beta
project = test_project
api = google_api_gateway_api.api_gw.api_id
api_config_id_prefix = "config-api-gw-test"
gateway_config {
backend_config {
google_service_account = <your_service_account_email>
}
}
openapi_documents {
document {
path = "spec.yaml"
contents = base64encode(<api定義のYAMLファイルパス>)
}
}
lifecycle {
create_before_destroy = true
}
}
resource "google_api_gateway_gateway" "api_gw" {
provider = google-beta
project = test_project
api_config = google_api_gateway_api_config.api_gw.id
gateway_id = "gateway-test"
display_name = "gateway-test"
region = asia-northeast1
}
resource "google_apikeys_key" "api_key" {
project = test_project
name = "api-key-auth-compute-crun-test"
display_name = "api-key-auth-compute-crun-test"
restrictions {
api_targets {
service = google_api_gateway_api.api_gw.managed_service
methods = ["*"]
}
}
}
resource "google_project_service" "enable_api_gateway" {
project = test_project
service = google_api_gateway_api.api_gw.managed_service
disable_dependent_services = true
depends_on = [google_api_gateway_api.api_gw]
}
# api.yaml
swagger: '2.0'
info:
title: Hello API
description: Hello World API with API Key and Google Backend
version: 1.0.0
schemes:
- https
produces:
- application/json
x-google-backend:
address: https://your-backend-url.example.com
security:
- api_key: []
securityDefinitions:
api_key:
type: "apiKey"
name: "x-api-key"
in: "header"
paths:
/hello:
get:
summary: Returns a hello message
operationId: getHello
responses:
200:
description: Successful response
schema:
type: object
properties:
message:
type: string
example: "Hello, world!"
x-google-backend:
address: https://your-backend-url.example.com/hello
このようなファイルを準備した状態でTerraformを実行するとAPI Gatewayが作成できます。
API Gatewayの更新について
開発の中でapi.yamlのファイルを更新したい(api定義を更新したい)場合、yamlファイルを更新後に再度terraform apply
を実行する必要があります。
この際、「google_api_gateway_api_config」は更新できず、「409 Conflict Error」になります。
API Gatewayの「構成」は基本的に不変であり、変更可能なものは「ラベルと表示名を更新する場合のみ」です。
対策
更新の失敗を回避するには、下記のようにTerraformを更新します。
以下のような点に差異があります。
- ランダムIDを生成するリソースの追加
- google_api_gateway_api_configの名前にランダムな文字列を付けるようにする
具体的には下の2点が変更されています。
resource "random_id" "api_gw_prefix" {
byte_length = 2
}
api_config_id_prefix = "config-api-gw-test-${random_id.api_gw_prefix.hex}"
変更結果を適用したTerraformは以下になります。
# Terraform の main.tf
resource "random_id" "api_gw_prefix" {
byte_length = 2
}
resource "google_api_gateway_api" "api_gw" {
provider = google-beta
project = test_project
api_id = "api-gw-test"
display_name = "api-gw-test"
}
resource "google_api_gateway_api_config" "api_gw" {
provider = google-beta
project = test_project
api = google_api_gateway_api.api_gw.api_id
api_config_id_prefix = "config-api-gw-test-${random_id.api_gw_prefix.hex}"
gateway_config {
backend_config {
google_service_account = <your_service_account_email>
}
}
openapi_documents {
document {
path = "spec.yaml"
contents = base64encode(<api定義のYAMLファイルパス>)
}
}
lifecycle {
create_before_destroy = true
}
}
resource "google_api_gateway_gateway" "api_gw" {
provider = google-beta
project = test_project
api_config = google_api_gateway_api_config.api_gw.id
gateway_id = "gateway-test"
display_name = "gateway-test"
region = asia-northeast1
}
resource "google_apikeys_key" "api_key" {
project = test_project
name = "api-key-auth-compute-crun-test"
display_name = "api-key-auth-compute-crun-test"
restrictions {
api_targets {
service = google_api_gateway_api.api_gw.managed_service
methods = ["*"]
}
}
}
resource "google_project_service" "enable_api_gateway" {
project = test_project
service = google_api_gateway_api.api_gw.managed_service
disable_dependent_services = true
depends_on = [google_api_gateway_api.api_gw]
}
この変更を行うことによって、Conflictを回避しapi gatewayの更新が可能になります。create_before_destroy = true
としているため、新しいリソースを作成したのち古いリソースは削除されるという動作をします。
終わりに
Terraformで構成管理をしていると開発環境を作ったあと、ステージング環境や本番/商用環境用のディレクトリを作成してそれぞれの環境に展開される事があると思いますが、リソース間の依存などで1回の実行で環境を作り切るのが難しい場面に遭遇しました。よりよいTerraformの構成管理を学んだ際にはまた共有したいと思います。
Appendix
Google CloudのAPI Gatewayは2025年7月1日時点で、Swagger 2.0形式で記述されたAPIのみのサポートです。
OpenAPI 3.0ではないことに注意が必要です。
また、機能の制限についても確認しておく必要があります。
Google Cloudでは、Apigeeというエンタープライズ規模で利用可能なフルマネージドなAPI基盤のサービスも用意されています。
ご自身のユースケースに合わせてApigeeも選択肢になります!
参考資料