いしぐめも

プログラミングとかしたことを書きます。

【VSCode】コンテナを利用した開発環境の構築について【devcontainer】

Dockerコンテナを使った開発が結構便利で人にもオススメできるレベルになってきたのでご紹介します!正直コンテナって聞いただけで「ウッ」となってしまう方もいると思うので、極力メリットとかも書いてみたいと思います。

Dockerコンテナ×開発環境

そもそも、なんでDockerコンテナを開発環境で使おうという発想に至るのでしょうか?個人的には以下のような「Dockerコンテナが開発環境構築に向いているわけ」があると思います。

Dockerコンテナが開発環境構築に向いているわけ

その①:揮発性・冪等性

コンテナイメージは内部的にレイヤごとに分かれた構成になっていて、commitしない限りはそのコンテナイメージに変更が加わることはありません。つまり、変更をコミットせずにコンテナを落としてしまうと良くも悪くも変更した内容は消えてしまうわけです。本番でDockerコンテナを使用しようとするとこの特性は最初面倒に感じてしまうこともありますが、逆に言えば再現したい状態をコミットしておくことで、新しく作れば毎回同じ状態が再現される というのは試行錯誤するうえではとてもメリットがあるのです。 つまりこれは、ゴチャゴチャになった開発環境をリセットしたい!! という開発環境あるあるを解決できます。

その②:エコシステム

Dockerコンテナを立ち上げるときに使用するイメージはもちろん自分でDockerfileを利用して1から組み立てることもできますが、Dockerが優秀なのはその イメージをベンダーが提供していたり、またそれがレジストリから落とすことができる「エコシステム」 にあると思います。

たいていのミドルウェアはコンテナイメージも簡単に入手できるようになっており、開発環境構築はこのイメージを持ってくるだけで完了してしまうことが多いんじゃないでしょうか。

その③:軽量

上にあげたメリットはうまくやればVMでもディスクイメージを取っておいて使いまわせば達成できなくもないですが、Dockerコンテナの場合はカーネルはホストOSが担うので、軽量ですし起動も早いと言われています。VM立ち上がるの待たなきゃ~~~っていうのがなくなりますね。

また、VMを複数立ち上げると複数のカーネルが立ち上がってしまうのでそこでメモリなりCPUなりリソースを少なからず消費するということも頭に入れておきましょう。別個のカーネルで動いていなければならない要件がなければ、カーネルを複数の環境で使いまわせるコンテナを使った開発環境がリソース的には有利になります。(まぁそんなカツカツな環境で開発するなというのはありますが)

Dockerコンテナを開発環境に組み込む3パターン

ではそんなDockerコンテナの開発環境への活用ですが、以下のように3パターンぐらい考えることができます。

パターン1: 依存しているものを1つでもDockerコンテナにしてみる

例えば開発にelasticsearchを使っていたら、開発機にインストールしているelasticsearchをDockerコンテナに置き換えてみましょう!

docker run -d -p 9200:9200 elasticsearch 

といった感じで使っているポート番号と同じポートでバインドするようにして起動すれば今までと同様に開発をすることができます。

汚れてきて気に食わなくなったらコンテナを落としてみましょう。コマンド1つできれいさっぱり、メリットにあげた「揮発性」の素晴らしさを体感できることでしょう。

パターン2: docker-compose.ymlを書いてみる

パターン1では開発環境の一部に単体のDockerコンテナを使用しましたが、さらに踏み込んで docker-compose.yml を書いてみましょう。今までいちいちdockerコマンドを叩いていたのが、docker-compose up -d で済みますし、何より開発環境に必要なコンテナ一式を一挙に管理できる のが便利です。

パターン3: 開発環境そのものをdockerコンテナ化する(devcontainer)

パターン2を使っていると自身がコードを書いている開発環境自体もDockerコンテナにしたくなるはずです!

開発環境自体をDockerコンテナ化することによるメリットとしては、例えばコンパイラやツール類のバージョンごとにコンテナイメージを作成することで、使用するコンテナイメージを切り替えるだけでパッとコーディングする環境自体を変えられることが挙げられます。

実際にPersoniumというOSSの開発ではJava1.8とJava17で同時に開発が走った時期があり、また、同時にelasticsearchの6系と7系の切り替えを検討するといったそれなりに環境に対してもインパクトのある改修を行ってきたんですが、それぞれDockerコンテナを用意して

みたいな4パターンを駆使して開発することがありました。これを手動でやっていたとすると、PATHだったりポートだったりもうゴチャゴチャになってしまい効率はすごく落ちていたと思います。

さらにdocker-compose.ymlにコーディングする環境自体を入れてしまうことによって得られるメリットとして、dockerネットワークが開発環境に活用できることがあります。いままで、開発環境がホスト側にある前提で構築してきた「パターン1」「パターン2」では、ホストへのポートのマッピングが必須でした。例えば、elasticsearchを3パターン用意するとしたら、9201, 9202, 9203といった感じで開発環境のパターンごとにポートを決め打ちで準備 する必要がありました。これってすごく面倒で、開発環境の設定も変えなきゃいけなくなります。

普段意識することは少ないんですが、composeを使用するとdocker-compose.ymlファイル(プロジェクト)ごとに dockerネットワーク というものが作成されています。dockerコンテナ間で通信をするときはコンテナごとに設定されたホスト名で通信できると思うんですが、これはdockerネットワーク単位で管理されます。つまり、A/docker-compose.ymlB/docker-compose.yml で別々のdockerネットワークが作成されていると、Aのコンテナ内で名前解決できるコンテナはAに閉じ、B内のコンテナに向くことはありません。

よって、必要な開発環境のバリエーションに合わせて「開発環境入りのdocker-compse.yml」を分けてしまえば、開発環境からは「同じcomposeファイルのxコンテナ9200番ポート」という設定のまま開発を行うことができ、また他の開発環境から独立して開発を進めていくことができるのです。

例: 実際の開発環境

と、ここまで書いて実践例もないのもアレかなと思い、拙作で恐縮ですが、自分がPersoniumというOSSの開発に使用しているdevcontainerについて紹介したいと思います。

github.com

devcontainerは、Visual Studio Codeでコンテナを使用した開発環境を構築できる拡張機能です。

Personiumの開発環境構成

Personiumではバックエンドに以下のようなものを使用しています。

また、バックグラウンドでタスク実行を行うエンジンと呼ばれるものがあります(Tomcat上で動かします)。

  • personium-engine (タスク実行エンジン)

動かし方

それでは使い方を説明します。

まずは開発環境を配置したいディレクトリで以下のコマンドを実行して、必要なソースコードを揃えます。

git clone https://github.com/yoh1496/personium-core-dev.git
git clone https://github.com/personium/personium-core.git
git clone https://github.com/personium/personium-plugins.git
git clone https://github.com/personium/personium-plugin-base.git
git clone https://github.com/personium/personium-lib-es-adapter.git

実行することで、以下のようなディレクトリ構成になることを確認してください。

┗┳ /personium-core-dev
 ┣ /personium-core
 ┣ /personium-plugins
 ┣ /personium-plugin-base
 ┗ /personium-lib-es-adapter

(とりあえずpersonium-coreが依存するリポジトリを全部入れてますが、personium-core-devとpersonium-coreだけでも大丈夫です)

次に、クローンしたpersonium-core-dev フォルダをvscodeで開きます。開けたら、左下の緑色のボタンをクリックし、「Reopen in container」をクリックします。

すると、vscodeの画面が更新され、左下には「Dev Container」と表示されていると思います。これはpersonium-core-devフォルダ内の.devcontainer/devcontainer.json が読み込まれ、指定されたコンテナにアタッチされている状態を示します。

あとは煮るなり焼くなり開発することができるというわけです。

ちょっと解説

なぜこういう構成に至ったのかなどは過去記事を書いているのでそちらを参照してください。

yoh1496.hatenablog.com

yoh1496.hatenablog.com

当時に比べたらvscodeの.devcontainerは格段に取り回しやすく(以下の記事参照)なっており、ところどころ書いてあることが違ったりしますが根幹はあまり変わっておりません。

いつの間にかVSCode の Remote - Containersが超絶パワーアップしていた件【docker client 不要】 - いしぐめも

devcontainerのメリット: 開発環境構成のコード化

と、ここまでコンテナを開発環境に使用するメリットと実際に使用してみた例を紹介しましたが、もっとも重要なdevcontainerの利点の紹介が抜けていました。正直、ここまでの説明ではdocker-composeでコンテナあげて、アタッチしてVimで開発するのと何が違うの?という話にもなってしまうかなと思います。

コンテナを用いた開発環境を整える1つの方法として「devcontainer」を紹介しましたが、devcontainerの本当の利点はVSCodeを使用した開発環境構成一式をコード化できる」 点にあると思っています。

今回devcontainerを使うことで、vscodeとdocker環境を整えることができさえすればボタン一つで開発環境構築を行えることを紹介しました。言ってみれば自動化なわけですが、vscodeとdockerが動く環境であればどこでも開発できるのです。

また、それだけでなく開発環境構築を自動的に行えるようにすることで、今までにあったような躓きやすいステップバイステップの開発環境構築ドキュメントとは別の手段を提供できるようになります。Personiumプロジェクトでも、開発環境の構築方法をドキュメントとして公開していますが、これを初学者に順に追ってもらうのはとても大変で、おそらくこの時点で挫折してしまう人も多いのではないかと思います。

開発用環境の構築手順 · Personium

その点、devcontainerを使用した開発環境構築では、ボタン一つで開発環境が整うのです。つまり、ちょっと興味を持っただけの1OSSを開発するためにJavaの開発環境構築やらなにやらといったノウハウを身につける必要がなくなります。プログラミングの最初のハードルは開発環境構築だと言われている(著者調べ)中で、ここが簡単になるのは非常に助かるんじゃないでしょうか。

GitHub Codespaces

は?何言ってんだvscode導入もdocker環境構築も難しいじゃねえか!!

というご意見もあるかなと思います。ごもっともです。

そもそも開発環境用のリソースを準備できないんだが??

ごもっとも(?)です。

そういうケースでも、devcontainerを使った開発環境構築をできるようにしておくことで使える、便利なサービスがあります。

GitHub Codespaces です。

Codespaces | GitHub

このサービスはGitHub上で使用できるSaaSサービスで、リポジトリソースコードから開発環境がVM上に構築され、ブラウザから操作できるというものです。

新規Codespace作成画面

起動中…

devcontainerから構築された開発環境が!

拡張機能も使える!

ついに「vscodeとdocker環境を揃える」という手間も必要なくなってしまいました。後はお金を払うだけ!!! (個人向けのベータ版は現在無料で使えるみたいです。)

現在はベータ版が提供されています

こんな便利なサービスが使えてしまうなんて、我々は未来に来てしまったんだな~と感じずにはいられませんね。

そしてついについに、このCodespacesも全ユーザーに開放され、月に無料で60H使えるようになるらしいですね。素晴らしい!

www.publickey1.jp

終わりに

だらだらと書いていたらまとまりのつかない記事になってしまいましたが、Codespacesが無料開放というビッグニュースが出てしまったので、今まで下書き状態だったのを公開します。

devcontainer便利なので是非使ってみてください。他にもこんな便利な使い方あるよ~だとかこうするといいよ~といった内容があればコメントなどいただけると嬉しいです。Personium参加してみたいとかそういうのも大歓迎です。

ではでは、よいdevcontainerライフを

Windows 8.1 でも VSCode で Remote - Containers したい!

Visual Studio Code の Remote - Containersですが、Remote - SSH と組み合わせることで、リモートのDockerホストを用意すればクライアント側にdockerが入っていなくてもdevcontainerを使用することができるようになりました。

詳細は以下に記事にしたのでそちらを参照してください。

yoh1496.hatenablog.com

これってつまり、Windows 8(8.1)などのWSL2が導入できないようなOSでもバリバリdocker使って開発ができるというわけです。

スゴイ。

注意点

というわけで、Windows 8や8.1でやるにあたって注意点を書きます。

SSHにパスを通す

Windows 10 と Windows 8 の大きな違い。

それは sshコマンドが標準で入っていない 点です。今やSSHDサーバーもWindowsのオプション機能から追加できるようになっていますので、便利なものです。その点Windows 8は不便ですねえ。

というわけで、OpenSSHを適当なフォルダに展開してパスを通しましょう。

www.eaton-daitron.jp

上記サイトではWindows10で手順が書かれていますが、Windows8でもだいたい同じです(環境変数のダイアログが不便なぐらい?)。

終わりに

画期的ですね(注意点がもう少しあるかと思ったけど1個で終わってしまった)。Windows 8でdockerを使おうとするとVirtualBoxにdockerマシン作ってそこに乗り込む形でやらざるを得ませんでしたが、これならVirtualBoxの導入なんて不要ですね。

VSCode と OpenSSH さえあればよかったんやー。

Java 8 から Java 17 への移行メモ

私が携わっているOSS「Personium」のJava 8からの移行に係るトラブルに対しての対処を書いていきます。Personiumでは Java 8 → Java 11 → Java 17 というステップで移行を進めていく方針になっていて、今回は「Java 11 → Java 17」を行った際のメモです。よって「Java 8 → Java 11」で取り切れなかった不具合を含んでいる可能性も多分にありますのでご了承ください。

目次

Unsupported class file major version 61

こんなエラーが出まくりました。

Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 61
        at org.jacoco.agent.rt.internal_1f1cc91.asm.ClassReader.<init>(ClassReader.java:184)
        at org.jacoco.agent.rt.internal_1f1cc91.asm.ClassReader.<init>(ClassReader.java:166)
        at org.jacoco.agent.rt.internal_1f1cc91.asm.ClassReader.<init>(ClassReader.java:152)
        at org.jacoco.agent.rt.internal_1f1cc91.core.internal.instr.InstrSupport.classReaderFor(InstrSupport.java:247)
        at org.jacoco.agent.rt.internal_1f1cc91.core.instr.Instrumenter.instrument(Instrumenter.java:86)
        at org.jacoco.agent.rt.internal_1f1cc91.core.instr.Instrumenter.instrument(Instrumenter.java:118)

上記は jacoco のバージョンが低い時に出たエラーなんですが、surefireのバージョンが低いことでも同様のエラーが発生しました。

というわけで、こちらの問題に対しては pom.xmlJava 17 対応の jacoco, surefire のバージョンを記載してあげることで解決しました。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
</plugin>
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.7</version>
</plugin>

(バージョンは一例です。)

java.lang.reflect.InaccessibleObjectException

こういうやつです。

java.lang.reflect.InaccessibleObjectException: Unable to make field protected volatile java.io.InputStream java.io.FilterInputStream.in accessible: module java.base does not "opens java.io" to unnamed module @hogehoge

どうも調べるとJava 17ではより高位なAPIがあるので、今までのローレベルなAPIは叩くなよという例外だそうです。

とはいっても、テストではゴリゴリそのAPIを使ってしまっているみたいで仕方ないのでテスト時オプションで例外的に許可してあげます。以下のように surefire の実行時引数に --add-opens で必要なパッケージを列挙します。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
    <configuration>
        <argLine>
            --add-opens=java.base/java.io=ALL-UNNAMED
            --add-opens=java.base/java.lang=ALL-UNNAMED
        </argLine>
    </configuration>
</plugin>

文法的なものはドキュメント(Java Platform, Standard Edition Oracle JDK 9移行ガイド, リリース9)を参照していただくとして、例外の書式が以下だったとすると、

module <MODULE> does not "opens <PACKAGE>" to <TARGET_MODULE>

以下のように書けば素直に消えてくれるはずです。(TARGET_MODULEがunnamed moduleだった場合)

--add-opens=<MODULE>/<PACKAGE>=ALL-UNNAMED

ただこの、ワイルドカードが使えないみたいで、java.util java.util.regex java.util.zip など複数パッケージに対して行う必要がある場合は以下のように愚直に書いていくしか無さそうでした(やり方あったら教えてください)。

--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.base/java.util.regex=ALL-UNNAMED
--add-opens=java.base/java.util.zip=ALL-UNNAMED

コレ、1件解決したら次の1件、といった感じで芋づる式に出てくるので根気よく対処しましょう。ゆくゆくは根本対処を何かしたいところ・・・

It is forbidden to use algorithm ... 問題

SHA-1使ってたコードが軒並み動かなくなった問題です。

It is forbidden to use algorithm http://www.w3.org/2000/09/xmldsig#rsa-sha1 when secure validation is enabled

こちらは別記事にしたのでそちらをどうぞ。

yoh1496.hatenablog.com

Jerseyのバージョンが古すぎる

今まで Jersey 1.10とかいう化石のようなものを使っていたので、以下のようなエラーが出てしまっていました。

java.lang.IllegalArgumentException
    at jersey.repackaged.org.objectweb.asm.ClassReader.<init>(ClassReader.java:170)
    at jersey.repackaged.org.objectweb.asm.ClassReader.<init>(ClassReader.java:153)
    at jersey.repackaged.org.objectweb.asm.ClassReader.<init>(ClassReader.java:424)

というわけで一気に2系の2.36まであげました。

1から2への移行ということで、Jersey Test Frameworkの仕様も変わっていましたが、Grizzly + Servlet を使ったテストを実装するにあたって以下のサイトがとても参考になりました。

vividcode.hatenablog.com

終わりに

以上が、PersoniumをJDK17に移行させるにあたって詰まった主な内容でした。どなたかの参考になれば幸いです。

JDKアップデートしたらsecureValidationでテストがコケたので暫定対処をしました

JDK17へ移行しようとテストコードを流していたらRSA-SHA1を使用している部分で以下のエラー。

It is forbidden to use algorithm http://www.w3.org/2000/09/xmldsig#rsa-sha1 when secure validation is enabled

どうもsecureValidationが有効な状態だと、特定のアルゴリズムの仕様が許可されないセキュリティエンハンスのせいらしい。

以下は、Sean Mullan氏の記事。

JDK 17 Security Enhancements

--

本来であれば、正しいアルゴリズムを採用するようにコード側を変更すべきですが、いかんせん影響範囲が大きい部分だったりするといったんはこのアルゴリズムを許可してテストを通るようにして、アルゴリズム変更はまた別で行うべきかなと考え、暫定で許可することとしました。

以下は許可の方法です。

secureValidationで禁止するアルゴリズムを変更する

securyValidationの際に禁止されるアルゴリズムは、java.security に列挙されています。

Eclipse-Terumin JDK17 のコンテナイメージでは、以下の場所にありました。

/opt/java/openjdk/conf/security/java.security

これを覗くと確かに jdk.xml.dsig.secureValidationPolicy というポリシーがあり、それを見るとSHA-1とかもあります。

直接編集してしまってもいいんですが、さすがにシステム全体のポリシーを変えてしまうのもアレなので、テストを流したり実行したりする場合にのみこの変更を適用するようにします。

まずは適当な場所に java.security ファイルを作成します。(今回、自分の場合はプロジェクト直下においてしまいました)

そして、テスト実行する際に以下のオプションを指定するようにします。VSCodeでテストする際のvmArgsに書いたり、pom.xmlsurefireのargsに書いたりしてみました。

-Djava.security.properties=./java.security

java.securityを作成した位置に応じて適宜読み替えてください。そして、java.sourceの内容をもともとのjava.sourceからコピペし、必要なアルゴリズムをリストから削除して以下のようにしました。

jdk.xml.dsig.secureValidationPolicy=\
    disallowAlg http://www.w3.org/TR/1999/REC-xslt-19991116,\
    disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\
    disallowAlg http://www.w3.org/2001/04/xmldsig-more#hmac-md5,\
    disallowAlg http://www.w3.org/2001/04/xmldsig-more#md5,\
    disallowAlg http://www.w3.org/2007/05/xmldsig-more#sha1-rsa-MGF1,\
    disallowAlg http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1,\
    maxTransforms 5,\
    maxReferences 30,\
    disallowReferenceUriSchemes file http https,\
    minKeySize RSA 1024,\
    minKeySize DSA 1024,\
    minKeySize EC 224,\
    noDuplicateIds,\
    noRetrievalMethodLoops

削除したのは以下の3行です。

disallowAlg http://www.w3.org/2000/09/xmldsig#sha1,\
disallowAlg http://www.w3.org/2000/09/xmldsig#dsa-sha1,\
disallowAlg http://www.w3.org/2000/09/xmldsig#rsa-sha1,\

(ここは必要なアルゴリズムに応じて読み換えてください。)

動作確認

ここまでできたら、テストを流してみます。流れればOK。

おわりに

unmarshalXMLSignature 実行する系テストの以上系でエラーが出て、JDKのバージョンアップで javax.xml.crypto.dsig.XMLSignatureException の内容が変わってしまったのか~~~?!なんて思ってたらもっと根深いところに原因があったみたいです。

どなたかのご参考になれば幸いです。

Azure Functions の開発にはdevcontainerを使うと便利だった

Pythonを使用したAzure Functionsの開発に Visual Studio Codeのdevcontainerを使用しようというお話です。

はじめに

どうも、devcontainer大好きdevcontainerおじさんです。

最近Azure Functionsで普段は使用しないpythonを使って開発してみようと思い触ってみたんですが、いきなり npmでazure functions core toolsをインストール とか言われて、オイオイpython開発なのにnodeが要るなんて聞いてないぜとなったので、結構Azure Functions (Python)の開発環境整えるのって面倒くさそうだなと感じました。

そこでdevcontainerを使って開発環境を整えるとすごく便利ということがわかった(というかMicrosoftが便利なコンテナイメージを提供してくれている)ので、共有です。

Microsoftが提供してくれているAzure Functions用のコンテナ

github.com

提供されているコンテナイメージの一覧はAzure公式リポジトリから参照することができます。様々な種類があるうえに、Dockerfileも公開されているので参考にされてみてはいかがでしょうか。

Azure Functions用のコンテナイメージ(一部)

今回は、core toolsが同梱されている 4-python3.9-core-tools を使用してみました。

docker run --rm -it mcr.microsoft.com/azure-functions/python:4-python3.9-core-tools bash

上記コマンドで bash で試しに入ってみましたが、もちろんPythonは使用できますし、これからデプロイしたりするのに使用する Azure functions core tools も導入されていることがわかります。

Pythonが使用できるコンテナイメージ

Azure Functions用のCLI「func」が使える

始め方

ではこのコンテナイメージを使って開発する流れをご紹介します。始め方はとても簡単で、vscodeのメニューをポチポチいじるだけでdevcontainerを使った開発を始めることができます。

以下の手順はRemote - SSHで接続したリモート開発環境でもOKです。詳しくは↓

yoh1496.hatenablog.com

開発用フォルダをオープン

まずはFunctionsのソースコードが入るリポジトリ(フォルダ)を開きます。Functionsを作るのはこれから、という人は空のフォルダで大丈夫です。(例では適当にfuncapp001というフォルダを掘ってみました。)

開発に使用するフォルダを開く

devcontainerを準備する

というわけで次にdevcontainerを導入するんですが、ここではvscodeのウィザードを使用していきます。

左下の緑色のボタンをクリックし、開いたリモート関連のメニューから「Add Development Container Configuration Files...」をクリックします。

Add Development Container Configuration Files... を選択

すると追加するdevcontainerの種類を選ぶメニューが出るので、「Show All Definitions...」をクリック。

Show All Definitions... を選択

新たに開いた画面でコンテナイメージ一覧がずらっと出るので、「Azure Python 3」などと入力します。

検索単語を入力する

表示された「Azure Functions & Python 3」を選択します。

Azure Functions & Python 3

すると、.devcontainerフォルダが作成され、その中に devcontainer.jsonDockerfile が格納されていることが確認できると思います。 DockerfileFROM mcr.microsoft.com/azure-functions/python:4-python3.9-core-tools など、意図したコンテナイメージを使用する文言が含まれていることを確認します。

.devcontainerフォルダが作成される

その状態で、左下の緑色のボタンをクリックし、「Reopen in Container」をクリックすると、立ち上がったdevcontainerに今のフォルダが開かれた状態でvscodeが再起動します。

Reopen in Containerをクリック

あとはこれを使って通常通り開発していく、といった感じです。

終わりに

やはりdevcontainerは開発環境を整えるのにとても便利なツールですね。特に普段使用しないプログラミング言語を選択した場合など戸惑うことが多く、どうしても作っては壊しを繰り返すことになると思うので、コンテナでひとまとまりにして何度でもやり直せるのはとてもグッドだなと思いました。

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 拡張機能との組み合わせで行えるようになったことです。それについては以下に記事を書きました。

yoh1496.hatenablog.com

これができるようになったことで、共有サーバーに対して複数ユーザーでログイン、各々のユーザーがポートの管理無しに開発を行うみたいなことができるようになったのです。

基本的な設定方法

使い方については、vscodeのドキュメントが充実しているので、こちらを参照してください。

code.visualstudio.com

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 で快適開発ライフを!

いつの間にかVSCode の Remote - Containersが超絶パワーアップしていた件【docker client 不要】

以前、「Docker Desktop for Windows」無しで Visual Studio Codeで「Remote - Containers」を使用するために Docker client を手動ダウンロードするお話を書きましたが、それがもはや不要だそうです。

yoh1496.hatenablog.com

はじめに

なんとなく Remote - Containers のドキュメントを読んでいたら以下のような記述を発見しました。

もうDocker clientはいらないらしい

If you are using a Linux or macOS SSH host, you can use the Remote - SSH and Remote - Containers extensions together. You do not even need to have a Docker client installed locally.

code.visualstudio.com

どういうことか

超絶ハイパワーなサーバー上でコンテナを立ち上げて開発したいと思ったこと、あると思うんですが、以前までの拡張機能では単体でそういったことが行えず、ローカルに導入したDockerクライアントを使ってリモートのDockerホストを使用するという方法を取る必要がありました。

それが今回(?)、アップデートによって Remote - SSH と組み合わせて使うと、リモートで「Reopen in Container」するとリモートでコンテナが立ち上がり開発できるようになったみたいです!すごい!!!

ちなみに、これもしかして実は昔からできて、自分の勘違いだったんじゃ???って思いましたが、Webアーカイブから見るに、以前のドキュメントにはこのような記載はなく、新規に追加された機能っぽい です。

と、テンション上がっていたんですが、この機能 2021年11月に追加されたみたいですね・・・

実は 1.63(2021年11月) のリリースノートに書かれていました・・・

https://github.com/microsoft/vscode-docs/blob/main/remote-release-notes/v1_63.md#containers-version-0209x

もっと話題になればいいのに!!!というか自分のアンテナが低すぎか!!!!

メリット

ローカルマシンにdockerクライアント不要

これはうれしいですね。VSCode入れられればどこでもコンテナ使った開発ができると。

「devcontainer使うためにdocker導入してくださいね~~~~」っていうのが必要なくなります。

docker-compose.yml 不在問題がなくなる

ローカルマシン上のdocker-compose.ymlを使用してリモートのDockerホストにコンテナを立てるとどうなるか。

リモートからは見えない位置にdocker-compose.ymlがあるので、リモートから docker-compose down できなくなります。

リモートのコンテナを落とそうと思っても…

docker-compose.ymlがローカルにあるため失敗する・・・(従来)

それが今回、リモートホスト上のdocker-compose.ymlを使用してそのマシン上のDockerホストにコンテナを立てることになるので、そういった問題が発生しなくなります。

docker-compose.ymlはリモートにあるのでちゃんと落とせる!

手元のファイルシステムをコンテナのボリュームにマウントできる

今までの方法ではdevcontainerがあるフォルダはローカルマシンで、実行環境はリモートという構成になっていたためファイルシステムをマウントすることができず、代わりにvolumeを作成してマウントし、その中にgit cloneする必要がありました。

やってみればそれの何が不便というわけでもないんですが、devcontainerとソースコードをひとまとめに管理できるというのが良いかなと思います。

終わりに

あんまりDockerクライアントのみをローカルマシンにインストールしてリモートのDockerホストを使用するという方法を取ってる人はいないのかもしれませんが、個人的にこれがビッグニュースだったので書いてみました。