Lambda FunctionをCloudFormationでデプロイするときの考え方は、やってみて以下がしっくりきました。
Lambda関数は別ファイルとして保存し、aws cloudformation packageしてaws cloudformation deployする
(CloudFormationでAWS Lambdaを作成・更新する際のベストプラクティス)
実際の実施時には、デプロイ後に不要になるS3バケットに置いたソースコードファイルや、中間ファイル的に作成されるテンプレートファイルなどの削除もしているので、それを含めた手順をメモしておきます。またその手順が繰り返し行っているとちょっと手間に思えてきたので、 bash スクリプトにまとめてしまうことにします。
AWS CLIでのLambda Functionのデプロイ
AWS CLIでLambda Functionをデプロイする手順を確認しておきます(もしスクリプトが使えればいい、動作は分かる/興味がないようであれば、飛ばして先に進んでください)。
準備
- ソースコードファイルを作成します。
-
AWS::Lambda::Function
リソースをデプロイするCloudFormationテンプレートを書きます。
作成したファイルは、例えば次のように配置します。
(カレントディレクトリ)
+-- template/
| +-- my-lambda-stack-001.yml (テンプレートファイル)
+-- source/
+-- my-lambda-function-001/
+-- lambda-function.py (関数本体)
テンプレートは例えばこんな感じ。ポイントは、Lambda Functionの CODE
には作成したソースコードのあるディレクトリを指定することです。テンプレートからの相対パスで記述し、デリミタは /
を(もしWindows環境で実行するとしても)使います。
なおLambda Functionと同時に専用のIAM Roleをデプロイすることが多いと思いますが、既存ロールを使う場合は MyLambdaRole
は削除して role
は既存ロールの arn
に置換えてください。
AWSTemplateFormatVersion: '2010-09-09'
Description: "My Lambda Function"
Resources:
MyLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
RoleName: my_lambda_role
MyLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code: ../source/my-lambda-function-001
FunctionName: my-lambda-function-001
Handler: lambda_function.lambda_handler
Role: !GetAtt MyLambdaRole.Arn
Runtime: python3.8
デプロイの実行
- デプロイに使用する一時的なS3バケットを作成する。
-
aws cloudformation package
を実行する。コードファイルがアップロードされ一時的なテンプレートが生成される。 -
aws cloudformation create-stack
を実行する。生成された一時的なテンプレートを使用する。 - 生成された一時的なテンプレートを削除する。
- スタックのデプロイ完了を待つ。
- 作成した一時的なS3バケットを削除する。
作業はCloudFormationテンプレートのあるディレクトリで行います。以下の例では、先頭の $
はコマンドプロンプトと考えてください。
まず一時的に使用するS3バケットを作成します。バケット名は適当な文字列と日時などを組合わせると、既存のものと重なりにくいでしょう。
$ LAMBDA_BUCKET="YOURNAME-lambda-deploy-`date +%Y%m%d-%H%M%S`"
$ aws s3 mb "s3://${LAMBDA_BUCKET}"
次に aws cloudformation package
を実行します。これはLambda Functionのコードなど、ファイルで作成されたものを指定されたS3バケットにアップロードし、そのS3バケット名とオブジェクト名を反映したテンプレートを生成してくれます。 my-lambda-stack-001.yml
を元に my-lambda-stack-001-packaged.yml
を生成させることにします。
$ TEMPLATE_FILE=my-lambda-stack-001.yml
$ PACKAGED_FILE=my-lambda-stack-001-packaged.yml
$ aws cloudformation package --s3-bucket ${LAMBDA_BUCKET} --template-file ${TEMPLATE} --output-template-file ${PACKAGED}
生成されたテンプレート中では Code
の指定値がS3のバケット名とオブジェクト名に置換えられています。これがアップロードされたコードファイルを指しています。 aws cloudformation create-stack
を、生成されたテンプレートファイル my-lambda-stack-001-packaged.yml
を指定して実行します。スタック名は my-lambda-stack-001
とします。
なお、テンプレートファイルは file://フルパス
の形で指定する必要があります。またここではIAM Roleをテンプレート内で同時にデプロイしている想定なので --capabilities CAPABILITY_NAMED_IAM
が必要になっています。
$ STACK_NAME=my-lambda-stack-001
$ aws cloudformation create-stack --stack-name ${STACK_NAME} --template-body "file://`pwd`/${PACKAGED_FILE}" --capabilities CAPABILITY_NAMED_IAM
スタックが作成されてしまえば、生成されたテンプレートファイルは不要でしょう。削除することにします。
$ rm ${PACKAGED_FILE}
あとはAWSコンソール上でCloudFormationスタックの進行を確認します。デプロイが完了する(状態が"CREATE COMPLETE"になる)のを待ちます。
デプロイが完了してしまえば、作成したS3バケットと、そこにアップロードされたLambda Function用のコードファイルは不要でしょう。削除することにします。
aws s3 rb s3://${LAMBDA_BUCKET} --force
これで終了です。
スクリプト化する
以下を前提にします。
- CloudFormationスタック名を引数で受け取ります。
- CloudFormationテンプレートは名は
スタック名.yml
とします。 - AWSプロファイルが作成されており、デフォルトのもの以外を使うときは
AWS_PROFILE
環境変数に使用するプロファイル名が設定されているものとします。
以下がスクリプトです。
#!bash
LAMBDA_BUCKET_PREFIX=yourname_lambda_bucket
STACK_NAMES=$@
LAMBDA_BUCKET="tsukamoto-challenge-`date +%Y%m%d-%H%M%S`"
#--- function definitions ---
create_temporary_bucket() {
echo `date "+%Y/%m/%d %H:%M:%S"` S3 buckect: create "${LAMBDA_BUCKET}" started.
aws s3 mb "s3://${LAMBDA_BUCKET}" > /dev/null
echo `date "+%Y/%m/%d %H:%M:%S"` S3 buckect: create "${LAMBDA_BUCKET}" finished.
}
create_stacks() {
for STACK_NAME in $STACK_NAMES
do
echo `date "+%Y/%m/%d %H:%M:%S"` CloudFormation Stack: create "${STACK_NAME}" started.
TEMPLATE_FILE="${STACK_NAME}.yml"
PACKAGED_FILE="${STACK_NAME}-packaged.yml"
aws cloudformation package --template-file ${TEMPLATE_FILE} --output-template-file ${PACKAGED_FILE} --s3-bucket "$LAMBDA_BUCKET" > /dev/null
aws cloudformation create-stack --stack-name ${STACK_NAME} --template-body "file://`pwd`/${PACKAGED_FILE}" --capabilities CAPABILITY_NAMED_IAM > /dev/null
rm ${PACKAGED_FILE} > /dev/null
echo `date "+%Y/%m/%d %H:%M:%S"` CloudFormation Stack: create "${STACK_NAME}" finished.
done
}
wait_stacks() {
for STACK_NAME in $STACK_NAMES
do
STACK_STATUS=`aws cloudformation list-stacks | grep STACKSUMMARIES | grep ${STACK_NAME} | head -1 | cut -f 7`
if [ "${STACK_STATUS}" = "CREATE_IN_PROGRESS" ]; then
echo `date "+%Y/%m/%d %H:%M:%S"` CloudFormation Stack: "${STACK_NAME}" is ${STACK_STATUS}, wait.
aws cloudformation wait stack-create-complete --stack-name ${STACK_NAME} > /dev/null
STACK_STATUS=`aws cloudformation list-stacks | grep STACKSUMMARIES | grep ${STACK_NAME} | head -1 | cut -f 7`
echo `date "+%Y/%m/%d %H:%M:%S"` CloudFormation Stack: "${STACK_NAME}" is ${STACK_STATUS}.
else
echo `date "+%Y/%m/%d %H:%M:%S"` CloudFormation Stack: "${STACK_NAME}" is ${STACK_STATUS}.
fi
done
}
remove_temporary_bucket() {
echo `date "+%Y/%m/%d %H:%M:%S"` S3 buckect: "${LAMBDA_BUCKET}" exists.
aws s3 rb s3://${LAMBDA_BUCKET} --force > /dev/null
echo `date "+%Y/%m/%d %H:%M:%S"` S3 buckect: "${LAMBDA_BUCKET}" removed.
}
#--- main ---
echo `date "+%Y/%m/%d %H:%M:%S"` deploy.sh started.
create_temporary_bucket
create_stacks
wait_stacks
remove_temporary_bucket
echo `date "+%Y/%m/%d %H:%M:%S"` deploy.sh: finished.
exit 0
スクリプトをCloudFormationテンプレートと同じディレクトリに置きます。例えば(冒頭のディレクトリ構成を元にすれば)次のような配置になります。
(カレントディレクトリ)
+-- template/
| +-- deploy.sh (上記スクリプトファイル)
| +-- my-lambda-stack-001.yml (テンプレートファイル)
+-- source/
+-- my-lambda-function-001/
+-- lambda-function.py (関数本体)
コマンドラインでCloudFormationテンプレートのディレクトリに移動し、 deplo.sh
を実行します。引数で、作成するスタック名を指定します。
$ ./deploy.sh スタック名
スクリプトは スタック名.yml
ファイルを使用テンプレートとして、手作業時と同様に以下の手順を実行します。
- デプロイに使用する一時的なS3バケットを作成する。
-
aws cloudformation package
を実行する。コードファイルがアップロードされ一時的なテンプレートが生成される。 -
aws cloudformation create-stack
を実行する。生成された一時的なテンプレートを使用する。 - 生成された一時的なテンプレートを削除する。
- スタックのデプロイ完了を待つ。
- 作成した一時的なS3バケットを削除する。
なお、スタック名を複数指定しても構いません。
$ ./deploy.sh スタック名1 スタック名2 ...
この時は、上の手順の2~4を各スタックに対して行った後、5ですべてのスタックが終了するのを待って、6でS3バケットを削除する動作になります。
参考
以下の情報を参考にしました。
- CloudFormationでAWS Lambdaを作成・更新する際のベストプラクティス - Qiita
- S3 バケットへのローカルアーティファクトのアップロード - AWS CloudFormation
Lambda Function用のテンプレートの作成は、以下を参考にしました。
本ページ内容は筆者が参照の便のためにある時点でまとめた個人的なメモです。内容を保証するものではなく、また筆者の所属組織等とは一切かかわりがありません。