CloudFormationのDynamic referencesとAWS SAMのAutoPublishAliasを組み合わせた動作を確認してみた

Lambda Layersのみを更新すると、Lambda Layersは更新されましたが、新バージョン発行とエイリアス更新はされませんでした。
2021.04.23

先日の下記アップデートを活用すれば、Lambda LayersとLambdaのデプロイをスッキリできそうだったので、実際に試してみました。

以前、AWS SAMでAutoPublishAliasを使った際の動作を試してみたので、本記事でもAutoPublishAliasを使った際の動作を確認します。

おすすめの方

  • AWS SAMでDynamic referencesを使いたい方
  • AutoPublishAliasとLambda LayerとLambdaのデプロイについて知りたい方

最初に結論

  • Lambda Layersのみを更新すると、Lambda Layersは更新された
  • しかし、Lambdaの新バージョン発行とエイリアス更新はされなかった

環境

項目 バージョン
AWS SAM CLI 1.21.1

3つのCloudFormationスタックを作成する

3つのCloudFormationスタックを作成します。

  • Lambda Layersのみ
  • API Gateway + Lambda (その1)
  • API Gateway + Lambda (その2)

各Lambdaは、同じLambda Layersを参照します。

Lambda LayersとLambdaをデプロイする

sam init

3つのプロジェクトを作成します。

  • sam-lambda-layers
  • sam-lambda-1
  • sam-lambda-2
sam init \
    --runtime python3.8 \
    --name sam-lambda-layers \
    --app-template hello-world \
    --package-type Zip
sam init \
    --runtime python3.8 \
    --name sam-lambda-1 \
    --app-template hello-world \
    --package-type Zip
sam init \
    --runtime python3.8 \
    --name sam-lambda-2 \
    --app-template hello-world \
    --package-type Zip

SAMテンプレート

Lambda Layers

Lambda LayersとSSMパラメータを作成しています。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: sam-lambda-layers

Resources:
  TestDeployLayer:
    Type: AWS::Serverless::LayerVersion
    Metadata:
      BuildMethod: python3.8
    Properties:
      LayerName: test-deploy-layer
      ContentUri: layer
      CompatibleRuntimes:
        - python3.8

  TestDeployLayerSsmParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name: /Fujii/Test/MyLayer
      Type: String
      Value: !Ref TestDeployLayer

Lambda1 (Stack 1)

Lambdaを作成しています。Lambda Layersは、Dynamic ReferencesでSSMパラメータストアの最新バージョンを参照しています。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: sam-lambda-1

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: lambda-deploy-sample-1-function
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8
      AutoPublishAlias: dev
      Timeout: 10
      Layers:
        - '{{resolve:ssm:/Fujii/Test/MyLayer}}'
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get

  HelloWorldFunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${HelloWorldFunction}

Outputs:
  HelloWorldApi:
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"

Lambda2 (Stack 2)

Lambda関数名以外は、Stack1と同じです。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: sam-lambda-2

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: lambda-deploy-sample-2-function
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8
      AutoPublishAlias: dev
      Timeout: 10
      Layers:
        - '{{resolve:ssm:/Fujii/Test/MyLayer}}'
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get

  HelloWorldFunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${HelloWorldFunction}

Outputs:
  HelloWorldApi:
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"

ソースコード

Lambda Layers

my_layer.py

def hello():
    return 'hello'

Lambda1 (Stack 1)

Lambda Layersのコードを呼んでいます。どちらのLambdaが実行されたのかが分かるように番号も明記しています。

app.py

import json
import my_layer

def lambda_handler(event, context):
    return {
        "statusCode": 200,
        "body": json.dumps({
            "lambda": "1",
            "message": my_layer.hello(),
        }),
    }

Lambda2 (Stack 2)

Lambda Layerのコードを呼んでいます。

app.py

import json
import my_layer

def lambda_handler(event, context):
    return {
        "statusCode": 200,
        "body": json.dumps({
            "lambda": "2",
            "message": my_layer.hello(),
        }),
    }

デプロイ

注意点

AWS SAM CLIは、本記事の作成時点において、Dynamic Referencesに対応していません。

そのため、Lambdaコード側でsam buildコマンドを実行すると、下記のエラーで失敗します。

$ sam build
Error: {{resolve:ssm:/Fujii/Test/MyLayer}} is an Invalid Layer Arn.

もし、外部ライブラリ等を使う場合は、Lambda Layersに含めると良いですね。

Lambda Layers

Lambda Layersでは、Dynamic Referencesを使っていないため、samコマンドが利用できます。

cd sam-lambda-layers
sam build --use-container
sam deploy \
    --stack-name SAM-Lambda-Layers-Stack \
    --s3-bucket cm-fujii.genki-deploy \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

Lambda1 (Stack 1)

Lambdaでは、Dynamic Referencesを使っているため、samコマンドではなくawsコマンドを利用しています。

cd sam-lambda-1
aws cloudformation package \
    --template-file template.yaml \
    --output-template-file packaged.yaml \
    --s3-bucket cm-fujii.genki-deploy
aws cloudformation deploy \
    --template-file packaged.yaml \
    --stack-name SAM-Lambda-1-Stack \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

Lambda2 (Stack 2)

cd sam-lambda-2
aws cloudformation package \
    --template-file template.yaml \
    --output-template-file packaged.yaml \
    --s3-bucket cm-fujii.genki-deploy
aws cloudformation deploy \
    --template-file packaged.yaml \
    --stack-name SAM-Lambda-2-Stack \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

動作確認

APIエンドポイントを取得する

$ aws cloudformation describe-stacks \
    --stack-name SAM-Lambda-1-Stack \
    --query 'Stacks[].Outputs'
[
    [
        {
            "OutputKey": "HelloWorldApi",
            "OutputValue": "https://rx536ihwd7.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/"
        }
    ]
]
$ aws cloudformation describe-stacks \
    --stack-name SAM-Lambda-2-Stack \
    --query 'Stacks[].Outputs'
[
    [
        {
            "OutputKey": "HelloWorldApi",
            "OutputValue": "https://66yrzneyti.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/"
        }
    ]
]

APIから情報取得する

バッチリですね。

$ curl https://rx536ihwd7.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"lambda": "1", "message": "hello"}
$ curl https://66yrzneyti.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"lambda": "2", "message": "hello"}

SSMパラメータストアの内容を確認する

test-deploy-layer:1が格納されています。

$ aws ssm get-parameters \
    --names /Fujii/Test/MyLayer \
    --query Parameters[*].[Name,Type,Value,Version]

[
    [
        "/Fujii/Test/MyLayer",
        "String",
        "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1",
        1
    ]
]

API GatewayとLambdaの関係を確認する

Lambda1 (Stack 1)

作成したAPIのリソース情報を取得します。rx536ihwd7部分は、API GatewayのIDです(URLの先頭部分)。そして、レスポンスのGET /helloのリソースID(rm8kns)をメモしておきます。

$ aws apigateway get-resources \
    --rest-api-id rx536ihwd7 \
    --query items
[
    {
        "id": "rm8kns",
        "parentId": "wtvajqmwx9",
        "pathPart": "hello",
        "path": "/hello",
        "resourceMethods": {
            "GET": {}
        }
    },
    {
        "id": "wtvajqmwx9",
        "path": "/"
    }
]

API GatewayのMethod情報を取得し、どのLambdaが呼ばれているのかを確認します。alias:dev付きでlambda-deploy-sample-1-function:devが呼ばれています。

$ aws apigateway get-method \
    --rest-api-id rx536ihwd7 \
    --resource-id rm8kns \
    --http-method GET \
    --query methodIntegration.[uri]

[
    "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-deploy-sample-1-function:dev/invocations"
]

Lambda2 (Stack 2)

もうひとつのAPIも確認します。

$ aws apigateway get-resources \
    --rest-api-id 66yrzneyti \
    --query items

[
    {
        "id": "gq0bdw",
        "parentId": "quwnfgtni0",
        "pathPart": "hello",
        "path": "/hello",
        "resourceMethods": {
            "GET": {}
        }
    },
    {
        "id": "quwnfgtni0",
        "path": "/"
    }
]

alias:dev付きでlambda-deploy-sample-2-function:devが呼ばれています。

$ aws apigateway get-method \
    --rest-api-id 66yrzneyti \
    --resource-id gq0bdw \
    --http-method GET \
    --query methodIntegration.[uri]

[
    "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-deploy-sample-2-function:dev/invocations"
]

Lambdaのエイリアスとバージョンを確認する

Lambda1 (Stack 1)

$LATEST1の2つのバージョンがあります。それぞれtest-deploy-layer:1を参照しています。

$ aws lambda list-functions \
    --function-version ALL \
    --query Functions[?FunctionName==\'lambda-deploy-sample-1-function\'].[Version,Layers[*].Arn]

[
    [
        "$LATEST",
        [
            "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1"
        ]
    ],
    [
        "1",
        [
            "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1"
        ]
    ]
]

alias:devは、Lambda Version:1であり、test-deploy-layer:1を参照しています。

$ aws lambda get-function \
    --function-name lambda-deploy-sample-1-function:dev \
    --query Configuration.[Version,Layers]

[
    "1",
    [
        {
            "Arn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1",
            "CodeSize": 401
        }
    ]
]

Lambda2 (Stack 2)

$LATEST1の2つのバージョンがあります。それぞれtest-deploy-layer:1を参照しています。

$ aws lambda list-functions \
    --function-version ALL \
    --query Functions[?FunctionName==\'lambda-deploy-sample-2-function\'].[Version,Layers[*].Arn]

[
    [
        "$LATEST",
        [
            "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1"
        ]
    ],
    [
        "1",
        [
            "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1"
        ]
    ]
]

alias:devは、Lambda Version:1であり、test-deploy-layer:1を参照しています。

$ aws lambda get-function \
    --function-name lambda-deploy-sample-2-function:dev \
    --query Configuration.[Version,Layers]

[
    "1",
    [
        {
            "Arn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1",
            "CodeSize": 401
        }
    ]
]

Lambda Layersを確認する

Lambda Layersは、version:1だけ存在しています。

$ aws lambda list-layer-versions \
    --layer-name test-deploy-layer \
    --query LayerVersions[*].[Version]

[
    [
        1
    ]
]

ここまでの関係を絵で整理する

Lambda LayersとLambdaとLambda versionとAliasの関係

Lambda Layersのコードだけ変更する

Lambda Layersのコード

helloからhello world!!に変更しました。

my_layer.py

def hello():
    return 'hello world!!'

デプロイ

デプロイコマンドはすべて同じです。

Lambda Layers

cd sam-lambda-layers
sam build --use-container
sam deploy \
    --stack-name SAM-Lambda-Layers-Stack \
    --s3-bucket cm-fujii.genki-deploy \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

Lambda1

cd sam-lambda-1
aws cloudformation package \
    --template-file template.yaml \
    --output-template-file packaged.yaml \
    --s3-bucket cm-fujii.genki-deploy
aws cloudformation deploy \
    --template-file packaged.yaml \
    --stack-name SAM-Lambda-1-Stack \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

Lambda2

cd sam-lambda-2
aws cloudformation package \
    --template-file template.yaml \
    --output-template-file packaged.yaml \
    --s3-bucket cm-fujii.genki-deploy
aws cloudformation deploy \
    --template-file packaged.yaml \
    --stack-name SAM-Lambda-2-Stack \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

動作確認

APIから情報取得する

Lambda Layersのデプロイは、反映されませんでした。

$ curl https://rx536ihwd7.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"lambda": "1", "message": "hello"}
$ curl https://66yrzneyti.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"lambda": "2", "message": "hello"}

SSMパラメータストアの内容を確認する

test-deploy-layer:2が格納されています。

$ aws ssm get-parameters \
    --names /Fujii/Test/MyLayer \
    --query Parameters[*].[Name,Type,Value,Version]

[
    [
        "/Fujii/Test/MyLayer",
        "String",
        "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:2",
        2
    ]
]

API GatewayとLambdaの関係を確認する

Lambda1 (Stack 1)

API GatewayのMethod情報を取得し、どのLambdaが呼ばれているのかを確認します。alias:dev付きでlambda-deploy-sample-1-function:devが呼ばれています。こちらは今まで通りですね。

$ aws apigateway get-method \
    --rest-api-id rx536ihwd7 \
    --resource-id rm8kns \
    --http-method GET \
    --query methodIntegration.[uri]

[
    "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-deploy-sample-1-function:dev/invocations"
]

Lambda2 (Stack 2)

もうひとつのAPIも同様です。 alias:dev付きでlambda-deploy-sample-2-function:devが呼ばれています。

$ aws apigateway get-method \
    --rest-api-id 66yrzneyti \
    --resource-id gq0bdw \
    --http-method GET \
    --query methodIntegration.[uri]

[
    "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-deploy-sample-2-function:dev/invocations"
]

Lambdaのエイリアスとバージョンを確認する

Lambda1 (Stack 1)

$LATEST1の2つのバージョンがあります。$LATESTtest-deploy-layer:2の参照に変わっていますが、1test-deploy-layer:1から変化していません。

$ aws lambda list-functions \
    --function-version ALL \
    --query Functions[?FunctionName==\'lambda-deploy-sample-1-function\'].[Version,Layers[*].Arn]

[
    [
        "$LATEST",
        [
            "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:2"
        ]
    ],
    [
        "1",
        [
            "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1"
        ]
    ]
]

alias:devは、Lambda Version:1であり、test-deploy-layer:1を参照しています。

$ aws lambda get-function \
    --function-name lambda-deploy-sample-1-function:dev \
    --query Configuration.[Version,Layers]

[
    "1",
    [
        {
            "Arn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1",
            "CodeSize": 401
        }
    ]
]

Lambda2 (Stack 2)

$LATEST1の2つのバージョンがあります。$LATESTtest-deploy-layer:2の参照に変わっていますが、1test-deploy-layer:1から変化していません。

$ aws lambda list-functions \
    --function-version ALL \
    --query Functions[?FunctionName==\'lambda-deploy-sample-2-function\'].[Version,Layers[*].Arn]

[
    [
        "$LATEST",
        [
            "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:2"
        ]
    ],
    [
        "1",
        [
            "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1"
        ]
    ]
]

alias:devは、Lambda Version:1であり、test-deploy-layer:1を参照しています。

$ aws lambda get-function \
    --function-name lambda-deploy-sample-2-function:dev \
    --query Configuration.[Version,Layers]

[
    "1",
    [
        {
            "Arn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1",
            "CodeSize": 401
        }
    ]
]

Lambda Layersを確認する

Lambda Layersは、version:1version:2が存在しています。増えました。

$ aws lambda list-layer-versions \
    --layer-name test-deploy-layer \
    --query LayerVersions[*].[Version]

[
    [
        2
    ],
    [
        1
    ]
]

ここまでの関係を絵で整理する

さいごに

AWS SAM CLIでDynamic referencesをサポートしていない部分でハマりました。どなたかの参考になれば幸いです。

参考