devcontainer × docker-compose で共有サーバーで複数人開発を行う
Visual Studio Code の devcontainer (Remote Containers拡張機能)を使用した共有サーバーでの開発について書きます。
課題感
プロジェクトで共通のサーバーを用意してみんなで使いまわそう!みたいになった時、ミドルやDBの管理はどのようにしていますか?
開発の中では以下のような要望がでてくると思います。
- ミドルを複数バージョンで試したい
- 開発の途中でDBをいったんリセットしたい
- 同じ構成でも複数ブランチのソースコードを試したい
複数人で共通サーバーを使ってこれをやろうとした場合、ポートの割り当てを1つ1つ分けて設定するなど七面倒くさい管理が発生してしまいます。
- 開発に必要なサーバー一式を管理できるようにしたい
- 複数ユーザーでそれを使えるとなおよい
という課題感がありました。
課題解決への筋道
docker-compose で開発環境に必要なサーバー一式をそろえる
「課題感」にも書きましたが、複数構成のミドルウェアを1つのホストで動作させるのは骨が折れます。1つのミドルに対して複数のコンポーネントが依存関係にあったりすると地獄ですね。
それを解決するのがDockerに代表されるコンテナ技術であり、それらのコンテナをまとめて管理できるのが docker-compose
です。「docker-compose.prod.yml」と「docker-compose.dev.yml」といった感じで複数 docker-compose を用意して、プロダクション環境に合わせた開発環境、新しいバージョンを取り入れたリリース前の開発環境みたいな感じで切り替えるのはよくあるケースだと思います。
docker-composeのプロジェクトファイルに記述されたサービスのポートをlocalhostにバインドして、localhost:9200に対してリクエストすることでelasticsearchコンテナのAPIを叩く、みたいなやつですね。
docker-compose のネットワーク構成を使用しよう
上記のやり方ではlocalhostのポートをバインドしてしまっているので、結局複数パターンの開発環境を立ち上げるためにはポート管理を手動で行う必要がでてきてしまいます。これではdocker-composeを使って環境を作ったり壊したりするのは楽になったものの、複数環境の比較が楽になったとは言えません。
そこで活用したいのが、dockerのネットワーク機能です。dockerのネットワークはコンテナ間の通信を分離する仮想的なネットワークで、同一ネットワークに属するコンテナ同士はそのコンテナのポートで通信をすることができます。つまり、開発者が操作する開発環境をdockerのネットワークにつなげられるようにしてしまえば、localhostのポートにバインドする必要なくコンテナと通信することができます。(実際にはランダムなポートがバインドされます)
docker-compose は、プロジェクトごとにコンテナ用のネットワーク(default)を構成し、各コンテナは自動的にそのネットワークに接続されます。つまり、docker-compose内に開発用のコンテナをサービスとして定義し、そこに乗り込んで開発することができれば、プロジェクトごとに一意のホスト(コンテナ)名・ポートで通信することができるのです。つまり、プロジェクトAとBで「elasticsearchホストの9200ポート」という見かけ上同じでありつつ別のコンテナと通信する、ということができます。
devcontainerを使用することで、docker-composeを使って構成されたサービスにアタッチして開発することができます。
共有サーバーでも使用できます
さらにうれしいのが、最近(2021年11月)、リモートのdockerホストに対してのdevcontainer使用が Remote SSH 拡張機能との組み合わせで行えるようになったことです。それについては以下に記事を書きました。
これができるようになったことで、共有サーバーに対して複数ユーザーでログイン、各々のユーザーがポートの管理無しに開発を行うみたいなことができるようになったのです。
基本的な設定方法
使い方については、vscodeのドキュメントが充実しているので、こちらを参照してください。
Tips&トラブルシューティング
EACCES: permission denied, mkdir '/tmp/vsch…
どうも複数ユーザーでの使用は想定されていないらしく(?)、各ユーザーの VSCode devcontainer の一時ファイルが /tmp 直下に作られてしまいます。それぞれオーナーは作成ユーザーとなるため、Aさんが試した後にBさんが使おうとしたりするとパーミッションの関係でエラーが出ます。
GitHub上でもエラーは報告されており、2022年6月現在解決されていませんが、回避策があります。
EACCES: permission denied, mkdir '/tmp/vsch · Issue #2347 · microsoft/vscode-remote-release · GitHub
mkdir -p /tmp/$USER export TMPDIR=/tmp/$USER
上記コマンドを ~/.bashrc
に書くことで、TMPDIRが /usr/$USER になるため、devcontainerの一時ファイルがユーザーごとに異なるフォルダに作成され、前に述べたような競合が起こらなくなります。
とりあえずは上記対応で問題は発生しなくなりますが、早いこと公式に対応してもらえると嬉しいなと思います。
他のコンテナを触りたい
基本的にdevcontainerで触れるのはアタッチされているコンテナのみです。つまり他のミドルやDBに対して何かしらAPIを叩きたいときはdevcontainerでいったん開発用コンテナに入って踏み台的にAPIを実行する必要があります。
ただ、それだとちょっと使いづらいので、開発用コンテナのlocalhostへの通信を特定のコンテナにリレーします。
通常のdevcontainerのセットアップでは、コンテナのcommandには /bin/sh -c "while sleep 1000; do :; done"
として無限に待機するように設定しますが、そこを socat
を実行します。ここでは私が開発に使ってるdevcontainerの設定を示します。
personium-core-dev/docker-compose.local.yml at main · yoh1496/personium-core-dev · GitHub
command: >- bash -c ' socat TCP-LISTEN:61616,reuseaddr,fork TCP:activemq:61616 | socat TCP-LISTEN:9300,reuseaddr,fork TCP:elasticsearch:9300 | socat TCP-LISTEN:9200,reuseaddr,fork TCP:elasticsearch:9200 | socat TCP-LISTEN:11211,reuseaddr,fork TCP:memcached-lock:11211 | socat TCP-LISTEN:11212,reuseaddr,fork TCP:memcached-cache:11211 | socat TCP-LISTEN:18888,reuseaddr,fork TCP:personium-engine:8080 '
リレーするものが多かったので冗長になってしまいましたが、このようにパイプでつないであげることで、localhost:61616に対してはactivemqコンテナの61616に転送したり、9200ポートをelasticsearchの9200ポートにリレーするといった設定を同時に行うことができます。
socat
はコンテナを落とさない限り実行されますので前の設定のような無限ループは必要ありません。
終わりに
devcontainer × docker-compose で快適開発ライフを!