AWS CDKでDocker Desktopを使わずにLambda関数(aws_lambda_nodejs)をローカルビルドする

2022.11.04

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部 IoT事業部の若槻です。

Docker Desktopは、従業員250人以上もしくは年間売り上げ1000万ドル以上の組織での利用は有料となり、2022/1/31に移行の経過措置期間も終了しました。

弊社内でもDocker Desktopの利用が有用な場面では引き続き活用しつつ、必須では無い場面では置き換えを探るということを行っています。

そこで今回は、その判断に一役買う情報として、AWS CDKでDocker Desktopを使わずにLambda関数(aws_lambda_nodejs)をローカルビルドする方法を紹介します。

既定ではDocker Desktop未起動ならビルド失敗する

次のようにaws-cdk-lib.aws_lambda_nodejs moduleを使用してLambda関数(Node.js)をローカルでビルドしたいとします。

lib/aws-cdk-app-stack.ts

import { aws_lambda_nodejs, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class AwsCdkAppStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    new aws_lambda_nodejs.NodejsFunction(this, 'nodejsFunc', {
      entry: 'src/handler.ts',
    });
  }
}

ここでローカル環境でDocker Desktopのプロセスを起動済みの場合。

CDK Synthを実行すると、Dockerコンテナ上でesbuildによるLambda関数のビルド(TypeScriptのトランスパイルや依存関係のバンドリング)が行われます。

$ cdk synth
[+] Building 0.7s (13/13) FINISHED                                                                                                                      
 => [internal] load build definition from Dockerfile                                                                                               0.0s
 => => transferring dockerfile: 37B                                                                                                                0.0s
 => [internal] load .dockerignore                                                                                                                  0.0s
 => => transferring context: 2B                                                                                                                    0.0s
 => [internal] load metadata for public.ecr.aws/sam/build-nodejs14.x:latest                                                                        0.6s
 => [1/9] FROM public.ecr.aws/sam/build-nodejs14.x@sha256:5864c7c7752eb0bb315aa3ca29cf159dc125a444fdface0215edca698eaaed1f                         0.0s
 => CACHED [2/9] RUN npm install --global yarn@1.22.5                                                                                              0.0s
 => CACHED [3/9] RUN npm install --global pnpm                                                                                                     0.0s
 => CACHED [4/9] RUN npm install --global typescript                                                                                               0.0s
 => CACHED [5/9] RUN npm install --global --unsafe-perm=true esbuild@0                                                                             0.0s
 => CACHED [6/9] RUN mkdir /tmp/npm-cache &&     chmod -R 777 /tmp/npm-cache &&     npm config --global set cache /tmp/npm-cache                   0.0s
 => CACHED [7/9] RUN mkdir /tmp/yarn-cache &&     chmod -R 777 /tmp/yarn-cache &&     yarn config set cache-folder /tmp/yarn-cache                 0.0s
 => CACHED [8/9] RUN npm config --global set update-notifier false                                                                                 0.0s
 => CACHED [9/9] RUN /sbin/useradd -u 1000 user && chmod 711 /                                                                                     0.0s
 => exporting to image                                                                                                                             0.0s
 => => exporting layers                                                                                                                            0.0s
 => => writing image sha256:5c1fba5053f06d7eb1a3d5cfbdb6e1abf73b04fa17b99681aa7a8b626b1147d8                                                       0.0s
 => => naming to docker.io/library/cdk-6eb1d2c4b5ad8cdc0fe72e5b801077911f90d144d926342b98a5f87b245a4b50                                            0.0s
Bundling asset AwsCdkAppStack/nodejsFunc/Code/Stage...
esbuild cannot run locally. Switching to Docker bundling.

よってDocker Desktopが未起動の場合は、次のようなDockerに接続できない旨のエラーとなり、ビルドが失敗してしまいます。

$ cdk synth
Sending build context to Docker daemon  54.27kB
Error response from daemon: dial unix docker.raw.sock: connect: no such file or directory
/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/aws-cdk-lib/core/lib/bundling.js:4
stderr: ${proc.stderr?.toString().trim()}`):new Error(`${prog} exited with status ${proc.status}`);return proc}function isSeLinux(){if(process.platform!="linux")return!1;const prog="selinuxenabled",proc=child_process_1.spawnSync(prog,[],{stdio:["pipe",process.stderr,"inherit"]});return proc.error?!1:proc.status==0}
                                            ^
Error: docker exited with status 1
    at dockerExec (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/aws-cdk-lib/core/lib/bundling.js:4:45)
    at Function.fromBuild (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/aws-cdk-lib/core/lib/bundling.js:1:3494)
    at new Bundling (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.js:1:1897)
    at Function.bundle (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.js:1:2733)
    at new NodejsFunction (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/aws-cdk-lib/aws-lambda-nodejs/lib/function.js:1:1229)
    at new AwsCdkAppStack (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/lib/aws-cdk-app-stack.ts:8:5)
    at Object.<anonymous> (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/bin/aws-cdk-app.ts:7:1)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
    at Module.m._compile (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)

Subprocess exited with error 1

このままだとDocker Desktopの利用をやめた時にローカルビルドできず困ってしまいますね。

Docker Desktopを使わずにビルドする

CDK環境にesbuildがインストール済みであれば、Docker Desktopを使わずにビルド可能です。

インストールします。

npm i -D esbuild

Docker Desktopが未起動となっていることを確認した上で、CDK Synthを実行すると、正常にビルドが実行できました。コンテナ実行のオーバーヘッドがない分ビルド速度はとても速く感じます。

$ cdk synth
Bundling asset AwsCdkAppStack/nodejsFunc/Code/Stage...

  cdk.out/bundling-temp-ba412dee7f9165a1131fa22d754bb093190a09059a0eb55d5e7c840cfdc7a38e/index.js  1.1kb

 Done in 7ms

aws_lambda_nodejsはnode modulesにesbuildがインストールされていることを検出すると、Docker Desktopを使わずにesbuildでビルドする動作となります。

補足

以前まではbundling.forceDockerBundlingは既定でtrueだった

aws_lambda_nodejsbundling.forceDockerBundlingプロパティを使用すると、ビルドのDocker上での実行を強制するかどうかを制御できます。

forceDockerBundling?
Type: boolean (optional, default: false)

Force bundling in a Docker container even if local bundling is possible.

This is useful if your function relies on node modules that should be installed (nodeModules) in a Lambda compatible environment.

現在の既定値はfalse(強制しない)ですが、以前まではtrue(強制する)だったようです。下記エントリは既定値がtrueである前提で書かれたものでした。

なのでbundling.forceDockerBundlingtrueとしてビルドを試みた場合。

    new aws_lambda_nodejs.NodejsFunction(this, 'nodejsFunc', {
      entry: 'src/handler.ts',
      bundling: {
        forceDockerBundling: true,
      },
    });

Docker Desktopが起動していない場合はエラーとなります。

$ cdk synth
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
ERRO[0000] Can't add file /Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/aws-cdk-lib/aws-lambda-nodejs/lib/types.d.ts to tar: io: read/write on closed pipe 
/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/aws-cdk-lib/core/lib/bundling.js:4
stderr: ${proc.stderr?.toString().trim()}`):new Error(`${prog} exited with status ${proc.status}`);return proc}function isSeLinux(){if(process.platform!="linux")return!1;const prog="selinuxenabled",proc=child_process_1.spawnSync(prog,[],{stdio:["pipe",process.stderr,"inherit"]});return proc.error?!1:proc.status==0}
                                            ^
Error: docker exited with status 1
    at dockerExec (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/aws-cdk-lib/core/lib/bundling.js:4:45)
    at Function.fromBuild (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/aws-cdk-lib/core/lib/bundling.js:1:3494)
    at new Bundling (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.js:1:1897)
    at Function.bundle (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.js:1:2733)
    at new NodejsFunction (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/aws-cdk-lib/aws-lambda-nodejs/lib/function.js:1:1229)
    at new AwsCdkAppStack (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/lib/aws-cdk-app-stack.ts:8:5)
    at Object.<anonymous> (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/bin/aws-cdk-app.ts:7:1)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
    at Module.m._compile (/Users/wakatsuki.ryuta/projects/cm-rwakatsuki/aws-cdk-app/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)

Subprocess exited with error 1

もしtrueに設定していて不本意にDocker Desktopによるビルドが行われてしまっている場合は、プロパティの記述を削除してしまいましょう。

おわりに

AWS CDKでDocker Desktopを使わずにLambda関数(Node.js)をローカルビルドする方法を紹介しました。

esbuildさまさまでしたね。

ちなみに今回の内容は2年前に加藤諒さんが下記エントリ内で既に示して下さっていたりします。引き続き勉強をさせてもらっています。

esbuildはDockerコンテナ上で実行されますが、ローカルにesbuildがインストールされている場合はそれを識別してローカルで実行します。しかし、Lambda関数はLinux環境で動くのでプロダクションに乗せる際はデプロイパイプラインを構築し、その環境で直接もしくはDocker上で実行するのが好ましいでしょう。ローカルにesbuildが存在するがコンテナ上でバンドルを行わせたい場合は forceDockerBundling というオプションがあるのでこれを true に設定すれば、文字通りコンテナ上でのバンドルを強制できます。

以上