Puppeteer をDockerコンテナで利用する


Webサイトのスクリーンショットを取るCLIツール[`site-checker`](https://www.npmjs.com/package/site-checker)を作った。

インストール方法を書いていて、「Node.jsのvX.X.X以上をインストールして下さい」というのも今日的ではないなぁと感じて、実行環境のバージョンを気にせず利用できるようDockerイメージも用意した。その際、ちょっと引っかかったことを書く。

## 利用バージョン
- [Puppeteer](https://github.com/GoogleChrome/puppeteer) v0.10.2
- Node.js v8.4.0 の[公式Dockerイメージ](https://hub.docker.com/_/node/)

## Dockerfileの作成

最終的に作成したDockerfileは次の通り。

```Dockerfile
FROM node:8.4
MAINTAINER HeRoMo

RUN echo 'deb http://ftp.jp.debian.org/debian jessie-backports main' >> /etc/apt/sources.list
RUN set -ex; \
        apt-get update; \
        apt-get install -y --no-install-recommends \
                gconf-service \
                libasound2 \
                libatk1.0-0 \
                libc6 \
                libcairo2 \
                libcups2 \
                libdbus-1-3 \
                libexpat1 \
                libfontconfig1 \
                libgcc1 \
                libgconf-2-4 \
                libgdk-pixbuf2.0-0 \
                libglib2.0-0 \
                libgtk-3-0 \
                libnspr4 \
                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 \
                ca-certificates \
                fonts-liberation \
                libappindicator1 \
                libnss3 \
                lsb-release \
                xdg-utils \
                wget \
                fonts-noto-cjk

RUN yarn global add site-checker

RUN mkdir -p /output
WORKDIR /output
ENV NO_SANDBOX=true
ENTRYPOINT [ "site-checker" ]
CMD [ "-h" ]
```

この内容になる前に次の3点の問題が発生した。

- Puppeteerのインストールでエラーが発生
- Chromeが起動できない
- 日本語の文字が化ける

これらについて以下に述べる。

## Puppeteerのインストールでエラーが発生
Puppeteerリポジトリの[troubleshooting\.md](https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md)に依存パッケージの一覧があるのでそれらを`apt-get`でインストールする。そして、`RUN npm install -g site-checker` すれば良いと思ったのだが、ここで次のようなエラーが発生した。

```
> puppeteer@0.10.2 install /usr/local/lib/node_modules/site-checker/node_modules/puppeteer
> node install.js

fs.js:891
  return binding.mkdir(pathModule._makeLong(path),
                 ^

Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/site-checker/node_modules/puppeteer/.local-chromium'
    at Object.fs.mkdirSync (fs.js:891:18)
    at Object.downloadRevision (/usr/local/lib/node_modules/site-checker/node_modules/puppeteer/utils/ChromiumDownloader.js:97:10)
    at Object.<anonymous> (/usr/local/lib/node_modules/site-checker/node_modules/puppeteer/install.js:33:12)
    at Module._compile (module.js:573:30)

    at Object.Module._extensions..js (module.js:584:10)
    at Module.load (module.js:507:32)
    at tryModuleLoad (module.js:470:12)
    at Function.Module._load (module.js:462:3)
    at Function.Module.runMain (module.js:609:10)
    at startup (bootstrap_node.js:158:16)
npm info lifecycle puppeteer@0.10.2~install: Failed to exec install script
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! puppeteer@0.10.2 install: `node install.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the puppeteer@0.10.2 install script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
```

`npm install`がダメなら、`RUN yarn global add site-checker` にしてみようと試すと、インストールに成功した。

## Chromeが起動できない
イメージのビルドには成功して、次のように実行した。

```
docker run --rm -v $(pwd):/output:rw site-checker -u http://qiita.com/
```

すると次のようなエラーが発生した。

```
Error: Failed to launch chrome!
[0907/150437.316459:ERROR:zygote_host_impl_linux.cc(88)] Running as root without --no-sandbox is not supported. See https://crbug.com/638180.



TROUBLESHOOTING: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md

    at Interface.onClose (/usr/local/share/.config/yarn/global/node_modules/puppeteer/lib/Launcher.js:142:14)
    at emitNone (events.js:110:20)
    at Interface.emit (events.js:207:7)
    at Interface.close (readline.js:367:8)
    at Socket.onend (readline.js:147:10)
    at emitNone (events.js:110:20)
    at Socket.emit (events.js:207:7)
    at endReadableNT (_stream_readable.js:1059:12)
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
    at process._tickCallback (internal/process/next_tick.js:180:9)
```

「Running as root without --no-sandbox is not supported.」と書いてあるので、rootでないユーザで実行してみたが、それでもエラーが発生する。

[troubleshooting.md](https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md)を見よと示されているので参照すると、「try running without the sandbox」と書いているので、従うことにする。
Chromeを起動するときに次のように引数を与えれば良いらしい。

```
const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
```

[troubleshooting.md](https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md) には`--no-sandbox`での実行はセキュリティ的に推奨しないと書かれているが、Dockerコンテナの中なので割り切ることとした。

環境変数`NO_SANDBOX`で切り替えられるようにツールの実装を変更して対応した。

## 日本語の文字が化ける

なんとか動くようになったものの、日本語のサイトのスクリーンキャプチャを取ると次のように日本語が表示できなかった。

![capture_00000s.png](https://qiita-image-store.s3.amazonaws.com/0/32533/123e6061-77b7-b1d7-9c48-f432db3dae3f.png "capture_00000s.png")

日本語を表示できるフォントがイメージにインストールされてないからなのねと思い至り、[Google Noto Fonts](https://www.google.com/get/noto/)のインストールを追加した。

Nodeの公式イメージのOSであるdebian jessieのパッケージリポジトリにはないため、次のようにリポジトリを追加して`apt-get`でインストールした。

```
RUN echo 'deb http://ftp.jp.debian.org/debian jessie-backports main' >> /etc/apt/sources.list
```

すると次のように見事表示されるようになった。

![capture_00000s.png](https://qiita-image-store.s3.amazonaws.com/0/32533/869b0bb5-7f6b-3ca6-b062-9da433cc2f97.png "capture_00000s.png")