CLOVER🍀

That was when it all began.

AWS SAM(Node.js)でAWS Lambda関数をパッケージングする時に、package-lock.jsonを見るようになっていたという話

これは、なにをしたくて書いたもの?

AWS Lambda関数を書く時にAWS SAMをよく使っているのですが、Node.jsを使ってsam buildした時に.aws-samディレクトリに
package-lock.jsonをコピーせずにnpm installしてしまうイメージがありました。

実際に前はそうだったのですが、ちゃんと確認してみるとこの挙動が変わっていたので、書いておこうかなと。

また、npm ciも使えるようになったみたいです。

AWS SAMとpackage-lock.json

最初に書いたとおり、以前のAWS SAMを使ってNode.jsのAWS Lambda関数に対してsam buildするとpackage-lock.jsonをコピーしてくれない
(たとえプロジェクト内には存在していても)という挙動だったのですが、これが以下のPull Requestで修正されているようです。

feat: Use flag to run npm ci and respect lockfile by mildaniel · Pull Request #338 · aws/aws-lambda-builders · GitHub

ビルド時のNodejsNpmrcCopyActionというアクションで.npmrcしかコピーしてくれなかったのが、加えてpackage-lock.jsonと
npm-shrinkwrap.jsonが存在していればコピーしてくれるNodejsNpmrcAndLockfileCopyActionアクションになったようです。

2022年3月の修正ですね。

ソースコードとしては、こちらが該当します。

https://github.com/aws/aws-lambda-builders/blob/v1.18.0/aws_lambda_builders/workflows/nodejs_npm/actions.py

AWS SAM CLIのバージョンとしては1.41.0での内容みたいです。

https://github.com/aws/aws-sam-cli/blob/v1.41.0/requirements/reproducible-linux.txt#L15

Release aws-lambda-builders v1.14.0 release · aws/aws-lambda-builders · GitHub

また、ドキュメントを読んでいるとtemplate.yamlのMetadata / BuildProperties配下にUseNpmCi: Trueと記述することでnpm ciを
実行してくれるようにもなっているみたいです。

Node.js アプリケーションでは、npm installの代わりに、npm ciを使用して依存関係をインストールします。npm ci を使用するには、Lambda 関数の Metadata リソース属性の BuildProperties の下に UseNpmCi: True を指定します。npm ci を使用するには、アプリケーションは Lambda 関数の CodeUri に package-lock.json または npm-shrinkwrap.json ファイルが必要です。

AWS Serverless Application Model / アプリケーションの構築 / 例

こんなところに書いてあるんだ、という気はしますが…。

今回は、これらを確認してみようと思います。

環境

AWS SAM CLIのバージョン。

$ sam --version
SAM CLI, version 1.53.0

利用するNode.jsのバージョン。

$ node --version
v16.16.0


$ npm --version
8.11.0

AWS SAMプロジェクトを作成する

まずは、AWS SAMプロジェクトを作成します。ランタイムはnodejs16.xで、Amazon API Gateway+AWS Lambdaのテンプレートを選択。

$ sam init --name sam-nodejs-npm-ci --runtime nodejs16.x --app-template hello-world --package-type Zip --no-tracing

作成されたプロジェクト内に移動。

$ cd sam-nodejs-npm-ci

ディレクトリツリー内に移動。

$ tree
.
├── README.md
├── events
│   └── event.json
├── hello-world
│   ├── app.js
│   ├── package.json
│   └── tests
│       └── unit
│           └── test-handler.js
└── template.yaml

4 directories, 6 files

package.jsonを確認すると、dependenciesとdevDependenciesが含まれているので、こちらをこのまま使うことにします。

hello-world/package.json

{
  "name": "hello_world",
  "version": "1.0.0",
  "description": "hello world sample for NodeJS",
  "main": "app.js",
  "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs",
  "author": "SAM CLI",
  "license": "MIT",
  "dependencies": {
    "axios": ">=0.21.1"
  },
  "scripts": {
    "test": "mocha tests/unit/"
  },
  "devDependencies": {
    "chai": "^4.2.0",
    "mocha": "^9.1.4"
  }
}

そのままビルドしてみる

最初はこの状態のままビルドしてみます。

$ sam build

以下のようなアクションが実行されます。

Running NodejsNpmBuilder:NpmPack
Running NodejsNpmBuilder:CopyNpmrcAndLockfile
Running NodejsNpmBuilder:CopySource
Running NodejsNpmBuilder:NpmInstall
Running NodejsNpmBuilder:CleanUp
Clean up action: .aws-sam/deps/ef743125-04fc-4805-a623-5281392d189f does not exist and will be skipped.
Running NodejsNpmBuilder:CopyDependencies
Running NodejsNpmBuilder:CleanUpNpmrc
Running NodejsNpmBuilder:LockfileCleanUp
Running NodejsNpmBuilder:LockfileCleanUp

ここで、.aws-samディレクトリ内を確認してみます。

$ find .aws-sam | grep -v node_modules/
.aws-sam
.aws-sam/build
.aws-sam/build/template.yaml
.aws-sam/build/HelloWorldFunction
.aws-sam/build/HelloWorldFunction/app.js
.aws-sam/build/HelloWorldFunction/package.json
.aws-sam/build/HelloWorldFunction/node_modules
.aws-sam/build.toml
.aws-sam/deps
.aws-sam/deps/ef743125-04fc-4805-a623-5281392d189f
.aws-sam/deps/ef743125-04fc-4805-a623-5281392d189f/node_modules

AWS Lambda関数が配置されているディレクトリ内の一部がコピーされ、npm installが行われたらしき状態であることが確認できます。

ここで、いったん.aws-samディレクトリを削除します。

$ rm -rf .aws-sam

package-lock.jsonを作成してsam buildする

では、今度はpackage-lock.jsonを作成してからsam buildしてみます。

まずは、npm iでモジュールをインストール。

$ cd hello-world
$ npm i
$ cd ..

このようになりました。

$ tree -L 2
.
├── README.md
├── events
│   └── event.json
├── hello-world
│   ├── app.js
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   └── tests
└── template.yaml

4 directories, 6 files

では、sam buildしてみます。

$ sam build

実行されたアクション。

Running NodejsNpmBuilder:NpmPack
Running NodejsNpmBuilder:CopyNpmrcAndLockfile
Running NodejsNpmBuilder:CopySource
Running NodejsNpmBuilder:NpmInstall
Running NodejsNpmBuilder:CleanUp
Clean up action: .aws-sam/deps/8f327018-920e-4514-817b-d48da3872879 does not exist and will be skipped.
Running NodejsNpmBuilder:CopyDependencies
Running NodejsNpmBuilder:CleanUpNpmrc
Running NodejsNpmBuilder:LockfileCleanUp
Running NodejsNpmBuilder:LockfileCleanUp

今度は、package-lock.jsonも.aws-samディレクトリ内に現れます。

$ find .aws-sam | grep -v node_modules/
.aws-sam
.aws-sam/build
.aws-sam/build/template.yaml
.aws-sam/build/HelloWorldFunction
.aws-sam/build/HelloWorldFunction/app.js
.aws-sam/build/HelloWorldFunction/package-lock.json
.aws-sam/build/HelloWorldFunction/package.json
.aws-sam/build/HelloWorldFunction/node_modules
.aws-sam/build.toml
.aws-sam/deps
.aws-sam/deps/8f327018-920e-4514-817b-d48da3872879
.aws-sam/deps/8f327018-920e-4514-817b-d48da3872879/node_modules

最初にAWS SAMのソースコードを確認したように、CopyNpmrcAndLockfileでコピーが行われているようです。

Running NodejsNpmBuilder:CopyNpmrcAndLockfile

ちなみに、参照しているソースコードが正しければnpm installは以下のオプションで実行されているはずです。

            self.subprocess_npm.run(
                ["install", "-q", "--no-audit", "--no-save", mode, "--unsafe-perm"], cwd=self.artifacts_dir
            )

https://github.com/aws/aws-lambda-builders/blob/v1.18.0/aws_lambda_builders/workflows/nodejs_npm/actions.py#L113-L115

1度.aws-samディレクトリを削除して

$ rm -rf .aws-sam

確認してみましょう。

$ strace -f -e trace=execve sam build

straceの出力内容を追っていくと、こんな感じになっていたので大丈夫でしょう。

[pid 29457] execve("$HOME/.nvm/versions/node/v16.16.0/bin/node", ["node", "$HOME/.nvm/versions/nod"..., "install", "-q", "--no-audit", "--no-save", "--production", "--unsafe-perm"], 0x7fff2c4a5c98 /* 73 vars */) = 0

npm ciを使う

最後は、こちらに習ってnpm ciを使うようにしてみましょう。

AWS Serverless Application Model / アプリケーションの構築 / 例

template.yamlのAWS Lambda関数のリソース定義部分はこちらですが、

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: nodejs16.x
      Architectures:
        - x86_64
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get

以下のようにMetadataを追加してUseNpmCiをTrueにします。

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: nodejs16.x
      Architectures:
        - x86_64
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get
    Metadata:
      BuildProperties:
        UseNpmCi: True

この部分ですね。

    Metadata:
      BuildProperties:
        UseNpmCi: True

1度、package-lock.jsonを削除してみましょう。

$ rm hello-world/package-lock.json

sam buildします。

$ sam build

ふつうに動作してしまいました。package-lock.jsonがない場合は、npm installになるようです。

Running NodejsNpmBuilder:NpmPack
Running NodejsNpmBuilder:CopyNpmrcAndLockfile
Running NodejsNpmBuilder:CopySource
Running NodejsNpmBuilder:NpmInstall
Running NodejsNpmBuilder:CleanUp
Clean up action: .aws-sam/deps/4819e4df-04ba-4cd1-8921-702ea890f991 does not exist and will be skipped.
Running NodejsNpmBuilder:CopyDependencies
Running NodejsNpmBuilder:CleanUpNpmrc
Running NodejsNpmBuilder:LockfileCleanUp
Running NodejsNpmBuilder:LockfileCleanUp

.aws-samディレクトリの中身はこちら。

$ find .aws-sam | grep -v node_modules/
.aws-sam
.aws-sam/build
.aws-sam/build/template.yaml
.aws-sam/build/HelloWorldFunction
.aws-sam/build/HelloWorldFunction/app.js
.aws-sam/build/HelloWorldFunction/package.json
.aws-sam/build/HelloWorldFunction/node_modules
.aws-sam/build.toml
.aws-sam/deps
.aws-sam/deps/4819e4df-04ba-4cd1-8921-702ea890f991
.aws-sam/deps/4819e4df-04ba-4cd1-8921-702ea890f991/node_modules

もう1度、package-lock.jsonを作成します。

$ cd hello-world
$ npm i
$ cd ..

1度.aws-samディレクトリを削除。

$ rm -rf .aws-sam

sam build。

$ sam build

すると、今度はNpmCIアクションが使われるようになりました。UseNpmCiが有効であることの表示も出ています。

Building codeuri: /path/to/sam-nodejs-npm-ci/hello-world runtime: nodejs16.x metadata: {'BuildProperties': {'UseNpmCi': True}} architecture: x86_64 functions: HelloWorldFunction
Running NodejsNpmBuilder:NpmPack
Running NodejsNpmBuilder:CopyNpmrcAndLockfile
Running NodejsNpmBuilder:CopySource
Running NodejsNpmBuilder:NpmCI
Running NodejsNpmBuilder:CleanUp
Clean up action: folder .aws-sam/deps/4819e4df-04ba-4cd1-8921-702ea890f991 will be cleaned
Running NodejsNpmBuilder:CopyDependencies
Running NodejsNpmBuilder:CleanUpNpmrc
Running NodejsNpmBuilder:LockfileCleanUp
Running NodejsNpmBuilder:LockfileCleanUp

ポイントは、こちらのようですね。

npm ci を使用するには、アプリケーションは Lambda 関数の CodeUri に package-lock.json または npm-shrinkwrap.json ファイルが必要です。

AWS Serverless Application Model / アプリケーションの構築 / 例

CodeUriは今回はこうなっています。

    Properties:
      CodeUri: hello-world/

.aws-samディレクトリの内容。

$ find .aws-sam | grep -v node_modules/
.aws-sam
.aws-sam/build
.aws-sam/build/template.yaml
.aws-sam/build/HelloWorldFunction
.aws-sam/build/HelloWorldFunction/app.js
.aws-sam/build/HelloWorldFunction/package-lock.json
.aws-sam/build/HelloWorldFunction/package.json
.aws-sam/build/HelloWorldFunction/node_modules
.aws-sam/build.toml
.aws-sam/deps
.aws-sam/deps/4819e4df-04ba-4cd1-8921-702ea890f991
.aws-sam/deps/4819e4df-04ba-4cd1-8921-702ea890f991/node_modules

npm ciが動作していることも確認してみましょう。

https://github.com/aws/aws-lambda-builders/blob/v1.18.0/aws_lambda_builders/workflows/nodejs_npm/actions.py#L158

.aws-samディレクトリを削除。

$ rm -rf .aws-sam

straceを仕込んで確認。

$ strace -f -e trace=execve sam build

OKですね。

[pid 31187] execve("$HOME/.nvm/versions/node/v16.16.0/bin/node", ["node", "$HOME/.nvm/versions/nod"..., "ci"], 0x7ffcadd9b850 /* 73 vars */) = 0

まとめ

AWS SAMでNode.jsを使ったAWS Lambda関数をパッケージングする際に、以前はpackage-lock.jsonが利用されなかったのですがこの状況が
変わっていること、そして設定次第ではnpm ciを利用できることを確認できました。

package-lock.jsonの扱いについては気になっていたことのひとつだったので、解消されていたことに気づけて良かったかなと思います。