コンテナ形式LambdaでPuppeteer v19を使ってハマった話

# はじめに

AWS LambdaでPuppeteerを使いたいという時に、デプロイサイズの都合でコンテナ形式を採用するというということはよくあると思います。
自分は今まで特にハマった記憶はないのですが、今回初めてPuppeteer v19を使った環境を作成した際に、少々ハマったところがあったので、共有したいと思います。

# ハマった内容
まずハマったソースファイルとDockerfileの抜粋はこちら。
余計なものはあるかも知れないですけど、やりたいこととしてはyahooのサイトのタイトルをログに出すだけのコードです。

```ts:index.ts
import puppeteer from 'puppeteer';

export const handler = async () => {
  // ↓の引数は昔から使っていたものなので、不要なものがあったりするかも…
  const browser = await puppeteer.launch({
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '-–disable-dev-shm-usage',
      '--disable-gpu',
      '--no-first-run',
      '--no-zygote',
      '--single-process',
    ],
    headless: true,
  });
  const page = await browser.newPage();
  await page.goto('https://www.yahoo.co.jp/');
  const title = await page.title();

  console.log(title);
};
```

```docker:Dockerfile
FROM public.ecr.aws/lambda/nodejs:18 AS builder
WORKDIR /usr/app
COPY package*.json tsconfig.json index.ts  ./
RUN npm ci && npm run build

FROM public.ecr.aws/lambda/nodejs:18
RUN yum -y install libX11 libXcomposite libXcursor libXdamage libXext libXi libXtst cups-libs libXScrnSaver libXrandr alsa-lib pango atk at-spi2-atk gtk3 google-noto-sans-japanese-fonts
WORKDIR ${LAMBDA_TASK_ROOT}
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=builder /usr/app/dist/* ./
CMD ["index.handler"]
```

これをビルドした後、https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/images-test.html に従ってローカルでの動作確認。

```
docker run -p 9000:8080  puppeteer_sample:latest
```

```
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'
```

すると、いろいろログが出る中で以下の行が出ているので期待通りに動作していると認識。
```
2023-04-29T10:32:54.491Z        6aaf34cf-02e6-40a6-9e75-69fbe5b491a5    INFO    Yahoo! JAPAN
```

ですが、これを実際にECRにプッシュした上で、Lambdaで動かしてみると、以下メッセージが出て動かない。ローカルでは動いたのになぜ?

```json
{
  "errorType": "Error",
  "errorMessage": "Could not find Chromium (rev. 1108766). This can occur if either\n 1. you did not perform an installation before running the script (e.g. `npm install`) or\n 2. your cache path is incorrectly configured (which is: /home/sbx_user1051/.cache/puppeteer).\nFor (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.",
  "trace": [
    "Error: Could not find Chromium (rev. 1108766). This can occur if either",
    " 1. you did not perform an installation before running the script (e.g. `npm install`) or",
    " 2. your cache path is incorrectly configured (which is: /home/sbx_user1051/.cache/puppeteer).",
    "For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.",
    "    at ChromeLauncher.resolveExecutablePath (/var/task/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ProductLauncher.js:289:27)",
    "    at ChromeLauncher.executablePath (/var/task/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js:182:25)",
    "    at ChromeLauncher.computeLaunchArguments (/var/task/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js:99:37)",
    "    at async ChromeLauncher.launch (/var/task/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ProductLauncher.js:83:28)",
    "    at async Runtime.handler (/var/task/index.js:10:21)"
  ]
}
```

# 原因

Lambda上で出ているメッセージに従って、https://pptr.dev/guides/configuration を見てみると、
> Starting in v19.0.0, Puppeteer stores browsers in ~/.cache/puppeteer to globally cache browsers between installation.

という記載がある通りv19からブラウザーが`~/.cache/puppeteer`にキャッシュされるようになったとのこと。

ですけど、ログをみるとわかるようにこのLambda関数では「sbx_user1051」というユーザーで動作していて、そのホームディレクトリを見に行っている模様。

ということで、ビルド時のユーザーと実行時のユーザーが異なるため、ブラウザが見つからないという状況のようです。

# 対応

これも https://pptr.dev/guides/configuration に書かれている通りで、`.puppeteerrc.cjs`にてキャッシュディレクトリを明確に指定してnpm ci、実行時も`.puppeteerrc.cjs`を参照できるようにWORKDIRにコピーしておく、という対応になります。

```js:.puppeteerrc.cjs
const {join} = require('path');

/**
 * @type {import("puppeteer").Configuration}
 */
module.exports = {
  // Changes the cache location for Puppeteer.
  cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
};
```
```docker:Dockerfile
FROM public.ecr.aws/lambda/nodejs:18 AS builder
WORKDIR /usr/app
COPY package*.json tsconfig.json index.ts  ./
RUN npm ci && npm run build

FROM public.ecr.aws/lambda/nodejs:18
RUN yum -y install libX11 libXcomposite libXcursor libXdamage libXext libXi libXtst cups-libs libXScrnSaver libXrandr alsa-lib pango atk at-spi2-atk gtk3 google-noto-sans-japanese-fonts
WORKDIR ${LAMBDA_TASK_ROOT}
COPY package*.json .puppeteerrc.cjs ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=builder /usr/app/dist/* ./
CMD ["index.handler"]
```

これで作ったコンテナイメージをLambda上で動かすと
```
2023-04-29T11:12:47.144Z	15156fcc-8572-4184-a1c7-997e15220ae3	INFO	Yahoo! JAPAN
```
が出力されており期待通りに動作しました。