API GatewayのOpenAPIインポートとTerraformのテンプレートでいい感じにAPI管理する

目次
本記事では、Terraform のテンプレート機能を使って、API Gateway の OpenAPI インポート機能で、動的な値(Lambda の ARN など)を注入する方法を紹介します。
1. この記事の要約
- API Gateway の OpenAPI インポート機能を使って REST API を定義します
- OpenAPI ファイルをテンプレート化し、Terraform で動的な値(Lambda の ARN など)を注入することで、宣言的かつ DRY に API を管理できます。
2. API Gateway の OpenAPI インポートとは?課題は?
API Gateway の OpenAPI のインポートとは、OpenAPI 仕様(Swagger 仕様)で記述した API 定義ファイルを、そのまま API Gateway の設定として取り込める機能です。この機能を使うことで、API のエンドポイントやリクエスト/レスポンスの定義、認証方式などを一括で管理できます。
- OpenAPI ファイルをインポートすることで、API Gateway のリソースやメソッド、統合設定などを自動生成できます。
- AWS 固有の拡張(x-amazon-apigateway-xxx)を使うことで、統合先や認証方式なども OpenAPI ファイル内で指定できます。
- Terraform でも、OpenAPI インポートを使うことができます。
課題
API Gateway の OpenAPI インポート機能だけでは、Lambda の ARN など動的な値を直接注入できません。ただし、OpenAPI ファイル内に Lambda 名称などをハードコードすることは、保守性の観点から、なるべく避けたいです。
3. 解決策:OpenAPI インポート + Terraform テンプレート の組み合わせ
- OpenAPI ファイルをテンプレート化し、Terraform の `templatefile` 関数で動的な値(例:Lambda の ARN)を注入します。
- 動的な値を埋め込んだ後の OpenAPI ファイルをインポートします
サンプルコード
今回は、統合先を Lambda プロキシ統合とし、認証に Cognito オーソライザーを設定する例を提示します。
まずは、OpenAPI テンプレートです。
# OpenAPI テンプレート例
openapi: 3.0.3
info:
title: Demo - Terraform x API Gateway x OpenAPI
version: 1.0.0
servers:
- url: https://example.com/
components:
securitySchemes: # 認証設定は`securitySchemes`に定義。今回はCognito認証の例を記載。
CognitoAuthorizer:
type: apiKey
name: Authorization
in: header
x-amazon-apigateway-authtype: cognito_user_pools
x-amazon-apigateway-authorizer:
type: cognito_user_pools
providerARNs:
- ${cognito_user_pool_arn} # CognitoユーザプールのARNをプレースホルダーにして、Terraformで動的に注入。
paths:
/hello:
get: # 1つ目のエンドポイントを定義。
operationId: hello
summary: Hello endpoint
security:
- CognitoAuthorizer: [] # securitySchemesで定義した認証設定を参照すると、API Gatewayの認証設定が適用されます。
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
message:
type: string
headers:
Access-Control-Allow-Origin:
schema:
type: string
x-amazon-apigateway-integration: # API Gatewayの統合設定を定義。
uri: ${lambda_hello_invoke_arn} # LambdaのARNをプレースホルダーにして、Terraformで動的に注入。
httpMethod: POST
type: aws_proxy
payloadFormatVersion: 2
options: # 2つ目のエンドポイントを定義。
operationId: helloOptions
responses:
"200":
description: OK
headers:
Access-Control-Allow-Headers:
schema:
type: string
Access-Control-Allow-Methods:
schema:
type: string
Access-Control-Allow-Origin:
schema:
type: string
x-amazon-apigateway-integration: # API Gatewayの統合設定を定義。
type: mock # モック統合を定義(CORSプリフライトリクエストを処理するため)
responses: # モック統合のレスポンスを定義。
default:
statusCode: "200"
responseParameters:
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'"
method.response.header.Access-Control-Allow-Origin: "'*'"
requestTemplates: # モック統合のリクエストテンプレートを定義。
application/json: '{"statusCode": 200}'
passthroughBehavior: "never"
/uppercase: # 以降も、上記と同様に定義。
post:
operationId: uppercase
summary: Convert text to uppercase
security:
- CognitoAuthorizer: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
text:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
result:
type: string
headers:
Access-Control-Allow-Origin:
schema:
type: string
x-amazon-apigateway-integration:
uri: ${lambda_uppercase_invoke_arn}
httpMethod: POST
type: aws_proxy
payloadFormatVersion: 2
options:
operationId: uppercaseOptions
responses:
"200":
description: OK
headers:
Access-Control-Allow-Headers:
schema:
type: string
Access-Control-Allow-Methods:
schema:
type: string
Access-Control-Allow-Origin:
schema:
type: string
x-amazon-apigateway-integration:
responses:
default:
statusCode: "200"
responseParameters:
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'"
method.response.header.Access-Control-Allow-Origin: "'*'"
requestTemplates:
application/json: '{"statusCode": 200}'
passthroughBehavior: "never"
type: mock
Swagger UI での表示イメージです。(普通に表示できていますね)

次に、Terraform のコードです。
# `template_file`を利用して、OpenAPIファイル内のプレースホルダーを埋めて、dataとして取り込む。
data "template_file" "openapi" {
template = file("${path.module}/openapi.yaml")
vars = {
lambda_hello_invoke_arn = aws_lambda_function.hello.invoke_arn
lambda_uppercase_invoke_arn = aws_lambda_function.uppercase.invoke_arn
cognito_user_pool_arn = aws_cognito_user_pool.main.arn
}
}
# テンプレート化したOpenAPIファイルをAPI Gatewayにインポート。
resource "aws_api_gateway_rest_api" "api" {
name = local.project_prefix
body = data.template_file.openapi.rendered
}
# このほか、`aws_api_gateway_deployment`や`aws_api_gateway_stage`などのリソースも必要。(省略)
# OpenAPIから参照するCognitoユーザプールの作成が必要
resource "aws_cognito_user_pool" "main" {
name = local.project_prefix
}
resource "aws_cognito_user_pool_client" "main" {
name = local.project_prefix
user_pool_id = aws_cognito_user_pool.main.id
# ALLOW_USER_PASSWORD_AUTHよりも、よりセキュアな認証フローを使用した方が良い
explicit_auth_flows = [
"ALLOW_USER_PASSWORD_AUTH",
"ALLOW_REFRESH_TOKEN_AUTH"
]
}
# OpenAPIから参照するLambda関数の作成が必要
resource "aws_lambda_function" "hello" {
# 省略
}
resource "aws_lambda_function" "uppercase" {
# 省略
}
# そのほかLambda関数に関するリソースの作成も必要(省略)
# - Lambdaロール
# - Lambdaパーミッション
コードの全量はGitHub 上のデモプロジェクトにあります。
CodeSpaces で動作します。(GitHubアカウントとAWS のアカウントが必要です)
さいごに
REST API のリソース定義を Terraform にベタ書きするのは、コードがかなり増えてしまうので嫌いでした。そこで、OpenAPI インポートを使いたくなるのですが、今度は Lambda の ARN を OpenAPI に書かなくてはいけないという問題が浮上しました。
Terraform のテンプレート機能を挟むことで、上記の問題をまるっと解決できるので、今回はその方法を紹介しました。
ちなみに、CDK でも同じことができます。Node.js で前処理できるので、当然ながら断然自由度が高いです。openapixのような、サードパーティのコンストラクトもありますね。