既存のLambda FunctionにLambda Layersを導入し、AWS SAMで管理する(Node.js)
Summary
- axiosなどのモジュールに依存した既存のNode.js Lambda Functionがある
- このfunctionで使用しているモジュールをAWS Lambda Layersに移行し、aws-sam-cliでデプロイする
- 既存functionで個別にnpm installしていたモジュールを削除し、デプロイしたLayerを使用するように変更する
- Layer移行後も既存functionが
sam local invoke
コマンドでローカルで実行できる事を確認する
- Layerを使うように変更した既存functionをデプロイする
- 移行した結果
- 既存functionのサイズを半分以下に削減できた
- 既存functionの実行時間を半分以下に削減できた
- AWS SAM 便利
- Lambda Layerも便利、だけど、チーム開発でCIの仕組み構築まで考え始めると一工夫必要かも?
前提条件
1
2
3
4
5
6
7
|
# aws-cliのバージョン
$ aws --version
aws-cli/1.16.80 Python/3.7.1 Darwin/18.2.0 botocore/1.12.70
# aws-sam-cliのバージョン
$ sam --version
SAM CLI, version 0.10.0
|
既存Functionで使用中のモジュールをLayersに移行する
Layer用のプロジェクトディレクトリを作成し、Layer化したいnpmモジュールをインストールします。
ディレクトリの構成は公式ドキュメントの記載 の通り nodejs/node_modules
という構成になるようにします。
1
2
3
4
5
6
|
$ cd <PROJECT_ROOT>
$ mkdir nodejs
$ cd nodejs
$ npm init -y
$ npm install [必要なモジュール群]
|
ここまで完了すると、PROJECT_ROOTの下には下記のディレクトリとファイルが作成された状態になります
1
2
3
4
5
|
.
└── nodejs
├── node_modules
├── package-lock.json
└── package.json
|
zipでアーカイブ
成果物をnodejs
ディレクトリがトップディレクトリとなるようにアーカイブします。
1
2
|
$ cd <PROJECT_ROOT>
$ zip -r layer.zip ./
|
zipをS3にアップロード
作成したzipをS3にアップロードします。バケットが別途必要であれば事前に作成しておきます。
1
2
3
4
5
6
7
|
# BUCKET, REGION, FOLDERは適宜各自の環境の内容に置き換え
# バケットを作成(必要であれば)
$ aws s3 mb s3://${S3_BUCKET} --region ${S3_REGION}
# この例では、zipを指定バケット・指定フォルダの下にアップロード
$ aws s3 cp layer.zip s3://${S3_BUCKET}/${S3_FOLDER}/
|
AWS SAMのテンプレートを作成
デプロイテンプレートを作成します。テンプレートの形式はSAMの公式ドキュメント を参考にします。ContentUri
がS3にアップロードしたzipのURLになっている事がポイントです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
My Node.js modules Layer
Resources:
MyLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: my-layer
Description: My Node.js modules layer
ContentUri: 's3://S3_BUCKET/S3_FOLDER/layer.zip'
CompatibleRuntimes:
- nodejs8.10
LicenseInfo: 'Available under the MIT-0 license.'
RetentionPolicy: Retain
|
作成したらaws-sam-cliでyamlのvalidationをしておくと良いです。
デプロイ/公開
aws-sam-cliでデプロイします。
1
2
3
4
5
|
$ sam deploy --template-file ./template.yaml --stack-name layer-stack --capabilities CAPABILITY_IAM
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - layer-stack
|
既存functionをLayerを使用する形式に変更する
今回筆者が対象にしたfunctionは下記のようなディレクトリ構成で、API Gatewayをeventトリガーとして処理を行うfunctionです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
.
├── Makefile
├── README.md
├── hello_world
│ ├── app.js
│ ├── lib
│ ├── node_modules
│ ├── package-lock.json
│ ├── package.json
│ └── tests
│ └── unit
│ └── test_handler.js
├── serverless-output.yaml
└── template.yaml
|
Layer移行により node_modules
に含まれている大部分のmoduleがこのfunctionでは不要になる想定です。これを念頭に、
- Layer移行済モジュールを削除
- samテンプレートを修正
- ローカルでfunctionを実行して正常動作するか確認する
- デプロイ&動作確認
という流れで移行作業を進めていきます。
Layer移行済のモジュールを削除
まずLayerに移行したモジュールを対象のfunctionから削除します。package.jsonのdependenciesから不要モジュールの記述を削除し、npm install
を再実行します。
例えば私のfunctionを例にすると、aws-sdk, axios, cheerioの3モジュールを全てLayerに移行したので、下記のdependenciesは全て不要になるので削除しました。
1
2
3
4
|
# CODE_URIは適宜各自の環境の内容に置き換え
$ cd <FUNCTION_ROOT>
$ vi ${CODE_URI}/package.json
|
1
2
3
4
5
6
|
"dependencies": {
"aws-sdk": "^2.387.0",
"axios": "^0.18.0",
"cheerio": "^1.0.0-rc.2"
},
:
|
1
2
|
$ cd hello_world
$ npm install
|
template.yamlの修正
デプロイしたLayerのARNを下記コマンドで確認します。 --layer-name
にはLayerのtemplate.yamlで設定した LayerName
を指定します。正常終了すると下記形式の結果が返ってきます。(REGION と ACCOUNT_ID は適宜ご自身の環境に読み替えてください)
1
2
3
|
$ aws lambda list-layer-versions --layer-name my-layer | grep LayerVersionArn
"LayerVersionArn": "arn:aws:lambda:REGION:ACCOUNT_ID:layer:my-layer:1",
|
確認したARNを使用して、functionのテンプレートファイルをLayerを使用する形式に修正します。こちらのドキュメント に記載の通り、functionのリソース定義のProperties指定に Layers
というセクションを追加し、そこにLayerのARNを設定します。
1
2
|
$ cd <FUNCTION_ROOT>
$ vi template.yaml
|
1
2
3
4
5
6
7
8
9
10
11
12
|
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: hello_world/
Handler: app.lambdaHandler
Runtime: nodejs8.10
(中略)
Layers:
- arn:aws:lambda:ap-northeast-1:123456789012:layer:my-layer:1
|
ローカルでfunctionの動作確認
Layerに移行した&移行済モジュールを削除したので、ローカルでの実行がこれまで通り行えるかを確認しておきます。ローカルでの実行コマンドはLayer移行前から変える必要は無く、これまで通り sal local invoke
コマンドを使用します。
1点挙動が異なるのは、Layer移行後初回の実行時にデプロイ済のLayer Functionのダウンロードが実行される事です。デフォルトではダウンロード結果は ${HOME}/.aws-sam/layers-pkg/
下に保存(キャッシュ)され、Layerのバージョンアップを行わない限りは2回目以降の実行時はキャッシュが利用されます。
1
2
3
4
|
$ sam local invoke -e event.json HelloWorldFunction
Downloading arn:aws:lambda:ap-northeast-1:123456789012:layer:my-layer:1 [####################################] 7298732/7298732
:
|
注意点: Layers設定時のハマりどころ
こちらのドキュメントの例では、template.yamlの Layers
セクションで組み込み関数 Fn::Sub
の短縮形 !Sub
が使われているのですが、これはlocalでの実行時は機能しません。この内容で sam local invoke
を行うと、Fn::Sub
関数で期待している変数置換が実行されずLayerのARNが正しい内容で処理されない為、Layer Functionのダウンロードが実行されません。その結果「layerに含まれているモジュールが見つからない」という旨のエラーになってしまいます。localでの実行も考慮するなら、LayersのARNは文字列のみで記載するか、あるいは Ref
関数で「文字列のみで記載された別リソースを参照する」という手段で記述します。
デプロイ/公開
aws-sam-cliでデプロイします。
1
2
3
4
5
6
7
8
9
10
|
# BUCKET, FOLDER, STACKは適宜各自の環境の内容に置き換え
$ sam package --template-file ./template.yaml --output-template-file ./serverless-output.yaml --s3-bucket ${S3_BUCKET} --s3-prefix ${S3_FOLDER}
Successfully packaged artifacts and wrote output template to file ./serverless-output.yaml.
$ sam deploy --template-file ./serverless-output.yaml --stack-name ${STACK} --capabilities CAPABILITY_IAM
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - layer-stack
|
Layerの更新
node moduleの追加/削除などでLayerの更新が必要になった場合も作業の流れはほとんど変わりません。
まずzipアーカイブとS3へのアップロードを再度実行します。
1
2
3
4
|
$ cd <PROJECT_ROOT>
$ zip -r layer.zip ./
$ aws s3 cp layer.zip s3://${S3_BUCKET}/${S3_FOLDER}/
|
更新デプロイは sam deploy
ではなく aws lambda publish-layer-version
コマンドで行います。
1
2
3
4
5
6
|
$ aws lambda publish-layer-version \
layer-name my-layer \
description "My Node.js modules Layer" \
license-info "Available under the MIT-0 license." \
content S3Bucket=${S3_BUCKET},S3Key=${S3_FOLDER}/layer.zip \
compatible-runtimes nodejs8.10
|
上記コマンドでLayerの更新が完了したら、Layerのバージョンが更新された旨が処理結果として返ってきます。利用側functionのtemplate.yamlのLayersバージョンも合わせて更新し、functionの再デプロイが必要になります。
1
2
|
$ cd <FUNCTION_ROOT>
$ vi template.yaml
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: hello_world/
Handler: app.lambdaHandler
Runtime: nodejs8.10
(中略)
# my-layer:2 にバージョンアップ
Layers:
- arn:aws:lambda:ap-northeast-1:123456789012:layer:my-layer:2
|
ブックマーク