🤮

docker for macのファイルioが遅い問題に終止符を打つ

2021/02/17に公開
5

09/21 追記

docker-machineのコマンドがまたまた動かない状態になっていたので最新バージョンで動くように更新しました。

05/21 追記

docker-machineを作成するコマンドが動かない状態になっていたので最新バージョンで動くように更新しました。

02/20 追記

xdebugを止める設定について追記しました。#xdebugで接続したい問題

TL;DR

環境を作るのは

  1. #環境を作ってみる
  2. #対処法
  3. #localhostで接続したい問題

に記載してあるコマンドを順に実行するとできます。

歴史

docker for macが遅い。。。

私が初めてdocker for macを触った時のことです。

ファイルIOが遅い!!画面遷移が遅い!!!mysqlのデータの書き込みページが遅い!!!!!!

自分のマシンではこの程度かと思っていたのですが、実際に自分が作った機能がローカル環境と本番orステージング環境で明らかに速度が違っていたし、そもそもmampを使っていた時はこんなにストレスを感じたことはありませんでした。

これはなんとかしなければ

初めはdocker公式のイメージに問題があるのではないかと思い、自分でdockerimageを作成し、lamp環境を作ったりしました。
以下がその時作成したイメージです。alpineを使って長いビルドが必要な部分をイメージ化して、軽量に仕上げてあります。

GitHub - Diwamoto/docker-lamp: lamp in docker
alpine + php7.3(バージョン選べる) + apacheなdocker image作った

その症状は自分より新しいマシンを使っていた他の先輩エンジニアの方も同じ様に頭を悩ませており、なんとかしたいとずっと調査していました。
docker for macが遅い問題は会社の中だけでなく、ネット上の様々なエンジニアを悩ませており、なかなか効果的な対応策は見つかっていない様でした。

docker-sync

するとまずdocker-syncというものを発見しました。

docker-syncの詳細は以下のリンクを参照してください。
docker-sync — docker-sync 0.5.11 documentation

docker-syncはdockerのwebコンテナとローカルのファイルシステムのioが遅い問題を間にファイルのio監視だけを行うsyncコンテナというものを用意して、io遅い問題を解決しようとした物でした。

結果としては、確かに普通の運用よりは早くなったんですが、余計なコンテナが一つ増える為にmacのメモリは常に上限に近くなり、ファンがずっと全力疾走していましたので割と早めに使用をやめました。

ここまでの話はこちらにまとめられています。

ローカル開発環境をmampからdockerに移した話(Docker-sync対応)

docker in vagrant (革命)

次にやったことは先輩に教えていただいたVirtualbox上にdockerを立てるというものです。
dockerは元々linuxの環境を前提に作られた技術なので、mac上にvirtualboxを使ってlinuxの仮想マシンを立ててその上にdockerを立ち上げることでファイルioが遅い問題を解決しようとしたのです。

私はそのvirtualboxの操作に以前使用したことがあるvagrantを使用しました。vagrantを使用することで、virtualboxのマシンの操作がコマンドラインから簡単に行うことができ、かつVagrantfileを用いることで設定をわざわざコマンドを打つことなく設定することができる為です。

そしてvagrantで使用するlinuxディストリビューションにはbargeを採用しました。bargeはdockerを動かす為だけに作られた軽量ディストリビューションです。他のcentOSや、ubuntuと比べてdockerを立てること以外の機能はありませんが、結局ローカル環境がdockerコンテナとして構築されているのであればcentOS等を使用する必要はないと考えました。

その結果としてはファイルマウントをdockerではなくvirtualboxの機能で行う為、ファイルioが改善し、画面遷移等もこれまでで最速になりました。
これはいいと思い、ある程度の期間はずっとこの方式でdockerを動かしていたのですが、問題点がいくつかあります。

革命の裏で

問題点は以下の三つです。

  1. コンテナに入るまでに一度vagrantにsshして接続する必要がある
  2. xdebug等がそのままでは動かない
  3. xdebugやnpmのホットリロード等が不得意

1.について、 dockerはvirtualbox上で動いていますので、当然接続するには仮想マシン上からdocker exec -it {コンテナ名} bash を叩く必要があります。
docker for Macのdocker.sockを共有していないからです。
これが割と面倒で、ちょっとコンテナ等に入りたい時に二度手間をかける必要があります。

2.について、xdebugはホスト(今回で言うとMacのマシン)のipを入れる必要があり、docker for macにはdockerコンテナからホストのipを名前解決してくれるhost.docker.internalが存在します。
これを使用することによってxdebugを動かすことができるのですが、vagrantを用いた仮想マシン上のdockerにはそれがなく、
macOS上で動かしているVSCodeとの間でリモートデバッグする為に仮想マシンからみたmacOSのipに設定を変える必要があります。

3.について、docker for macを使ってxdebugを行う時、(docker-syncを使う時も含めて)そんなにストレスを感じなかったのですが、vagrantを用いたdocker環境でxdebugを動かすと、おそい!!!

これとはまた別件で、最近vue.jsを用いてSPAの開発をしているのですが、vagrantのdockerで自動コンパイルをさせていると、こちらもとても遅い。

これはパケット等を監視しないと詳しくはわからないことではありますが、おそらくdockerコンテナ <-> vagrant <-> ホストOSの通信が大量に行われることには少し不向き?なのではないでしょうか。

ということでvue.jsの開発は大人しくローカルでnpm run serveを行っています。

理想の環境を求めて

これらの状況を打破する為に、以下の条件を満たすdocker環境を作成すべく、たくさん苦しみました。

  1. ファイルioがdocker for macよりは早い
  2. ホストのdockerからコンテナのプロセスが見える
  3. docker以外のものをインストールすることはなるべく避ける(開発環境の共有を1ファイルで済ませるというdockerの思想に反する為)

それを満たす為に、docker in dockerの環境を作り、比較してみました。
docker in dockerとは、dockerそのもののコンテナを立てて、docker.sockを共有することで、ホストからもコンテナが見える状態にしようというものです。本来の使い方はテストの際に捨てる前提のテストコンテナ等を立てるための物であって、開発環境向きのものではない様です。

各環境での速度比較

比較対象、条件

速度比較には僕が初めてdocker for macでストレスを感じたbasercms
のインストールのステップ4→5の所要時間を使用します。
basercmsは国産のオープンソースなCMSフレームワークです。
国産なので日本人の方は貢献しやすいと思います。
以下のリンクからダウンロードできます。
GitHub - baserproject/basercms: baserCMS : Based Website Development Project

比較結果

以下が結果です。

  1. docker for Mac ・・・平均13.8秒
  2. docker in docker ・・・平均13.5秒
  3. docker in vagrant ・・・平均7.8秒

結果としてはやはりvagrantが最強になってしまいました。docker in dockerも平均こそdocker for Macにまさっていますが、場合によってはdocker for macより遅い場合もあったので、1 2 については改善が見込めませんでした。

次はどうするか

こうなればvagrantの仮想マシンとmacのdocker.sockを共有して使ってやろうと思ったのですが、docker for macの仮想マシンとlinuxの仮想マシンは場所が違うらしく、単にソケットファイルを共有するだけではうまくいかない様で断念しました。

これで我慢しながら使うしかない。。。と思っていました。
失意の中、docker for macにいい感じの設定がないか調べていたところ、docker-machineの仮想マシンをvirtualbox上にたてることができそうな情報を手にしたので、そちらについて調べてみました。

ようやく本編ですw

docker-machine

概要

Docker Machine 概要 — Docker-docs-ja 19.03 ドキュメント

先ほどから何度か言っていますが、docker for macの実態とは、macOSにそのままdockerが立っているわけではなく、独自に仮想マシンを立てた上でdockerを実行しているに過ぎません。その仮想マシンとmacOSのファイルioが遅いのです。docker for macを作成した方には悪いですが、なぜそうした…

docker-machineをつかえば、docker for macの仮想マシンを使用する必要はなく、独自に仮想マシンを起動することができるというのです。
dockerコマンドはmacOSから実行することができるので、(わざわざmachineに入る必要がないので)ホストからdocker psなどの操作ができます。

また、先ほどの章で行った計測結果から、virtualboxとホストOSのioはdocker for macの仮想マシンより早いことがわかるかと思います。

つまり、こう言えるわけです。

docker-machineを使ってvirtualbox上にdockerを立てれば最強なのではないか?

先ほどの条件3つを見てみましょう。

  1. ファイルioがdocker for macよりは早い
  2. ホストのdockerからコンテナのプロセスが見える
  3. docker以外のものをインストールすることはなるべく避ける(開発環境の共有を1ファイルで済ませるというdockerの思想に反する為)

1はこれから計測して測ってみますが、2と3は満たしているかと思います。(docker-machineはdockerそのものの機能なのでスルーするということで)

環境を作ってみる

まずは環境を作ってみないことには始まりません。
作っていきましょう。

Docker Machine のインストール — Docker-docs-ja 19.03 ドキュメントに沿ってインストールしていきます。

まず、本体を落としてきます。

> curl -L https://github.com/docker/machine/releases/download/v0.12.2/docker-machine-`uname -s`-`uname -m` >/usr/local/bin/docker-machine && \
chmod +x /usr/local/bin/docker-machine

これでもうコマンドが打てる状況になったかと思います。
確認してみましょう。

> docker-machine version
docker-machine version 0.12.2, build 9371605

インストールできていますね。
次はdocker-machineコマンドを使ってvirtualbox上にdocker-machineを作成します。

> docker-machine create —d virtualbox default

これでできたかと思います。virtualboxを確認してみましょう。

defaultという名前でマシンが作成されていますね。これで完成!と思いきや、設定をする必要があります。

> docker-machine env default
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/{user名}/.docker/machine/machines/default"
export DOCKER_MACHINE_NAME="default"
# Run this command to configure your shell: 
# eval $(docker-machine env default)

これで設定が完了しました。最後にdockerの接続先を変更します。(今まではdocker for macの仮想マシンに接続していた為、docker-machineの仮想マシンに接続する様に変えてやる必要がある)

> eval $(docker-machine env default)

これで環境の完成です。おめでとうございます!!

環境を構築したのだが、、、

permission問題

概要

環境が完成したので、早速lamp環境を構築してみましょう。
lamp環境には、GitHub - Diwamoto/docker-lamp: lamp in dockerを使用します。(私のlamp環境です。結構拘って作っているので是非試してみてください。)

いざ、起動!!

> git clone https://github.com/Diwamoto/docker-lamp.git
> cd docker-lamp
> docker-compose up -d

一度docker for macで使用したことがあるdockerコンテナを起動するとわかるかと思いますが、新しくビルドされており、docker-machine上でdockerが動いていることがわかるかと思います。

> docker-compose up -d

~~~~~~~~~~省略~~~~~~~~~~~~

Creating network "docker-lamp_default" with the default driver
Creating db      ... done
Creating mailhog  ... done
Creating pma      ... done
Creating web-php7 ... done
> 

よし!起動できた!
と思ったのも束の間。basercmsを起動してみると

Operation not permittedの嵐。。。
なんやねんこれ、、、、

対処法

調べてみたことや試してみたことはいろいろありますがその辺は割愛して問題と対処法を説明します。

問題: docker-machineにフォルダがマウントされる時に、ファイル全てのユーザとグループが書き換えられてしまう
という問題でした。具体的にはgid:50のstaffグループに所属するuid:1000のdockerユーザに書き換えられてしまいます。

なので対処として、apacheの実行ユーザをuid:1000かつgid:50のユーザに変えます(コンテナ内のユーザです。)具体的にはdockerfileに以下の一文を追加します

RUN addgroup -S -g 50 dapacheg && adduser -S -u 1000 -g 50 dapache \

そしてapacheの実行ユーザを作成したユーザのものに変更します。

<IfModule unixd_module>
#
# If you wish httpd to run as a different user or group, you must run
# httpd as root initially and it will switch.  
#
# User/Group: The name (or #number) of the user/group to run httpd as.
# It is usually good practice to create a dedicated user and group for
# running httpd, as with most system services.
#
User dapache
Group dapacheg

</IfModule>

これで大丈夫です!!

localhostで接続したい問題

概要

さて、ローカル環境を作成して、開発に入ろう!と思った方、少しお待ちください。docker-machine特有の問題が後一つあります。それはlocalhostで接続できないというところです。このまま接続するには仮想マシンのipを叩く必要があり、サブドメインが完全に使えません。

Vagrantを使用していた頃はlocalhostで接続できていたかと思いますが、それについてはvirtualboxの機能でポートマッピング(localhost:指定ポートを仮想マシンの指定ポートにフォワード)をしていただけで、本来はその仮想マシンのipを叩かなくてはいけません。

対処法

そこでvagrantはどうやってポートマッピングをしているのかと調べたところ、いいコマンドを見つけました。以下のコマンドを実行することで自動でマッピングされます。

> vboxmanage controlvm default natpf1 "http,tcp,,80,,80"

他にポートを開けたければhttpのところを任意の名前にしてポートを変えてコマンドを実行してください。httpにしている理由は特にありません。

これでlocalhostでも接続できる様になりました!

xdebugを止めたい問題

概要

この問題は@Atsushi Morimoto
さんの情報提供により解決することができました。ありがとうございました。

さて、docker for mac環境をよくするための条件には入っていませんでしたが、もう一つ大事な条件がありました。
それはxdebugがスムーズに動くことです。
docker for macのxdebugはdocker for mac単体で動かした時はスムーズで初めて使った時は感動していたのですが、
vagrantを使ってdockerを動かした時に、vscode側でデバッグを起動していると全てのアクションでホストと通信している様で、(詳しくは分かりませんが)動作がすごくもっさりしてしまっていました。

対処法

xdebugの設定を以下の様にしてあげるとスムーズなxdebug環境を手に入れることができます。
初めはなんとかdocker-machine上のdockerコンテナ内からmacOSのホストipを取り出そうと苦戦していたのですが、xdebug.discover_client_host = trueを設定してやると自動で探してくれる様です。
私は一応9003ポートを開けていますが、おそらく開けなくても動くと思います。
xdebug最高!!!!!

[xdebug]
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20180731/xdebug.so
xdebug.mode = debug
xdebug.client_port = 9003
xdebug.discover_client_host = true
xdebug.start_with_request = yes

まとめ

これでついに、いろんな条件を満たしつつちゃんと早いdocker for mac環境ができました。
僕の長かった戦いもこれで終わってくれると嬉しいです。

m1 mac上のdocker for macはどうなんでしょうね、、、docker for intel macの弱いところを克服しているといいんですが。

以上になります。ここまで読んでくださった方、本当にありがとうございました。

GitHubで編集を提案

Discussion

DiwamotoDiwamoto

xdebugがまだうまく動いていませんので情報がある方は提供お願い致します。。
具体的にはhost.docker.internalが使えない為、docker-machine上からmac上のvscodeに接続できないという状況です。
ifconfig等して取得したデータを使ってみたりしましたが、うまくいっておりません。。。
ここさえクリアできればlamp環境は完全終了なのですが、、、

Atsushi MorimotoAtsushi Morimoto

近いことにトライしていたので、面白く読ませていただきました!
ちょっと情報が古いかもしれませんが、以下でリモートデバッグ時のPHPとvscodeの設定をまとめています。ご参考までに。
https://github.com/vscode-debug-specs/php#remote-debug

追加でご参考までになのですが、VirtualBoxで立てられたのであれば、VS CodeのRemote SSHを使って、デバッガーも仮想マシンの内で一緒に動かすのがおすすめです。もしくは、Remote Containerを使って、Dockerの中で開発してしまう手もあります。記録には残っていないのですが、VirtualBoxとParallels Desktopのioとcpuをベンチマークで比較して、Parallels Desktopの方が良かった(その上に、MacOS公式の仮想化フレームワークに対応している)ので、私はParallels Desktopを使用しています。よろしければお試しください。

DiwamotoDiwamoto

情報ありがとうございます!!
そうですね、これはxdebug3.0以前の設定になりますね。そちらは既に私の方で書き換えているのですが、
launch.jsonの設定の中のserverについては初めて見ました。
こちらの設定は仮想マシンのip(今回で言うとdocker-machineのip)を指定しているのでしょうか?

DiwamotoDiwamoto

情報ありがとうございました。
xdebugの設定を以下に変えることで、ホストマシンのipを指定せずともxdebugでブレイクさせることができました。
ありがとうございました。記事を修正させていただきます!!!

[xdebug]
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20180731/xdebug.so
xdebug.mode = debug
xdebug.client_port = 9003
xdebug.discover_client_host = true
xdebug.start_with_request = yes