api gateway + lambda の構成を作成したらローカルで動かして動作確認をしたいですよね。
今回は、terraform で作成した上記リソースをローカルで動かす方法をまとめました。
意外と癖が強くて、動くようになるまで苦戦しました。そのあたりについても書いていこうと思います。
はじめに
AWS は公式で AWS SAM というサーバレス環境構築ツールを提供しています。
こちらは宣言した内容をそのままAWSにデプロイできる上、dockerを使えばローカルで動作させることも可能な優れものです。ただ、リソースを作成するために書くSAMテンプレートはなかなか癖があり、学習コストも少し高めです。
公式もそれを意識しているのか、2年ほど前に terraform で作成したコードからSAMテンプレートを作成してくれるツールが発表されました。
今回は、このツールを使用して API gateway + lambda の構成をローカルで動作させてみようと思います。
今回できるもの
今回の構成は以下のようになります。
ちなみに以前同じ構成をServeless Frameworkを利用して作成しています。
しかしながら、Serverless Framework は v.4 からある程度の規模を超えると有償になってしまいました。
今後は複数クラウドに対応していて無料で使えるterraformがIaCの最有力候補になると思います。(AWS CDK や CloudFront, bicep, Pulumi, CDM などなど…。IaCの世は大戦国時代ですね。)
前置きが長くなってしまいましたが、ここからは実際に動かしてみようと思います。
動かし方
コード全文はこちらです。
以下のインストトールとセットアップはできているものとするので、ここでの説明は割愛します。
- aws cli
- aws sam
- terraform
今回はlambdaをビルドするコードをshell scriptで書いているので、bashやzshでの実行を前提としています。
git clone https://github.com/Nalagami/terraform-lambda-local-sample
cd terraform-lambda-local-sample
sam build --hook-name terraform --beta-features --terraform-project-root-path ./../
## sam template の作成が始まる ##
sam local start-api --hook-name terraform
## sam template を使用して仮想環境が起動する ##
ここまで実行できれば、local環境にapiが作成されるはずです。
以下のようなコメントが出ていれば成功です。
sam local start-api --hook-name terraform
Skipped prepare hook. Current application is already prepared.
Mounting hello-terraform at http://127.0.0.1:3000/example [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to
restart/reload SAM CLI while working on your functions, changes will be reflected
instantly/automatically. If you used sam build before running local commands, you will need
to re-run sam build for the changes to be picked up. You only need to restart SAM CLI if
you update your AWS SAM template
2024-06-08 20:46:53 WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:3000
2024-06-08 20:46:53 Press CTRL+C to quit
試しに叩いてみます。
curl -v "http://127.0.0.1:3000/example"
* Trying 127.0.0.1:3000...
* Connected to 127.0.0.1 (127.0.0.1) port 3000
> GET /example HTTP/1.1
> Host: 127.0.0.1:3000
> User-Agent: curl/8.6.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Werkzeug/3.0.3 Python/3.12.3
< Date: Sat, 08 Jun 2024 13:44:11 GMT
< Content-Type: application/json
< Access-Control-Allow-Origin: *
< Content-Length: 17
< Connection: close
<
* Closing connection
Request Received!%
きちんとレスポンスが返ってきてますね。
コード解説
ここからはコード内のどの部分でローカル実行を宣言しているか解説していきます。
~~~省略~~~
resource "null_resource" "sam_metadata_aws_lambda_function_hello_terraform" {
triggers = {
resource_name = "aws_lambda_function.hello_lambda"
resource_type = "ZIP_LAMBDA_FUNCTION"
original_source_code = "${local.lambda_src_path}"
built_output_path = "${local.building_path}/${local.lambda_code_filename}"
}
depends_on = [
null_resource.build_lambda_function
]
}
~~~省略~~~
AWS SAMがLambda関数を識別するために書かれている設定です。
ここで使われているnull_resource
はterraformの構文でサポートされていないリソースを管理する際に宣言します。AWS SAMはsam_metadata_aws_lambda_function_hello_terraform
が宣言されていた場合、ここの設定を読み取ってSAMテンプレートを作成します。
また、depends_on
にはLambdaのzipファイル作成用のnull_resource
が宣言されています。
~~~省略~~~
resource "null_resource" "build_lambda_function" {
triggers = {
build_number = "${timestamp()}" # TODO: calculate hash of lambda function. Mo will have a look at this part
}
provisioner "local-exec" {
command = substr(pathexpand("~"), 0, 1) == "/" ? "./py_build.sh \"${local.lambda_src_path}\" \"${local.building_path}\" \"${local.lambda_code_filename}\" Function" : "powershell.exe -File .\\PyBuild.ps1 ${local.lambda_src_path} ${local.building_path} ${local.lambda_code_filename} Function"
}
}
~~~省略~~~
lambdaのファイルをzipファイルにするためのnull_resource
です。
triggers
が timestampになっているため、terraform plan
やterraform apply
の際は必ず実行されます。
#!/bin/bash
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
src_code=$1
build_path=$2
output_name=$3
resource_type=$4
[[ -z "$src_code" ]] && echo "ERROR: src_code is not defined" && exit 1
[[ -z "$build_path" ]] && echo "ERROR: build_path is not defined" && exit 1
[[ -z "$output_name" ]] && echo "ERROR: output_name is not defined" && exit 1
[[ -z "$resource_type" ]] && echo "ERROR: resource_type is not defined" && exit 1
echo "building ${resource_type} ${src_code} into ${build_path}"
temp_path=${build_path}/tmp_building
if [[ "${resource_type}" == "Layer" ]]; then
temp_path=${build_path}/tmp_building/python
echo "new path ${temp_path}"
fi
pwd
mkdir -p ${build_path}
rm -rf ${build_path}/*
mkdir -p ${build_path}/tmp_building
mkdir -p ${temp_path}
cp -r $src_code/* ${temp_path}
pip install -r ${temp_path}/requirements.txt -t ${temp_path}/.
pushd ${build_path}/tmp_building/ && zip -r $output_name . && popd
mv "${build_path}/tmp_building/${output_name}" "${build_path}/$output_name"
rm -rf ${build_path}/tmp_building˜
Lambdaでにアップロードするpythonのzipファイルを作成するためのshellscriptです。
やっていることは、requirements.txt
に書かれているパッケージをダウンロードして、ソースコードと一緒にzipファイルへ圧縮しているだけです。
公式のリポジトリにpowershell版もあるので、windowsを利用している方は参考にしてください。
まとめ
terraformで作成したリソースをローカルで動作させる方法をまとめました。ちなみにLambda→DynamoDBの通信もできるみたいですが、その際はAWS環境のDynamoDBを見に行く挙動となりました。そのため、DynamoDB local でローカルDBを作成しつつ、コードでboto3のエンドポイントを切り替えれば、ローカルだけで動かすこともできそうでした。
ただ、ここまでやるならAWS SAMを勉強した方が早い気もするので、使い所が難しいと感じました。
参考