savanna blog

savanna.io に関するお知らせなどを書いていきます

M1 mac上のDockerコンテナ内でChromiumを動かそうとしてやったこと&やろうとしてること

savanna.ioの開発をお手伝いしている masa-iwasaki です。現在進行中の案件で調査が甘いところもあるのですが、表題の件について同じく行き詰まってる or 今後行き詰まる人が居るかもしれないので自分が試行錯誤した結果を共有します。ちなみにsavanna公式ブログでは初の開発エントリのようです。

tl;dr

背景

savannaではRailsを利用して作られていて、標準の開発環境がmacOSとなっており、開発環境はDocker化されています。ソースコード本体はホストOS上にありますが、これをDockerコンテナからマウントしています。rspecもDockerコンテナ内で動作させていて、Google Chromeはsystem specの実行に使っています。

M1 mac上のDockerでGoogle Chromeは動作しない

linux/amd64なイメージで発生します

QEMUのcore dump

Docker Desktop for Apple silicon にはQEMUのバイナリエミュレーションによる限界として inotify 関連のAPIが動かないと書かれていますが、Chromeは inotify を利用しているせいか、起動するとQEMUがcore dumpします。

とりあえずcore dumpする様子を再現するだけのシンプルなDockerfileを用意しました。google-chromeが立ち上がるかどうかだけを見るだけのもので実用に耐えるものではないことにご注意ください。

FROM --platform=linux/amd64 ruby:3.0.3

RUN curl -sSL https://dl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && echo "deb https://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list

RUN apt-get update && apt-get install -y google-chrome-stable

CMD google-chrome --no-sandbox --headless --disable-gpu --disable-dev-shm-usage --ignore-certificate-errors

x86_64なLinuxの上で上記Dockerfileをbuild&runさせると以下のログだけ出ます。

[1128/123643.999213:ERROR:bus.cc(393)] Failed to connect to the bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or directory

M1 mac上でビルドして実行するとこうなります。

WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
qemu: uncaught target signal 5 (Trace/breakpoint trap) - core dumped
qemu: uncaught target signal 5 (Trace/breakpoint trap) - core dumped
[1128/123755.275362:ERROR:bus.cc(393)] Failed to connect to the bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or directory
[1128/123755.288569:ERROR:file_path_watcher_linux.cc(326)] inotify_init() failed: Function not implemented (38)
qemu: unknown option 'type=utility'
[1128/123755.412196:ERROR:gpu_process_host.cc(961)] GPU process launch failed: error_code=1002
[1128/123755.412413:WARNING:gpu_process_host.cc(1273)] The GPU process has crashed 1 time(s)
[1128/123755.457749:ERROR:network_service_instance_impl.cc(916)] Network service crashed, restarting service.
qemu: unknown option 'type=utility'
[1128/123755.592330:ERROR:gpu_process_host.cc(961)] GPU process launch failed: error_code=1002
[1128/123755.592408:WARNING:gpu_process_host.cc(1273)] The GPU process has crashed 2 time(s)
[1128/123755.599114:ERROR:network_service_instance_impl.cc(916)] Network service crashed, restarting service.
qemu: unknown option 'type=utility'
[1128/123755.640851:ERROR:gpu_process_host.cc(961)] GPU process launch failed: error_code=1002
[1128/123755.640946:WARNING:gpu_process_host.cc(1273)] The GPU process has crashed 3 time(s)
[1128/123755.650075:ERROR:network_service_instance_impl.cc(916)] Network service crashed, restarting service.
[1128/123755.657661:ERROR:gpu_process_host.cc(961)] GPU process launch failed: error_code=1002
[1128/123755.657748:WARNING:gpu_process_host.cc(1273)] The GPU process has crashed 4 time(s)
[1128/123755.667036:ERROR:gpu_process_host.cc(961)] GPU process launch failed: error_code=1002
[1128/123755.667221:WARNING:gpu_process_host.cc(1273)] The GPU process has crashed 5 time(s)
[1128/123755.671744:ERROR:gpu_process_host.cc(961)] GPU process launch failed: error_code=1002
[1128/123755.671812:WARNING:gpu_process_host.cc(1273)] The GPU process has crashed 6 time(s)
[1128/123755.671873:FATAL:gpu_data_manager_impl_private.cc(417)] GPU process isn't usable. Goodbye.
qemu: uncaught target signal 4 (Illegal instruction) - core dumped
qemu: unknown option 'type=utility'
Illegal instruction

inotifyだけが問題ではなさそうですが、いずれにしてもqemuの叫びが聞こえてくるのがわかりますね。

arm64 linux向けのGoogle Chromeは存在しない

ここまで読んで「x86_64向けのGoogle Chromeが動かないならarm64向けのGoogle Chromeを使えばいいじゃない」と思った方がいるかもしれません。が、残念ながら本記事執筆時点でarm64 linux向けのGoogle Chrome提供されていません

System requirements

- 64-bit Ubuntu 18.04+, Debian 10+, openSUSE 15.2+, or Fedora Linux 32+
- An Intel Pentium 4 processor or later that's SSE3 capable

Chromiumの最新ビルドを求めて

ここまで読んで「Google Chromeが動かないならChromiumを使えばいいじゃない」と思った方がいるかもしれません。開発環境でもChromeでsystem specを実行できるのが理想ですが、 Chromeが提供されていないプラットフォームならChromiumを使えばいいということになりますね(CI環境でGoogle Chromeを利用できればより安心でしょう)。

が、各ディストリビューションでは必ずしも最新バージョンのChromiumを提供していません。最新バージョンのChromiumをビルドしているところがどこかにあるのでは、と探してもなかなか見つかりません。savannaが利用しているdockerイメージがDebianベースなのでdebパッケージ系のみ調べて見ましたが、自分が見つけられたのは以下だけでした。

いずれもCanonicalが提供しているもので、Ubuntu 19.04まではdebパッケージとして、19.10以降はsnapパッケージとして提供されています。debからsnapに切り替わった経緯は以下の記事に書かれています。

Chromium in Ubuntu – deb to snap transition | Ubuntu

docker build時にsnapを利用ができなさそう

snap経由でChromiumが入ればめでたしめでたしなのですが、私が試した限りではうまくいきませんでした。長くなるので試したことと結果を箇条書きでまとめます。

  • 普通に snap install chromium する => だめ
    • snapdが動いていないといけない
  • docker build中にsnapdを起動させたい => 挫折
    • snapdはsystemd経由で起動していてこれをなんとかする方法が見つからず
    • systemd依存というわけではないのでsystemdを使わない方法もありそうだが...
  • snapパッケージを持ってきて snap installする=> だめ
    • snap download chromium でパッケージは落とせるが snap install chromium_*.snap のようにパッケージ指定してもsnapdにアクセスしようとして落ちる
  • snapパッケージを開いて自力でインストールする => 調査中
    • SnapパッケージはSquashFS でパッケージ化されているので ループバックデバイスとして mountunsquashfs することで中身が取れる
    • tarballみたいに単純に解凍してコピーすればいいかと思ったがどうもそのままコピーできるディレクトリ構成ではない

今やってる or これからやろうとしてること

docker build中にsnapdを動かす方法を模索

あるんでしょうか。情報お待ちしてます。

snapパッケージからバイナリを抜き出す方法を探る

snapパッケージを作るsnapcraftのチュートリアル 読んでパッケージの作り方がわかれば逆算(?)でやり方が見つかるのではないかと思案中。

もしくはsnapコマンドのコードを読んでやっていることを読み解くか。実はsnapdにアクセスするコマンドとパッケージをインストールするコマンドが内部で分かれていて後者を叩けばいいだけ、なんて展開だと嬉しいんですが。

でも、必ずインストールする方法はあるはずなんですよね。見つかってないだけで。こちらも情報お待ちしてます。

Ubuntu 18.04を使う

  • pros
    • 何も考えずaptで入る
    • 2023/04まではLTSとしてアップデートが提供される
  • cons
    • dockerイメージに利用しているOSがubuntuでないといけない
    • security updateとしての提供なのでChromeのバージョンアップに常時追随するわけではない
      • 基本的にsecurity updateが発生してないバージョンなんてなさそうだけど、もしupdateがなければ数バージョンリリースされない可能性もありうる
    • 古いライブラリの対応が必要になる可能性がある

OS標準で入るChromiumで我慢する

debianならaptで入ります。本記事執筆時点で、各Chromiumのバージョンはstableで90、sidで93。

必ずしも最新に追随しているわけではないのでCIでは最新のGoogle Chromeを利用するなどのヘッジが必要になるでしょう。

ubuntuは先ほど書いたように19.10以降でChromium自体がsnapでの管理に移動しています。LTSの20.04でもパッケージ自体はvirtual packageは残っていますが、中身はsnapなのでインストールしようとするとsnap installが走ります。snap経由のインストール問題をなんとかしないと手がなさそうです。

まとめ

手当たり次第心当たる選択肢を当たってますが、冒頭に書いたようにここまで書いた範囲のところで実は簡単な解決策があったりするかもしれないのであったら教えていただけるとありがたいです。

とりあえずGoogleがarm64 Linux向けChrome出してくれたら万事解決するので出してくれると嬉しいですね。MacのM1採用やAWSのGravitionプロセッサを利用したサービスの拡充、Arm版Windowsに関する噂等を考慮すると開発環境・CI向けだけでなくデスクトップLinux向けとしても需要は高っていくように思います。