Lambda Layerだけを更新したとき、Lambdaが参照するLayerのバージョンが追従しないのでAutoPublishAliasを止めてみた話

Lambda Layerだけを更新したとき、Lambdaが参照するLambda Layerのバージョンが追従しなかったので、AutoPublishAliasの設定を止めてみました。
2021.04.13

AWS SAMを使ってLambdaとLambda Layerの管理をしています。LambdaではAutoPublishAliasでaliasを付与しています。 このとき、Lambda Layerのコードだけを修正してデプロイしても、Lambdaが参照するLayerのバージョンは変更になりませんでした。

本記事では、状況を確認したあと、AutoPublishAliasを未使用に変更し、Lambda Layerのコードのみを変更するとLambdaが参照するLayerのバージョンも追従することを確認します。

なお、AutoPublishAliasが未使用の場合は、$LATESTを参照するため大丈夫でした。

GitHubにIssuesがある

AWS SAMのGitHubには下記のIssuesがあります。どうやら不具合みたいです。

最初にまとめ

本記の執筆時点において、「Lambda Layerだけを更新したとき、Lambdaが参照するLayerのバージョンを追従したい」ならば、下記いずれかとなりそうです。

  1. AutoPublishAliasを止める
  2. AutoPublishAliasを使い、関連するLambdaも何らかの修正を行う(Lambda自体もデプロイの対象とする)
  3. AutoPublishCodeSha256を使って、「Lambdaa Layerのコード+Lambdaのコード」をzip化したハッシュ値をParametersでLambda毎に渡す
    • これをするぐらいなら、Lambda Layerの利用を止めて、デプロイパッケージにまとめたほうがシンプルだと思います

おすすめの方

  • AutoPublishAliasとLambda LayerとLambdaのデプロイについて知りたい方

環境

項目 バージョン
AWS SAM CLI version 1.21.1

まずは再現確認をする

動作確認用のLambdaをデプロイする

sam init

sam init \
    --runtime python3.8 \
    --name lambda-layer-deploy-test \
    --app-template hello-world \
    --package-type Zip

SAMテンプレート

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: lambda-layer-deploy-test

Resources:
  MessageLayer:
    Type: AWS::Serverless::LayerVersion
    Metadata:
      BuildMethod: python3.8
    Properties:
      LayerName: my-layer
      ContentUri: src/layer
      CompatibleRuntimes:
        - python3.8

  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: lambda-layer-deploy-test-function
      CodeUri: src/hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8
      AutoPublishAlias: dev
      Layers:
        - !Ref MessageLayer
      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のコード

Lambda LayerのAPIを叩いて、メッセージをReturnしています。

app.py

import json

import my_layer

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

Lambda Layerのコード

メッセージを返しています。

my_layer.py

def hello():
    return 'hello'

デプロイ

sam build  --use-container

sam deploy \
    --stack-name Lambda-Layer-Deploy-Test-Stack \
    --s3-bucket cm-fujii.genki-deploy \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

動作確認

APIのエンドポイントを取得します。

$ aws cloudformation describe-stacks \
    --stack-name Lambda-Layer-Deploy-Test-Stack \
    --query 'Stacks[].Outputs'

[
    [
        {
            "OutputKey": "HelloWorldApi",
            "OutputValue": "https://jbp01lz74i.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/"
        }
    ]
]

APIを叩くと、期待通りのResponseが得られます。

$ url https://jbp01lz74i.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "hello"}

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

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

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

[
    {
        "id": "gaaohbff76",
        "path": "/"
    },
    {
        "id": "hm0sge",
        "parentId": "gaaohbff76",
        "pathPart": "hello",
        "path": "/hello",
        "resourceMethods": {
            "GET": {}
        }
    }
]

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

$ aws apigateway get-method \
    --rest-api-id jbp01lz74i \
    --resource-id hm0sge \
    --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-layer-deploy-test-function:dev/invocations"
]

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

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

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

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

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

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

[
    "1",
    [
        {
            "Arn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:my-layer:1",
        }
    ]
]

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

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

[
    [
        1
    ]
]

絵で整理すると下記です。

Lambda LayersとLambdaの概要図

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

Lambda Layerのコード

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

my_layer.py

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

デプロイ

sam build  --use-container

sam deploy \
    --stack-name Lambda-Layer-Deploy-Test-Stack \
    --s3-bucket cm-fujii.genki-deploy \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

動作確認

APIを叩くと、変更前のLambda Layerの内容が取得できました。Lambda Layerを変更しても反映されませんでした。

$ url https://jbp01lz74i.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "hello"}

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

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

$ aws apigateway get-method \
    --rest-api-id jbp01lz74i \
    --resource-id hm0sge \
    --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-layer-deploy-test-function:dev/invocations"
]

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

layer-deploy-test-functionには、$LATEST1の2つのバージョンがあります。$LATESTは、my-layer:2の参照に変わりました。

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

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

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

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

[
    "1",
    [
        {
            "Arn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:my-layer:1",
        }
    ]
]

Lambda Layerは、version:1version:2が存在しています。

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

[
    [
        2
    ],
    [
        1
    ]
]

絵で整理すると下記です。$LATESTが参照しているLayerバージョンのみが更新されています。

Lambda LayersとLambdaの概要図

Lambda Layerのみを変更してもデプロイしたい

まずは AutoPublishAlias をやめる

SAMテンプレート

AutoPublishAliasをコメントアウトしています。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: lambda-layer-deploy-test

Resources:
  MessageLayer:
    Type: AWS::Serverless::LayerVersion
    Metadata:
      BuildMethod: python3.8
    Properties:
      LayerName: my-layer
      ContentUri: src/layer
      CompatibleRuntimes:
        - python3.8

  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: lambda-layer-deploy-test-function
      CodeUri: src/hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8
      # AutoPublishAlias: dev
      Layers:
        - !Ref MessageLayer
      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/"

デプロイ

sam build  --use-container

sam deploy \
    --stack-name Lambda-Layer-Deploy-Test-Stack \
    --s3-bucket cm-fujii.genki-deploy \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

動作確認

APIを叩くと、最新のLambda Layerの内容が取得できました。

$ url https://jbp01lz74i.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "hello world!!"}

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

API GatewayのMethod情報を取得し、どのLambdaが呼ばれているのかを確認します。Aliasは無く、lambda-layer-deploy-test-functionが呼ばれています。

$ aws apigateway get-method \
    --rest-api-id jbp01lz74i \
    --resource-id hm0sge \
    --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-layer-deploy-test-function/invocations"
]

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

layer-deploy-test-functionには、$LATEST1の2つのバージョンがあります。

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

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

alias:devは無くなったので、ResourceNotFoundExceptionが発生しました。

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

An error occurred (ResourceNotFoundException) when calling the GetFunction operation: Function not found: arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-layer-deploy-test-function:dev

Lambda関数名からalias:devを無くすと、$LATESTであることが分かります。$LATESTは、my-layer:2を参照しています。

$ aws lambda get-function \
    --function-name lambda-layer-deploy-test-function \
    --query Configuration.[Version,Layers]

[
    "$LATEST",
    [
        {
            "Arn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:my-layer:2",
        }
    ]
]

Lambda Layerは、version:1version:2が存在しています。

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

[
    [
        2
    ],
    [
        1
    ]
]

絵で整理すると下記です。

Lambda LayersとLambdaの概要図

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

Lambda Layerのコード

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

my_layer.py

def hello():
    return 'hello??'

デプロイ

sam build  --use-container

sam deploy \
    --stack-name Lambda-Layer-Deploy-Test-Stack \
    --s3-bucket cm-fujii.genki-deploy \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

動作確認

APIを叩くと、変更後のLambda Layerの内容が取得できました!

$ url https://jbp01lz74i.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "hello??"}

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

API GatewayのMethod情報を取得し、どのLambdaが呼ばれているのかを確認します。Aliasは無く、lambda-layer-deploy-test-functionが呼ばれています。

$ aws apigateway get-method \
    --rest-api-id jbp01lz74i \
    --resource-id hm0sge \
    --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-layer-deploy-test-function/invocations"
]

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

layer-deploy-test-functionには、$LATEST1の2つのバージョンがあります。$LATESTは、my-layer:3の参照に変わりました。

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

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

alias:devは無いので、ResourceNotFoundExceptionが発生します。

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

An error occurred (ResourceNotFoundException) when calling the GetFunction operation: Function not found: arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-layer-deploy-test-function:dev

Lambda関数名からalias:devを無くすと、$LATESTであることが分かります。$LATESTは、my-layer:3を参照しています。

$ aws lambda get-function \
    --function-name lambda-layer-deploy-test-function \
    --query Configuration.[Version,Layers]

[
    "$LATEST",
    [
        {
            "Arn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:my-layer:3",
        }
    ]
]

Lambda Layerは、version:1version:2version:3が存在しています。

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

[
    [
        3
    ],
    [
        2
    ],
    [
        1
    ]
]

絵で整理すると下記です。$LATESTが参照しているLayerバージョンが更新されました。

Lambda LayersとLambdaの概要図

さいごに

かなりハマりました。AutoPublishAliasとLambda Layerの組み合わせにご注意ください。

参考