はじめに
Node.js、バージョンアップの足がかなり早いですよね。
AWS Lambdaにおけるランタイムサポート期間も、これにあわせてハイテンポになっています。
ちゃんとバージョンアップをしろというご意見は重々承知の上ではありますが、
Node.js v8.10でLambda Functionを使い続けざるを得ない場合に、カスタムランタイムを使ってEoLとなったランタイムを動かし延命処置を図ります。
動作確認環境
- Arch Linux (2020.04.04)
- Docker 19.03.8-ce
- aws-cli 1.18.36
カスタムランタイムの使い方
カスタムランタイムの仕様については、公式ドキュメントが詳しいので割愛します。
カスタムランタイムを使用するには、デプロイパッケージあるいはLayerに、node
実行ファイルと、ハンドラー関数を起動するためのbootstrap
実行ファイルを含める必要があります。
今回は既存のLambda Functionを使用することを想定していますので、関数のデプロイパッケージには手を加えずLambda Layerでランタイムを読み込ませます。
Lambda Layerの作成には、以下のリポジトリを活用させていただきます。
https://github.com/lambci/node-custom-lambda
こちらのリポジトリにはNode v10、v12のファイルが含まれています。(2020/04/04現在)
今回はこのリポジトリをフォークし、v8.10用のファイルを追加することでLayerを作成します。
bootstrap
については、CとJavascriptで書かれたものがそれぞれv12.x/bootstrap.c
、v12.x/bootstrap.js
にあります。(v10.xも同様)
bootstrap.c
(をコンパイルしたbootstrap
)がまずAWS Lambdaによって起動され、これがbootstrap.js
スクリプトをカスタムランタイムのNodeで実行します。
bootstrap.js
は、AWS Lambda ランタイムインターフェイスから関数の呼び出しイベントの受け取り、デプロイパッケージのスクリプト実行、実行結果のPOSTを行います。
上記リポジトリのbootstrap.js
はNode v8.10でも問題なく動くので、
必要な変更点はLambda Layerに含めるnode
実行ファイルをv8.10のものに変更するだけとなります。
カスタムランタイムの作成
前節で紹介したリポジトリをクローンするところからはじめます。
Dockerが必要となります。
$ git clone https://github.com/lambci/node-custom-lambda.git
$ cd node-custom-lambda
v12.xのディレクトリを元に、v8.10のディレクトリを作成します。
$ cp -r v12.x v8.10
$ cd v8.10
v12.xのLayerファイルを削除しておきます。
$ rm layer.zip
このプロジェクトでは、Docker上でbootstrap.c
のビルドとNodeのダウンロードを行います。
config.sh
を編集し、Nodeのバージョンを指定します。
< export NODE_VERSION=12.16.1
---
> export NODE_VERSION=8.10.0
ビルドします。
$ ./build.sh
v8.10/layer.zip
ファイルが出来上がります。
これを解凍すると以下のようなファイルが入っています。
layer
├── bin
│ └── node
├── bootstrap
└── bootstrap.js
Nodeのバージョンを確認しておきます。
$ ./layer/bin/node -v
v8.10.0
テストが用意されていますので、実行してみます。
$ ./test.sh
以上でカスタムランタイムのLayerが完成しました。
カスタムランタイムのデプロイ
リポジトリにはpublish.sh
が用意されていますが、このスクリプトは全てのリージョンにデプロイされてしまいます。
今回はap-northeast-1にのみデプロイできればよいので、AWS CLIを使って手動でLayerを作成します。
まず、作成したレイヤーのファイル(layer.zip
)を任意のS3にアップロードします。
aws s3api put-object --bucket ${BUCKET_NAME} --key nodejs/8.10.0/layer.zip --body layer.zip --output json
次に、Lambda Layerを作成します。
ここではCloudFormationで作成します。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
S3BucketName:
Description: A S3 bucket name contains layer.zip
Type: String
Resources:
Nodejs8Runtime:
Type: AWS::Lambda::LayerVersion
Properties:
Content:
S3Bucket: !Ref S3BucketName
S3Key: nodejs/8.10.0/layer.zip
Description: Layer for Node.js 8.10.0 Custom Runtime
LayerName: custom-runtime-nodejs-8
Outputs:
Nodejs8RuntimeLayerARN:
Description: A lambda layer ARN of Node.js 8.10.0 Custom Runtime
Value: !Ref Nodejs8Runtime
Export:
Name: !Sub ${AWS::StackName}-runtime-nodejs8
Stackを作成。
aws cloudformation create-stack --stack-name dev-lambdalayers-nodejs \
--template-body file://template.yml \
--parameter ParameterKey=S3BucketName,ParameterValue=${S3_BUCKET_NAME}
Layerができていることを確認します。
$ aws lambda list-layer-versions --layer-name custom-runtime-nodejs-8
{
"LayerVersions": [
{
"LayerVersionArn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:custom-runtime-nodejs-8:1",
"Version": 1,
"Description": "Layer for Node.js 8.10.0 Custom Runtime",
"CreatedDate": "2020-04-04T16:59:31.629+0000"
}
]
}
Lambda Functionの作成とテスト
上記で作成したカスタムランタイムをテストします。
Lambda FunctionはServerless Frameworkを使用して作ることにします。
provider.runtime
にprovided
を指定することでカスタムランタイムを使用できます。
Lambda Layerは、先程のCloudformation StackのOutputをインポートしてARNを指定します。
service: test-lambda-function
provider:
name: aws
runtime: provided
stage: dev
region: ap-northeast-1
functions:
hello:
handler: handler.hello
layers:
- 'Fn::ImportValue': dev-lambdalayers-nodejs-runtime-nodejs8
関数のコードはシンプルに、実行しているNode.jsのバージョンを返すだけです。
module.exports.hello = async event => {
return process.version;
};
これを実行して、v8.10.0
という文字列が帰ってきたら成功です。
$ sls invoke -f hello
"v8.10.0"
注意点
- AWS公式のNode.jsランタイムには
aws-sdk
が含まれていますが、この方法で作成したカスタムランタイムにはいずれのnpmパッケージも含まれていません。
おわりに
以上でNode v8を使用するLambda Functionの延命措置ができました。
同様の方法で、Node v6、v4も動かすことが可能です。
しっかりバージョンアップしていくのがベストであることは言うまでもありませんが、
node-gypなどネイティブモジュールはバージョンアップで動かなくなることも多々ありますので、とりあえずの措置には使えるかと思います。
また今回使用したコードはすべて以下リポジトリにアップしています。
https://github.com/uhey22e/node-custom-lambda