Mutagenを使って開発環境の重たいDockerを3倍高速化する

AvatarPosted by

はじめまして!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をベータ版としてリリースしました。

Compose | Mutagen

以前は専用コンテナの手動作成など、複雑な手順を踏む必要があった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

Permissions | Mutagen

そのほかの設定・フィールド

紹介したほかにも様々なフィールドや設定が用意されています。詳しくは公式ドキュメントやサンプル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倍の高速化ができました。

WindowsmacOS
ノーマル10046ms2475ms
Mutagen2781ms1590ms
3.6倍高速化1.5倍高速化

/Admin 管理画面API

管理画面を表示するためのAPIです。単純な構造なのでノーマル環境でも比較的高速でしたが、Mutagen環境ではさらに高速化できました。

WindowsmacOS
ノーマル5217ms685ms
Mutagen1759ms262ms
2.9倍高速化2.6倍高速化

/UserDetail ユーザ詳細表示API

管理画面にて、ユーザー情報を表示するためのAPIです。いくつかのクエリが走るAPIですが、他と同じ様に高速化ができました。

WindowsmacOS
ノーマル8699ms1099ms
Mutagen2320ms556ms
3.7倍高速化1.9倍高速化

Mutagenを利用することによって、最大で3.7倍、平均で2.7倍の高速化を実現することができました。

課題

高いパフォーマンスをもつMutagenですが、いくつかの課題がチーム内から報告されました。

  • 導入したのにもかかわらず速度が変わらない
  • 一部ファイルがコンフリクトする
  • たまに遅くなる

この辺りはチームの方にフィードバックをいただきつつ、さらに調査を進めていきたいと思います。

まとめ

Mutagenを利用することによって、大幅な高速化を実現することができました。以前は敷居の高かったMutagen導入ですが、Docker Compose統合コマンドの登場とx-mutagenにより、簡単に導入・共有ができる様になりました。

一方で、課題がないわけではありません。この点はフィードバックをもらいつつ、上手く解消していきたいと考えています。

以上です。ここまで読んでいただきありがとうございました!