Playwright を Lambdaで動かす

ちょっと苦労したのでポイントをまとめておく。

前提

  • TypeScriptでPlaywrightを動かすコードを書きたい
  • AWSが提供しているLambda用のAWSベースイメージはFedoraベース?(CentOSベース?)なのでPlaywrightの推奨ではない
    • 自分でコンテナイメージを作成する

準備

独自でコンテナイメージを作る

基本的に以下のサイトのコンテナの項を参考に作成しました。

LambdaでPlaywrightを動かす(Lambdaレイヤー / コンテナ)

FROM node:20

ARG CHORME_VERSION=131.0.6778.86

# Chromeの依存ライブラリインストール
# https://pptr.dev/troubleshooting#chrome-doesnt-launch-on-linux
RUN apt-get clean && \
    apt-get update && \
    apt-get install -y ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils && \
    apt-get install -y g++ make cmake unzip libcurl4-openssl-dev

# Chrome for Testingインストール
RUN npx -y @puppeteer/browsers install chrome@${CHORME_VERSION} --platform linux && \
    mv chrome/linux-${CHORME_VERSION}/chrome-linux64 /browser && \
    rm -r chrome

WORKDIR /function
ARG basedir="/tmp"

RUN npm install aws-lambda-ric
RUN npm install tsx @types/node typescript
RUN npm install -D @types/aws-lambda
RUN npm install playwright@latest
RUN npx playwright install

COPY /app/index.ts /function/index.ts

RUN npx tsc index.ts

ENTRYPOINT ["/usr/local/bin/npx", "aws-lambda-ric"]
CMD ["index.handler"]

動作させる index.ts は以下のような形にしています。 これを `npx tsc index.ts" で index.jsに変換して動作させます。

// main
export const handler:Handler = async (event: APIGatewayEvent, context:Context):Promise<APIGatewayProxyResult> => {
  const body = JSON.parse(event.body ?? "{}");

  //setup
  const args = [
    '--single-process',
    '--window-size=1920,1080',
    '--use-angle=swiftshader',
    '--disable-setuid-sandbox',
    '--no-sandbox',
    '--no-zygote',
    '--disable-dev-shm-usage',
    '--disable-gpu',
    '--disable-extensions',
    '--incognito',
    '--enable-automation',
  ];

  const browser = await chromium.launch({
    args:args,
    headless: true,
    executablePath: "/browser/chrome"
  });
  try {
    const ctx = await browser.newContext();
    const page = await ctx.newPage();

    // 何らかの処理
    await anyFunction(page);

    await page.close(); // ページを閉じる
    await browser.close(); // ブラウザを閉じる

    return {
        statusCode: 200,
        body: JSON.stringify({
            message: 'OK',
        }),
    };
  } catch(err) {
    console.log(err);
    return {
        statusCode: 500,
        body: JSON.stringify({
            message: 'some error happened',
        }),
    };
  }
};

chromiumのオプションについて

(chatgptに教えてもらいました)

  1. –no-sandbox と –disable-setuid-sandbox コンテナ環境では、サンドボックスが不要または非対応のため、これを無効化します。

  2. –disable-dev-shm-usage コンテナの共有メモリサイズ(/dev/shm)がデフォルトでは小さいため、このオプションで問題を回避します。特に、大きなDOMやグラフィカルな処理で必要です。

  3. –disable-gpu ヘッドレス環境ではGPUが不要なため無効化します。

  4. –no-zygote Zygoteプロセスを無効化することで、プロセスオーバーヘッドを軽減します。

  5. –single-process マルチプロセスを無効化し、単一プロセスで動作させます(メモリ効率の向上)。

  6. –lang=ja-JP 言語を日本語に設定します。日本語Webサイトの適切な表示を確保するためです。

  7. –disable-extensions 拡張機能を無効化し、不要なリソース使用を防ぎます。

  8. –incognito プライバシー保護のため、シークレットモードで起動します。

  9. –enable-automation ブラウザが自動化モードで動作していることを明示し、安定性を向上させます。

ただし –no-sandbox はセキュリティを低下させる可能性があるため、必要最小限の環境で使用してください。

ポイント

page.waitForTimeoutは3秒以上あったほうがいい

Lambdaは1~2秒程度だとアクセス先のページをレンダリングしきれない場合がある(間に合わない) 多少時間がかかってもいいなら 5秒程度がおすすめ。

何かのファイルを出力したい場合

Lambdaのコンテナイメージは /tmp 以外全部リードオンリーの場所でマウントされる。よって何か処理の途中で一時的にファイルを吐き出したい場合は /tmp に書き込む必要がある。

LambdaのRAM量

1024MB程度は必要と思われる(chromium動作のため)