Edited at

AWS CloudFormationのAWS Lambda-backedカスタムリソースで最新のAWS SDKを利用する

cloudpack あら便利カレンダー 2019 の記事となります。誕生秘話 はこちら。

AWS Lambda-backed カスタムリソースを利用するとAWS CloudFormationが対応していないリソースを管理することができて便利なのですが、AWS Lambdaで利用できるAWS SDK(ここではPythonのboto3)のバージョンが最新じゃない場合に困ることがあります。

AWS Lambda-backed カスタムリソース - AWS CloudFormation

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html

そんなときにどうしたら良いものか悩んでいたのですが、AWS Lambda Layersが利用できるみたいだったので試してみました。


前提


  • AWSアカウントがある

  • AWS CLIが利用できる

  • AWS Lambda、Lambda Layers、CloudFormationの作成権限がある


AWS Lambda Layersに最新のAWS SDKのLayerを作成する

AWS Lambda Layersで最新のAWS SDKを利用する方法は下記を参考にさせてもらいました。(感謝

Lambda Layers で最新の AWS SDK を使用する - Qiita

https://qiita.com/hayao_k/items/b9750cc8fa69d0ce91b0

> mkdir 任意のディレクトリ

> cd 任意のディレクトリ

> mkdir python
> pip install -t ./python boto3
> zip -r python.zip ./python

> aws lambda publish-layer-version \
--layer-name boto3 \
--zip-file fileb://python.zip \
--compatible-runtimes python3.7

{
"Content": {
"Location": "https://prod-04-2014-layers.s3.amazonaws.com/snapshots/(略)",
"CodeSha256": "JZM5sEEyGBPgips+y+F0/X5rHXJIPkcLYeazyXiLkTk=",
"CodeSize": 8572137
},
"LayerArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:boto3",
"LayerVersionArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:boto3:1",
"Description": "",
"CreatedDate": "2019-06-21T08:57:29.995+0000",
"Version": 1,
"CompatibleRuntimes": [
"python3.7"
]
}


AWS CloudFormationのテンプレートを作成する

AWS Lambda-backedカスタムリソースでboto3 のバージョンを確認するテンプレートを作成します。

比較のためにLayer利用する/しないのリソースを準備します。

> touch cfn-template.yaml


cfn-template.yaml

Resources:

NonUseLambdaLayer:
Type: Custom::CustomResource
Properties:
ServiceToken: !GetAtt NonUseLambdaLayerFunction.Arn

UseLambdaLayer:
Type: Custom::CustomResource
Properties:
ServiceToken: !GetAtt UseLambdaLayerFunction.Arn

# 標準のboto3を利用
NonUseLambdaLayerFunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Role: !GetAtt FunctionExecutionRole.Arn
Code:
ZipFile: !Sub |
import cfnresponse
import boto3
def handler(event, context):
print(boto3.__version__)
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
Runtime: python3.7

# Lambda Layerのboto3を利用
UseLambdaLayerFunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Role: !GetAtt FunctionExecutionRole.Arn
Code:
ZipFile: !Sub |
import cfnresponse
import boto3
def handler(event, context):
print(boto3.__version__)
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
Runtime: python3.7
Layers:
- arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:boto3:1

FunctionExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: root
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "arn:aws:logs:*:*:*"


AWS CLIからCloudFormationのスタックを作成します。Lambda関数実行用のロールを作成するので、--capabilities CAPABILITY_IAM オプションを指定します。

> aws cloudformation create-stack \

--stack-name cfn-lambda-backed-test \
--template-body file://cfn-template.yaml \
--capabilities CAPABILITY_IAM

{
"StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-lambda-backed-test/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

スタック作成できたらリソース一覧からAWS Lambdaの関数名を取得します。

> aws cloudformation list-stack-resources \

--stack-name cfn-lambda-backed-test

{
"StackResourceSummaries": [
{
"LogicalResourceId": "FunctionExecutionRole",
"PhysicalResourceId": "cfn-lambda-backed-test-FunctionExecutionRole-XXXXXXXXXXXX",
"ResourceType": "AWS::IAM::Role",
"LastUpdatedTimestamp": "2019-06-24T07:25:29.253Z",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
}
},
{
"LogicalResourceId": "NonUseLambdaLayer",
"PhysicalResourceId": "2019/06/24/[$LATEST]8e94b0b2ffc54b00acf35a004e68c522",
"ResourceType": "Custom::CustomResource",
"LastUpdatedTimestamp": "2019-06-24T07:25:37.368Z",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
}
},
{
"LogicalResourceId": "NonUseLambdaLayerFunction",
"PhysicalResourceId": "cfn-lambda-backed-te-NonUseLambdaLayerFunctio-XXXXXXXXXXXXX",
"ResourceType": "AWS::Lambda::Function",
"LastUpdatedTimestamp": "2019-06-24T07:25:32.817Z",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
}
},
{
"LogicalResourceId": "UseLambdaLayer",
"PhysicalResourceId": "2019/06/24/[$LATEST]200facdec8cb4d77ac3dd5f333ceb848",
"ResourceType": "Custom::CustomResource",
"LastUpdatedTimestamp": "2019-06-24T07:25:41.716Z",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
}
},
{
"LogicalResourceId": "UseLambdaLayerFunction",
"PhysicalResourceId": "cfn-lambda-backed-test-UseLambdaLayerFunction-XXXXXXXXXXXXX",
"ResourceType": "AWS::Lambda::Function",
"LastUpdatedTimestamp": "2019-06-24T07:25:36.240Z",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
}
}
]
}

リソースが取得できたらLambda関数のログからboto3 のバージョンを確認します。

上記リソースリストにあるResourceTypeAWS::Lambda::FunctionPhysicalResourceId が関数名になります。

スタック作成時のログを確認しても良いのですが、ここでは簡単にしたかったので関数を実行して確認します。

# 標準のboto3

> aws lambda invoke \
--function-name cfn-lambda-backed-te-NonUseLambdaLayerFunctio-XXXXXXXXXXXX \
--log-type Tail \
outputfile.txt \
--query 'LogResult' | tr -d '"' | base64 -D

START RequestId: 859e7e0b-4041-4fa6-bf61-84a31bd72cc9 Version: $LATEST
1.9.42
    responseUrl = event['ResponseURL']e 15, in sendCCESS, {})
END RequestId: 859e7e0b-4041-4fa6-bf61-84a31bd72cc9
REPORT RequestId: 859e7e0b-4041-4fa6-bf61-84a31bd72cc9 Duration: 54.69 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 56 MB

# Lambda Layerのboto3
> aws lambda invoke \
--function-name cfn-lambda-backed-test-UseLambdaLayerFunction-XXXXXXXXXXXXX \
--log-type Tail \
outputfile.txt \
--query 'LogResult' | tr -d '"' | base64 -D

START RequestId: 2e422fb4-95ae-4274-b9a5-4ced212a78ec Version: $LATEST
1.9.173
    responseUrl = event['ResponseURL']e 15, in sendCCESS, {})
END RequestId: 2e422fb4-95ae-4274-b9a5-4ced212a78ec
REPORT RequestId: 2e422fb4-95ae-4274-b9a5-4ced212a78ec Duration: 28.95 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 35 MB

Layerを利用している関数で最新のAWS SDKを利用できることが確認できました。


まとめ

AWS Lambda LayersへのLayer作成部分もスタックに含めることができればよいのですが、Zipファイルを事前にS3へ上げるなりの準備が必要で、そうなるとS3のリソースを事前に作成しなきゃ。。。

など、、、

どうしても1アクションで完結しなさそうだったので、Layer作成は手動ですることにしました。

もう少し考えればうまくまとまりそうな気がしてますが、今のところはこれで満足です。


参考

AWS Lambda-backed カスタムリソース - AWS CloudFormation

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html

Lambda Layers で最新の AWS SDK を使用する - Qiita

https://qiita.com/hayao_k/items/b9750cc8fa69d0ce91b0