AWS SAMによるLambda Layers & Layers利用Functionの作成手順と運用観点での注意点
Summary
- Node.jsのLambdaで、レイヤー用のスタックと、それを利用するFunctionスタックのAWS SAMによる作成を以下の手順で実施
- レイヤーのスタックをデプロイ
- レイヤーを利用するLambda Functionのスタックで、
sam local invoke
によるローカル実行で動作確認 - レイヤーを利用するLambda Functionのスタックをデプロイ
- レイヤーのスタックでは、
<テンプレートのContentUriで指定したフォルダ>/nodejs/node_modules
というフォルダにライブラリがインストールされるようにフォルダを構成する -
sam deploy
を--guided
オプション付きで実行するとデプロイ成果物配置用のS3バケットを自動で作成(既にあれば再利用)してくれるが、このバケットは "バージョニング有・ライフサイクル未設定" で作成されるので、samconfig.tomlによって2回目以降のデプロイ手順を簡略化する事が出来る恩恵とバージョニングの恩恵 vs コスト面の課題 のメリデメを判断して利用する-
--guided
オプションと--s3-bucket
オプションでの固有バケット指定を併用すると、--s3-bucket
オプションの内容は無視される
-
前提条件
- Summaryに記載の通り、スタックがレイヤー用とそれを利用するLambda Functionで別々になっている、というケースを想定しています
# aws-cliのバージョン
$ aws --version
aws-cli/2.1.28 Python/3.9.2 Darwin/19.6.0 source/x86_64 prompt/off
# aws-sam-cliのバージョン
$ sam --version
SAM CLI, version 1.19.1
# Dockerのバージョン
$ docker version | grep Version | head -1
Version: 20.10.2 # Docker Desktop for Mac Ver 3.1.0
# nodeのバージョン
$ node -v
v12.18.4
レイヤーのスタックを構築・デプロイ
レイヤー用スタックのフォルダ構成
下記構成で用意します。テンプレートファイルと、レイヤーに含めるライブラリ群を配置するフォルダ(例では node-base-layer
)、という構成です。
.
├── .node-version
├── node-base-layer
│ └── nodejs
│ ├── node_modules
│ ├── package-lock.json
│ └── package.json
└── template.yaml
template.yaml
例えば下記のような内容になります。
---
AWSTemplateFormatVersion: 2010-09-09
Description: Lambda Layer for Node.js
Transform: AWS::Serverless-2016-10-31
Parameters:
NodeBaseLayerNameParam:
Description: Layer Name for NodeBaseLayer
Type: String
Default: node-base-layer
Resources:
NodeBaseLayer:
Type: AWS::Serverless::LayerVersion
Properties:
Description: Base Layer for Node.js
LayerName: !Ref NodeBaseLayerNameParam # デフォルト = "node-base-layer"
ContentUri: node-base-layer/
RetentionPolicy: Retain
CompatibleRuntimes:
- nodejs12.x
NodeBaseLayerPermission:
Type: AWS::Lambda::LayerVersionPermission
Properties:
Action: lambda:GetLayerVersion
LayerVersionArn: !Ref NodeBaseLayer
Principal: !Ref AWS::AccountId
Outputs:
Name:
Description: Stack Name.
Value: !Ref AWS::StackName
Export:
Name: !Ref AWS::StackName
NodeBaseLayerArn:
Description: Published NodeBaseLayer ARN.
Value: !Ref NodeBaseLayer
Export:
Name: !Sub ${AWS::StackName}-layer-arn
package.json
例えば下記のような内容になります。※ 後ほどの動作確認用にライブラリとして cheerio
だけを含めています。
{
"name": "nodejs",
"version": "1.0.0",
"description": "Lambda Layer for Node.js",
"main": "index.js",
"dependencies": {
"cheerio": "^1.0.0-rc.5"
},
"devDependencies": {},
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC"
}
npm install → デプロイ
公式ドキュメントで言及されているフォルダ配置のルール に則ってレイヤーに含めるライブラリ(Nodeの場合は node_modules
)を配置し、テンプレートの ContentUri
で指定するローカルパスもそれに合わせたものにする必要があります。今回の例では node-base-layer/nodejs/node_modules
というパスにライブラリがあり、テンプレートの ContentUri
には node-base-layaer/
を指定します。
結果的に <テンプレートのContentUriで指定したフォルダ>/nodejs/node_modules
というフォルダがライブラリ配置先と合致していればOK です。
※ 公式リポジトリに用意されているNode.js用の雛形プロジェクト の場合では、lib/nodejs/node_modules
というフォルダをライブラリ配置先にしてあります 。 (なので ContentUri にはlib/
が指定されています)
実際の運用は
- package.jsonを編集してライブラリを追加なり更新なり削除し
-
npm install
して -
sam deploy
する- デプロイ成果物を置くS3バケットの作成・管理をAWS SAMにおまかせしたければ
--guided
オプションを指定する(参考:--guided
オプション初出時の公式記事)
- デプロイ成果物を置くS3バケットの作成・管理をAWS SAMにおまかせしたければ
という流れになります。今回はバケット自動作成のお試しも兼ねて --guided
オプション付きでデプロイしてみます。
# ライブラリのインストール
$ cd node-base-layer/nodejs
$ npm install
# デプロイ
$ cd ../..
$ sam deploy --template template.yaml --region <YOUR_REGION> --stack-name layer-stack --capabilities CAPABILITY_IAM --guided
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Not found
Setting default arguments for 'sam deploy'
=========================================
Stack Name [layer-stack]:
AWS Region [ap-northeast-1]:
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: Y
Save arguments to configuration file [Y/n]: Y
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
Deploy this changeset? [y/N]: y
デプロイ結果確認
デプロイが正常に完了すれば、AWS::Lambda::LayerVersion リソースと AWS::Lambda::LayerVersionPermission リソースがそれぞれ作成されます。aws lambda list-layer-versions
コマンドでそのARNを確認できればOKです。
# 出力結果の末尾の数値はバージョン番号なので、バージョンが増える度に表示される結果も増えます
$ aws lambda list-layer-versions --layer-name node-base-layer | jq '.LayerVersions[].LayerVersionArn'
"arn:aws:lambda:ap-northeast-1:<自身のAccountId>:layer:node-base-layer:1"
--guided
オプション付きでデプロイした際に自動作成されるS3バケットについて
注意点: ところで、sam deploy
を --guided
オプション付きで実行すると samconfig.toml(=デフォルトのファイル名) というファイル が作成されます。
version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "layer-stack"
# バケット
s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxxxxxx"
# プレフィクス
s3_prefix = "layer-stack"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
この中の s3_bucket
が、--guided
オプション付きでのデプロイ時にSAMが自動で作成してくれるデプロイ成果物配置用バケットの名前です。実は2年前に同テーマの記事を書いた当時はレイヤーをzipアーカイブ→S3にアップロード という操作は自前で行う必要があったのですが、2019年11月リリースのバージョンからバケット自動作成に対応されました。これによりレイヤー用スタックの管理運用作業が「ライブラリの追加や更新」→「sam deploy
」という操作だけで済むようになりました。
作成されたバケットには、デプロイされたレイヤーのテンプレートとパッケージングされたライブラリ群の2種類のオブジェクトが存在しています。
$ aws s3api list-objects --bucket aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxxxxxx | jq '.Contents[].Key'
"layer-stack/xxxxxxxxxxxxxxx.template"
"layer-stack/yyyyyyyyyyyyyyyyyyyy"
この自動作成されるバケットの注意点は、バージョニングが有効になっている、且つ、ライフサイクルが未設定 な事です。つまりこのバケットを配置先としてデプロイを実行するとその都度成果物が新バージョンで登録され続ける&それらが残り続ける事になります。--guided
オプション付きでのデプロイによりsamconfig.tomlに(デフォルトの)設定を出力しておく事によって2回目以降のデプロイ手順を簡略化する事が出来るという恩恵+バージョニングの恩恵は得られるものの、コスト面の注意は払っておいた方が良いかも知れません。
例えば sam deploy
にはバケット明示用の --s3-bucket
やプレフィクス指定用の --s3-prefix
というオプションも用意されていますので、これらを利用して自動作成バケットの利用を回避する、という策も検討の余地がありそうです(ちなみにSAM 1.19では、--guided
と --s3-bucket
を両方指定すると --s3-bucket
の方が無視される。という挙動になっています)
sam local invoke
によるローカル実行
レイヤーを利用するFunctionのスタックで、Function用スタックの準備
続いてレイヤーを利用するFunctionを準備します。デプロイ済のレイヤーに含まれているライブラリを参照できるという事だけを確認したいので、とりあえず sam init
で単純なプロジェクトを作っておきます。
$ sam init
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
What package type would you like to use?
1 - Zip (artifact is a zip uploaded to S3)
2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 1
Which runtime would you like to use?
1 - nodejs14.x
2 - python3.8
3 - ruby2.7
4 - go1.x
5 - java11
6 - dotnetcore3.1
7 - nodejs12.x
8 - nodejs10.x
9 - python3.7
10 - python3.6
11 - python2.7
12 - ruby2.5
13 - java8.al2
14 - java8
15 - dotnetcore2.1
Runtime: 7
Project name [sam-app]:
Cloning app templates from https://github.com/aws/aws-sam-cli-app-templates
AWS quick start application templates:
1 - Hello World Example
2 - Step Functions Sample App (Stock Trader)
3 - Quick Start: From Scratch
4 - Quick Start: Scheduled Events
5 - Quick Start: S3
6 - Quick Start: SNS
7 - Quick Start: SQS
8 - Quick Start: Web Backend
Template selection: 1
-----------------------
Generating application:
-----------------------
Name: sam-app
Runtime: nodejs12.x
Dependency Manager: npm
Application Template: hello-world
Output Directory: .
Next steps can be found in the README file at ./sam-app/README.md
sam-app というプロジェクトが作成されますので、この中のFunctionをデプロイ済レイヤーを参照・利用するよう変更します。
template.yaml
「パラメータでレイヤーのARNを指定し、それを対象FunctionのLayersに設定」するように修正しておきます(以下差分)
@@ -5,6 +5,11 @@
Sample SAM Template for sam-app
+Parameters:
+ LayerArnParam:
+ Description: Layer ARN
+ Type: String
+
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
@@ -23,6 +28,8 @@
Properties:
Path: /hello
Method: get
+ Layers:
+ - !Ref LayerArnParam
Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function```
関数コード app.js
「レイヤーに含まれているライブラリをrequireする」というコードを追加しておきます。
@@ -1,5 +1,6 @@
// const axios = require('axios')
// const url = 'http://checkip.amazonaws.com/';
+const cheerio = require('cheerio')
let response;
/**
sam local invoke でローカル実行
npm install
→ sam build
→ sam local invoke
の順で、正常に実行されるか確認してみます。
# sam-app のpackage.jsonにもデフォルトでいくつかライブラリを含んでいるのでそれをインストールしておく
$ cd hello-world
$ npm install
# build (--parameter-overrides の LayerArnParam でデプロイしたレイヤーのARNを指定)
$ cd ..
$ sam build --template template.yaml --region <YOUR_REGION> --parameter-overrides LayerArnParam=<DEPLOYED_LAYER_ARN>
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
# local invoke (レイヤーのARNは --parameter-overrides オプションのパラメータで設定)
$ sam local invoke HelloWorldFunction --template template.yaml --region <YOUR_REGION> --parameter-overrides LayerArnParam=<DEPLOYED_LAYER_ARN>
{"statusCode":200,"body":"{\"message\":\"hello world\"}"}
requireしたライブラリがレイヤーから読み込めていれば正常終了します。
補足: デプロイ済レイヤーを sam local invoke 実行時に参照する仕組み
公式ドキュメントに解説がありますが、ザックリまとめると、
-
${HOME}/.aws-sam/layers-pkg/<LAYER_NAME>-<LAYER_VERSION_NUMBER>-xxxxxxxxxx
というパスにレイヤーがダウンロード&キャッシュされる- Node.jsの場合、
${HOME}/.aws-sam/layers-pkg/<LAYER_NAME>-<LAYER_VERSION_NUMBER>-xxxxxxxxxx/nodejs/node_modules
というフォルダ構成になっていればOK
- Node.jsの場合、
-
sam local invoke
の実行環境であるDockerイメージ = "samcli/lambda" イメージがビルドされる。この際にこのDockerイメージ内へダウンロード&キャッシュ済のレイヤーが取り込まれる -
レイヤーは実行環境の
/opt
ディレクトリ下に展開される ので、つまりNode.jsの場合は/opt/nodejs/node_modules
下に展開されていればOK- ローカルの"samcli/lambda" Dockerイメージでも同様のフォルダが展開されていればOK。実際に
/opt
下にどんなパスで展開されたか?はdocker run
でコンテナに入って確認できる(公式でも同様の確認手順が紹介されている)- ※逆に、
/opt/nodejs/node_modules
に展開できていなかったりすると、sam local invoke
時にレイヤーに含まれているライブラリを参照しようとしたタイミングで "Cannot find module" なエラーが発生します
- ※逆に、
- ローカルの"samcli/lambda" Dockerイメージでも同様のフォルダが展開されていればOK。実際に
レイヤーを利用するFunctionのスタックのデプロイ
レイヤーの初回デプロイ時と同様に、Functionスタックの初回デプロイも --guided
オプション付きで実行してみます。
$ sam deploy --template template.yaml --region <YOUR_REGION> --stack-name sam-app --capabilities CAPABILITY_IAM --guided --parameter-overrides LayerArnParam=<DEPLOYED_LAYER_ARN>
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Not found
Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]:
AWS Region [ap-northeast-1]:
Parameter LayerArnParam [arn:aws:lambda:ap-northeast-1:<自身のAccountId>:layer:node-base-layer:1]:
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: Y
HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
Save arguments to configuration file [Y/n]: Y
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
Deploy this changeset? [y/N]: y
レイヤーデプロイ時と同様に生成される samconfig.toml の内容を見てみると、レイヤーデプロイ時に自動作成されたバケットが(特に実行時に指定したわけでも無いのに)再利用されている事が分かります。
# レイヤーデプロイ時に作成されたバケット
s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxxxxxx"
sam deploy
の --guided
オプションは、
- まず「"aws-sam-cli-managed-default" というスタックと、そのスタックで作成する "aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxxxxxx" という形式の名前のS3バケット」の取得を試みる
- 存在していなければ、その作成処理が実行される
という仕様になっており(参考: ソースコード = samcli/lib/bootstrap/bootstrap.py の manage_stack 関数 )、今回のケースではレイヤーデプロイ時にスタック&バケットが既に作成済であった為、Functionのデプロイ時にはそれが自動で再利用されました。
繰り返しになりますが、このバケットは バージョニングが有効になっている、且つ、ライフサイクルが未設定 なので、レイヤー・Function共にこのバケットを利用する場合はそれぞれの成果物が削除されずデプロイの度に新バージョンが登録され続ける事になります。samconfig.tomlによる2回目以降のデプロイ簡略化の恩恵+バージョニングの恩恵 vs コスト面の課題、これらのメリデメを判断して利用した方が良さそうです。
Discussion