Docker Desktop が重くて仕方ないのでリモートサーバの Docker へ unison で同期して開発できるようにしてみた。
Docker Remote API のセットアップ
どこかのリモートサーバに普通に Docker をセットアップした後、Docker Remote API を有効にします。
まずはローカルで証明書&秘密鍵を作ります。本来であればルート・サーバ・クライアントでそれぞれ作るものだと思いますが、面倒だし自分専用として作るだけなのでオレオレ1本でやります。
# オレオレ証明書&秘密鍵、subjectAltName のところはサーバ名に一致させる必要があります openssl req -batch -new -x509 -newkey rsa:2048 -nodes -sha256 \ -subj /O=oreore-for-docker -days 3650 \ -addext subjectAltName=DNS:ore-no-server \ -keyout ~/.docker/key.pem \ -out ~/.docker/ca.pem # オレオレ証明書をクライアント証明書としても使う cp ~/.docker/ca.pem ~/.docker/cert.pem # サーバに送る rsync ~/.docker/ca.pem ~/.docker/cert.pem ~/.docker/key.pem ore-no-server:/etc/docker/ --rsync-path='sudo rsync' -v
systemd のユニットファイルで Remote API を有効にします。
# リモートサーバで作業します ssh ore-no-server # オリジナルの設定を確認しておく cat /usr/lib/systemd/system/docker.service | grep -A3 ExecStart # 環境変数でコマンドラインオプションを追加できるようにする sudo mkdir -p /etc/systemd/system/docker.service.d/ sudo tee /etc/systemd/system/docker.service.d/sysconfig.conf <<'EOS' [Service] EnvironmentFile=/etc/sysconfig/docker ExecStart= ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock $OPTIONS EOS # Remote API 関係のコマンドラインオプションを指定 sudo tee /etc/sysconfig/docker <<'EOS' OPTIONS="\ -H 0.0.0.0:2376 \ --tlsverify \ --tlscacert=/etc/docker/ca.pem \ --tlscert=/etc/docker/cert.pem \ --tlskey=/etc/docker/key.pem \ " EOS # 反映 sudo systemctl daemon-reload sudo systemctl restart docker sudo systemctl status docker # リモートサーバの作業終わり ssh ore-no-server
ローカルから docker が操作できることを確認します。
env DOCKER_TLS_VERIFY=1 DOCKER_HOST=tcp://ore-no-server:2376 docker ps env DOCKER_TLS_VERIFY=1 DOCKER_HOST=tcp://ore-no-server:2376 docker run --rm hello-world
ローカルの環境変数設定
direnv とかで環境変数を設定します。DOCKER_CERT_PATH
はデフォルトが ~/.docker
なので指定不要です。
export DOCKER_HOST=tcp://ore-no-server:2376 export DOCKER_TLS_VERIFY=1
ファイル同期
ローカルとリモートサーバでファイル同期する必要があります。
以前は autofs+cifs をよく使っていましたが、cifs でファイルシステムの I/O が遅いのがボトルネックになってるのかもしれないので unison で同期するようにしてみます。
unison は CentOS なら epel から入れられますが、それだとディレクトリ監視がまともに動かなかったので最新版を使います。ただ、自前でビルドするのはめんどくさいので次のように適当な docker イメージから引っこ抜きます。
docker run --rm -v /usr/local/bin:/x eugenmayer/unison:2.51.2.1 sh -c 'cp /usr/local/bin/unison* /x'
このイメージは alpine ベースなのですが unison のバイナリが静的リンクになっているようなので x86_64 ならそのまま動くと思います。
ローカルとリモートの両方に unison
と unison-fsmonitor
の2つのバイナリをインストールしたら、次のようにディレクトリを同期できます。
unison . "ssh://ore-no-server/$PWD" \ -ignore 'Name .git' \ -ignore 'Name storage' \ -ignore 'Name vendor' \ -ignore 'Name node_modules' \ -auto -batch -repeat watch
ローカルとリモートでディレクトリ名を一致させるのが重要です。一致していないと docker-compose
でボリュームマウントがうまくできません。
PhpStorm
PhpStorm で下記あたりを設定します。
- Docker
- URL は
https://ore-no-server:2376
のようにhttps
とします - 証明書のパスは↑で作成した証明書一式を Windows 側にコピーしてそのディレクトリを指定
- パスのマッピングは
/c/Users -> C:\Users
とかで
- URL は
- PHP Remote Interpreter
- configuration option で
-dxdebug.remote_host=192.0.2.123
のようにローカルの IP アドレスを指定
- configuration option で
- Test Framework
- 普通に設定すれば OK です
いろいろ比較
劇的に動作が早くなったのだけど、そもそも Docker Desktop の何が原因でここまで遅かったのか謎い。ディレクトリのマウントが cifs なので遅いのは仕方ないけどそれだけでこれほど遅くなるものか・・というぐらい遅い。
いろいろ試行錯誤してみたところ、とあるプロジェクトのユニットテストの実行結果は以下の通りでした。
Docker Desktop local Time: 1.02 minutes Docker Desktop cifs Time: 1.37 minutes Hyper-V local Time: 1.16 minutes Hyper-V cifs Time: 1.53 minutes VirtualBox local Time: 44.74 seconds VirtualBox cifs Time: 1.17 minutes VirtualBox vboxsf Time: 1.13 minutes ESXi local Time: 34.54 seconds ESXi cifs Time: exceeded the timeout of 300 seconds
Hyper-V/VirtualBox/ESXi は CentOS7 に Docker を入れて Remote API 経由で実行しています。Hyper-V と VirtualBox はローカルホスト上なので TLS 無し、ESXi は別ホストなので TLS ありです。
local はローカルとのフォルダ共有は無しでリモートサーバ上にソースファイルを配置した場合です。Docker Desktop は無理やり MobyLinuxVM のホスト側の /var/lib/
にソースを配置して実行しました。
local よりも cifs や vboxsf のほうがパフォーマンスが落ちるのは当然として、なぜか Hyper-V よりも VirtualBox のほうがだいぶ性能が良い結果になっています。Hyper-V が劣ってるとも考えにくいですが・・・ディスクがシンプロビジョニングとかそういう違いがあるのかもしれない。
ESXi は物理ホスト自体が別なので比較の対象としては参考になりません。しかも Wi-Fi 経由なので cifs だとおそすぎて composer がデフォルトの 300s だとタイムアウトしました。これは実用に耐えません。逆に local だともっともパフォーマンスが出ました、単純に ESXi のホストがそれなりなのでホストの性能差と思います。
なお、cifs のマウントオプションは次の通り指定しました(Docker Desktop はマウントオプションいじれないので素のままです)。
rw,vers=3.02,iocharset=utf8,soft,nobrl,noperm,nounix,actimeo=1
vboxsf は次のとおりです。
rw,ttl=1000
↑の結果は MySQL を --innodb_flush_method=nosync --innodb_flush_log_at_trx_commit=0
を付けて実行しています。これらを付けないときと比較すると次のようになりました(nosync と書いているのが設定ありの方)。
Docker Desktop Time: 1.57 minutes Docker Desktop nosync Time: 1.02 minutes Hyper-V Time: 1.42 minutes Hyper-V nosync Time: 1.16 minutes VirtualBox Time: 51.13 seconds VirtualBox nosync Time: 44.74 seconds ESXi Time: 40.49 seconds ESXi nosync Time: 34.54 seconds
いずれもフォルダ共有はなしでリモートサーバ上にソースを配置した場合の結果です。結構な違いがありました。
???
なぜか同じ PC の Docker Desktop でも接続している Wi-Fi ? によってだいぶパフォーマンスが異なる?
Docker Desktop local Time: 1.02 minutes -> 1.09 minutes Docker Desktop cifs Time: 1.37 minutes -> 2.37 minutes Hyper-V local Time: 1.16 minutes -> 1.24 minutes Hyper-V cifs Time: 1.53 minutes -> 2.23 minutes VirtualBox local Time: 44.74 seconds -> 45.33 seconds VirtualBox cifs Time: 1.17 minutes -> 1.23 minutes VirtualBox vboxsf Time: 1.13 minutes -> 54.86 seconds
左と右でそれぞれとある別の場所での結果ですが、Hyper-V + cifs という組み合わせで著しく性能が劣化することがある?
と思ったんだけど再起動直後に試したらそうでもなかった。
Docker Desktop cifs Time: 1.37 minutes -> 1.50 minutes
なぜかはわからないけれども、Hyper-V で cifs だと長時間起動しているとパフォーマンスが超悪くなる音でもあるのだろうか(作業環境の都合で片方が長時間起動しっぱなしでもう片方はわりと細切れ・・シャットダウンはせずにスリープしてるだけだけど)。
さいごに
改めて確認すると、重いなーと感じたら再起動しておけばわりと改善するような気がしたので、普通に Docker Desktop 使っておけば良いような気がしてきた。
物理ホストが別な ESXi にコードを配置して実行するのが早いのですが、unison で同期させるのを忘れて「なぜか更新されない」となりそうです。
vagrant との共存考えると VirtualBox が都合が良いので Docker Desktop はやめて Docker Machine とすることも考えられますが・・・
WSL 2 で Docker が動くようになればそれで全て解決?なのかもしれないし、ひとまず現状維持しておこう。