以前、IaCでREST APIを設定するための要素やデプロイ方法について書きました。
記事内にも記載していますが、実際はTerraformやCloudFormationでAPI Gatewayをすべて構築することは少ないのではないかと思います。
今回は、Terraformで構築したREST APIに対してServerless Frameworkからデプロイを行いたいと思います。
誰が何を使ってAPI Gatewayを管理するか
API Gatewayをもっとも簡単に構築する方法はServerless Frameworkにすべて任せてしまうことだと思います。
しかし、以下のような理由からログ出力等の煩雑な設定を実施する人がいない場合があります。
- 開発担当者は機能実装に忙しいし、何を設定すればいいのか分からない
- インフラ担当者はServerless Frameworkについて知識がない
また、デプロイされるまでapi-idが分からないため、カスタムドメインやAWS WAFとの連携が設定できません。
これらもServerless Frameworkで実装可能ですが、上のように実施する人をさらに選ぶようになると思います。
そこで、今回はなるべくTerraform側に設定を寄せていきます。
作成するリソースの境界
API GatewayをTerraformで作るときでも、デプロイはServerless Frameworkで実施するのが適切です。その上で、ログ出力の設定等はTerraformで実施したいと思います。
前の記事で説明したとおり、ログ出力の設定はステージ(aws_api_gateway_stage)にあります。
しかし、aws_api_gateway_stageを作るためにはデプロイメント(aws_api_gateway_deployment)が必要です。
そこで、今回紹介するのは以下の構成です。
- TerraformでREST APIと最初のデプロイメントを作成する
- ダミーのAPIを定義
aws_api_gateway_stageのdeployment_idは変更を検知しないようにする
- Serverless Frameworkで上記のREST APIを指定してデプロイする
- Serverless FrameworkはREST APIを作成しなくなる
- デプロイで
StageNameの指定されたAWS::ApiGateway::Deploymentが作成される - ステージの参照先デプロイメントも更新される
次から実際の設定を見ていきます。
Terraformの設定
まずTerraformのリソースを作成します。
実際に動作するメソッドがないとデプロイでエラーになるため、/staticcheckというリソースにGETメソッドを作成しています。

Serverless Frameworkのデプロイ先はルート(/)でもいいのですが、今回はTerraformで用意した/api以下にデプロイします。
まず始めに、REST APIの作成とデプロイ先のリソースを作成するコードです。
# REST API resource "aws_api_gateway_rest_api" "api" { name = "test-api" } # /apiリソース resource "aws_api_gateway_resource" "api" { path_part = "api" rest_api_id = aws_api_gateway_rest_api.api.id parent_id = aws_api_gateway_rest_api.api.root_resource_id }
次に、/staticcheckを作成するコードです。
最後のaws_api_gateway_integration_responseリソースに応答内容が定義されています。
# ダミー用statickcheck API resource "aws_api_gateway_resource" "staticcheck" { path_part = "staticcheck" rest_api_id = aws_api_gateway_rest_api.api.id parent_id = aws_api_gateway_rest_api.api.root_resource_id } resource "aws_api_gateway_method" "staticcheck_get" { rest_api_id = aws_api_gateway_resource.staticcheck.rest_api_id resource_id = aws_api_gateway_resource.staticcheck.id http_method = "GET" authorization = "NONE" } resource "aws_api_gateway_method_response" "staticcheck_get_200" { rest_api_id = aws_api_gateway_method.staticcheck_get.rest_api_id resource_id = aws_api_gateway_method.staticcheck_get.resource_id http_method = aws_api_gateway_method.staticcheck_get.http_method status_code = "200" } resource "aws_api_gateway_integration" "staticcheck_get" { rest_api_id = aws_api_gateway_method.staticcheck_get.rest_api_id resource_id = aws_api_gateway_method.staticcheck_get.resource_id http_method = aws_api_gateway_method.staticcheck_get.http_method type = "MOCK" passthrough_behavior = "WHEN_NO_TEMPLATES" request_templates = { "application/json" = jsonencode({ statusCode = 200 }) } } resource "aws_api_gateway_integration_response" "staticcheck_get_200" { rest_api_id = aws_api_gateway_method.staticcheck_get.rest_api_id resource_id = aws_api_gateway_method.staticcheck_get.resource_id http_method = aws_api_gateway_method.staticcheck_get.http_method status_code = aws_api_gateway_method_response.staticcheck_get_200.status_code response_templates = { "application/json" = jsonencode({ deployed = true }) } }
そして、これらをデプロイしてステージを設定するためのコードです。
# 初期デプロイ+ステージ resource "aws_api_gateway_deployment" "first" { rest_api_id = aws_api_gateway_rest_api.api.id description = "infra empty deployment" depends_on = [ aws_api_gateway_integration.staticcheck_get, aws_api_gateway_integration_response.staticcheck_get_200 ] } resource "aws_api_gateway_stage" "stage" { deployment_id = aws_api_gateway_deployment.first.id rest_api_id = aws_api_gateway_rest_api.api.id stage_name = var.env description = "${var.env} created by Terraform." lifecycle { ignore_changes = [ deployment_id, ] } }
上記ではステージの設定は入れていないの状態ですが、必要に応じてログ出力等の設定を入れていきます。
ここでポイントは、以下の2点です。
aws_api_gateway_deploymentのdepends_onを定義するaws_api_gateway_stageのignore_changesにdeployment_idを指定する
最初のdepends_onの定義は、リソースの作成が完了する前にデプロイメント作成が走るのを防ぐためです。
デプロイメントはリソースやメソッドと依存関係を持たないので、実行順序を明示しています。
(デプロイはServerless Frameworkでコントロールしたいため、triggersは定義していません)
次のignore_changesの指定は、deployment_idの変更を無視するように設定しています。
Serverless Frameworkのデプロイでステージの参照先デプロイメント(deployment_id)が更新されるため、Terraformがそれを変更として検知しないようにします。
リソースの作成はここまでですが、最後にServerless Frameworkに渡す必要のある値をoutputします。
output "apigw_api_id" { value = aws_api_gateway_rest_api.api.id } output "apigw_api_resource_id" { value = aws_api_gateway_resource.api.id }
REST APIのID(上記apigw_api_id)とデプロイ先リソースのID(上記apigw_api_resource_id)になります。
serverless.yml等に直接書かずCodeBuildの変数として渡す場合は、outputではなく直接CodeBuildの変数等に設定してください。
また、ルートリソース(/)にデプロイする場合は、apigw_api_resource_idはaws_api_gateway_rest_api.api.root_resource_idになります。
serverless.ymlの設定
serverless.ymlの設定に、上で作成したAPI Gatewayを設定します。
特別に必要な設定は以下の箇所のみです。
provider: apiGateway: restApiId: "<<REST APIのID(Terraformでoutputとしたapigw_api_id)>>" restApiRootResourceId: "<<リソースのID(Terraformでoutputとしたapigw_api_resource_id)>>"
restApiIdとrestApiRootResourceIdに、それぞれTerraformでoutputした内容を設定するだけです。
(ルート(/)にデプロイする場合、restApiRootResourceIdは省略可能です)
あとは、REST APIのログ設定等を入れずに通常通りserverless.ymlの設定をおこなってください。
たとえば、以下のようになります。
service: apigwtest frameworkVersion: '3' provider: name: aws runtime: python3.9 stage: ${opt:stage, self:custom.defaultStage} region: ap-northeast-1 apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: yyyyyy package: individually: true excludeDevDependencies: true patterns: - '!**' custom: defaultStage: dev functions: test1: handler: functions/test/test.handler role: arn:aws:iam::123456789012:role/${self:provider.stage}-api-lambda-test disableLogs: true package: patterns: - functions/test/** events: - http: path: /test method: GET
これをデプロイすると、以下のようになります。赤いところがデプロイにより追加・変更されたところです。

restApiRootResourceIdに/apiのリソースIDを指定しているため、その下にリソースが作成されます。
デプロイの裏ではCloudFormationのAWS::ApiGateway::Deploymentリソースが作成されます。
このリソースにはStageNameが指定されているため、デプロイメントを作成するとともにステージの参照先デプロイを更新します。
これにより、Terraformで作成したステージの向き先デプロイメントが書き換えられ、Serverless Frameworkで作成したAPIもアクセス可能な状態になります。
(restApiRootResourceIdにルート以外のリソースIDを指定しているときはコマンド結果にあるURLのパスはずれてしまうようなのでご注意ください)
Lambda関数の定義
今回のテーマであるAPI Gatewayから少し外れますが、Lambda関数デプロイの話しも少し書いておきます。
先のserverless.ymlの例では以下のようにroleとdisableLogsを指定していました。
functions: test1: handler: functions/test/test.handler role: arn:aws:iam::123456789012:role/${self:provider.stage}-api-lambda-test disableLogs: true
roleはLambda関数が使うIAMロールで、disableLogsはCloudWatch LogsのLogGroupを作らないようにする設定です。
いずれも、以下のような理由からTerraformで集中管理しています。
- 開発者にはなるべく機能の実装に集中してもらいたい
- セキュリティに関する設定はなるべく少人数で管理したい(開発者は人員の増減が多い)
- ログ保存期間等、運用に関する設定をなるべく近いところにまとめたい
API Gatewayと同じように、他のリソースもTerraformに寄せられる部分があるという例でした。
最後に
今回はTerraformで構築したAPI GatewayにServerless Frameworkからデプロイする方法について紹介しました。
どのリソースを誰が何を使って管理するか、その時の状況によって変わってくるかと思います。
Terraformに寄せて管理することはメリットも多いと思いますので、役割分担を決めるときには考えていいただければと思います。