結論
まずは結論から
Lambda関数は別ファイルとして保存し、aws cloudformation package
してaws cloudformation deploy
する
まえがき
Lambdaをcloudformationでリファレンスを見ながらとりあえず作ることはできる人向きの記事です。
CloudFormationからLambdaを作る際、そのソースコードの指定方法は2種類あります。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html
ZipFile
-
S3Bucket
とS3Key
(とS3ObjectVersion
)
いずれもProperties
のCode
に記述します。
ZipFile
ZipFile
で指定する際は利用するテンプレートファイルの中でソースコードを直接書くことになります。ちなみにPythonかNode.jsでしか作成できないです。また、PythonにおけるBeautiful Soupなど、外部モジュールをインポートすることもできません(一つのファイルとしてしかアップロードできないため)。
さらにテンプレートファイルに直接書くのでシンタックスハイライトがソースコードに反映されないため見にくい、テンプレートファイルもソースコードの分長くなる、ということでひと目でわかるような小さな関数を書くときのみにしかおすすめしないです。
リファレンスからの引用ですが、以下にyaml形式で記述したサンプルコード(一部抜粋)を載せておきます。いずれも受け取った値に5をかけるだけの関数です。
Resources:
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: >
var response = require('cfn-response');
exports.handler = function(event, context) {
var input = parseInt(event.ResourceProperties.Input);
var responseData = {Value: input * 5};
response.send(event, context, response.SUCCESS, responseData);
};
#------------------以下省略------------------#
Resources:
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
import json
import cfnresponse
def handler(event, context):
responseValue = int(event['ResourceProperties']['Input']) * 5
responseData = {}
responseData['Data'] = responseValue
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")
#------------------以下省略------------------#
コードを記述する前に置く記号がNode.jsだと>
でPythonでは|
であること以外はコードをベタ書きするだけです。
S3Bucket
とS3Key
S3Bucket
とS3Key
で指定する場合にはzip形式であらかじめアップロードしておいたファイルのパスを指定します。
ソースコードが長くても、他のモジュールを読みこんでいても一つのS3オブジェクト内に圧縮しておけばこちらが利用できます。
以下にyaml形式で記述したサンプルコード(一部抜粋)を示します。
Code:
S3bucket: YOUR_S3_BUCKET_NAME
S3key: YOUR_S3_OBJECT_KEY
それぞれ値はString型で、S3keyは特にバケット内での目的のオブジェクトへのパスのことです。
S3Bucket
とS3Key
による指定の際におこる問題
外部モジュールもアップロード時に含めることができ、テンプレートファイルの行数も短くできるのでS3Bucket
とS3Key
を用いた更新方法をなるべく使いたいです。
ですが、関数のコードにのみ変更を加えてスタックを更新するときに問題が起きます。
関数のコードにのみ変更を加えた後、S3bucketに保存したソースコードのzipファイルをアップロードし直すことで更新します。
この後、作成に使ったテンプレートファイルを用いてスタックをアップデートしようとしても
「No updates are to be performed」と怒られます。
テンプレートファイル自体には変更を加えていないので、当たり前といえば当たり前なのですが。。。
回避方法: aws cloudformation package
を使う。
aws cloudformation package
( https://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html)
このコマンドはテンプレートファイル内で指定したローカルにあるディレクトリやファイルをS3にアップロードし、それに応じたS3のオブジェクトのパスを記入したテンプレートファイルを出力してくれるコマンドです。
これで作成されたテンプレートファイルを用いてaws cloudformation deploy
でスタックを作成・更新するのがよいでしょう。
ただしローカルのファイルパスに関してなんでも解決してくれるわけではなく特定のリソースの特定のプロパティについてのみ有効です。
リソース名 | プロパティ名 |
---|---|
AWS::ApiGateway::RestApi | BodyS3Location |
AWS::Lambda::Function | Code |
AWS::Serverless::Function | CodeUri |
AWS::AppSync::GraphQLSchema | DefinitionS3Location |
AWS::AppSync::Resolver | RequestMappingTemplateS3Location |
AWS::AppSync::Resolver | ResponseMappingTemplateS3Location |
AWS::Serverless::Api | DefinitionUri |
AWS::ElasticBeanstalk::ApplicationVersion | SourceBundle |
AWS::CloudFormation::Stack | TemplateURL |
また、AWS::Include Transform
のLocation
パラメータにも使えます。
サンプル
まず、lambda関数のファイルを置くためのS3バケット(my-lambda-bucket
)をあらかじめ作成しておき、bashのコマンドは全てプロジェクトルートで実行します。
また、ディレクトリの構造が以下のようになっているとします。(こちらを参考にしました)
$ tree
.
├── sample.yml
└── src
└── handlers
└── my_lambda_function
├── my_lambda_function.py
└── other_modules
└── ・・・
aws cloudformation package
を用いてテンプレートを作成するためのテンプレートのサンプルを作成します。
先程の例と同じく、デプロイするlambda関数(my_lambda_function.py
)は受け取った値に5を掛ける関数です。
AWSTemplateFormatVersion : '2010-09-09'
Description: Lambda Function.
Resources:
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code: src/handlers/my_lambda_function # 相対パスを用いるときはテンプレートファイルからの相対パスである必要がある
#------------------以下省略------------------#
import json
import cfnresponse
def handler(event, context):
responseValue = int(event['ResourceProperties']['Input']) * 5
responseData = {}
responseData['Data'] = responseValue
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")
そして、aws cloudformation package
を実行します。
$ aws cloudformation package \
--template-file sample.yml \
--s3-bucket my-lambda-bucket \
--output-template-file packaged-template.yml
実行できればmy_lambda_function
以下のモジュールも含めたlambdaの関数が一つのオブジェクトとして圧縮されS3にアップロードされます。
さらに、このアップロードしたS3オブジェクトへのパスが書き込まれたテンプレートファイル(packaged-template.yml
)が出力されます。
AWSTemplateFormatVersion : '2010-09-09'
Description: Lambda Function.
Resources:
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket: my-bucket-name
S3Key: 4b5ba6316bdbweac8b1de6a498ef6317
#------------------以下省略------------------#
あとはこの作成されたpackaged-template.yml
に対してaws cloudformation deploy
を実行することでlambdaを作成・更新ができます。
$ aws cloudformation deploy \
--template-file packaged-template.yml \
--stack-name my-lambda-stack-name \
--capabilities CAPABILITY_NAMED_IAM CAPABILITY_IAM
補足
aws cloudformation package
ではサンプルのようにランダムな文字列を用いて命名したファイルをアップロードしていくのでS3バケット上にどんどん圧縮されたファイルが保存されていきます。
過去の関数をS3に置き続ける必要がない場合はライフサイクルを設定して消してしまってもいいと思います。
実は関数以外のところを書くのが一番面倒なのがCloudformationです。
こんな記事を書いておきながらですが、雰囲気掴んでしまった後はAWS SAM使いましょう。
ちなみにSAMでもpackageコマンド使えます。
参考資料
リファレンス
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html
https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deploy/index.html
https://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html
ブログ
https://www.ketancho.net/entry/2017/12/30/022511
https://dev.classmethod.jp/cloud/aws/understanding-codeuri-property-and-deployment-package-in-serverless-application-model/
https://www.magata.net/memo/index.php?Lambda%A1%F5API%20Gateway%A4%F2CloudFormation%A4%C7%BA%EE%C0%AE%A4%B9%A4%EB