AWS SAM CLI で Terraform で定義されたテンプレートと Lambda 関数のビルドとローカル実行がプレビュー機能としてサポートされました

2022.11.16

いわさです。

AWS SAM CLI を使うと SAM で定義されたサーバーレスアプリケーションをローカル実行することが出来ます。

これまでは SAM テンプレートで定義されたものを実行する必要があったのですが、今回プレビュー機能として Terraform で定義されたサーバーレスアプリケーションもサポートされました。

何が出来るのか

具体的には、SAM CLI のコマンドのうち以下に関して--hook-nameオプションが追加され、Terraform であることを指定することが出来るようになりました。
これによって template.yml などを前提としていた各コマンドが main.tf でも実行出来ます。

  • sam build
  • sam local invoke
  • sam local start-lambda

なお、サポートされているのはビルドとローカル実行のみなので、Terraform をsam deploy出来るというわけではありません。
もしサーバーレスアプリケーションで DynamoDB などを実行してそれらを利用する場合は引き続きterraform applyが必要です。

サンプルアプリケーション構造の確認

本日のアップデートにあわせてサンプルリポジトリが公開されていますのでこちらを使ってみます。

また、以下の AWS ブログでも上記リポジトリが利用されています。
このリポジトリには 2 つのサーバーレスアプリケーションが作成されていて、以下のブログでは API Gateway + Lambda + DynamoDB のapi-lambda-dynamodb-exampleが使われていますが、私は本日はよりシンプルなlambda-exampleを使ってみました。

サンプル Lambda 関数が固定のレスポンスをするだけの非常にシンプルなものです。
以下の記事では関連リソースとして事前にterraform applyしていますが、こちらの場合は単独でローカルコンテナで実行するだけなので不要です。

サンプルアプリケーションのコードをポイントを抜粋して取り上げます。

Lambda 関数は以下のみです。

index.py

# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

import json

def lambda_handler(event, context):
    event_body = json.loads(event['body'])
    print(event_body)

    return {
        'statusCode': 200,
        'body': "Request Received!",
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        }
    }

続いて main.tf です。
template.yml は存在せず、main.tf でハンドラーが定義されています。

main.tf

# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

terraform {

:

resource "aws_lambda_function" "hello-terraform" {
    filename = "${local.building_path}/${local.lambda_code_filename}"
    handler = "index.lambda_handler"
    runtime = "python3.8"
    function_name = "hello-terraform"
    role = aws_iam_role.iam_for_lambda.arn
    timeout = 30
    depends_on = [
        null_resource.build_lambda_function
    ]
}

resource "null_resource" "sam_metadata_aws_lambda_function_hello_terraform" {
    triggers = {
        resource_name = "aws_lambda_function.hello-terraform"
        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
    ]
}

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"
    }
}

:

ポイントとしてはメタデータリソースとして null_resource を使っています。
サンプルアプリケーションでは local-exec からpy_build.shを実行し依存関係の解決などを行っています。

local-exec には以下の記事を参考にしてください。

py_build.sh

# 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
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

ビルドとローカル実行してみる

では実際に実行してみましょう。
前提として AWS SAM CLI バージョンが 1.63.0 以上である必要があり、Terraform バージョンが 1.1 以上である必要があります。

% sam --version           
SAM CLI, version 1.64.0

% terraform --version
Terraform v1.3.4
on darwin_arm64

ビルド

以下のように--hook-nameterraformを指定します。
本日時点で許容されているパラメータはterraformのみです。

また、プレビュー機能なので、実行すると以下のように確認が発生します。
--beta-featuresオプションを追加することで確認を省略することが出来ます。

% sam build --hook-name terraform
Supporting Terraform applications is a beta feature.
Please confirm if you would like to proceed using AWS SAM CLI with terraform application.
You can also enable this beta feature with 'sam build --beta-features'. [y/N]: y

Experimental features are enabled for this session.
Visit the docs page to learn more about the AWS Beta terms https://aws.amazon.com/service-terms/.

Running Prepare Hook to prepare the current application
Executing prepare hook of hook "terraform"
Initializing Terraform application
..
Creating terraform plan and getting JSON output
...
Generating metadata file
Finished generating metadata file. Storing in /Users/iwasa.takahito/work/hoge1116sam/aws-sam-terraform-examples/zip_based_lambda_functions/lambda-example/.aws-sam-iacs/iacs_metadata/template.json
Prepare hook completed and metadata file generated at: /Users/iwasa.takahito/work/hoge1116sam/aws-sam-terraform-examples/zip_based_lambda_functions/lambda-example/.aws-sam-iacs/iacs_metadata/template.json
Building codeuri: /Users/iwasa.takahito/work/hoge1116sam/aws-sam-terraform-examples/zip_based_lambda_functions/lambda-example/src runtime: python3.8 metadata: {'SkipBuild': False, 'BuildMethod': 'makefile', 'ContextPath': '/Users/iwasa.takahito/work/hoge1116sam/aws-sam-terraform-examples/zip_based_lambda_functions/lambda-example/.aws-sam-iacs/iacs_metadata', 'WorkingDirectory': '/Users/iwasa.takahito/work/hoge1116sam/aws-sam-terraform-examples/zip_based_lambda_functions/lambda-example', 'ProjectRootDirectory': '/Users/iwasa.takahito/work/hoge1116sam/aws-sam-terraform-examples/zip_based_lambda_functions/lambda-example'} architecture: x86_64 functions: aws_lambda_function.hello-terraform
Running CustomMakeBuilder:CopySource
Running CustomMakeBuilder:MakeBuild
Current Artifacts Directory : /Users/iwasa.takahito/work/hoge1116sam/aws-sam-terraform-examples/zip_based_lambda_functions/lambda-example/.aws-sam/build/AwsLambdaFunctionHelloTerraform03805755
python3 ".aws-sam-iacs/iacs_metadata/copy_terraform_built_artifacts.py" --expression "|values|root_module|resources|[?address==\"null_resource.sam_metadata_aws_lambda_function_hello_terraform\"]|values|triggers|built_output_path" --directory "/Users/iwasa.takahito/work/hoge1116sam/aws-sam-terraform-examples/zip_based_lambda_functions/lambda-example/.aws-sam/build/AwsLambdaFunctionHelloTerraform03805755" --target "null_resource.sam_metadata_aws_lambda_function_hello_terraform"

Initializing the backend...

:

内部でterraform planなどが実行されています。

なお--hook-nameオプションを省略した場合はterraformが動作しないので通常どおり SAM テンプレートが要求されます。

% sam build                                   
Error: Template file not found at /Users/iwasa.takahito/work/hoge1116sam/aws-sam-terraform-examples/zip_based_lambda_functions/lambda-example/template.yml

ローカル実行

では実行してみます。
ターゲットの関数はリクエストボディの内容を取得して出力するコードがあるので、パラメータを付与して実行してみます。サンプルイベント(sample_event.json)が用意されていますのでそちらを使いましょう。

% sam local invoke --hook-name terraform --beta-features -e events/sample_event.json 

Experimental features are enabled for this session.
Visit the docs page to learn more about the AWS Beta terms https://aws.amazon.com/service-terms/.

Skipped prepare hook. Current application is already prepared.
Invoking index.lambda_handler (python3.8)
Skip pulling image and use local one: public.ecr.aws/sam/emulation-python3.8:rapid-1.64.0-x86_64.

Mounting /Users/iwasa.takahito/work/hoge1116sam/aws-sam-terraform-examples/zip_based_lambda_functions/lambda-example/.aws-sam/build/AwsLambdaFunctionHelloTerraform03805755 as /var/task:ro,delegated inside runtime container
START RequestId: 3a619826-14d5-4a7d-98a9-bd3ac8df6fa0 Version: $LATEST
{'Hello': 'World'}
END RequestId: 3a619826-14d5-4a7d-98a9-bd3ac8df6fa0
REPORT RequestId: 3a619826-14d5-4a7d-98a9-bd3ac8df6fa0  Init Duration: 2.22 ms  Duration: 655.38 ms     Billed Duration: 656 ms       Memory Size: 128 MB     Max Memory Used: 128 MB
{"statusCode": 200, "body": "Request Received!", "headers": {"Content-Type": "application/json", "Access-Control-Allow-Origin": "*"}}

sam local invokeで実行することが出来ました。
割愛しましたが、sam local start-lambdaで同じように実行し、エンドポイントに対して HTTP クライアントから呼び出しすることも出来ます。

さいごに

本日は AWS SAM CLI で Terraform で定義されたテンプレートと Lambda 関数のビルドとローカル実行がプレビュー機能としてサポートされたので実際に試してみました。

まず、サポートされた範囲としてはローカルでのビルドと実行になるので、Terraform でサーバーレスアプリケーションを作成する際のローカル実行補助ツールとして AWS SAM CLI を利用することが出来るようになりました。
Lambda 関数のローカル実行はしたかったが SAM では無く Terraform を使っていてそれが出来なかったというケースで活用出来そうです。