Running Puppeteer in Docker の翻訳とメモ

頭の中で翻訳しても忘れる & 記述を一つ一つ追うために、メモとして残す。

NodeイメージのAlpineは今回使用しないので省略する。

 

Githubの当該ページ

github.com

以下、本文翻訳

 

 DockerでPuppeteerを実行する

v3.0.xまでは、DockerコンテナでPuppeteerのテストを実行するのにCirrus Ciを使用した。 参照するには、Dockerfile.linux(v3.0.1)の履歴をみてね。

ヘッドレスChromeを起動・実行するのは難しいことがある。PuppeteerがインストールするバンドルされたChromiumには、必要な共有ライブラリの依存関係がない。

修正するには、不足している依存関係と、最新のChomiumパッケージをあなたのDockerfileにインストールする必要がある。

 

 ↓ 以下、Dockerfileの記述。コメントは訳す。

FROM node:12-slim

# 主要な文字コード(中国語、日本語、アラビア語ヘブライ語タイ語他)をサポートするため、最新のChrome Devパッケージとフォントをインストールしてね。
# 注:これは、PuppeteerがインストールするバンドルされたChromiumのバージョンを動かすため、必要なライブラリをインストールするよ。 RUN apt-get update \ && apt-get install -y wget gnupg \ && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ && apt-get update \ && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \ --no-install-recommends \ && rm -rf /var/lib/apt/lists/*

# Dockerが1.13.0以上のバージョンを実行している場合は、ゾンビプロセスを刈り取るため、docker runコマンドの--init引数を使用してね。もしくは、PID 1として'dumb-init'を持つため、次の行のコメントアウトを外してね。
# ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_x86_64 /usr/local/bin/dumb-init
# RUN chmod +x /usr/local/bin/dumb-init
# ENTRYPOINT ["dumb-init", "--"]
# Puppeteerをインストールするとき、Chromiumのダウンロードをスキップするにはコメントアウトを外してね。もしそれをするなら、下記コマンド(browser.~)でPuppeteerを起動する必要があるよ。
# browser.launch({executablePath: 'google-chrome-stable'})
# ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true

# Puppeteerをインストールして、コンテナで使用できるようにする。 RUN npm i puppeteer \
# --no-sandboxオプションが必要ないようユーザーを追加してね。
# npm installと同じレイヤーで、ユーザー権限が再変更されたファイルが、数百MB以上のスペースを使い尽くないようにする。 && groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \ && mkdir -p /home/pptruser/Downloads \ && chown -R pptruser:pptruser /home/pptruser \ && chown -R pptruser:pptruser /node_modules

# 非特権ユーザーとしてすべてを実行する。 USER pptruser CMD ["google-chrome-stable"

↑Dockerfileここまで

コンテナをビルドする:

docker build -t puppeteer-chrome-linux .

コマンドとしてnode -e "<yourscript.js content as a string>"を渡して、コンテナを実行する(yourscript.jsは任意のファイル):

docker run -i --init --rm --cap-add=SYS_ADMIN \
   --name puppeteer-chrome puppeteer-chrome-linux \
   node -e "`cat yourscript.js`"

https://github.com/ebidel/try-puppeteerに完全な例があり、App Engine Flex (Node)上で実行しているウェブサーバーから、どうやってこのDockerfileを実行するかを示している。

 

翻訳は以上。次はDockerfileの中身を逐一読み下していく。

 

Dockerfileと実行コマンドの内容

FROM node:12-slim

Nodeイメージのバージョン指定。例は12のslim(軽量版)。

 

RUN apt-get update \

        && apt-get install -y wget gnupg \

        && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \

        && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \

        && apt-get update \

        && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \ --no-install-recommends \

        && rm -rf /var/lib/apt/lists/*

インストール可能なパッケージの一覧を更新。

 ↓

wget(指定したURIに存在するファイルをダウンロードするコマンド)と gnupg(暗号化ソフト)をインストール。

-yオプション:インストール中の質問にすべてYes。

 ↓

-qオプション:処理結果の表示を行わない。

-O(の後の - はファイル名)オプション:標準出力(ここでは|(パイプ)以下)に出力。

つまり、https://dl-ssl.google.com/linux/linux_signing_key.pubからファイル(公開鍵)をダウンロードして、パッケージを認証するのに使用するkeyの一覧にそれを追加。

 ↓

シェルで’ ’の中のコマンドを実行。

' 'の中身:"deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main"を表示して、/etc/apt/sources.list.dディレクトリのgoogle.listに追記。

つまり、Chromeをインストール可能パッケージの一覧に追加している。

 ↓

インストール可能なパッケージの一覧を更新。

 ↓

google-chrome-stableとフォント関連パッケージとlibxss1をインストール。--no-install-recommendsオプション:OS推奨パッケージのインストールを抑制。

 ↓

/var/lib/apt/listsディレクトリ以下のディレクトリをすべて削除。

 

長い…。要は、Chrome上でPuppeteerを使ってテストするために、必要な最新のChromeとフォントとXウィンドウ関連パッケージをインストールしている、ということか。

 

# Dockerが1.13.0以上のバージョンを実行している場合は、ゾンビプロセスを刈り取るため、docker runコマンドの--init引数を使用してね。もしくは、PID 1として'dumb-init'を持つため、次の行のコメントアウトを外してね。
# ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_x86_64 /usr/local/bin/dumb-init
# RUN chmod +x /usr/local/bin/dumb-init
# ENTRYPOINT ["dumb-init", "--"]

 使用しているDcokerのバージョンによって対応が違うと。1.13は2017年1月リリースのため、2021年現在であればこのコメントアウトを外す必要はないはず。後ほど出てくるdocker runコマンドで--initを引数に書けばいい。

 

# Puppeteerをインストールするとき、Chromiumのダウンロードをスキップするにはコメントアウト(ENV ~)を外してね。もしそれをするなら、下記コマンド(browser.~)でPuppeteerを起動する必要があるよ。
# browser.launch({executablePath: 'google-chrome-stable'})

# ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true 

ChromiumはChromeの元となっているオープンソースプロジェクト。 正確には分からないが、コメントアウトしてあるということは、わざわざ外す必要はない → Chromiumをダウンロードする必要がある、ということだろう。

 

# Puppeteerをインストールして、コンテナで使用できるようにする。

RUN npm i puppeteer \
# --no-sandboxオプションが必要ないようユーザーを追加してね。
# npm installと同じレイヤーで、ユーザー権限が再変更されたファイルが、数百MB以上のスペースを使い尽くないようにする。

    && groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \

    && mkdir -p /home/pptruser/Downloads \

    && chown -R pptruser:pptruser /home/pptruser \

    && chown -R pptruser:pptruser /node_modules

npmでPuppeteerをインストール(package.jsonが存在するディレクトリで)。

 ↓

システムアカウントとグループ(pptruser)を作成し、新規ユーザー(pptruser)を作成しpptruserグループ(プライマリグループ)に所属させ、audio,videoのセカンダリグループに所属させる。

 ↓

-pオプションついてるので、/home, /home/pptruser, /home/pptruser/Downloadsの各ディレクトリを作成する。

 ↓

/home/pptruserディレクトリ以下の所有権を、ユーザーpptruser:グループpptruserに再帰的(そのディレクトリ以下のディレクトリ以下のディレクトリ以下の…)に変更。

 ↓

/node_modulesディレクトリ以下の所有権を、ユーザーpptruser:グループpptruserに再帰的に変更。

 

Linux系のユーザー権限まわりの理解が不足しているので細かいことが分からないが、Puppeteerインストール後、Puppeteer用にユーザーを作成してファイル所有権を移しているみたい。コメントにあるように、容量を過剰に使わないようにするための処置?

翌日追記:

root権限ではChromeを実行できないらしい。root権限で実行するには、Chromeの実行コマンドに--no-sandboxが必要。なるほど。

 

# 非特権ユーザーとしてすべてを実行する。

USER pptruser

CMD ["google-chrome-stable"

pptruserを実行ユーザーに指定。

Chromeを実行。

 

以上がDockerfileの中身。次のコマンドを実行することにより、コンテナ上でPuppeteerを実行できるようになる。

 

docker build -t puppeteer-chrome-linux .

イメージ名をpuppeteer-chrome-linuxとし、カレントディレクトリ( . )でDockerイメージを作成。

 

docker run -i --init --rm --cap-add=SYS_ADMIN \ --name puppeteer-chrome puppeteer-chrome-linux \ node -e "`cat yourscript.js`"

 ややこしいのでオプションを一つずつ見ていく。

-i:コンテナに操作端末(キーボード)を繋ぐ

--init:上のDockerfileで付けてねって出てたやつ。PID1が/dev/initになる。UNIXプロセスの問題で必要。

--rm:実行後、コンテナを削除。

--cap-add=SYS_ADMIN:コンテナにシステム管理権限を許可。

--name puppeteer-chrome puppeteer-chrome-linux:さきほどbuildしたpuppeteer-chrome-linuxイメージを指定、puppeteer-chromeという名前を付ける。

node -e "`cat yourscript.js`":Node.jsでcat yourscript.jsを実行(yourscript.jsの中身を出力)

となる。docker runでコンテナを稼働させ、Node.jsで任意のコマンド・スクリプトファイルを実行、その後コンテナを削除している。

 

以上、Dockerコマンドの内容を確認した。これらにより、任意のJSファイルをコンテナ上のPuppeteerで実行できる。

 

ということで、適当なsample.jsで動作確認。

 

const pptr = require('puppeteer');

async function showTitle() {
const browser = await pptr.launch();
const page = await browser.newPage();
await page.goto('https://www.google.co.jp/');
console.log(await page.title());
await browser.close()
}

showTitle();

 

これで先ほどのdocker run ~ のファイル名をsample.jsに変更して実行

$ docker run -i --init --rm --cap-add=SYS_ADMIN \
--name puppeteer-chrome puppeteer-chrome-linux \
node -e "`cat sample.js`"

  ↓

Google と表示された!

 

 

イメージのバージョンを上げてみる

今回は公式の記述通りnodeイメージのバージョンを「12」と指定したが、古い気がするので、2021年5月現在の最新版の「16」に上げるとどうなるか。

=> ERROR [3/3] RUN npm i puppeteer && groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruse 2.6s
------
> [3/3] RUN npm i puppeteer && groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser && mkdir -p /home/pptruser/Downloads && chown -R pptruser:pptruser /home/pptruser && chown -R pptruser:pptruser /node_modules:
#6 2.498 npm notice
#6 2.498 npm notice New minor version of npm available! 7.13.0 -> 7.14.0
#6 2.498 npm notice Changelog: <https://github.com/npm/cli/releases/tag/v7.14.0>
#6 2.498 npm notice Run `npm install -g npm@7.14.0` to update!
#6 2.498 npm notice
#6 2.517 npm ERR! Tracker "idealTree" already exists
#6 2.535
#6 2.535 npm ERR! A complete log of this run can be found in:
#6 2.535 npm ERR! /root/.npm/_logs/2021-05-25T05_31_07_057Z-debug.log
------
executor failed running [/bin/sh -c npm i puppeteer && groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser && mkdir -p /home/pptruser/Downloads && chown -R pptruser:pptruser /home/pptruser && chown -R pptruser:pptruser /node_modules]: exit code: 1 

 とエラーが出る。「npm ERR! Tracker "idealTree" already exists」が原因。ググると、ルートディレクトリでnpm installが実行されるのが問題みたい。

かといって、WORKDIRを指定(適当に/appとか)してからdocker run ~ してもスクリプトファイルの応答なし。

 

ここから色々調べたけど解決できなかった。解決方法が分かれば追記したい。

とりあえず、nodeイメージのバージョン指定は14までにしておけば実行はできる。