はじめに
以前からいろんなところで話していますが、僕は普段、手元のMacには言語系のランタイムとかは入れておらずVS CodeとDocker for Macだけ入れてRemote Containersの環境で開発しています。
この環境自体はとても便利でいいのですが、一点大きな問題があります。
それは遅いということ。自分の場合は最近だとJSでの開発が多いのですが、例えばNext.jsで開発している場合に以下のような操作が特に遅く感じます。
- yarn install
- yarn add
- yarn jest
- next dev
- next start
- next build
yarn jest
とかnext dev
が遅いのは起動だけだったりします。起動後は割とさくさく動きます。
なぜ遅いのか
実はこれ、Docker for Macの問題なんですね。結構有名な話です。
僕のMacはIntel Macなんですが速いと噂のM1 Macだと改善するかと思いきやそうでもないようです。詳細は割愛しますが、原因はMacのFile Systemとの相性問題なんですね。ファイルシステムの同期周りに時間がかかるようで、先のコマンド群はいずれもファイルシステムに細かい書き込みが発生するんですよね。なので遅いみたいです。
このあたりにもその議論があります。
あとはこっちも。
中の人もこんな感じでなかなか改善は遠そうです。
We are aware this is still a big pain point and we are sorry we cannot find a way to fix it faster at the moment. We have added a couple more bits in like experimenting with Dev Environments and the Volumes UI to try and make it easier to work with your code in a container or in a volume to alleviate some of this pain.
とは言いつつも最近新しい変更を試したりはしてるみたいです。ただ、いずれにせよ解決までは時間がかかりそうです。
というわけで絶望し、諦めていたのですが同じRemote Container仲間の偉大なる知人(@yasu2704)から有力な情報がもたらされて、試したら効果あったのでそれを紹介します。
何をやるのか
ヒントは公式ドキュメントで示されていました。
これは特定のフォルダの保存先としてNamed Volume (名前付きボリューム)
を利用するというもの。上記のページの説明によるとMacとかWinの場合、VM内でコンテナを実行するのですが、その場合のバインドマウントは遅いそうな。さらにMacの場合、前述のファイルシステムの問題もあるみたいなのでNamed Volumeを使って書き込みパフォーマンスを改善しちゃおうということ。
なお、リポジトリ全体をこのボリュームにすることも可能みたいなんだけど、それだとMac上でファイルが残らなくなってしまう。それもまあいいんだけど自分の場合、React Nativeを使っているプロジェクトがあってシミュレータとかの実行はMacから直接実行する必要があるのでMac上でもファイルが見えてる必要がある。
というわけで、ひとまずnode_modules
とbuild
あたりだけこれにする。
計測
比較のためにNamed Volumeを使わない場合と使う場合の速度を簡単に計測してみる。計測にはNext.jsのアプリをyarn create next-app
で作成して生成されたpackage.json
の解決にどのくらいの時間がかかるかってのにしてみた。とりあえずの比較なので5回の平均値にする。あと検証に使うプロジェクト以外は閉じて他にコンテナが動いていない状態にしてる(普段は常時4〜5個のプロジェクトを開いているのでその数分以上のコンテナが動いている)。それ以外のアプリも大体落としてあるけどそこまでしっかりは環境整えてない。でも試す前後で同じものが動いている状態ではある。なのであくまでも参考程度に。
名前付きボリュームを使ってない場合
まず使ってない場合。計測に使ったプロジェクトのdevcontainer.json
はこちら。既存のやつを使いまわしたので関係ない設定とかも入ってるけど気にしないでください。
{ "name": "Existing Dockerfile", "context": "..", "dockerFile": "../Dockerfile", "build": { "target": "dev" }, "settings": { "terminal.integrated.shell.linux": null }, "extensions": [ "visualstudioexptteam.vscodeintellicode", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint", "hookyqr.beautify", "alefragnani.bookmarks", "lacroixdavid1.vscode-format-context-menu", "eamodio.gitlens", "oderwat.indent-rainbow", "ionutvmi.path-autocomplete", "chrmarti.regex", "humao.rest-client", "wayou.vscode-icons-mac", ], "forwardPorts": [ 3000 ], }
そしてDockerfile
はこちら。いたってシンプル。
FROM node:16.9.1 AS dev RUN echo "source /usr/share/bash-completion/completions/git" >> ~/.bashrc
計測はシンプルにyarn install
にかかる時間を測るだけだ。かかった時間はyarn install
完了時に画面に出力されている。なお、実行のたびにnode_modules
フォルダの中身を削除するのはもちろん、yarn.lock
も削除してコンテナのリビルドもしている。どれくらい結果に関係あるかはわからないけど。
検証に使ったpackage.json
、つまりyarn create next-app
してできあがったものの中身はこちら。
{ "name": "my-app", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "next": "11.1.2", "react": "17.0.2", "react-dom": "17.0.2" }, "devDependencies": { "eslint": "7.32.0", "eslint-config-next": "11.1.2" } }
そして5回計ったときの結果がこちら。
回数 | 時間(秒) |
---|---|
1回目 | 57.57 |
2回目 | 72.42 |
3回目 | 76.59 |
4回目 | 77.85 |
5回目 | 79.87 |
平均 | 72.86 |
平均で72.86秒と結構遅い。なお、この遅さには試したときのネットワーク環境の問題も多分にある。今これを書いている自宅の1室はWiFiの電波が弱めで速度が出にくいことだったこともあり余計に時間がかかっている。
Named Volumeを使う場合
続いてNamed Volumeを使う場合。この場合は先のdevcontainer.json
にNamed Volumeを使う設定をする必要がある。
実際には以下の内容をdevcontainer.json
に追加する。
"mounts": [ "source=sample-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume" ],
source=
の箇所は自分の環境にあわせてお好みで。また、この設定をしたらコンテナをリビルドする必要がある。
公式ドキュメント上ではpostCreateCommand
でオーナーを変える以下の設定も入れるようになっているが、僕の環境ではこれは不要。
"postCreateCommand": "sudo chown node node_modules"
なぜ不要かというと、Remote Containersではコンテナ内での操作を行うユーザをremoteUser
というプロパティで設定できるんだけど、自分の場合は先に示したようにこの設定をしていない。この場合に、コンテナ内で作成されるファイルのオーナーがrootになってしまうのでMac側からは権限エラーが出てしまうかと思いきや、Docker for Macではこのあたりをうまく処理してくれるようになっていてMac側からはMacのユーザがファイルのオーナーとなっている。
Windowsの場合はわからないのでご自分の環境で確認してください。
さて、そしてこちらも5回計ってみる。
回数 | 時間(秒) |
---|---|
1回目 | 31.58 |
2回目 | 31.45 |
3回目 | 29.95 |
4回目 | 29.06 |
5回目 | 29.76 |
平均 | 30.36 |
正直、思った以上の効果があった。平均で30秒強半分以下になったじゃないか。
ネットワークの環境が悪かったのだがそれでも効果が出た事自体には変わりない。もう少しまともな環境で試せば
Macからどう見えているか
さて、このときにMac側からはどう見えているのか確認してみた。ご想像の通りかと思うけどマウントポイントとなるnode_modules
フォルダはあるけど中身は空の状態。当たり前と言えば当たり前。
なお、React Nativeのプロジェクトの場合、普段の開発はRemote Container上で行いつつ、iOSのシミュレータでの確認はMac側でpod install
してからのyarn ios
を実行していたのでそれがどうなるか気になっていたが試したところ普通に動いた。正確にはマウントポイントとなっているnode_modules
フォルダだけはMac側でも見えていて中身は空の状態。この状態でMac側でyarn install
してあげたらMac側のnode_modules
配下に依存パッケージが入ってくるので普通にpod install
からのyarn ios
で問題なかった。
結論とまとめ
少なくとも普通のJSのプロジェクトだととても効果があることがわかった。なので手元の環境では全プロジェクトで採用していく。Pythonでもsite-package
とかを設定すればいけると思う。
でも、node_modules
だけだと当然ながら上であげたyarn dev
とかは速くならないので、ここまで効果あるならやはりリポジトリ全体をNamed Volume上に置きたくなってくる。そうしたときに問題なのはやはりMac側では全く操作できなくなるってことか。両方から操作するのって僕の場合はReact Nativeの環境いじる場合だけなので大した影響がない気もする。Next.jsのプロジェクトなら.next
フォルダだけでもNamed Volumeに置いてあげるとこの辺も改善するのかも。
なお、そこまでするならおとなしくWindowsでやればいいじゃんと思うかもしれないが、前述のとおりReact Nativeを使ったアプリ開発もしているのでXCodeが必要なのだ。