開発環境の遍歴とDevContainerのススメ
最近は開発環境にDevContainerを使っています。
プロジェクト毎に開発環境がコンテナで独立して構築できるので、複数のプロジェクトで開発する必要がある際にとても良いです。
これまでの開発環境の遍歴と、最終的にDevContainerに移行した理由やメリットについてお話ししたいと思います。
これまでの遍歴
Docker for Mac
まずはMacにDocker Desktop、Docker for Macを入れた状態です。
開発中はペアプロを頻繁するため変更をホットリロードで検知し、lintやspecが流れるようにしています。
開発初期は問題なかったのですが、コードが増えていくにつれて次第に実行時間が遅くなってきてしまいました。
各所で言われていますが、ホストとコンテナ間でbind mount
したボリュームへのアクセスが遅い問題です。
Mutagen
上記の問題を改善するために取り入れたのが「Mutagen」でした。
MutagenとはDocker社が後に買収することになる高性能なファイル同期ツールです。
Dockerコンテナがホストのディレクトリを直接利用するために使うbind mount
が非常に遅いため、この部分をサードパーティーのMutagenに任せることで速度改善を試みるという手法です。
Mutagen compose というツールもあるのでdocker composeと合わせて簡単に使うことができます。
この結果、lintやspecの実行時間をかなり短縮することができ、快適に開発を行える様になりました。
Mini PC + Ubuntu Server
Mutagenを使い始めてからしばらく経過し、開発機をintelMacからM1Macに買い換えました。
その結果、当時はApple Siliconが登場したばかりということもあり、x86系のイメージを動かすために様々な場面でワークアラウンドを強いられていました。
また動作できてもQEMU等のエミュレータを介して動くことになり、実行時間が遅くなってしまいました。
そこでいっその事Linux環境で開発したいと思い始め、NUCというintel製のMiniPCを購入し、Ubuntu Serverをインストールして開発環境を構築しました。
その際に購入したものや、セットアップの詳細については以下のブログにまとめています。
開発用にNUCを買った | DC5
結果として、ワークアラウンド等を駆使しなくてもよくなり、
VSCodeの拡張機能、Remote SSHのおかげでリモートにあるデメリットも感じることなく、快適に開発できる環境を手に入れることができました。
当初危惧していた代替ツールについてもlazydocker等とても充実していて困ることはなかったです。
DevContainer
上記の環境で問題をなく開発を行えていたのですが、この頃から複数のプロジェクト環境下で作業することが多くなってきました。
プロダクトで使っている言語等のバージョンについては、既にコンテナ化されているので困ることはないのですが、プロジェクトによってlintやspec等のバージョンに差異があるためローカル環境の切り替え作業が発生していました。
もっと簡単にできないものかとDevContainerを試してみることにしました。
DevContainerとは
DevContainerとは、開発環境をコンテナで管理し、誰でも同じ開発環境を立ち上げれるようにするための仕組みです。
開発用のコンテナを立ち上げ、その中にツールをインストールし、コンテナ内部でVS Code Serverが立ち上がっているのでホストのVSCodeから操作/開発することが可能になっています。
最初に見たときは、リモート環境で使えないなら再度bind mount
の問題にぶつかりそうだな...と思っていたのですが、Remote SSHにも対応しており、試してみることにしました。
DevContainerでRails環境を作ってみる
Development Container Templates によく使われる開発環境のテンプレートが既に用意されており、VScodeから簡単にDevContainerを立ち上げることができます。
今回はこちらのテンプレートを使ってRuby on Rails+PostgresのDevContainer環境を立ち上げてみようと思います。
まずは作業ディレクトリを作成します。
❯ mkdir devcontainer-rails-example
❯ cd devcontainer-rails-example
VSCodeのコマンドパレットからadd dev container configuration files
を選択します。
Development Container Templates の一覧が表示されるので「Ruby on Rails+Postgres」を選択します。
選択すると以下ディレクトリのファイルがプロジェクトに配置されます。
pullが完了するとVSCodeからリロードを促されます。
リロードが完了すると自動的にdocker buildが始まるのですが、この際にプロジェクトを開いている環境のDockerを使って構築してくれます。
なのでRemote SSHでプロジェクトを開いておけばリモート環境のDockerを利用してくれるということになります。
docker buildが完了し、そのままVScodeでターミナルで開くとベースコンテナにアタッチした状態になっています。
コマンドを打ってみるとRailsが入っていることが確認できます。
リモートホスト(Mini PC + Ubuntu Server)からdocker psするとコンテナが立ち上がっていることが確認できます。
❯ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
544165f406a5 vsc-devcontainer-rails-example-...-uid "/bin/sh -c 'echo Co…" 8 minutes ago Up 7 minutes devcontainer-rails-example_devcontainer-app-1
6a278d88efdf postgres:latest "docker-entrypoint.s…" 8 minutes ago Up 7 minutes 5432/tcp devcontainer-rails-example_devcontainer-db-1
続けてrails new
をしてブラウザからアクセスしてみます。
$ bin/rails db:create
Created database 'devcontainer_rails_example_development'
Created database 'devcontainer_rails_example_test'
vscode ➜ /workspaces/devcontainer-rails-example (main) $ bin/rails s
=> Booting Puma
=> Rails 7.2.0 application starting in development
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 6.4.2 (ruby 3.3.4-p94) ("The Eagle of Durango")
* Min threads: 3
* Max threads: 3
* Environment: development
* PID: 4979
* Listening on http://127.0.0.1:3000
Use Ctrl-C to stop
特に設定をしていなくても、自動的にポートフォワーディングまでしてくれるため、ローカル環境からアクセスすることができます。
devcontainer.json
DevContainerの設定は、プロジェクトディレクトリ内の .devcontainer
フォルダに配置される devcontainer.json
に記述します。
このファイルには、使用するイメージや、コンテナにインストールする拡張機能、ツールやパッケージ、ポートフォワーディングの設定を記述します。
features
features
を使うとツールの追加やセットアップをプラグイン形式で行うことができます。
例えばコンテナでnodejsを使える様にしておきたい場合、以下のようにfeaturesで指定するとbuild時にインストールしてくれます。
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby-rails-postgres
{
"name": "Ruby on Rails & Postgres",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "16"
}
}
}
この状態でVSCodeで保存、リロードするとrebuildが始まりnodejsがインストールされます。
vscode ➜ /workspaces/devcontainer-rails-example (main) $ node --version
v16.20.2
内部的には以下のshellが実行されています。
その他のfeatureのついてはこちらから探すことができます。
extensions
開発時にするプラグインについてはcustomizations.vscode.extensions
で追加することが可能です。
たとえばcopilotとRubyのLSPを使いたい場合だと以下の様に記述します。
{
"name": "Ruby on Rails & Postgres",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "16"
}
},
"customizations": {
"vscode": {
"extensions": [
"Shopify.ruby-lsp",
"GitHub.copilot"
]
}
}
インストールしたextensionsはコンテナ上の/home/vscode/.vscode-server/extensions
下に配置されます。
vscode ➜ /workspaces/devcontainer-rails-example (main) $ ls -la /home/vscode/.vscode-server/extensions/
total 28
drwx------ 6 vscode vscode 4096 Aug 22 03:26 .
drwxr-xr-x 6 vscode vscode 4096 Aug 22 03:25 ..
drwxr-xr-x 5 vscode vscode 4096 Aug 22 03:26 dbaeumer.vscode-eslint-3.0.10
-rw-r--r-- 1 vscode vscode 2528 Aug 22 03:26 extensions.json
drwxr-xr-x 5 vscode vscode 4096 Aug 22 03:26 github.copilot-1.223.0
drwxr-xr-x 5 vscode vscode 4096 Aug 22 03:26 github.copilot-chat-0.18.2
drwxr-xr-x 5 vscode vscode 4096 Aug 22 03:26 shopify.ruby-lsp-0.7.16
Lifecycle hooks
その他にもLifecycle hooksにコマンドを記述することによって、コンテナのライフサイクルごとに実行したいコマンドを設定することができます。
たとえばbundle install
やnpm install
をしたい場合などはpostCreateCommand
にコマンドを書くことでコンテナが作成完了した後に実行してくれます。
その他
Dev Container CLIというVSCodeから切り離されたCLIツールもあるので、他のエディタでも使えるかもしれません(未検証)
おわりに
今回は現在使用しているDevContainerの概要と、それに至るまでの経緯についてご紹介させていただきました。
システムが複雑化する中で、コマンド一つで環境を整えることが難しくなることもありますが、それでも可能な限り簡単にセットアップできることが理想ですよね。
特に、新しいメンバーがスムーズに開発に取り組めるような環境づくりが一番重要かと思います。
環境を整えたとしても、避けられない小さな問題が発生することはありますが、そうした問題を気軽に修正し、早めにPRを出すことで、チーム内でのコミュニケーションが活発になり、心理的なハードルを下げることにもつながるのではないかと思います。
Discussion