Macで使うVS CodeとRemote Containerの性能を大幅改善

はじめに

以前からいろんなところで話していますが、僕は普段、手元の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の問題なんですね。結構有名な話です。

僕のMacIntel Macなんですが速いと噂のM1 Macだと改善するかと思いきやそうでもないようです。詳細は割愛しますが、原因はMacのFile Systemとの相性問題なんですね。ファイルシステムの同期周りに時間がかかるようで、先のコマンド群はいずれもファイルシステムに細かい書き込みが発生するんですよね。なので遅いみたいです。

このあたりにもその議論があります。

github.com

あとはこっちも。

github.com

中の人もこんな感じでなかなか改善は遠そうです。

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)から有力な情報がもたらされて、試したら効果あったのでそれを紹介します。

何をやるのか

ヒントは公式ドキュメントで示されていました。

code.visualstudio.com

これは特定のフォルダの保存先としてNamed Volume (名前付きボリューム)を利用するというもの。上記のページの説明によるとMacとかWinの場合、VM内でコンテナを実行するのですが、その場合のバインドマウントは遅いそうな。さらにMacの場合、前述のファイルシステムの問題もあるみたいなのでNamed Volumeを使って書き込みパフォーマンスを改善しちゃおうということ。

なお、リポジトリ全体をこのボリュームにすることも可能みたいなんだけど、それだとMac上でファイルが残らなくなってしまう。それもまあいいんだけど自分の場合、React Nativeを使っているプロジェクトがあってシミュレータとかの実行はMacから直接実行する必要があるのでMac上でもファイルが見えてる必要がある。

というわけで、ひとまずnode_modulesbuildあたりだけこれにする。

計測

比較のために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が必要なのだ。

©Keisuke Nishitani, 2020   プライバシーポリシー