[やってみた] 2020年1月6日からAWS Lambda(Node.js 8.10)が作成できなくなっていて、去年のre:InventのWorkshopを復習したら躓いたので、node.js 10.x 用にLambda Layer作成して移行してみた #reinvent

2020.01.09

AWS事業本部の梶原@福岡オフィスです。去年のre:Inventで実施されていたWorkshopを今年に入って実施しようとすると見事に Node.js 8.10を使用するAWS Lambdaがランタイムサポートポリシーにより新規作成ができない状況となっていましたので移行しました

ランタイムサポートポリシー
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtime-support-policy.html

自分でAWS Lambdaを作成して修正できるものはいいんですが、公開されているS3などからソースを取得して、ワークショップで使用する環境をCloudFormation一発で作るワークショップなどではStackがエラーするケースがあります。(Bootstrapやカスタムリソースなど)

ということで、該当するCloudFormationのAWS Lambdaの部分のnodejs8.10nodejs10.xに更新して

LambdaFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      Handler: index.handler
      MemorySize: 256
      Runtime: nodejs8.10 # <== これを nodejs10.x にする

で動くかとおもったのですが(バージョン依存、モジュール依存してないやつは動くときもあります)、nodejs8.10で含まれていたモジュールがnodejs10.xで存在しない場合、実行時にrequire("uuid") 等でError: Cannot find module 'uuid'が発生します。

ということで、モジュール無いなら足せばいいじゃないということで、とりあえず、Lambda Layerを作成して対応したので共有します。

なお、自分が検証したのはuuidのみですので、ほかのモジュール(特にライブラリが必要なImageMagic当たりが怪しい)で動かない等があるかもしれません。使用の際は自己責任で徹底的にテストすることを強くお勧めします。

AWS LambdaLayerの作成

AWS Lambda Layer用のzipを作成

ローカル環境でLayer用のzipファイルを作成します。

  1. node.jsのバージョンを10.xに
  2. zip用のフォルダを作成(node8_modules)
  3. Node.jsのLayerでつかうので、nodejsフォルダを作成
  4. node.js 8.10 からnode.js 10.xのランタイム環境から消えたモジュールをnpmで追加
  5. aws-sdkはランタイム環境に含まれるので削除,package-lock.jsonは不要なので削除
  6. ディレクトリ階層を保持した状態で、zipファイルを作成

追加するモジュールは選別してもいいかとおもいます(自分は無くなったモジュールをまとめてつくりました)

追加したモジュール
base64-js
buffer
dynamodb-doc
events ieee754
imagemagick
isarray
jmespath
lodash
punycode
querystring
sax
url
uuid
xml2js
xmlbuilder

ちなみに追加したモジュールは既存のnode.js8.10のソースを変更して /var/runtime/node_modules/ のパスからバージョン間の差異をとってます

$ nvm use 10
Now using node v10.18.0 (npm v6.13.4)
$ mkdir node8_modules
$ mkdir node8_modules/nodejs
$ cd node8_modules/nodejs
$ npm install base64-js buffer dynamodb-doc events ieee754 imagemagick isarray jmespath lodash punycode querystring sax url uuid xml2js xmlbuilder
+ dynamodb-doc@1.0.0
+ imagemagick@0.1.3
+ jmespath@0.15.0
+ base64-js@1.3.1
+ punycode@2.1.1
+ events@3.0.0
+ querystring@0.2.0
+ lodash@4.17.15
+ url@0.11.0
+ uuid@3.3.3
+ xml2js@0.4.23
+ xmlbuilder@13.0.2
+ buffer@5.4.3
+ ieee754@1.1.13
+ isarray@2.0.5
+ sax@1.2.4
added 28 packages from 70 contributors and audited 39 packages in 5.804s
found 0 vulnerabilities
$ rm -rf ./node_modules/aws-sdk
$ rm -rf ./package-lock.json
$ cd ../
$ zip -r ../node8_modules.zip .

zipファイルを置くS3バケットを作成(スキップ可)

Lambda Layer用のzipファイルを置くS3バケットすでにある場合はスキップしてください。
AWS CLIでlambda-layers-$AWS_DEFAULT_REGION-$ACCOUNT_IDを作成してます。

注:Lambdaレイヤー&CloudFormationはリージョン依存なので、re:Inventでよく使われている米国東部 (バージニア北部)us-east-1米国西部 (オレゴン)us-west-2で使用する場合は AWS_DEFAULT_REGIONを変更してください

$ AWS_DEFAULT_REGION=ap-northeast-1
$ echo $AWS_DEFAULT_REGION
$ ACCOUNT_ID=`aws sts get-caller-identity --query 'Account' --output text`;
$ echo $ACCOUNT_ID
$ LAMBDA_LAYER_S3BUCKET=lambda-layers-$AWS_DEFAULT_REGION-$ACCOUNT_ID
$ echo $LAMBDA_LAYER_S3BUCKET
$ aws s3api create-bucket --bucket $LAMBDA_LAYER_S3BUCKET --region $AWS_DEFAULT_REGION --create-bucket-configuration LocationConstraint=$AWS_DEFAULT_REGION

AWS Lambda Layerを作成

  1. S3バケットにLambda Layer用のzipファイルをアップロード
  2. LambdaLayerを作成
$ aws s3 cp ../node8_modules.zip s3://$LAMBDA_LAYER_S3BUCKET/
$ aws lambda publish-layer-version --layer-name node8_modules_layer \
--content S3Bucket=$LAMBDA_LAYER_S3BUCKET,S3Key=node8_modules.zip --compatible-runtimes nodejs10.x nodejs12.x
{
    "Content": {省略
    },
    "LayerArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:layer:node8_modules_layer",
    "LayerVersionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:layer:node8_modules_layer:1",
    "Description": "",
    "CreatedDate": "2020-01-09T10:54:43.318+0000",
    "Version": 1,
    "CompatibleRuntimes": [
        "nodejs10.x",
        "nodejs12.x"
    ]
}

AWS Lambda Layerの作成を確認

  1. AWS Lambda Layerの作成を確認し
  2. LayerVersionArn をメモします。
$ aws lambda list-layers --compatible-runtime nodejs10.x
{
    "Layers": [
        {
            "LayerName": "node8_modules_layer",
            "LayerArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:layer:node8_modules_layer",
            "LatestMatchingVersion": {
                "LayerVersionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:layer:node8_modules_layer:1",
                "Version": 1,
                "CreatedDate": "2020-01-09T10:54:43.318+0000",
                "CompatibleRuntimes": [
                    "nodejs10.x",
                    "nodejs12.x"
                ]
            }
        }
    ]
}
arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:layer:node8_modules_layer:1

LambaLayerをAWS Lambdaに追加する

CloudFormationの場合

  1. Runtime をnodejs10.xに更新
  2. 作成したLambaLayerを追加
LambdaFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      Handler: index.handler
      MemorySize: 256
      Runtime: nodejs10.x # update form nodejs8.10
      Layers: # Layerを追加
        - arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:layer:node8_modules_layer:1 # 前の工程で取得したARNを記載

上記の修正をおこなったのち、スタックを作成するか、更新を実施してください

コンソールから

  1. Lambdaを選択
  2. レイヤーの追加を選択

  3. 互換性のあるレイヤーを選択で作成したLambdaレイヤーを指定

  4. 忘れずにLambdaを保存

Lambdaの検証

更新したLambda関数の検証を実施します。
複数AWS Lambda関数がある場合もレイヤーの追加ですむので、検証も捗るかとおもいます。

まとめ

re:Invnetでおこなわれたワークショップのサンプルコード等が随時公開されていますが、今年に入りエラーすることが多かったのでしらべてみると AWS Lambdaのランタイムサポートのことをうっかり忘れていました。同じようにハマる人の助けになれば幸いです。 ちなみに、新規の作成ができなくなった状況で、既存のLambdaが実行できなくなるという状況ではありません(いまのところランタイムの廃止のアナウンスはありません) がnode.jsのバージョンを更新するときなどに依存モジュールの事を気にかけていただけるといいかとおもいます

参考情報

AWS>ドキュメント>AWS Lambda>開発者ガイド>ラインタイムポリシー
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtime-support-policy.html

AWS Lambda レイヤー
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html