Help us understand the problem. What is going on with this article?

Docker x Laravel めちゃくちゃ遅い Docker for Mac を爆速化する

Docker for Mac 遅い問題

たくさんの記事が出ている通り、Docker for Macが遅いというのはみんなが思うところ...
Dockerの勉強を始めて1年が経ち、今まで目を逸らしていた問題にようやく重い腰をあげてこの問題に取り組む時が来た...!

Docker for Mac 遅い原因

大量のファイルが変更され、ホストとコンテナ間でファイル同期処理が行われるため。
(Docker for Macの速度がアップデートで勝手に改善してくれればいいな...と願って待ってました...。)

Docker for Mac 遅い問題の解決策

  • 案1. docker-syncを使う
  • 案2. 仮想マシンにLinuxを入れて、docker-composeを使う
  • 案3. バインドマウントオプションのcached, delegatedを使う
  • 案4. ボリュームマウントを使う

上記の参考記事は大体この4つの案のどれかで対応してました。
すべて試した訳じゃないのでハッキリしたことは言えないですが、どの方法もメリットデメリットはあるので許容できる方法を使うのが良いかと思います。おそらくどの方法も高速化は見込めると思います。

案1. docker-syncを使う

https://github.com/EugenMayer/docker-sync

docker-syncの仕組みとしては、
通常のバインドマウントでホスト側のディレクトリをコンテナ側へマウントするのではなく、rsyncやunisonといった仕組みでホスト側のファイルをコンテナ側に転送するようです。

デメリットとしては、docker-syncを導入しなくてはいけないというそのもの。
構成が大きく変わるし、学習コストが跳ね上がる気がする。

案2. 仮想マシンにLinuxを入れて、docker-composeを使う

Docker for Macが遅いんだから、Linuxを通して使っちゃえという案。
既存のdocker-compose.yml等の変更が一番抑えられるし、本番により近い構成でとても良い方法に思える。

デメリットとしては、私はMacで開発したいんだ。

案3. バインドマウントオプションのcached, delegatedを使う

https://docs.docker.com/docker-for-mac/osxfs-caching/

Dockerの17.04以降に追加されたオプション cached, delegated は読み込みや書き込みの一貫性を担保しない代わりにパフォーマンスが向上されるというものです。

デメリットとしては、あれれー?コードの変更が反映されないんだけど?みたいなことが多発しそう。。

案4. ボリュームマウントを使う

vendorやnode_modulesのデータを名前付きボリュームに格納して、ホスト側とコンテナ側と分離して管理する案。
同期処理が発生しないので、その分速くなる。

デメリットとしてはホスト側でもcomposer installしなければIDEの自動補完等の恩恵が受けられなくなる。

どの案にするか?

個人的には案2と案4で迷うところですが、今回は案4を試したいと思います。

Laravelで大量のファイルの読み書きが多く発生するディレクトリは?

下記の3つです。(他にもあったら教えてください)

  • vendor (composer installで生成されるディレクトリ)
  • node_modules (npm, yarn installで生成されるディレクトリ)
  • storage (フレームワークのキャッシュファイル等が生成されるディレクトリ)

今回は、vendorとnode_modulesに高速化対策したいと思います。
storageは別の話になるので別記事で対応します。

案4を採用した結果、当社比20倍の高速化に成功!

結果から言うと大満足です!
(まだ実際にこの案で業務利用したことないので要検証ですが...)

$ time composer install
real    2m40.358s => 0m8.383s
user    0m4.003s => 0m1.849s
sys 0m8.209s => 0m2.526s

Composerのインストールは152秒の短縮、20倍の高速化に成功!!!

$ time npm install
real    2m 35.33s => 0m 20.54s 
user    0m 44.96s => 0m 19.64s
sys 0m 19.44s => 0m 7.56s

npmのインストールは135秒の短縮、7倍の高速化に成功!!!

案4. ボリュームマウントを使う 手順

本題のDocker高速化の手順です。

環境

上記の環境を使用してます。
StarやLGTMもらえると記事を書くモチベになるので良い記事だなと思っていただければぜひよろしくお願いします!

前提

Laravelのインストールまで完了している状態

docker-compose.yml を編集

下記、コメントした4行を追記してください。

docker-compose.yml
version: "3.8"
volumes:
  php-fpm-socket:
  db-store:
  vendor-store: # add
  node_modules-store: # add
services:
  app:
    build: ./infra/docker/php
    volumes:
      - php-fpm-socket:/var/run/php-fpm
      - ./backend:/work/backend
      - vendor-store:/work/backend/vendor # add

  web:
    build: ./infra/docker/nginx
    ports:
      - 80:80
    volumes:
      - php-fpm-socket:/var/run/php-fpm
      - ./backend:/work/backend
      - node_modules-store:/work/backend/node_modules # add

  db:
    build: ./infra/docker/mysql
    ports:
      - 3306:3306
    volumes:
      - db-store:/var/lib/mysql

設定内容を反映させます。

$ docker-compose up -d

設定内容を反映させるとコンテナ側の vendor ディレクトリの中身はなくなってるはずです。
ホスト側の vendor の中身はあってもいいし、中身を削除しちゃってもいいです。
それぞれ独立したのでホスト側の vendor の中身がなくなっても動きます。

あと若干のコンテナの起動が遅くなってる気はします。(私だけかもしれない)

一応、下記のコマンドでファイルがどうなってるか確認しましょう。
node_modulesは最初は元々ファイルないのでホスト側もコンテナ側も空ですけど...

$ ls -l ./backend/vendor
$ docker-compose exec app ls -l ./vendor

$ ls -l ./backend/node_modules
$ docker-compose exec web ls -l ./node_modules

下記のコマンドを実行して依存パッケージをインストールします。
(爆速になってるはずです)

$ docker-compose exec app composer install

Nodeの場合は下記のコマンドになります。

$ docker-compose exec web npm install

このままだと新しくライブラリを追加してもホスト側の vendor ディレクトリは更新されないので、下記のコマンドを使用して更新します。

$ docker-compose run --rm -v $(pwd)/backend:/code -w /code app composer install
$ docker-compose run --rm -v $(pwd)/backend:/code -w /code web npm install
  • docker-compose run サービスに対して1回限りのコマンドを実行します
  • --rm コンテナ実行後に削除
  • -v バインドボリュームを指定
  • -w コンテナ内のワーキング・ディレクトリを指定

バインドボリュームでホスト側のディレクトリに依存ライブラリをインストールしています。
(これはちょっと時間かかります)

ホスト側、コンテナ側のそれぞれに依存ライブラリのインストールする必要があるので、総合的な所要時間は増えてしまったように思う。
ホスト側はIDEの自動補完ができればいいので、そう頻度高く更新しなくても良いのかなと思って妥協してます。

Makefile を作る

長いのでMakefileにすると便利です。

Makefile
composer-install:
    @make composer-install-for-container
    @make composer-install-for-host
composer-install-for-container:
    docker-compose exec app composer install
composer-install-for-host:
    docker-compose run --rm -v $$(pwd)/backend:/code -w /code app composer install

Makefileでは変数展開は実行前に行われてしまうので $$(pwd) のように $$ 2個付けると良いです。

最後に

記事中にも言いましたが、あまり検証していないのでこの記事を参考にお試しいただけた方はコメントいただけると嬉しいです!

ucan-lab
Backend Developer at ROLO. I love PHP and I'm focusing on Laravel, Docker, GraphQL.
https://u-can.pro
yyphp
PHPerが毎週集まり、ざっくばらんに情報交換する雑談コミュニティ
https://yyphp.connpass.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away