🌮

開発環境の遍歴とDevContainerのススメ

2024/08/23に公開

最近は開発環境にDevContainerを使っています。

プロジェクト毎に開発環境がコンテナで独立して構築できるので、複数のプロジェクトで開発する必要がある際にとても良いです。

これまでの開発環境の遍歴と、最終的にDevContainerに移行した理由やメリットについてお話ししたいと思います。

これまでの遍歴

Docker for Mac

まずはMacにDocker Desktop、Docker for Macを入れた状態です。

開発中はペアプロを頻繁するため変更をホットリロードで検知し、lintやspecが流れるようにしています。
開発初期は問題なかったのですが、コードが増えていくにつれて次第に実行時間が遅くなってきてしまいました。
各所で言われていますが、ホストとコンテナ間でbind mountしたボリュームへのアクセスが遅い問題です。

Mutagen

上記の問題を改善するために取り入れたのが「Mutagen」でした。
MutagenとはDocker社が後に買収することになる高性能なファイル同期ツールです。

https://www.docker.com/ja-jp/blog/mutagen-acquisition/
https://mutagen.io/documentation/introduction

Dockerコンテナがホストのディレクトリを直接利用するために使うbind mountが非常に遅いため、この部分をサードパーティーのMutagenに任せることで速度改善を試みるという手法です。

Mutagen compose というツールもあるのでdocker composeと合わせて簡単に使うことができます。
https://mutagen.io/documentation/orchestration/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のおかげでリモートにあるデメリットも感じることなく、快適に開発できる環境を手に入れることができました。
https://code.visualstudio.com/docs/remote/ssh

当初危惧していた代替ツールについてもlazydocker等とても充実していて困ることはなかったです。
https://github.com/jesseduffield/lazydocker

DevContainer

上記の環境で問題をなく開発を行えていたのですが、この頃から複数のプロジェクト環境下で作業することが多くなってきました。

プロダクトで使っている言語等のバージョンについては、既にコンテナ化されているので困ることはないのですが、プロジェクトによってlintやspec等のバージョンに差異があるためローカル環境の切り替え作業が発生していました。

もっと簡単にできないものかとDevContainerを試してみることにしました。

DevContainerとは

DevContainerとは、開発環境をコンテナで管理し、誰でも同じ開発環境を立ち上げれるようにするための仕組みです。
https://code.visualstudio.com/docs/devcontainers/containers

開発用のコンテナを立ち上げ、その中にツールをインストールし、コンテナ内部でVS Code Serverが立ち上がっているのでホストのVSCodeから操作/開発することが可能になっています。

最初に見たときは、リモート環境で使えないなら再度bind mountの問題にぶつかりそうだな...と思っていたのですが、Remote SSHにも対応しており、試してみることにしました。
https://code.visualstudio.com/docs/devcontainers/containers#_system-requirements

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からリロードを促されます。
https://github.com/devcontainers/templates/tree/main/src/ruby-rails-postgres/.devcontainer

リロードが完了すると自動的に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 に記述します。

このファイルには、使用するイメージや、コンテナにインストールする拡張機能、ツールやパッケージ、ポートフォワーディングの設定を記述します。
https://containers.dev/implementors/json_reference/

features

featuresを使うとツールの追加やセットアップをプラグイン形式で行うことができます。
https://containers.dev/implementors/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が実行されています。
https://github.com/devcontainers/features/blob/main/src/node/install.sh

その他のfeatureのついてはこちらから探すことができます。
https://containers.dev/features

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にコマンドを記述することによって、コンテナのライフサイクルごとに実行したいコマンドを設定することができます。

https://containers.dev/implementors/json_reference/#lifecycle-scripts

たとえばbundle installnpm installをしたい場合などはpostCreateCommandにコマンドを書くことでコンテナが作成完了した後に実行してくれます。

その他

Dev Container CLIというVSCodeから切り離されたCLIツールもあるので、他のエディタでも使えるかもしれません(未検証)
https://github.com/devcontainers/cli

おわりに

今回は現在使用しているDevContainerの概要と、それに至るまでの経緯についてご紹介させていただきました。

システムが複雑化する中で、コマンド一つで環境を整えることが難しくなることもありますが、それでも可能な限り簡単にセットアップできることが理想ですよね。
特に、新しいメンバーがスムーズに開発に取り組めるような環境づくりが一番重要かと思います。

環境を整えたとしても、避けられない小さな問題が発生することはありますが、そうした問題を気軽に修正し、早めにPRを出すことで、チーム内でのコミュニケーションが活発になり、心理的なハードルを下げることにもつながるのではないかと思います。

Discussion