日々常々

ふつうのプログラマがあたりまえにしたいこと。

DockerのアップデートしたらTestContainersが動かなくなったとかゆー

起こったこと

SpringBoot(3.5.7以前)でTestContainersを使っていて、Dockerをアップデートしたらテストが通らなくなった。

という状況のお話です。

環境を最新化するのは重要ですが、やると動かなくなるというのは困ったもの。 それを嫌ってアップデートを止めるというのも現実解としてはありますが、現在はどんどん更新していく方が主流かと思います。 動かなくなるというのも減りましたし。

対処

正道: SpringBootを更新する

リリースノートを読んでいればわかります。

Release v3.5.8 · spring-projects/spring-boot · GitHub 一番上にデカデカと書いています。

あとこういうのは雑にアップデートしてみたら動いたりします。 私は SpringBootを更新したら動いた→リリースノートを見た(教えてもらった) という順でした。 リリースノートを見るのは大事。順番はどっちでも。

SpringBoot3.5.8でもSpringBoot4.0.0でも対処されてます。

回避

とはいえアップデートができるとも限らない。

考えられる対応はこんな感じです。

  • Dockerのダウングレード
  • DockerのAPIバージョンを指定する(おすすめ)
  • Testcontainersを使用するテストをこの環境では諦める

回避するならAPIバージョン指定で対応するのがよいです。

Dockerのダウングレードはそれはそれで手間なのと、Dockerのセキュリティ対応などを見送るってことなのでよろしくない。 他の方法がない場合とか、全く手掛かりもない場合に切り分けのために試してみるなどでは有効なアプローチですが、なるべくなら取りたくない選択肢です。

諦めるのは、まぁ、諦められるならそれでも。 一部のテストでしか使ってないとか、CIでは動いてて自分の手元の環境だけだからーとかなら、まぁ、うん。(奥歯に物を挟みながら)

参考: https://github.com/testcontainers/testcontainers-java/issues/11212

個人レベルでの回避方法

ホームディレクトリに .docker-java.properties ファイルを作って以下を記述します。

api.version=1.44

プロジェクトごとに設定するとか実行時に指定するとかの方法もありますが、Dockerのアップデートなどの統制をとっていないということは環境が個々人に委ねられているということかと思います。 Testcontainersを使用しているプロジェクトが複数あるなら、環境問題としてHOMEに置いてしまうのがよかろうかと思います。

で、早くSpringBootのバージョンアップしよーぜーって言い続ける。 HOMEに置く場合、対象のバージョンアップが終わったら削除するの忘れないように……。 (ってのを気にするなら実行時にパラメタ渡すほうがいいのか?)

指定の方法は色々あって、詳しくは docker-java のREADMEをみるとよいですが、たとえば -D で渡したりもできます。

./mvnw -Dapi.version=1.44 clean test

ほんとの一時的な回避ならこんな感じで。(フォークしてるとかだと -D をテスト実行時に届くように細工がいった記憶あるけど、手元に手頃なのはなかった。)

チームレベルの回避ならテスト実行時のクラスパスに含まれるように docker-java.propertis を配置してしまうのがよかでしょう。 多人数で個々人が仕組みを理解せず「とにかく ~/.docker-java.properties を置く」とかやってしまうと、当面は動くでしょうがDockerやdocker-javaの更新でどちらかが指定したAPIバージョンに対応しなくなったら動かなくなっちゃうでしょうし。 それができるならSpringBootバージョンアップやっちゃえよ

仕組みと対処と選択の話

対応だけが知りたいならここまででOK。以下はどうやって調べるかとか、どうやって選ぶかとかの話です。

SpringBootTestがDockerを動かすにあたってはこういう構造になっています。

SpringBootTest -> Testcontainers -> docker-java -> DockerEngine

今回の動かなくなるのはDockerEngineのバージョンが 29.0.0 に更新されたことで、docker-javaとDockerEngine間がつながらなくなることによります。

SpringBootがDockerを動かすのに登場するライブラリにTestcontainersとdocker-javaの2つあるのがポイント。

Testcontainersもメジャーバージョンが上がっていて、こちらではDocker29.0.0の対応がされていることが謳われています。

「動きゃいいんだよ」という暫定対処をするなら、SpringBoot3.5.7 + Testcontainers2.x としてしまっても動いたりはするのですが、SpringBootは3.5.8でTestcontainers2.xにせず、Testcontainers2.xにしたのはSpringBoot4から。 ということで SpringBoot3.5.7 + Testcontainers2.x とするのはあまり筋が良くなさそうです。 ちゃんとしないとTestcontainersが1系と2系が混在することになっちゃいますし、この組み合わせはSpringBootDependenciesで検証されているものではないため、「SpringBootを使用する際はSpringBootDependenciesのバージョンに乗っかる」(私的)ベストプラクティスに逆行することになります。

とにかく更新すれば動くのは動かしてみたらわかりますが、自分の使っている道具がどのようになっているかを理解して対応を考えることも大事です。 全ての道具の低レベルなところまで理解しろというのは不可能に近い昨今ですが、こういう時に一段階だけでも深堀りしておくなどして少しずつ守備範囲を広げるのがいいんじゃないかなって。

ということで調べ方ですが、たとえばSpringBoot側の対応を見ると docker-java.properties というものでAPIバージョンを指定することで対応としていることがわかります。(SpringBootのコミットとかを辿ってみてください。)

動作している対処方法から、それがどのように作用しているかを見て把握するというアプローチ。 常にできるものでもありませんが、答えがわかっているところから辿るのは繋がることが保証されているのでやりやすいです。 (答えに辿り着くかどうか不明なものを試行錯誤するのは苦痛に感じることもあるわけで。) 不具合でも「直し方はわかっているけどなぜそれで直るのかはわからない」と言うのは稀によくある話。 とにかく直すのは大事ですが、直したとに「なぜそれで直るのか」を調べて裏をとっていくのもある種のスキルかと思います。

docker-java.properties は名前が示す通りTestcontainersではなくdocker-javaの設定です。 今回はファイル名がそのままなのでわかりやすかったですが、たとえば hoge.properties のような名前だったら「このプロパティは誰が読み取ってどう使っているんだ?」と探っていくことになります。 いつかは到達するのでしょうが、これも面倒臭い。わかりやすいファイル名で作ってくれていることに感謝です。

以上から「docker-javaの使用するAPIバージョンをテスト時に指定すればOK」ということになります。 ここまでわかったので、docker-javaの説明を見に行きましょう。

docker-java/docs/getting_started.md at main · docker-java/docker-java · GitHub

色々な指定方法があることがわかります。 その中で自分の状況にあった指定方法を選ぶことになります。

  • システムプロパティ
    • 実行時に都度指定するならこれが良さそう。単発実行ならこれかな。
  • 環境変数
    • システムプロパティは届かない時もあるからできるなら環境変数の方が良いかもだけど、今回は . を含むから使えないかな。
  • クラスパスの docker-java.properties
  • HOMEの .docker-java.properties
    • 個人の環境のDockerのバージョンに依存する話と捉えればこれかな。アップデートされても普通に動いてるうちは削除するの忘れたりしそうだなぁ。

みたいな。

万能な解があることってあんまないので、使える選択肢と、その選択肢はどういう状況の時に使うかと、今の自分(たち)の状況を見て選択していくのは重要な訓練かなと思います。 できれば言語化もしておくといい。私もうまくできているわけではないけど、うまくなるためにはやるしかないかなって。

きっとAIに書かせたら選択肢や条件もそれっぽいことを書いてはくれるんだろうけど、その内容を評価して選択するためには書く練習もいると思うんだよね……少々自分に老害感も感じつつ。

おまけ: SpringBootの自動更新について

SpringBoot4.0.0 がリリースされていますが、まだ3.5系を使用し続けているところも多いかと思います。

dependabotなど自動で最新化するツールを導入している場合であっても、SpringBoot4への更新を見送っていることがSpringBoot3.5.8への更新を覆い隠す状態になっていることで、やるべきアップデートを受け取れなくなるなどもあります。 「常に最新」という方針でないなら、バージョンを指定して調整する方法も道具箱に入れておきましょう。

たとえばdependabotなら「(当面は)4系はignoreにしておく」のような設定をしておく感じです。

Dependabot options reference - GitHub Docs