🐳

Apple Silicon(M2)上のDockerでPuppeteer(Chromium)をHeadlessモードで動かす

2024/02/27に公開

結構地雷が多いのでその点を踏まえて簡単に解説しようと思います。

この記事でやること

  • M2(Apple silicon)上で動作するDevContainer内にPuppeteer+chromiumの動作環境の構築方法と要所の解説を行います。
  • 極力無駄なパッケージやコードは書かないようにしています。カスタマイズは皆様のお好きなように行なってください。
  • DevContainerは公式が提供するDebian(bullseye)をベースに構築します。
  • ローカル環境にはDockerとVS Code(DevContainer)のみインストールし、NodeJSやChromium等のランタイムはDevContainer内にて構築します。

前提条件

今回検証に使用した環境は以下のとおりです。

DevContainerの作成

DevContainerを使用することで、プロジェクトごとに独立した開発環境を構築できます。これにより、チームメンバー間での環境の違いによる問題を排除し、新しいメンバーがプロジェクトに参加する際の環境構築を迅速に行うことが可能になります。Dockerは、アプリケーションの依存関係をコンテナとしてパッケージ化し、どの環境でも一貫した実行を保証します。

今回はDevContainerが公式で出しているDebianのImageを使って環境を構築していきます。

コマンドパレットからDevContainers: New Dev Container...を選択

コマンドパレットに> devcontainers new dev containerと入力し、一番上に出てきたDevContainers: New Dev Container...を選択してください。

ImageからDebianを検索し選択

バージョン選択が出たらbullseyeを選択してください。(デフォルトがbullseyeなので基本的にはそのまま進めてOKです。)

Select FeaturesからNode.js(via nvm), yarn and pnpmを選択

Node.jsの実行環境がDevContainer上ですぐに使えるようになります。(ローカルが汚れない!最高!)

必要なパッケージ類のインストール

sudo apt update
sudo apt upgrade -y
sudo apt install -y fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 libgtk2.0-0 libnss3 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libgbm1 libasound2
sudo apt install -y chromium

# Puppeteer用の環境変数を設定
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium # インストールしたChromiumのPathをPuppeteerに渡す
export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true # Puppeteerが勝手にChromiumを入れるのを防ぐ

実装例

package.json
{
    "name": "debian",
    "version": "1.0.0",
    "main": "index.js",
    "license": "MIT",
    "dependencies": {
        "puppeteer": "^22.2.0"
    },
    "scripts": {
        "start": "node index.js"
    }
}
index.json
const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch({ headless: true });
    const page = await browser.newPage();
    await page.goto('https://developer.chrome.com/');

    // タイトルを取得し出力する
    console.log(await page.title()) // Chrome for Developers

    await browser.close();
})();

実行

# 依存関係のインストール
yarn install

# 実行
yarn start
実行結果
vscode ➜ /workspaces/debian-2 $ yarn start
yarn run v1.22.19
$ node index.js
Chrome for Developers
Done in 1.66s.

注意点

Puppeteer用の環境変数を設定しないと、変なChromiumが勝手に入ってくる

必要なパッケージのインストールで説明した以下の環境変数が非常に重要である。

# Puppeteer用の環境変数を設定
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium # インストールしたChromiumのPathをPuppeteerに渡す
export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true # Puppeteerが勝手にChromiumを入れるのを防ぐ

まず、PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromiumで事前にインストールしたchromiumのパスをPuppeteerに伝達します。これだけだとpuppeteerをインストールしたときに自前でchromiumをインストールしてくるため、PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=trueでインストールしないようにします。
上記の環境変数が正しく設定できていないと、以下のようなエラーが出ます。

エラーその1
vscode ➜ /workspaces/debian $ yarn start
yarn run v1.22.21
$ node index.js
/workspaces/debian/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ProductLauncher.js:285
                    throw new Error(`Could not find Chrome (ver. ${this.puppeteer.browserRevision}). This can occur if either\n` +
                          ^

Error: Could not find Chrome (ver. 122.0.6261.69). This can occur if either
 1. you did not perform an installation before running the script (e.g. `npx puppeteer browsers install chrome`) or
 2. your cache path is incorrectly configured (which is: /home/vscode/.cache/puppeteer).
For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.
    at ChromeLauncher.resolveExecutablePath (/workspaces/debian/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ProductLauncher.js:285:27)
    at ChromeLauncher.executablePath (/workspaces/debian/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js:196:25)
    at ChromeLauncher.computeLaunchArguments (/workspaces/debian/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js:89:37)
    at async ChromeLauncher.launch (/workspaces/debian/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ProductLauncher.js:70:28)
    at async /workspaces/debian/index.js:5:21
エラーその2
vscode ➜ /workspaces/debian $ yarn start
yarn run v1.22.21
$ node index.js
/workspaces/debian/node_modules/@puppeteer/browsers/lib/cjs/launch.js:267
                reject(new Error([
                       ^

Error: Failed to launch the browser process!
rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2
 


TROUBLESHOOTING: https://pptr.dev/troubleshooting

    at Interface.onClose (/workspaces/debian/node_modules/@puppeteer/browsers/lib/cjs/launch.js:267:24)
    at Interface.emit (node:events:530:35)
    at Interface.close (node:internal/readline/interface:527:10)
    at Socket.onend (node:internal/readline/interface:253:10)
    at Socket.emit (node:events:530:35)
    at endReadableNT (node:internal/streams/readable:1696:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

さいごに

試行錯誤の途中で

Error: Failed to launch the browser process!
rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2

みたいなアーキテクチャの互換性起因と思われるエラーが連発したので、arm64でも動かせるようにしてました。(スクラップにて雑に公開してます
今思えば環境変数の設定だけで終わる簡単な話でしたが、各所に地雷があってキツかったです。

WSL2でUbuntuに環境構築していた名残で、M2 MacでもUbuntuベースで構築しようと思っていたが、apt installでchromiumを入れようと思っても存在しないという謎現象。
snap経由でインストールできるらしいが、snapdが一生立ち上がらずに挫折。
知り合いに相談したらDebianだったらいけるかもとの事で、実際に試したらすんなり動いた。

GitHubのIssueやStackoverflowをなん度も周回したが、情報が古かったり纏まっていなかったりで解決にすごく時間がかかってしまった。

どこかの誰かがこの記事で少しでも救われますように。

Discussion