はじめまして!CyberAgent 21卒内定者の笹です。現在、内定者アルバイトとしてグリフォンに配属されています。
今回はMutagen Composeを使って開発環境Docker上のPHPのレスポンスをWindowsでは平均3.4倍、macOSでは平均2.0倍することに成功したので、その手順と以前の環境との比較を書きたいと思います。
DockerのVolumeマウント遅すぎ問題
開発環境のDockerのVolumeマウント、重いですよね。私たちのプロジェクトでも、Dockerの重さは問題視されてきました。とはいえ、Docker抜きで開発することはできません。
Dockerの開発側もこのVolumeマウント遅すぎ問題に対処している様で、いわゆるベータ版であるEdgeチャンネルでは、それに対処するための様々な新機能が搭載され、実験が重ねられています。
ですが、それらの機能はOS間での互換性が無かったり、ある日突然アップデートで消えてしまったりと、安定して使用できるものではありませんでした。
そこで、Mutagenというクロスプラットフォームの開発ツールを使って高速化を図ってみることにしました。
Mutagenとは
Mutagenとは、ローカル環境とリモート環境のディレクトリを高速に同期させることができるオープンソースの開発ツールです。Go言語で書かれています。
一見すると「Dockerに関係ないじゃないか!」と思うかもしれませんが、Dockerが動作するホストをローカル環境、コンテナをリモート環境と捉えると理解しやすいかと思います。これについては下で詳しく解説しています。
MutagenをDockerで簡単に利用する Compose Integration
ご存知の方もいるかもしれませんが、実はこのMutagen、一時Docker DesktopのEdgeチャンネルで本体への取り込みが行われており、簡単に一部機能を利用することができていました。しかしながら、いくつかの問題が発生したためおよそ2週間で削除されています。
https://github.com/docker/for-mac/issues/1592#issuecomment-678397258
要約: キャッシュが追いつかなかったりサブディレクトリが上手く同期できないから削除したよ。代わりにgRPC FUSEを使ってね。
Docker本体からは削除されましたが、本体であるMutagenはまだDockerをサポートしています。そればかりか、Mutagenは7月末にDocker Composeとの統合を実現するための機能、Compose Integrationをベータ版としてリリースしました。
以前は専用コンテナの手動作成など、複雑な手順を踏む必要があったMutagenですが、このCompose Integrationにより圧倒的に使いやすくなったのです。
Mutagenを利用することでDockerが高速化する仕組み
なぜMutagenを利用するとDockerが高速化するのでしょうか?それを理解するには、Dockerがコンテナ内でファイルを利用する仕組みから知る必要があります。
まず、Dockerコンテナがホストのディレクトリを直接利用するのに使うバインドマウントは非常に遅いことで有名です。これには様々な要因がありますが、その一つはDockerが一貫性を担保しようとするためです。ホストとコンテナは遅延なく同一のものになりますが、それを実現するための代償としてディスクIOが低速となっているのです。
それに対して、ホストのディレクトリから切り離されたDocker専用記憶域を使うボリュームマウントは高速に動作します。このボリュームはDocker専用ですので、ホストのディレクトリとの連携を考える必要がないため高速に動作するのです。
ただし、見て分かる通りホストのディレクトリとファイルを同期することはできません。開発環境とのファイル同期を行いたいなら、何らかの手段を用いてこのVolumeにファイルを転送してあげる必要があります。
そこでMutagenが登場します。
Mutagenはホストのディレクトリとボリュームを高速に同期します。変更をリアルタイムで検知し、転送することでボリュームの高速さとホストのディレクトリのコンテナ内利用を両立するのです。
Mutagenを実際に導入してみよう
導入、といってもCompose Integrationが導入されたためとても簡単です。
STEP.1 Mutagenをコンピュータに導入する
2020/09/03現在、mutagen composeコマンドはベータでしか使えません。バージョンv0.12.0-beta1以上を利用してください。
Macの場合はbrewコマンドを利用してダウンロードができます。
brew install mutagen-io/mutagen/mutagen-beta
Windowsやその他プラットフォームの場合はGitHubからバイナリをダウンロードできます。パスを通して利用してください。
Release v0.12.0-beta1 · mutagen-io/mutagen
STEP.2 Mutagen用 docker-compose.ymlファイルを用意する
下に、プロジェクトで使用しているdocker-compose.yml
ファイルを記載します。
version: '3.7'
services:
api:
image: nginx
volumes:
- ./:/source/
- ./docker/nginx/config/nginx.conf:/etc/nginx/nginx.conf
php:
build: docker/php-fpm/
volumes:
- ./:/usr/share/nginx/
- ./docker/php-fpm/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini
- ./log_stream:/source/log_stream/
db:
image: mysql:5.7
NginxとPHP-FPMを利用した、一般的な構成であることがわかると思います。volumes
を見ればわかる様に、docker-compose.yml
があるディレクトリをそのままコンテナ内の/source/
にバインドマウントしています。
これをMutagen用に変更したのがこちらです。これをmutagen-compose.yml
としています。
version: '3.7'
services:
api:
image: nginx
volumes:
- source:/source/
- ./docker/nginx/config/nginx.conf:/etc/nginx/nginx.conf
php-fpm:
build: docker/php-fpm/
volumes:
- source:/source/
- log_stream:/source/log_stream/
- ./docker/php-fpm/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini
db:
image: mysql:5.7
volumes:
source:
log_stream:
x-mutagen:
sync:
defaults:
ignore:
vcs: true
source:
alpha: "."
beta: "volume://source"
mode: "one-way-replica"
ignore:
paths:
- "./php_code/cache"
- "./php_code/logs"
configurationBeta:
permissions:
defaultFileMode: 0666
defaultDirectoryMode: 0777
logStream:
alpha: "./php_code/logs"
beta: "volume://log_stream"
mode: "two-way-resolved"
Volumes
フィールドが追加され、今までバインドマウントを利用していたところをボリュームマウントに変更しました。加えて、x-mutagen
フィールドが追加されていることがわかると思います。一つづつ見ていきましょう。
Syncフィールド
Syncフィールドはその名の通りSyncする一対のディレクトリを単位に指定しています。Mutagenでは、これをセッションと定義しています。
ここでは、プログラムのソースを表すsource
セッション、ログストリームを表すlogStream
セッションが定義しました。
セッションフィールド
次は、セッションフィールドの内容について書いていきます。
Alpha, Betaキー
簡単に言えば、ディレクトリの同期元と同期先のことを指定するキーです。ここではAlphaにdocker-compose.yml
があるディレクトリを、Betaにsourceというボリュームを指定しています。Dockerのボリュームであることを示す、volume://
という接頭辞をつけ忘れないよう注意してください。
source:
alpha: "."
beta: "volume://source"
Alpha, Betaと表記されているのは、必ずしも同期元から同期先にファイルが流れるわけではないからです。下記で解説するモードの設定次第では、Betaで生成されたファイルがAlphaに流れてくるというシチュエーションもあるからです。
Modeキー
同期モードを指定するキーです。Mutagenでは4つの同期モードが存在し、シチュエーションごとに使い分けが可能です。それぞれを簡単に解説します。
two-way-safe
(Default)
双方向同期モード。データの損失が発生しない場合にのみ自動的に競合が解消されます。競合が発生した場合はmutagen sync list
コマンドで手動解決する必要があります。two-way-resolved
双方向同期モード。競合が発生した場合、Alpha優先で自動的に上書きされます。one-way-safe
単方向同期モード。Alphaの変更のみBetaへ同期がされます。Alphaの変更が競合した場合、mutagen sync listコマンドで手動解決する必要があります。one-way-replica
単方向同期モード。BetaはAlphaの正確なレプリカになります。Betaの変更は即時Alphaと同様になるように上書き・削除されます。競合は発生しません。
source
セッションでは、コンテナ内の変更をホストに伝搬する必要がなく、cache
ディレクトリとlog_stream
ディレクトリを除きコンテナ内で作成されるファイルはなかったためone-way-replica
を指定しました。
logStream
セッションでは、コンテナ内でログが作成され、それをホストでみたい場合もあるためtwo-way-resolved
を指定しました。
source:
# ...
mode: "one-way-replica"
logStream:
# ...
mode: "two-way-resolved"
ここで上手く指定を行わないと、競合が発生してしまう可能性があります。「あれ?変更が届いてないな」とおもったら、mutagen sync list
コマンドを使い競合が発生していないか確認しましょう。
ignoreキー
ディレクトリの除外設定ができます。今回、source
セッションで同期するディレクトリ内に、logStream
で使用しているディレクトリが存在したため、それを除外指定しています。また、cache
ディレクトリも特に同期する必要がないため、除外指定しています。
source:
ignore:
paths:
- "./php_code/cache"
- "./php_code/logs"
より高度に、例えば○○除く全てを除外するのような指定もできるようです。この辺りは下記公式ドキュメントやサンプルYamlを参照してください。
configurationキー
詳細な設定を行うことができます。今回は、Betaのみパーミッションの変更を行う設定を追記しました。この辺りに関しても、公式ドキュメントを参照してください。
configurationBeta:
permissions:
defaultFileMode: 0666
defaultDirectoryMode: 0777
そのほかの設定・フィールド
紹介したほかにも様々なフィールドや設定が用意されています。詳しくは公式ドキュメントやサンプルYamlを参照してください。
公式ドキュメント
Compose | Mutagen
サンプルYaml
mutagen/docker-compose.yml at master · mutagen-io/mutagen
STEP.3 コンテナを起動する
docker compose
コマンドの代わりにmutagen compose
コマンドを使用します。使用方法はほとんどdocker compose
コマンドと変わりありません。
mutagen compose -f mutagen-compose.yml up
# そのほかのコマンドも使用可能
mutagen compose -f mutagen-compose.yml down
mutagen compose -f mutagen-compose.yml ps
mutagen compose -f mutagen-compose.yml start
mutagen compose -f mutagen-compose.yml stop
これにて、高速な環境での開発を始めることができます。
パフォーマンスを測定してみる
Mutagenがどの程度のパフォーマンスを実現しているかを調べるため、実際のプロジェクトで使用しているAPIのレスポンス速度を各OSで調査を行いました。
* Windows 10 Docker Desktop 2.3.5.1 (edge channel) with WSL2 Dockerへの割り当て: CPU 8 cores, RAM 8GB * macOS Docker Desktop 2.3.5.1 (edge channel) with gRPC FUSE Dockerへの割り当て: CPU 12 cores, RAM 8GB
調査方法は、APIにブラウザからアクセスを行い、開発ツールからレスポンスまでにかかった時間を記録する方法を採用しました。それぞれの環境を変更する際には、Docker DesktopのClean / Purge data
を利用し、イメージごと削除しています。
前提条件として、Windows 10ではWSL2を、macOSではgRPC FUSEを利用しているためノーマルの検証結果でもある程度高速になっています。Stableチャンネルを利用している場合、さらなる高速化が期待できます。
/Login ログインするAPI
ユーザーがログインに使用するAPIです。複数のクエリが走るAPIのため、Windowsではレスポンスに10秒以上もかかっていましたが、Mutagen環境では2秒後半と、3.6倍の高速化ができました。
Windows | macOS | |
ノーマル | 10046ms | 2475ms |
Mutagen | 2781ms | 1590ms |
3.6倍高速化 | 1.5倍高速化 |
/Admin 管理画面API
管理画面を表示するためのAPIです。単純な構造なのでノーマル環境でも比較的高速でしたが、Mutagen環境ではさらに高速化できました。
Windows | macOS | |
ノーマル | 5217ms | 685ms |
Mutagen | 1759ms | 262ms |
2.9倍高速化 | 2.6倍高速化 |
/UserDetail ユーザ詳細表示API
管理画面にて、ユーザー情報を表示するためのAPIです。いくつかのクエリが走るAPIですが、他と同じ様に高速化ができました。
Windows | macOS | |
ノーマル | 8699ms | 1099ms |
Mutagen | 2320ms | 556ms |
3.7倍高速化 | 1.9倍高速化 |
Mutagenを利用することによって、最大で3.7倍、平均で2.7倍の高速化を実現することができました。
課題
高いパフォーマンスをもつMutagenですが、いくつかの課題がチーム内から報告されました。
- 導入したのにもかかわらず速度が変わらない
- 一部ファイルがコンフリクトする
- たまに遅くなる
この辺りはチームの方にフィードバックをいただきつつ、さらに調査を進めていきたいと思います。
まとめ
Mutagenを利用することによって、大幅な高速化を実現することができました。以前は敷居の高かったMutagen導入ですが、Docker Compose統合コマンドの登場とx-mutagen
により、簡単に導入・共有ができる様になりました。
一方で、課題がないわけではありません。この点はフィードバックをもらいつつ、上手く解消していきたいと考えています。
以上です。ここまで読んでいただきありがとうございました!