この記事は、ニフティグループ Advent Calendar 2023 18日目の記事です。
こんにちは。会員システムグループでエンジニアをしている山田です。
前回はDocker Composeを利用し、コンテナ上でアプリケーションを実行する環境を整えました。今回はコーディングまでを含めた開発環境を整えていきます。
コンテナ環境の課題
コンテナという独立した環境を用意したので、コーディング作業もコンテナ内で行いたいところです。LinterやFormatterなどのツールもコンテナ内で完結させることにより、統一された開発環境をチームメンバー全員に提供できます。
一方でコンテナ環境は基本的にCUI環境です。GUIアプリケーションを利用する方法がなくはないですが、ローカルPC側・コンテナ側とも設定が煩雑になるため、基本的にCUIアプリケーションを利用することになります。こうなるとVSCodeなどのリッチなIDEを利用できません。
Dev Containers
前述の問題を解決するのがDev Containersです。
これは元々「VSCode Remote – Container」と呼ばれるVSCode拡張機能だったもので、現在ではコア部分がVSCodeとは独立したOSSになっています。最近ではIntelliJなどでも使えるようになっているようです。
仕組みとしては以下のようになっています。前回のモノレポ構成にDev Containersを追加した図です。
コンテナ作成時にVSCodeを追加インストールし、VSCodeごとコンテナ内で動かします。ローカルPC側のVSCodeはコンテナ内のVSCodeと通信し、画面描画のみを担当することになります。エディタ自体がサーバ・クライアント構成をとることで、コンテナ内で開発環境を完結させつつ、リッチなエディタを利用することができます。
設定方法
今回はVSCodeでの設定方法を紹介します。設定方法はDockerfileを利用する方法と、Docker Composeを利用する方法の2種類がありますが、後者の方が柔軟なのでこちらで解説を行います。
VSCode拡張機能のインストール
VSCodeをインストールし、Dev Containers拡張機能をインストールしておきます。
Docker Composeの用意
前回用意したようなDocker Composeの定義ファイルを用意しておきます。単一のcompose.ymlでも、統合機能を利用したcompose.yml + compose-dev.ymlのような複数ファイル構成でも、どちらでも可能です。
設定ファイルの用意
設定ファイルは .devcontainer/ディレクトリに配置するので、このディレクトリを作ります。これはコンテナごとに用意することになります。
ポリレポであれば
1 2 3 4 5 6 |
repository/ - .devcontainer/ - compose-devcontainer.yml - devcontainer.json - src/ - compose.yml |
のような構成になりますが、モノレポであれば
1 2 3 4 5 6 7 8 9 10 11 12 |
repository/ - frontend/ - .devcontainer/ - compose-devcontainer.yml - devcontainer.json - src/ - backend/ - .devcontainer/ - compose-devcontainer.yml - devcontainer.json - src/ - compose.yml |
のように、各サブプロジェクトごとに用意することになります。
設定ファイルとしては以下の2つが必要です。
compose-devcontainer.yml
名前は何でも良いのですが、動作上書き用のDocker Compose定義ファイルを用意します。
1 2 3 4 5 |
# compose-devcontainer.yml services: <開発対象コンテナのservice名>: command: /bin/sh -c "while sleep 1000; do :; done" |
あらかじめ用意したcompose.ymlではアプリケーションが全て起動してしまうため、コーディング後の反映にコンテナ再起動が必要になってしまいます。これは面倒なので、commandの書き換えによって「ただ動き続けるだけ」のコンテナにしてしまい、アプリケーション実行は自力でコマンドを叩くようにします。
devcontainer.json
Dev Containers特有の設定ファイルです。最低限必要なのは以下の設定になります。
1 2 3 4 5 6 7 8 9 |
{ "name": "frontend", "dockerComposeFile": [ "../../compose.yml", "compose-devcontainer.yml" ], "service": "frontend", "workspaceFolder": "/usr/src/app", } |
name | エディタ上での表示名 |
dockerComposeFile | Docker Composeの定義ファイル .devcontainerディレクトリからの相対パスで記述する 複数指定することで -fオプションと同じ動きになるので、最後に前述のcommand上書き用のファイルを指定する |
service | 開発対象となるコンテナの、Docker Compose上でのservice名 |
workSpaceFolder | コンテナ内で、VSCodeが開くディレクトリのパス compose.ymlでソースコードをマウントした先のパスになるはず |
起動方法
VSCodeで開発対象コンテナのプロジェクトルートディレクトリを開きます。 .devcontainerを置いたのと同階層です。
モノレポの場合は開くディレクトリを間違えやすいので注意してください。
1 2 3 4 5 6 |
repository/ <- ココじゃなくて - frontend/ <- ココ!!! - .devcontainer/ - backend/ - .devcontainer/ - compose.yml |
開いたら、「Cmd+Shift+P (WindowsはCtrl+Shift+P)」でコマンドパレットを開き、「Reopen with Container」を実行します。
またはプロジェクトを開いた時点でダイアログが出るはずなので、こちらからでも同じ操作が可能です。
初回起動時はコンテナ起動に加えてVSCodeのインストールなどが走るので時間がかかります。無事起動すると左下が「Dev Container」または「開発コンテナー」になります。
アプリケーション実行
commandの書き換えによりアプリケーションの実行を止めているため、自力でアプリケーションの実行が必要になります。
VSCode内蔵のターミナルはコンテナ内に接続されているため、ここからアプリケーション起動コマンドを実行しましょう。
1 2 3 |
# Node.jsアプリケーションの場合 pnpm install pnpm dev |
コンテナの終了
VSCodeを閉じた時点でコンテナも同時に終了されます。
ただし閉じた後すぐに開きなおすと前のコンテナの終了が完了しておらず、ポート干渉などでコンテナ起動に失敗することがあります。慌てずにリトライしましょう。
コンテナ定義を変えた場合
Dev Containersは一度起動・停止した後は前回起動したコンテナを再利用しようとします。compose.ymlやDockerfile、devcontainer.jsonなどを書き換えた場合はコンテナ定義の再読み込みが必要です。
すでにDev Containersを開いた状態の場合、再読み込みはコマンドパレット(Cmd+Shift+P)から「Rebuild Container」を実行します。
開く前の場合はコマンドが「Rebuild and Reopen in Container」に変わります。
追加設定
VSCode拡張機能
Dev Containersで利用されるVSCodeはコンテナに新規インストールしたものなので、ローカルPC側のVSCodeでインストール済みの拡張機能は引き継がれません。
devcontainer.jsonにはエディタごとのカスタマイズ設定があり、ここに拡張機能を記載することでインストールすることが可能です。
1 2 3 4 5 6 7 8 9 10 11 12 |
{ ... "customizations": { "vscode": { "extensions": [ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "stylelint.vscode-stylelint" ] } } } |
ユーザ拡張機能設定
Vimキーバインドを使いたい、Copilotを使いたいなど、プロジェクトごとではなくユーザごとにインストールする拡張機能を変えたい場合があります。この場合は、VSCodeのユーザ設定に記述を加えます。ユーザ設定はmacであれば $HOME/Library/Application Support/Code/User/settings.jsonにあるはずです。
1 2 3 4 5 6 |
{ ... "dev.containers.defaultExtensions": [ "github.copilot" ] } |
defaultExtensionsに設定した拡張機能は、そのユーザが起動するDev Containers全てに追加されるようになります。上記例ではGithub Copilot拡張機能が追加されることになります。
VSCodeの設定
VSCodeのユーザ設定は引き継がれますが、チーム内で統一する設定はリポジトリ内で管理すると良いでしょう。これは通常のVSCodeと同じく、 .vscode/settings.jsonに記載します。
1 2 3 4 |
{ "editor.formatOnSave": true, ... } |
なお拡張機能と同じくdevcontainer.jsonに書くことも可能ですが、設定の反映に「Rebuild Container」の実行が必要になってしまうため、あまりお勧めしません。
注意点
暗黙的なファイルコピー
Dev Containersは起動時にコンテナ定義を書き換えてVSCodeをインストールするほか、以下のファイルをローカルPCからコピーします。
- ~/.gitconfig
- ~/.ssh/*
これはコンテナ内でもGit操作を行えるようにするための措置ですが、プロキシの設定やローカルPC上にしかないツールの指定を行っている場合、コンテナ内でのGit操作に失敗する場合があります。
このような場合、 devcontainer.jsonでコンテナ起動後のコマンドを指定できるので、該当箇所を消す処理を入れるようにしましょう。
1 2 3 4 5 6 |
{ ... // postAttachCommandで、コンテナが起動してVSCodeにアタッチされた時の処理を指定できる // プロキシやテンプレートの削除、エディタ指定の上書きを実施 "postAttachCommand": "(git config --global --unset http.proxy || exit $(($? == 5 ? 0 : $?))) && (git config --global --unset commit.template || exit $(($? == 5 ? 0 : $?))) && git config --global core.editor vim" } |
モノレポ時のコンテナ切り替え
モノレポ環境では以下の点に注意が必要です。
- 同時に複数のコンテナでDev Containersを起動することは不可能
- Dev Containersの対象コンテナを切り替える場合、「Rebuild Container」の実行が必要
Dev ContainersはDocker Composeで起動したコンテナのうち、開発対象コンテナ1つだけにVSCodeをインストールします。複数同時にインストールするように作られてはいないので、複数コンテナ同時に開発を進めることはできません。必ず切り替えて使う必要があります。
またコンテナを切り替えた場合には既存コンテナをリセットし、VSCodeをインストールするコンテナを変える必要があります。このため、切り替え時に毎回「Rebuild Container」を実行する必要があります。忘れがちなので注意が必要です。
実際に使ってみて
最後に1年以上Docker ComposeとDev Containersによる開発を行ってみたメリット・デメリットをまとめてみます。
メリット
- 環境によって起こるエラーに悩まされなくなった
- メンバージョイン時の負担が少ない
- PC交換時にもすぐ開発を始められる
- DB接続状態での自動テストを考慮に入れられるようになった
やはり環境の可搬性・再現性という面が大きいです。git cloneしてVSCodeを開くだけで環境が出来上がるので、細かくメンテしていた構築手順書が不要になりましたし、新メンバーが来ても「とりあえず動かしてみて!」ができるようになりました。CI上でもDocker Compose一発なのは変わらないので、テストの幅も広がっています。
デメリット
- Dockerを動かす分のオーバーヘッドがある
- Windowsだとファイル更新検知ができない
一方で性能面での問題はどうしてもあり、MacbookだとM1 mac以降は問題ないのですが、Intel macだと正直厳しいと感じる時が多かったです。Docker for mac特有のI/O性能問題で遅いと感じることもあります。
またDocker for Windows特有の問題として、ファイル更新(inotify)がWindows – コンテナ間で共有されないという問題があります。ホットリロードが効かなくなるなどの弊害があるため、Windowsユーザが多い環境では注意が必要です。
まとめ
以上2回にわたってコンテナによるローカル開発環境を作る話でした。
デメリットも挙げましたが、環境構築に迷わなくなる利点は大きく、周りのプロジェクトでもコンテナベースの開発環境が浸透していっています。私個人としてもHomebrewやasdfの使い方を忘れるくらいに利用しています。
本番でコンテナを使っていなくとも、ローカル開発環境だけでも十分にメリットはあるので、皆さんも導入してみてはいかがでしょうか。