🍂

M3 Mac上のDocker(Linux OS)でChromiumを利用するとコンテナが落ちる(追記あり)

に公開

結論、Apple silicon系列のMac✖️LinuxOSのコンテナでChromiumを使うのはやめときましょう。
2025/04/26追記:こちらの記事公開後、ホストOSをArmからIntelのRossetaに切り替えれば良いのではないか、というアドバイスをいただきました! 詳細最下部に。

使用環境

  • ホスト: Mac M3
    • Kernel: Darwin(24.3.0)
  • Docker: 27.3.1
    • Docker Desktop自体はApple silicon(arm64アーキテクチャ)を利用
    • Linux Kernel: 6.10.14-linuxkit
      • Dockerコンテナ内のOSはLinuxみたいなイメージ
  • Chromium: 135.0.7049.95-1~deb12u1
    • Dokcerfileで追加パッケージとして最新版をインストール
  • フレームワーク: Ruby on Rails(7.2.2)
    • Ruby: 3.3.6
  • 今回影響してそうなGem:
    • Ferrum: 0.15

やろうとしていたこと

gem 'ferrum'を使ってページ上のHTMLをPDF変換しようとしていました。
ヘッドレスブラウザで操作するために、Chromiumを導入しました。
が、肝心のPDF出力をさせようとしたところでwebコンテナが落ちる現象が発生しておりました。

コンテナが落ちた場所

最初はwebコンテナ自体のメモリ不足かと思い、PDF処理をバックグラウンドで実行させるためにコンテナを分けました。
キューの一時保存場所RedisコンテナとPDF出力専用workerコンテナを作り、workerコンテナのsidekiqさんが処理を担当してくれるはずでした。
以下は具体的な処理内容です。

app/jobs/pdf_generation_job.rb

class PdfGenerationJob < ApplicationJob
  queue_as :default

  def perform(user_id, year_month)
    user = User.find(user_id)
    selected_date = Date.strptime(year_month + "-01", "%Y-%m-%d")
    start_date = selected_date.beginning_of_month
    end_date = selected_date.end_of_month
    sleep_logs = user.sleep_logs.where(sleep_date: start_date..end_date)

    all_dates = (start_date..end_date).to_a
    @sleep_logs = all_dates.map do |sleep_date|
      sleep_logs.find { |sleep_log| sleep_log.sleep_date == sleep_date } || user.sleep_logs.build(sleep_date: sleep_date)
    end
    @selected_date = selected_date
    @pdf_user_name = user.name

    html = ApplicationController.render(
      template: 'sleep_logs/pdf',
      layout: 'pdf',
      formats: [:html],
      locals: { sleep_logs: @sleep_logs, selected_date: @selected_date, pdf_user_name: @pdf_user_name  }
    )
    pdf = html2pdf(html)
  end

  private

  def html2pdf(html)
    # binding.pry
    browser = Ferrum::Browser.new(
      browser_path: '/usr/bin/chromium',
      browser_options: { "no-sandbox": nil, "disable-gpu": nil, "disable-dev-shm-usage": nil, "single-process": nil }
    )
    browser.go_to("data:text/html,#{html}")
    page = browser.pages.first
    page.disable_javascript # JavaScriptを無効化(特に意味はなかった)
    browser.network.wait_for_idle
    sleep 1.5
    pdf = browser.pdf(format: :A4, encoding: :binary)
  ensure
    browser&.quit # <- ココ!
    pdf
  end
end

browser.go_toあたりは全然問題なく動いていたのですが、htmlの内容をpdfにエンコーディングしたあと、browser&.quitを実行した際にwebコンテナのみが突然落ちる現象が発生しました。

HTMLのバイト数は過分なく、CSSデータなしの簡単な文書ですらも落ちていました。
Procfile.dev でjsとcssの監視をしていたので、それをストップして動作させても変わりなし。
Docker desktopのresources設定で、CPU limit・Memory limitを限界まで上げても変わりなし。
webコンテナのCPU使用率を監視しながら実行させたところ、平均10%前後だったものがbrowser&.quitのタイミングで一瞬70%を記録し、その後落ちました。

原因

利用していた環境の問題でした。

  • arm64 linux向けのGoogle Chromeは存在しない
  • Ferrum公式でも、LinuxのChromiumパッケージが非推奨
  • Mac M1上のDockerコンテナ内でChromiumを動かすとクラッシュする現象が報告されている

以下、参考になったページです。
https://github.com/rubycdp/ferrum
https://blog.savanna.io/entry/2021/12/06/182102

対策としてはコンテナのOSをLinux以外にすることぐらいかなぁと思ったりしますが、もう純粋にPDF出力に使うGemを変えた方が早いですね、ハイ。

一部のChromiumコマンドが使えてしまっていたことで、原因に気づくのが遅くなりました。つらい。

結論

Googleさんがarm64 linux向けのGoogle Chromeを出してくれるまでは、同環境の方は利用をお勧めいたしません。

追記:対応方法について

MacをArmベースからIntelベースに変えることで対処が可能なのではないか、とのアドバイスをいただきました。
こちらを参考に
https://qiita.com/funatsufumiya/items/cec08f1ba3387edc2eed

  • いつもつかっているターミナルを複製
  • 情報を見る->Rosettaとして起動
  • RosettaターミナルでHomebrewをインストール
  • Dockerfileで、コンテナ内のバージョンをRosettaにするよう指定

Dockerfile

FROM --platform=linux/amd64 docker.io/library/ruby:$RUBY_VERSION-slim AS base

Dockerfile.dev

FROM --platform=linux/amd64 ruby:3.3.6
  • キャッシュとボリュームを削除してからビルド

というふうに対処してみました。
確かにコンテナは落ちなくなりましたが、Chromiumが原因と思われるFerrum::ProcessTimeoutErrorというタイムエラーが発生するのでなんだかなぁ。。。という気持ちです。
Active Jobの実行中にエラーが発生し、そのまま時間が過ぎてしまっておりました(Gem Ferrumがタイムアウトになるデフォルトは5秒でしたが、30秒に設定しても変わらず).
どうやらChromiumとの接続に失敗したとのことらしいです。
また何か分かったことがあれば追記します。

Discussion