Dockerを使ってHeadless Chromeを動かしてみる

Chrome 59からChromeをヘッドレス環境で実行する[Headless Chrome](https://developers.google.com/web/updates/2017/04/headless-chrome?hl=ja)が搭載されました。
自動テスト等でこの機能を使えると便利そうな気がしたので、DockerでHeadless Chrome
が使える環境を整えてみます。

## Docker Imageの作成
alpine linuxにはgoogle chromeのパッケージはありません。chromeの元となるオープンソース実装のchromiumのpackageを検索してみると、最新版のchromiumが[59.0.3071.86-r0](https://pkgs.alpinelinux.org/package/edge/community/x86_64/chromium)と
なっています。
これを利用すればHeadlessで動かすことが出来そうなので、今回はこのパッケージを
利用してみました。Branchがedgeとなっていますので、alpine linuxのイメージを
edgeのものを使用しました。

```:Dockerfile
From alpine:edge

RUN  apk add --update chromium

ENTRYPOINT tail -f /dev/null
```
まずはchromiumをインストールしたImageを作成します。

```
$ docker build -t chromium_headless ./
Sending build context to Docker daemon 2.048 kB
Step 1/3 : FROM alpine:edge
 ---> 1673780955f9
Step 2/3 : RUN apk add --update chromium
 ---> Running in de86344e5110
fetch http://dl-cdn.alpinelinux.org/alpine/edge/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/edge/community/x86_64/APKINDEX.tar.gz
(1/78) Installing libxau (1.0.8-r1)
(2/78) Installing libbsd (0.8.3-r3)
...
(78/78) Installing chromium (59.0.3071.86-r0)
Executing busybox-1.26.2-r4.trigger
Executing glib-2.52.1-r0.trigger
Executing shared-mime-info-1.8-r0.trigger
Executing gdk-pixbuf-2.36.6-r0.trigger
Executing gtk-update-icon-cache-2.24.31-r0.trigger
OK: 260 MiB in 89 packages
 ---> 2e0a5a6babb5
Removing intermediate container 92491faa6417
Step 3/3 : ENTRYPOINT tail -f /dev/null
 ---> Running in 87ea1b256a97
 ---> 2772165613b3
Removing intermediate container 87ea1b256a97
Successfully built 2772165613b3
```

## 動作確認
イメージの作成が完了したので、コンテナを起動して、chromiumの動作を確認します。

```
$ docker run -d chromium_headless
b839da3b3b34964390a50c0747df475204977979e3d5192ccc98ec7d3643d66d
$ docker exec -it b83 ash
/ # chromium-browser --version
Chromium 59.0.3071.86
/ # chromium-browser --headless --disable-gpu --dump-dom --no-sandbox https://www.chromestatus.com/
[0613/121607.247566:WARNING:dns_config_service_posix.cc(326)] Failed to read DnsConfig.
[0613/121607.257087:FATAL:udev_loader.cc(38)] Check failed: false.
Received signal 6
  r8: 0000000000000061  r9: 00007fcf9c22a066 r10: 0000000000000008 r11: 0000000000000246
 r12: 00007fcf9c229ed0 r13: 00007fcf9c229e80 r14: 0000000000000000 r15: 0000000000000000
  di: 0000000000000002  si: 00007fcf9c229da0  bp: 00007fcf9c229da0  bx: 0000000000000006
  dx: 0000000000000000  ax: 0000000000000000  cx: 00007fcfb09e86bf  sp: 00007fcf9c229d88
  ip: 00007fcfb09e86bf efl: 0000000000000246 cgf: 002b000000000033 erf: 0000000000000000
 trp: 0000000000000000 msk: 0000000000000000 cr2: 0000000000000000
[end of stack trace]
Calling _exit(1). Core file will not be generated.
```
chromiumのバージョンまでは表示することが出来て、無事にインストールされていることが確認出来ましたが、
Headlessで起動すると上記のようなエラーが出てしまいました。このエラーを確認する限り[udev](https://pkgs.alpinelinux.org/package/v3.5/main/x86_64/udev)のパッケージの必要なようです。
最終的に[ttf-freefont](https://pkgs.alpinelinux.org/package/v3.6/main/x86_64/ttf-freefont)も必要で、以下のようなDockerfileになりました。

```:Dockerfile
From alpine:edge

RUN apk add --update \
        udev \
        ttf-freefont \
        chromium

ENTRYPOINT tail -f /dev/null
```
イメージをビルドして、もう動作を確認すると以下のような結果になり、Headlessで動くことが確認できました。

```
/ # chromium-browser --headless --disable-gpu --dump-dom --no-sandbox https://www.chromestatus.com/
[0613/124631.067381:WARNING:dns_config_service_posix.cc(326)] Failed to read DnsConfig.
<body class="loading">

<!--<div id="site-banner">
  <a href="https://www.youtube.com/watch?v=Rd0plknSPYU" target="_blank">
  <iron-icon icon="chromestatus:ondemand-video"></iron-icon> How we built it</a>
</div>-->
...
// End Google Analytics

lazyLoadWCPolyfillsIfNecessary();
</script>


</body>
```
pdfの出力とスクリーンショットの取得も試してみましたが、正常に動作していることが確認出来ました。

```
/ # mkdir outputs && cd outputs
/outputs # chromium-browser --headless --no-sandbox --disable-gpu --print-to-pdf https://www.chromestatus.com
[0613/125229.869694:WARNING:dns_config_service_posix.cc(326)] Failed to read DnsConfig.
[0613/125231.380375:INFO:headless_shell.cc(436)] Written to file output.pdf.
/outputs # chromium-browser --headless --no-sandbox --disable-gpu --screenshot https://www.chromestatus.com
[0613/125243.355846:WARNING:dns_config_service_posix.cc(326)] Failed to read DnsConfig.
[0613/125244.919023:INFO:headless_shell.cc(436)] Written to file screenshot.png.
/outputs # ls
output.pdf      screenshot.png
```

## 日本語フォントの追加
前段までの手順でスクリーンショットを取ることは出来ましたが、このままですと
日本語が表示されているサイトのスクリーンショットをとっても日本語の部分が
文字化けした状態でスクリーンショットになってしまいます。
[Google Noto Fonts](https://www.google.com/get/noto/)を入れることで日本語を含むサイトでも、文字化けせずに
スクリーンショットが取れるようになります。

```:Dockerfile
From alpine:edge

RUN apk add --update \
            udev \
            ttf-freefont \
            chromium

RUN mkdir /noto

ADD https://noto-website.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip /noto 

WORKDIR /noto

RUN unzip NotoSansCJKjp-hinted.zip && \
    mkdir -p /usr/share/fonts/noto && \
    cp *.otf /usr/share/fonts/noto && \
    chmod 644 -R /usr/share/fonts/noto/ && \
    fc-cache -fv

WORKDIR /
RUN rm -rf /noto

ENTRYPOINT tail -f /dev/null
```

Google Noto Fontsを入れたDockerfileは上記のようになります。これで、日本語を含む
サイトでも文字化けせずにスクリーンショットが取れるようになりました。