気になっていたのに触っていなかった技術としてdev containerがあった。
Dev Containerはその名前の通り、開発環境をコンテナ化する技術。 そうすると「開発環境とは何か」という話になるわけだけど、ここでいう開発環境とは「コードを書くための環境」であり、それは「コードを書くためのツールやライブラリ」を含む環境でもある。
アーキテクチャ
そうはいっても結局どういうことなのよという疑念は拭えないわけなので、より具体的なアーキテクチャを示す必要がある。以下はDeveloping inside a Container using Visual Studio Code Remote Developmentから引用したもの。
コンテナの中に、VS Code Serverがあり、ソースコードはローカルPCのものをコンテナにVolume Mountする。VS Codeの(UI周りを除く)拡張もコンテナ側で動作させる。これによって、ソフトウェア開発者がプロジェクトごとに必要なツールやライブラリを含んだ環境を簡単に構築、共有、再現できるようにしている。
もちろん、VS Codeだけあっても開発環境としては不十分なので、NodeやPythonといった言語のランタイムや、デバッガ等のツールも含めてコンテナに閉じ込める。 このアプローチにより、以下の問題が解決される。少なくとも解決に近づく。
- "私の環境では動作するがあなたの環境では動作しない"という問題の抑止
- 同一チームの中での開発環境構築をコード管理でき、構築作業も簡略化できる
- ローカルPCにツールを逐一インストールする必要がなくなり、PC環境を汚さなくて済む
設定ファイル
もちろん、この環境をチマチマと手で作っていたのでは結局環境構築の手間が大きくなってしまうし、コード管理できない。そこで、Dev Container metadataの仕様が決まっており、これに従って設定ファイルを作成することで、環境を構築できる。具体的には、.devcotainer
ディレクトリ配下の.devcontainer.json
へ記述することになる。
実際にブログ記述環境に使ってみる
このブログを記述する環境も、実は以下のようなランタイムやツールを必要としていた。
- node
- make
- hadolint
- textlint
- ShellCheck
- blogsync
というわけで、手始めにこのブログ記述環境をDev Container化してみる過程で、Dev Container化というのはどういうことかを理解することにする。
コンテナをどう作成するか
Dev Containerの一般的なテンプレートはTemplatesあたりに用意されているため、直接これを使っても良い。 他のオプションとしてはDockerfileを指定する、あるいは、Docker composeのファイルを指定するという方法もある。今回はDB等のサービスは使わないので、Dockerfileを指定する方法で進める。
Dockerfileを作成しツールをインストールする
nodeのRuntimeが必要だったので、Node.js Development Container Imagesを使うことにした。
dev containerに使うといっても、Dockerfileを使う以上dev containerを特別に意識することはない。 途中でhadolintとblogsyncをインストールする必要があったので、最終的に以下のようなDockerfileが爆誕した。
FROM mcr.microsoft.com/devcontainers/javascript-node:dev-20-bookworm ENV BLOGSYNC_VERSION v0.20.1 ENV HADOLINT_VERSION v2.12.0 SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN apt-get update \ # Install hadolint && curl -SL "https://github.com/hadolint/hadolint/releases/download/${HADOLINT_VERSION}/hadolint-Linux-arm64" -o /usr/local/bin/hadolint \ && chmod +x /usr/local/bin/hadolint \ # Install blogsync && curl -SL "https://github.com/x-motemen/blogsync/releases/download/${BLOGSYNC_VERSION}/blogsync_${BLOGSYNC_VERSION}_linux_arm64.tar.gz" | tar -xzC /tmp/ \ && mv /tmp/blogsync_${BLOGSYNC_VERSION}_linux_arm64/blogsync /usr/local/bin/ \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/*
VS Codeの拡張をインストールする
VS Codeの拡張をコンテナ側にインストールするには、.devcontainer.json
にcustomizations.vscode.extensions
を追加する。
devcontainer.json
は一応IDEに依存しない仕様となっているが、customizations
配下はプロダクト固有の設定を書いて良いことになっている。costomizations.vscode
はVS Code用の設定として、インストールすべき拡張が指定できる。
この辺りの詳細はSupporting tools and servicesを参照。
"customizations": { "vscode": { "extensions": [ "GitHub.copilot-chat", "GitHub.copilot", (snip) ] } }
逐一、必要な拡張を記述していくのは骨が折れるが、VS Codeの拡張機能メニューのコンテキストメニューから直接.devcontainer.json
に追加できる。
Workspaceに依存するツールを導入する
単純にDev containerを定義するシンプルなmetadataは次のようになった。
Dockerfileを指定するためにはbuild.dockerfile
を指定すれば良い。
{ "name": "はてなブログエディタ", "build": { "dockerfile": "Dockerfile" }, "forwardPorts": [ 3000 ], "postCreateCommand": "npm install", }
導入対象ツールであるtextlintはWorkspaceにあるpackage.json
で定義されている。これをコンテナに導入するためにはpostCreateCommand
でnpm install
を実行する。
DockerfileでRUN npm install
すれば良いじゃねーかという話はあるが、これはうまくいかない。
これは、VS CodeのWorkspaceにあるファイル(ここではpackage.json
)はコンテナが作成された後でマウントされるため。
the Dockerfile runs before the dev container is created and the workspace folder is mounted and therefore does not have access to the files in the workspace folder. A Dockerfile is most suitable for installing packages and tools independent of your workspace files.
Create a development container using Visual Studio Code Remote Development
このためにpostCreateCommand
というフックが用意されている。
Workspace外にある設定ファイルをコンテナに持ち込む
"開発環境"はVS CodeのWorkspaceに閉じない。$HOME/.config
(See: XDG Base Directory Specification)など、Workspace外にある設定ファイルをコンテナに持ち込まなければならないケースがある。実際、blogsyncは$HOME/.config/blogsync`に設定ファイルを置くことが想定されている。
これを実現するためには、.devcontainer.json
の中でmounts
を使う。mounts
はホスト側のファイルをコンテナにマウントするための設定。Dockerのマウント設定そのもの。
"mounts": [ { "source": "${localEnv:HOME}/.config/blogsync", "target": "/home/node/.config/blogsync", "type": "bind" } ]
あとはコンテナをビルドして、Workspaceをコンテナ側で開けば良い。
Workspaceを開いた後、blogsyncの設定ファイルがコンテナにマウントされていることが確認できる。
$ whoami; ls ~/.config/blogsync node config.yaml
使ってみて
コンテナの中で動作しているとは思えないほど軽快に動く。ローカルで開発をしている体験と何ら変わらない。 全員が同じ環境で開発を進められるというのは、プロジェクトにとってはありがたい。 このエントリもDev Containerを使って書いた。まったく違和感がない。
VS Code派とIntelliJ派が混在する環境ではどうなのだろう。