LoginSignup
49
51

More than 5 years have passed since last update.

CloudFormationでAWS Lambdaを作成・更新する際のベストプラクティス

Posted at

結論

まずは結論から

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
  • S3BucketS3Key(とS3ObjectVersion)

いずれもPropertiesCodeに記述します。

ZipFile

ZipFileで指定する際は利用するテンプレートファイルの中でソースコードを直接書くことになります。ちなみにPythonかNode.jsでしか作成できないです。また、PythonにおけるBeautiful Soupなど、外部モジュールをインポートすることもできません(一つのファイルとしてしかアップロードできないため)。
さらにテンプレートファイルに直接書くのでシンタックスハイライトがソースコードに反映されないため見にくい、テンプレートファイルもソースコードの分長くなる、ということでひと目でわかるような小さな関数を書くときのみにしかおすすめしないです。

リファレンスからの引用ですが、以下にyaml形式で記述したサンプルコード(一部抜粋)を載せておきます。いずれも受け取った値に5をかけるだけの関数です。

Node.jsのZipFileにおけるサンプルコード
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);
          };
#------------------以下省略------------------#
pythonのZipFileにおけるサンプルコード
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では|であること以外はコードをベタ書きするだけです。

S3BucketS3Key

S3BucketS3Keyで指定する場合にはzip形式であらかじめアップロードしておいたファイルのパスを指定します。
ソースコードが長くても、他のモジュールを読みこんでいても一つのS3オブジェクト内に圧縮しておけばこちらが利用できます。
以下にyaml形式で記述したサンプルコード(一部抜粋)を示します。

S3BucketとS3Keyによるソースコード指定をしたサンプルコード
Code:
  S3bucket: YOUR_S3_BUCKET_NAME
  S3key: YOUR_S3_OBJECT_KEY

それぞれ値はString型で、S3keyは特にバケット内での目的のオブジェクトへのパスのことです。

S3BucketS3Keyによる指定の際におこる問題

外部モジュールもアップロード時に含めることができ、テンプレートファイルの行数も短くできるのでS3BucketS3Keyを用いた更新方法をなるべく使いたいです。
ですが、関数のコードにのみ変更を加えてスタックを更新するときに問題が起きます。

関数のコードにのみ変更を加えた後、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 TransformLocationパラメータにも使えます。

サンプル

まず、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を掛ける関数です。

sample.yml
AWSTemplateFormatVersion : '2010-09-09'
Description: Lambda Function.

Resources:
  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code: src/handlers/my_lambda_function # 相対パスを用いるときはテンプレートファイルからの相対パスである必要がある
#------------------以下省略------------------#
src/handlers/my_lambda_function/my_lambda_function.py
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)が出力されます。

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

49
51
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
49
51