【SPA】AWS SAMで構築したAPI GatewayのリソースをTerraformで参照する
はじめに
自己学習のため、以下構成で簡単なSPA(シングルページアプリケーション)のTodoアプリを作成しました。フロントエンドはReact(TypeScript)、バックエンドはPythonです。
構成図
インフラ部分は全てTerraformを使って構築することも可能ですが、学習のため今回はあえてバックエンド部分のみSAMを利用して構築を行いました。
SAMを使うとサーバーレスなAWSサービス(API Gateway,Lambda,DynamoDB)をより少ないコードで構築できるなど様々なメリットがあります。
ただし部分的にSAMを利用するとTerraformで管理しているリソースと連携させたいケースが出てきます。
今回は例として、SAMで構築したAPI GatewayのリソースをTerraformで参照する方法を紹介します。
TL;DR
ポイントは以下2点です。CloudFormationのクロススタック参照先をTerraformにするイメージです。
- SAMテンプレート側のOutputsセクションにValueとExportフィールドを定義する
- Terraform側で
aws_cloudformation_export
のdataリソースを利用して参照する
今回はAPI Gatewayのみを参照していますが、SAM側でOutputの記述を行うことで他リソースを参照させることも可能です。
ディレクトリ構成
ディレクトリ構成は以下です。(一部省略)
terraformはAWSサービス単位でmoduleを分割しています。
├── backend
│ ├── src
│ ├── tests
│ ├── __init__.py
│ ├── samconfig.toml
│ └── template.yaml <- SAMテンプレートはここ
├── frontend
│ └── (省略)
└── terraform
├── main.tf
├── modules
│ ├── route53
│ │ ├── variables.tf
│ │ ├── output.tf
│ │ └── route53.tf <- dataリソースは今回はここに書く
│ ├── acm
│ │ ├── variables.tf
│ │ ├── output.tf
│ │ └── acm.tf
│ └── (省略)
└── variables.tf
SAM側でAPI Gatewayの構築と出力値の定義を行う
SAMテンプレートではAPI Gatewayのエンドポイント名を出力値として定義します。
SAMはCloudFormationがベースとなっているので、テンプレートのOutputsセクションに記述することでCloudFormationのスタックの出力として値が出力されます。
Exportフィールドには任意の名前を付けます。エクスポート名が一意になっていれば任意で問題ありません。
SAMテンプレートの例
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
backend
Globals:
Function:
Timeout: 3
Api:
OpenApiVersion: 3.0.2
Parameters:
StageName:
Type: String
Default: Prod
Resources:
(LambdaとDynamoDBの記述は省略)
### API Gateway ###
ToDoApi:
Type: AWS::Serverless::Api
Properties:
Name: ToDoApi
StageName: !Ref StageName
EndpointConfiguration: REGIONAL
Cors:
AllowOrigin: "'*'"
AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
AllowMethods: "'GET,POST,DELETE,OPTIONS'"
Outputs:
ApiGatewayRestApiId:
Description: "ID of the API Gateway"
Value: !Sub ${ToDoApi}
Export:
Name: ApiGatewayRestApiId <- 任意の名前でOK
ApiGatewayStageName:
Description: "API Gateway Stage Name"
Value: !Ref StageName
Export:
Name: ApiGatewayStageName <- 任意の名前でOK
SAMのデプロイ
以下手順でSAMのデプロイを行います。なお、構文が正しいかどうかはsam validate
コマンドを利用すると素早くトラブルシュートできるので活用しましょう。
ビルド
$ sam build
Build Succeededと出力されればOK
デプロイ
$ sam deploy
Successfully created/updated stack - {stack_name} in ap-northeast-1と出力されればOK
コンソール上で見る出力例
Terraform側で値を参照する
SAMのCloudFormationスタックの出力値を、Terraformのdataリソースで参照します。
例えば、API Gatewayのカスタムドメインを設定する場合を記載します。
route53.tfに書いていますが、apigatewayの部分なので別moduleに分けて記述しても問題ないです。
※ドメインはTerraform側で管理する前提です。TerraformとSAMを併用する場合、どちらで何を管理するかをうまく設計する必要があります。
参照例
resource "aws_api_gateway_domain_name" "api_name" {
domain_name = "api.${var.common.domain}" <- 取得したドメインを変数で宣言しています
regional_certificate_arn = var.acm.cert_tokyo.arn
endpoint_configuration {
types = ["REGIONAL"]
}
}
data "aws_cloudformation_export" "api_id" {
name = "ApiGatewayRestApiId" # SAMテンプレートで記載したExport名に合わせる
}
data "aws_cloudformation_export" "api_stage_name" {
name = "ApiGatewayStageName" # SAMテンプレートで記載したExport名に合わせる
}
resource "aws_api_gateway_base_path_mapping" "api" {
domain_name = aws_api_gateway_domain_name.api_name.domain_name
### ここで参照する ###
api_id = data.aws_cloudformation_export.api_id.value
### ここで参照する ###
stage_name = data.aws_cloudformation_export.api_stage_name.value
}
resource "aws_route53_record" "api" {
zone_id = aws_route53_zone.public.zone_id
name = aws_api_gateway_domain_name.api_name.domain_name
type = "A"
alias {
name = aws_api_gateway_domain_name.api_name.regional_domain_name
zone_id = aws_api_gateway_domain_name.api_name.regional_zone_id
evaluate_target_health = false
}
}
なお証明書作成は以下リソースで実施しています。(module間の変数渡しの記述は省略)
resource "aws_acm_certificate" "main_tokyo" {
domain_name = "*.${var.common.domain}"
subject_alternative_names = [
"*.${var.common.domain}"
]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
resource "aws_route53_record" "cert_validation_tokyo" {
depends_on = [ aws_acm_certificate.main_tokyo ]
allow_overwrite = true
zone_id = var.route53.public_zone.zone_id
name = tolist(aws_acm_certificate.main_tokyo.domain_validation_options)[0].resource_record_name
type = tolist(aws_acm_certificate.main_tokyo.domain_validation_options)[0].resource_record_type
records = [tolist(aws_acm_certificate.main_tokyo.domain_validation_options)[0].resource_record_value]
ttl = 60
}
resource "aws_acm_certificate_validation" "main_tokyo" {
certificate_arn = aws_acm_certificate.main_tokyo.arn
validation_record_fqdns = [aws_route53_record.cert_validation.fqdn]
}
terraform applyの実施
terraform applyを実行しリソースを作成します。
以下はplan結果の抜粋です。
$ terraform plan
(省略)
# module.route53.aws_api_gateway_base_path_mapping.api will be created
+ resource "aws_api_gateway_base_path_mapping" "api" {
+ api_id = "a1b2c3d4e5"
+ domain_name = "api.example.com"
+ id = (known after apply)
+ stage_name = "Prod"
}
# module.route53.aws_api_gateway_domain_name.api_name will be created
+ resource "aws_api_gateway_domain_name" "api_name" {
+ arn = (known after apply)
+ certificate_upload_date = (known after apply)
+ cloudfront_domain_name = (known after apply)
+ cloudfront_zone_id = (known after apply)
+ domain_name = "api.example.com"
+ domain_name_id = (known after apply)
+ id = (known after apply)
+ ownership_verification_certificate_arn = (known after apply)
+ regional_certificate_arn = "arn:aws:acm:ap-northeast-1:123456789012:certificate/12345678-1234-5678-9012-123456789012"
+ regional_domain_name = (known after apply)
+ regional_zone_id = (known after apply)
+ security_policy = (known after apply)
+ tags_all = (known after apply)
+ endpoint_configuration (known after apply)
}
# module.route53.aws_route53_record.api will be created
+ resource "aws_route53_record" "api" {
+ allow_overwrite = (known after apply)
+ fqdn = (known after apply)
+ id = (known after apply)
+ name = "api.example.com"
+ type = "A"
+ zone_id = "Z01234567890123456789"
+ alias {
+ evaluate_target_health = false
+ name = "api.example.com"
+ zone_id = "Z01234567890123456789"
}
}
apply後、カスタムドメインができていればOKです。
まとめ
今回は、SAMで構築したリソースをTerraformで参照する方法を解説しました。
注意点として、SAMのExport名を変更した場合、Terraform側も変更しないといけない点です。
また、連携するリソースが増えるたびにdataリソースが増えるため、ファイルを分割するなど管理を工夫も必要です。
さらに、この構成ではTerraformがSAMに依存しているため、デプロイの順番にも注意が必要です。
実運用では、CI/CDパイプラインを構築し、SAMのビルドからTerraformの適用までを自動化するのが望ましいでしょう。
AWS SAMは冒頭言及した通り、非常に少ないコードでサーバーレスアーキテクチャに必要なリソースを簡単に作成することができます。
特にIAM権限周りの設定が抽象化されており、例えばLambdaで必要なIAMロールやアクセス権限を詳細に記述しなくても、自動的に適切な設定を作成してくれる点は嬉しいですね。
一方で、APIの構築に専念したい、またはLambdaコードの管理を簡潔にしたい場合はSAMを併用するのも良いですが、Webアプリや規模の大きいアプリでは、すべてをTerraformで一元管理するのもアリです。
繰り返しですがSAMとTerraformを併用する場合は、どちらのツールで何を管理するかを明確に設計することが重要です。要件や方針に合わせて設計することを推奨します。
Discussion