
2023年9月5日に AWS SAM CLI の Terraform サポート機能が GA (正式リリース)になった👏
Amazon API Gateway や AWS Lambda 関数などサーバーレス関連のコンポーネントは Terraform で統一的に管理しつつも,AWS SAM CLI の開発支援機能(sam local invoke
コマンドや sam local start-api
コマンドでローカルデバッグ)は使いたい❗️という場面はあって非常に便利な組み合わせだと思う.
aws.amazon.com
実際にどういう開発体験なのかを確認するために AWS ブログに載っていたサンプルを試してみる \( 'ω')/
aws.amazon.com
検証環境
今回は macOS 上で SAM CLI 1.97.0(最新)と Terraform 1.5.7(最新)を使う.
$ sam --version
SAM CLI, version 1.97.0
$ terraform version
Terraform v1.5.7
on darwin_arm64
GitHub リポジトリ確認
サンプルコードは GitHub aws-samples/aws-sam-terraform-examples
リポジトリの ga
ディレクトリにある❗️
github.com
ga
ディレクトリには3種類のサンプルがあって,今回は Amazon API Gateway の REST API 前提の api_gateway_v1
を使う📝
$ tree ga -L 1
ga
├── README.md
├── api_gateway_v1
├── api_gateway_v2
└── api_gateway_v2_tf_cloud
4 directories, 1 file
さらに ga/api_gateway_v1
ディレクトリ配下のファイルをまとめた.tf-resources
ディレクトリに main.tf
など Terraform コードがあって,src
ディレクトリに AWS Lambda 関数コード(今回は Python 実装)があって,events
ディレクトリに AWS Lambda 関数 auth
の動作確認に使うイベント情報がある👌
$ tree ga/api_gateway_v1
ga/api_gateway_v1
├── events
│ └── auth.json
├── src
│ ├── auth
│ │ ├── app.py
│ │ └── requirements.txt
│ └── responder
│ ├── app.py
│ └── requirements.txt
└── tf-resources
├── api.tf
├── functions.tf
├── main.tf
├── samconfig.yaml
└── variables.tf
6 directories, 10 files
事前準備
サンプルコードを確認したところ,Python 3.9 の AWS Lambda 関数になっていた.せっかくなら最新の Python 3.11 で動作確認をしておきたくて ga/api_gateway_v1/tf-resources/functions.tf
を修正した.
--- a/ga/api_gateway_v1/tf-resources/functions.tf
+++ b/ga/api_gateway_v1/tf-resources/functions.tf
@@ -5,7 +5,7 @@ module "lambda_function_responder" {
source_path = "../src/responder/"
function_name = "responder"
handler = "app.open_handler"
- runtime = "python3.9"
+ runtime = "python3.11"
create_sam_metadata = true
publish = true
allowed_triggers = {
@@ -23,6 +23,6 @@ module "lambda_function_auth" {
source_path = "../src/auth/"
function_name = "authorizer"
handler = "app.handler"
- runtime = "python3.9"
+ runtime = "python3.11"
create_sam_metadata = true
}
しかし AWS Provider 側で Python 3.11 がサポートされてなく,sam build
コマンドの実行時に以下のエラーが出てしまう🔥
Error: expected runtime to be one of [nodejs nodejs4.3 nodejs6.10 nodejs8.10 nodejs10.x nodejs12.x nodejs14.x nodejs16.x java8 java8.al2 java11 python2.7 python3.6 python3.7 python3.8 python3.9 dotnetcore1.0 dotnetcore2.0 dotnetcore2.1 dotnetcore3.1 dotnet6 nodejs4.3-edge go1.x ruby2.5 ruby2.7 provided provided.al2 nodejs18.x python3.10 java17], got python3.11
そこで AWS Provider の最新を使えるようにga/api_gateway_v1/tf-resources/main.tf
を修正して terraform init -upgrade
コマンドを実行しておく.
--- a/ga/api_gateway_v1/tf-resources/main.tf
+++ b/ga/api_gateway_v1/tf-resources/main.tf
@@ -2,7 +2,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
- version = "~> 4.16"
+ version = "~> 5.17"
}
}
準備完了👏
さっそく試す: sam build
と sam local invoke
sam build
コマンド
Terraform コードや設定など細かいことは後ほど確認するとして,まずは試してみる❗️
ga/api_gateway_v1/tf-resources
ディレクトリに移動して,Terraform 変数にリージョンを指定して(今回は東京リージョンを使う),あとは使い慣れた sam build
コマンドを実行する.しかし今回は --hook-name
オプションで terraform
を指定して実行する点に注意💡
実行すると Terraform の local-exec Provisioner の実行ログが大量に出てきて,不安になる(Ctrl+C
を押したくなる)けどそのまま待ってると Build Succeeded
という表示が出て完了する.
$ cd ga/api_gateway_v1/tf-resources
$ export TF_VAR_aws_region=ap-northeast-1
$ sam build --hook-name terraform --terraform-project-root-path ../
Running Prepare Hook to prepare the current application
Executing prepare hook of hook "terraform"
(中略)
module.lambda_function_auth.null_resource.archive[0] (local-exec):
module.lambda_function_responder.null_resource.archive[0] (local-exec):
(中略)
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
sam local invoke
コマンド
そして使い慣れた sam local invoke
コマンドに --hook-name terraform
オプションを指定して実行すると,public.ecr.aws/lambda/python:3.11-rapid-x86_64
イメージを使って AWS Lambda 関数を実行できる.今回は responder を実行したら Hello TF World
って返ってきた \( 'ω')/ うおお〜
$ sam local invoke --hook-name terraform 'module.lambda_function_responder.aws_lambda_function.this[0]'
Skipped prepare hook. Current application is already prepared.
Invoking app.open_handler (python3.11)
Local image was not found.
Removing rapid images for repo public.ecr.aws/sam/emulation-python3.11
Building image.............................................................................................................................................................................................................................................................................................................................................................................................................................
Using local image: public.ecr.aws/lambda/python:3.11-rapid-x86_64.
(中略)
START RequestId: 71b6ab8c-53f9-41ac-8fb0-62bf4ba929b4 Version: $LATEST
END RequestId: 71b6ab8c-53f9-41ac-8fb0-62bf4ba929b4
REPORT RequestId: 71b6ab8c-53f9-41ac-8fb0-62bf4ba929b4 Init Duration: 0.74 ms Duration: 1551.46 ms Billed Duration: 1552 ms Memory Size: 128 MB Max Memory Used: 128 MB
{"statusCode": 200, "body": "{\"message\": \"Hello TF World\", \"location\": \"xxx.xxx.xxx.xxx\"}"}%
せっかくだから ga/api_gateway_v1/src/responder/app.py
を修正して,もう一度 sam build
コマンドと sam local invoke
コマンドを実行すると,今度は Hello kakakakakku World
って返ってきた \( 'ω')/ うおお〜
$ sam build --hook-name terraform --terraform-project-root-path ../
$ sam local invoke --hook-name terraform 'module.lambda_function_responder.aws_lambda_function.this[0]'
Skipped prepare hook. Current application is already prepared.
Invoking app.open_handler (python3.11)
Local image is up-to-date
Using local image: public.ecr.aws/lambda/python:3.11-rapid-x86_64.
(中略)
START RequestId: f8ddd88f-bfd0-45c5-8643-6db922e6d798 Version: $LATEST
END RequestId: f8ddd88f-bfd0-45c5-8643-6db922e6d798
REPORT RequestId: f8ddd88f-bfd0-45c5-8643-6db922e6d798 Init Duration: 0.57 ms Duration: 1201.38 ms Billed Duration: 1202 ms Memory Size: 128 MB Max Memory Used: 128 MB
{"statusCode": 200, "body": "{\"message\": \"Hello kakakakakku World\", \"location\": \"xxx.xxx.xxx.xxx\"}"}%
そして同じように auth も実行できた \( 'ω')/ うおお〜
$ sam local invoke --hook-name terraform 'module.lambda_function_auth.aws_lambda_function.this[0]' -e ../events/auth.json
Skipped prepare hook. Current application is already prepared.
Invoking app.handler (python3.11)
Local image is up-to-date
Using local image: public.ecr.aws/lambda/python:3.11-rapid-x86_64.
(中略)
START RequestId: c6503ad1-200d-453a-96d9-94c99d0e470a Version: $LATEST
END RequestId: c6503ad1-200d-453a-96d9-94c99d0e470a
REPORT RequestId: c6503ad1-200d-453a-96d9-94c99d0e470a Init Duration: 0.52 ms Duration: 269.49 ms Billed Duration: 270 ms Memory Size: 128 MB Max Memory Used: 128 MB
{"principalId": "user|a1b2c3d4", "policyDocument": {"Version": "2012-10-17", "Statement": [{"Action": "execute-api:Invoke", "Effect": "Allow", "Resource": ["arn:aws:execute-api:us-east-1:123456789012:1234567890/prod/*/*"]}]}, "context": {"number": 1, "bool": true, "passed": "from authorizer"}}%
また sam local invoke
コマンドの引数に実行する AWS Lambda 関数の Terraform リソース ID を指定する必要があるけど,指定なしで実行すると候補を表示してくれるようになってて親切設計だった👏
$ sam local invoke --hook-name terraform
Skipped prepare hook. Current application is already prepared.
Error: You must provide a function logical ID when there are more than one functions in your template. Possible options in your template: ['module.lambda_function_auth.aws_lambda_function.this[0]', 'module.lambda_function_responder.aws_lambda_function.this[0]']
ちなみに Terraform と AWS SAM CLI の連携に関しては以下のドキュメントに詳しく載っている📝 現時点だと最新情報は日本語に翻訳されてなく,まだ GA 前のドキュメントになっているため,英語を確認する必要がある.
docs.aws.amazon.com
samconfig ファイル
AWS SAM CLI でよく使う samconfig
にも対応していて,例えば以下のように samconfig.toml
を設定できる.
version = 0.1
[default]
[default.global]
[default.global.parameters]
hook_name = "terraform"
skip_prepare_infra = true
[default.build]
[default.build.parameters]
terraform_project_root_path = "../"
すると sam build
コマンドと sam local invoke
コマンドのオプションを減らせてシンプルに実行できるようになる👏
$ sam build
$ sam local invoke 'module.lambda_function_responder.aws_lambda_function.this[0]'
ちなみに2023年7月頃から AWS SAM CLI で YAML 形式の samconfig もサポートされててサンプルには samconfig.yaml
が含まれた.
version: 0.1
default:
global:
parameters:
hook_name: terraform
skip_prepare_infra: true
build:
parameters:
terraform_project_root_path: ../
さっそく試す: sam local start-api
今度は sam local start-api
コマンドを実行して API の動作確認をする❗️
$ sam local start-api --hook-name terraform
Skipped prepare hook. Current application is already prepared.
AWS SAM CLI does not guarantee 100% fidelity between authorizers locally
and authorizers deployed on AWS. Any application critical behavior should
be validated thoroughly before deploying to production.
Testing application behaviour against authorizers deployed on AWS can be done using the sam sync command.
Mounting responder at http://127.0.0.1:3000/open [GET]
Mounting responder at http://127.0.0.1:3000/secure [GET]
(中略)
2023-09-19 22:00:00 Press CTRL+C to quit
すぐに localhost:3000
で /open
API と /secure
API を呼び出せた \( 'ω')/ うおお〜
ちなみに /secure
API は Amazon API Gateway の Lambda オーソライザーを使ったアクセス制御になっているため,HTTP ヘッダーに myheader: 123456789
を設定した場合にレスポンスが返るように実装されている.
$ curl http://localhost:3000/open
{"message": "Hello kakakakakku World", "location": "xxx.xxx.xxx.xxx"}
$ curl http://localhost:3000/secure
{"message":"Unauthorized"}
$ curl http://localhost:3000/secure --header 'myheader: 123456789'
{"message": "Hello kakakakakku World", "location": "xxx.xxx.xxx.xxx"}
$ curl http://localhost:3000/secure --header 'myheader: IamInvalid'
{"message":"Unauthorized"}
うまく実行できたので,次は Terraform コードを確認する❗️
module "lambda_function_responder" {
source = "terraform-aws-modules/lambda/aws"
version = "~> 6.0"
timeout = 300
source_path = "../src/responder/"
function_name = "responder"
handler = "app.open_handler"
runtime = "python3.11"
create_sam_metadata = true
publish = true
allowed_triggers = {
APIGatewayAny = {
service = "apigateway"
source_arn = "${aws_api_gateway_rest_api.api.execution_arn}/*/*"
}
}
}
module "lambda_function_auth" {
source = "terraform-aws-modules/lambda/aws"
version = "~> 6.0"
timeout = 300
source_path = "../src/auth/"
function_name = "authorizer"
handler = "app.handler"
runtime = "python3.11"
create_sam_metadata = true
}
まず functions.tf
を確認すると,AWS Provider の aws_lambda_function
ではなく AWS Lambda Terraform module を使っていた.AWS ブログにも書いてある通り,あくまでこれは Terraform コードをシンプルに書くために使われていて,もちろん AWS SAM CLI の Terraform サポートは AWS Provider でも使える👌
github.com
そして AWS Lambda Terraform module を使うことでシンプルに書ける例として create_sam_metadata = true
があって,これは Terraform の null_resource
を使って sam metadata リソースを自動的に作ってくれる.sam metadata は AWS Lambda 関数のアーティファクトがどこにあるかを伝えるために必要で,さらに AWS Lambda 関数の ZIP ファイルを作るビルド処理にも依存している.sam metadata に関しては以下のドキュメントに載っている❗️
docs.aws.amazon.com
また AWS Lambda Terraform module の main.tf にある null_resource.sam_metadata_aws_lambda_function
と package.tf にある null_resource.archive
を読むと Terraform の local-exec Provisioner を使ってビルドをしてる流れ(sam build
コマンドを実行するとログが大量に出てくる部分)など理解できるからおすすめ〜👀
以下のドキュメントには AWS Provider の aws_lambda_function
を使ったサンプルも載っていた💡
docs.aws.amazon.com
まとめ
今月 GA になった AWS SAM CLI の Terraform サポート機能をさっそく試してみたけど良かった❗️
さっそく今の現場で実戦投入してみようと思う \( 'ω')/