まず結論からということでタイトルに要約したら長くなりました。

Mac の Docker は遅いということで有名(?)です。私も日頃の開発は Docker Desktop for Mac を通じて行っています。私の環境だと Mac のネイティブの環境での開発と比較するとやはり若干の遅さは感じるものの、ほとんど問題になるレベルではないため特に気にしていませんでした。

とはいえそこまで手間をかけずに速度改善できるのであればいくつか試してみようかなと調べたところ、掲題に気づくまでに時間を要したので覚え書きです。

前置き:なぜ遅いの?

主たる原因はホスト(Mac)とコンテナ(Docker)間のファイルの共有に時間がかかるためです。

ホストコンテナのファイルシステム一貫性とパフォーマンスの密接な関係

macOS や Windows を含む、数々のプラットフォーム上で Docker が利用できるようになり、コンテナ実行時にマウントに関連するワークロード(作業負荷)を最適化する必要性が一般化しました。

現時点で Linux 上のマウントに関する実装とは、コンテナ内にホスト側と一貫性したディレクトリツリーを提供するものです。つまり、読み書き処理とは、ホスト上だけでなくコンテナ内でも行われますので、他の環境の影響を直ちに受けます。また、ファイルシステムイベント( inotify 、 FSEvents )は、両方(ホスト上およびコンテナ)のディレクトリで直ちに反映します。

Linux 上ではホストとコンテナ間で VFS を基盤に共有しているので、オーバーヘッドのない反映を保証します。しかしながら、 macOS (および他の Linux 以外のプラットフォーム)では、完全な一貫性を保つために著しいオーバーヘッドがあります。

https://docs.docker.jp/docker-for-mac/osxfs-caching.html

前置き:ボリュームマウントのオプションでボトルネックを緩和させることが可能

Docker を用いた開発のパフォーマンス改善で調べると cacheddelegated オプションを使うのが良いと方々で言われていました。

consistent、cached、delegated 設定のチューニング

幸いにも、ほパフォーマンス劣化は多くの場合において最も深刻な例であり、また、コンテナとホスト間における一貫性が完全である必要はありません 。特に多くのケースでは、コンテナ内に書き込んだファイルを、即時ホスト上に反映する必要がありません。たとえば、双方向(インタラクティブ)の開発を行っていると、コンテナ内でのファイルシステムイベントの発生が、ホスト上のバインド・マウントしたディレクトリに書き込む必要がある場合、コンテナ内で構築した成果物(build artifacts)を即座にホスト上のファイルシステムに反映する必要はありません。これら特徴的な2つのケースでは、著しいパフォーマンス改善が可能です。

ここでは必要となる一貫性のレベルに応じ、3つのシナリオを検討しました。各ケースは、いずれもコンテナ内にバインド・マウントしたディレクトリを持っていますが、2つのケースではコンテナとホスト間で一時的な矛盾の発生を許容しています。

  • consistent :完全な一貫性(常にホストとコンテナが完全に同じ表示)
  • cached :ホストの表示が信頼できる(ホスト上の更新がコンテナ上に反映するまで、遅延が発生するのを許容)
  • delegated :コンテナの表示が信頼できる(コンテナ上の更新がホスト上に反映するまで、遅延が発生するのを許容)

https://docs.docker.jp/docker-for-mac/osxfs-caching.html

ところがうまくいかず

さっそく cacheddelegated のオプションを指定してみてもうまくいきませんでした。

色々な人が言っていることと同じことをやっているだけなのに何故?と思い調べてみるとファイルシステムの設定が関係していました。

バージョンアップによってファイルシステムが osxfs から gRPC-FUSE に変更された

先述の速度問題の大元は、Docker Desktop for Mac のファイルシステムである osxfs に起因するものでした。その対応としてバージョン 2.4.0.0 にて ファイルシステムが gRPC-FUSE に変更されました。

Docker Desktop now uses gRPC-FUSE for file sharing by default. This uses much less CPU than osxfs, especially when there are lots of file events on the host. To switch back to osxfs, go to Preferences > General and disable gRPC-FUSE.

https://docs.docker.com/desktop/mac/release-notes/2.x/#docker-desktop-community-2400

設定を変更すれば osxfs に戻すことも可能ですが、デフォルトでは以下の通り gRPC-FUSE が使われるようになっているはずです。

Use gRPC FUSE for file sharing

Docker Desktop for Mac | Preferences

cached や delegated が使えるのはファイルシステムが osxfs のときだけ

cacheddelegated のオプションは osxfs のみで使用できるオプションのため gRPC-FUSE を使用している場合これらは使用できません。

この部分については誤解を生みやすいようで、フォーラムや GitHub でも質問をしている人がいました。

Docker Desktop for Mac の公式ドキュメントは以下ですが、現在は以下からも cacheddelegated の記載はなくなっています。

Docker Desktop for Mac user manual | Docker Documentation

一方で過去に実践された方のブログや有志による日本語ドキュメントは osxfs を前提として書かれているものも未だ多く、私も混乱してしまったため今回記事に残すことにしたという経緯です。

gRPC-FUSE で本当に速度改善されたかは微妙

極力デフォルトに従おうと思うので私は設定を戻したことはないですが、検証してみた方によると osxfs と gRPC-FUSE で速度に大した違いはない様子です。osxfs に戻して cacheddelegated を使用するものありなのかもしれませんね。