Lambda上でnpm installできるLayerを作った

golangawsnode.js

Lambda上でnpm installするためにnpmとnode, npmrc入りのLambda Layerを作った。 GitHubにある。

Lambda Layerでバイナリやライブラリを切り出す - sambaiz-net

まずは/usr/bin/npmをそのまま入れて実行してみた。

FROM lambci/lambda-base:build

WORKDIR /opt

RUN curl -sL https://rpm.nodesource.com/setup_12.x | bash - && \
    yum install -y nodejs && \
    mkdir bin && \
    cp /usr/bin/node bin/node && \
    cp /usr/bin/npm bin/ && \
    zip -yr /tmp/npm-layer.zip ./*
$ docker build -t npmbin .
$ docker run npmbin cat /tmp/npm-layer.zip > npm-layer.zip && unzip npm-layer.zip -d layer

相対パスでの参照に失敗したようだが対象のパスが見当たらない。

internal/modules/cjs/loader.js:628
    throw err;
    ^

Error: Cannot find module '../lib/utils/unsupported.js'
Require stack:
- /opt/bin/npm
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:625:15)
    at Function.Module._load (internal/modules/cjs/loader.js:527:27)
    at Module.require (internal/modules/cjs/loader.js:683:19)
    at require (internal/modules/cjs/helpers.js:16:16)
    at /opt/bin/npm:19:21
    at Object.<anonymous> (/opt/bin/npm:152:3)
    at Module._compile (internal/modules/cjs/loader.js:776:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10)
    at Module.load (internal/modules/cjs/loader.js:643:32)
    at Function.Module._load (internal/modules/cjs/loader.js:556:12) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/opt/bin/npm' ]
}
 exit status 1

というのもnpmはシンボリックリンクで、その参照先に対象のパスがあった。

$ ls -l /usr/bin/npm 
lrwxrwxrwx 1 root root 38 Jul 23 09:34 /usr/bin/npm -> ../lib/node_modules/npm/bin/npm-cli.js

これごとLayerに含めることにした。

FROM lambci/lambda-base:build

WORKDIR /opt

RUN curl -sL https://rpm.nodesource.com/setup_12.x | bash - && \
    yum install -y nodejs && \
    mkdir bin nodejs && \
    cp /usr/bin/node bin/node && \
    cp -r /usr/lib/node_modules ./nodejs/node_modules && \
    ln -s ../nodejs/node_modules/npm/bin/npm-cli.js ./bin/npm && \
    zip -yr /tmp/npm-layer.zip ./*

すると次のエラーに変わった。Lambdaは/tmpにしかwriteできないのに$HOMEに書こうとしているようだ。

Unhandled rejection Error: EACCES: permission denied, mkdir '/home/sbx_user1051'
npm ERR! cb() never called!

npm ERR! This is an error with npm itself. Please report this error at:
npm ERR!     <https://npm.community>
exit status 1

npm configのデフォルト値をみてみたところ、いくつか$HOMEを参照している。

$ npm config ls -l | grep $HOME
cache = "/root/.npm"
init-module = "/root/.npm-init.js"
userconfig = "/root/.npmrc"

そこでこれらの値を書き換えたnpmrcを含めた。

ENV NPM_CONFIG_USERCONFIG /opt/nodejs/.npmrc

RUN ...
    npm config set cache /tmp/.npm && \
    npm config set init-module /tmp/.npm-init.js && \ 
    npm config set update-notifier false && \
    chmod a+r $NPM_CONFIG_USERCONFIG && \
    ln -s ../nodejs/node_modules/npm/bin/npm-cli.js ./bin/npm && \
    zip -yr /tmp/npm-layer.zip ./*
func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	if err := os.Setenv("NPM_CONFIG_USERCONFIG", "/opt/nodejs/.npmrc"); err != nil {
		return events.APIGatewayProxyResponse{
			Body:       err.Error(),
			StatusCode: 500,
		}, nil
	}
	...
}

これでlocalでうまくいくことが確認できたのでデプロイしてみたところ、 Error: Cannot find module '../lib/utils/unsupported.js' が再発してしまった。 原因はシンボリックリンクがコピーになっているためで、PRが上がっている。

out, err = exec.Command("ls", "-l", "/opt/bin/npm").CombinedOutput()
// => ls: -rwxr-xr-x 1 root root 4566 Jul 24 2019 /opt/bin/npm

代わりにshell scriptを置くことにした。

FROM lambci/lambda-base:build

WORKDIR /opt

ENV NPM_CONFIG_USERCONFIG /opt/nodejs/.npmrc

RUN curl -sL https://rpm.nodesource.com/setup_12.x | bash - && \
    yum install -y nodejs && \
    mkdir bin nodejs && \
    cp /usr/bin/node bin/node && \
    cp -r /usr/lib/node_modules ./nodejs/node_modules && \
    npm config set cache /tmp/.npm && \
    npm config set init-module /tmp/.npm-init.js && \ 
    npm config set update-notifier false && \
    chmod a+r $NPM_CONFIG_USERCONFIG && \
    echo -e "#!/bin/sh\n/opt/nodejs/node_modules/npm/bin/npm-cli.js \$@" > ./bin/npm && \
    chmod a+x ./bin/npm && \
    zip -yr /tmp/npm-layer.zip ./*

あとはタイムアウト対策のためMemorySizeを1024に増やしてようやく返ってくるようになった。

npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN [email protected] No description
npm WARN [email protected] No repository field.

+ [email protected]
added 50 packages from 37 contributors and audited 126 packages in 3.455s
found 0 vulnerabilities