#chiroito ’s blog

Java を中心とした趣味の技術について

Windows + Podman でQuarkusのDevサービスを使おうとしたら心が折れたけど、いつの間にか対応されてた

Quarkus は、開発モードで動かすと、使用しているミドルウェアをコンテナで起動してくれる Dev サービスという機能があります。開発環境に環境を構築しないで良いのはとても楽です。

Dev サービス は、その中で Testcontainers を使用しており、Testcontainers は podman と docker の両方に対応してます。

結果

Windows で podman を使っているとダメでした。

追記

2023年12月1日時点で試したところ、動くようになってました!

原因

ログは以下です。

2023-05-23 17:12:01,954 WARN  [org.tes.doc.DockerClientProviderStrategy] (build-45) Unknown DOCKER_HOST scheme ssh, skipping the strategy test...
2023-05-23 17:12:01,998 INFO  [org.tes.doc.DockerMachineClientProviderStrategy] (build-45) docker-machine executable was not found on PATH ([略])
2023-05-23 17:12:01,999 ERROR [org.tes.doc.DockerClientProviderStrategy] (build-45) Could not find a valid Docker environment. Please check configuration. Attempted configurations were:
    EnvironmentAndSystemPropertyClientProviderStrategy: failed with exception IllegalArgumentException (Unsupported protocol scheme: ssh://root@localhost:50725/run/podman/podman.sock)As no valid configuration was found, execution cannot continue.
See https://www.testcontainers.org/on_failure.html for more details.
2023-05-23 17:12:02,009 WARN  [io.qua.dep.IsDockerWorking] (build-45) Unable to connect to DOCKER_HOST URI ssh://root@localhost:50725/run/podman/podman.sock, make sure Docker is running on the specified host
2023-05-23 17:12:02,553 INFO  [io.qua.dep.dev.IsolatedDevModeMain] (main) Attempting to start live reload endpoint to recover from previous Quarkus startup failure

podman は SSH を使用してリモートからの接続を受けるけるため、DOCKER_HOST に該当する物が ssh:// で始まります。Testcontainers はこれをサポートしてないらしく失敗しました。

追記(2023年12月1日)

Podman が /run/podman/podman.sock で接続を受けるようになっていました。

対処法

何もしなくても動きます!

以下のどれかの方法で Dev サービスが使えるようになります。

  • Podman Desktop で作成された Linux に Java をインストールして、Mavenを実行しましょう。
  • Windows に docker を入れましょう。
  • リモートの docker を使いましょう。
  • Mac か Linux 上で開発する。

Windows に docker を入れる方法は以下にあります。

b.chiroito.dev

Testcontainers でリモートの docker を使うには環境変数 DOCKER_HOSTを設定するか、 ~/.testcontainers.propertiesファイルで以下を設定します。

docker.host=tcp://<リモートのIP>:<ポート>

Windows に Docker 環境を構築したメモ

普段は podman を使っていましたが、一部 docker にしか対応していないツールが使えず不便なので、Docker 素人ながら Docker Desktop for Windows を使わずに Windows で docker を使う手順のメモ。

ゴール

WLSに docker を入れて、Windows から使えるようにします。

ツールだけが docker を使うため、Windows で docker CLI はインストールしません。

WLS に docker を入れる

これは、やり方が変わりそうなので、最新の情報をググってください。需要もあるのでたくさん見つかると思います。

大まかな流れは以下です。

  • WLS に Ubuntu を入れます。
  • docker をインストールします。

これで、この Ubuntu 上では Unix domain socket を使って docker が使えるようになります。

IntelliJ IDEA からは Docker Connectionで WSL というのを選ぶと、なぜかここまでで使えるようになります。なぜなのか分からないので、詳しい人教えてください。

Windows から接続できるようにする。

手順は以下の 2 つです。

  • docker が TCP でも待ち受けるようにする
  • Windows の環境設定をする

docker が TCP でも待ち受けるようにする

docker は実行するスクリプトと設定用のファイルがあります。 実行するスクリプトは/etc/init.d/dockerで、設定用のファイルは/etc/default/dockerです。

今回は、設定用のファイルにオプションを追加します。

まず、WSL 上にインストールしている docker cli が必要としているエンドポイントを調べます。

> docker context ls
NAME        DESCRIPTION                               DOCKER ENDPOINT               ERROR
default *   Current DOCKER_HOST based configuration   unix:///var/run/docker.sock

エンドポイントは unix:///var/run/docker.sock でした。

今回は、追加で TCP の 2375 番ポートで受け付けるようにします。これは tcp://0.0.0.0:2375 になります。

それでは、設定用のファイルの/etc/default/docker に記載しましょう。

DOCKER_OPTS という環境変数に、それぞれを-H のあとに記載します。

DOCKER_OPTS="-H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock"

できたら docker を再起動しましょう。

service docker restart

あとは、netstat などで 2375 ポートで待ち受けしているのを確認しましょう。実行後しばらくすると待ち受け始めます。

netstat -tln | grep 2375
tcp6       0      0 :::2375                 :::*                    LISTEN

何も表示されなければ待ち受けてません。上記のように1行出力されれば待ち受けしてます。 私の環境では、起動してから20秒ぐらいで待ち受けました。

Windows の環境設定をする

WLS の IP アドレスを取得します。

ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:19:a0:27 brd ff:ff:ff:ff:ff:ff
    inet 172.26.130.33/20 brd 172.26.143.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::215:5dff:fe19:a027/64 scope link
       valid_lft forever preferred_lft forever

WSL の IP アドレスは 172.26.130.33 です。

Dockerの設定には、WSL の IP アドレスと docker が待ち受けているポート番号である 2375 を使用します。

各種ツールで tcp://172.26.130.33:2375 を設定すると docker に接続できるようになります。 環境変数に設定する場合は、 DOCKER_HOSTtcp://172.26.130.33:2375 を設定します。

GraalVM の native-imageで PostgreSQL の DataSource が使えない

要約

Java の native-image で PostgreSQL の JDBC ドライバに含まれる DataSource を使うには META-INF/native-image/serialization-config.json が必要です。

org.postgresql.ds.common.BaseDataSource を継承している全ての DataSource が対象になる可能性があります。

問題

以下のような PostgreSQL へ接続するアプリを作って native-image 化しました。

public class NativeImageTest {
    public static void main(String[] args) throws Exception {

        PGPoolingDataSource ds = new PGPoolingDataSource();

        ds.setUrl("jdbc:postgresql://localhost:5432/postgres");
        ds.setUser("postgres");
        ds.setPassword("postgres");

        try(Connection con = ds.getConnection();
            PreparedStatement stmt = con.prepareStatement("SELECT datname FROM pg_database");
            ResultSet rs = stmt.executeQuery()) {

            rs.next();
            System.out.println(rs.getString("datname"));
        }
    }
}

JVM上では問題なく動いたにもかかわらず、native-image では動きません。

Exception in thread "main" org.postgresql.util.PSQLException: Failed to setup DataSource.
        at org.postgresql.ds.PGPoolingDataSource.initialize(PGPoolingDataSource.java:275)
        at org.postgresql.ds.PGPoolingDataSource.getConnection(PGPoolingDataSource.java:331)
        at dev.chiroito.NativeImageTest.main(NativeImageTest.java:16)
Caused by: java.io.InvalidClassException: java.util.Properties; no valid constructor
        at java.base@11.0.19/java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:158)
        at java.base@11.0.19/java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:746)
        at java.base@11.0.19/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2202)
        at java.base@11.0.19/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1687)
        at java.base@11.0.19/java.io.ObjectInputStream.readObject(ObjectInputStream.java:489)
        at java.base@11.0.19/java.io.ObjectInputStream.readObject(ObjectInputStream.java:447)
        at org.postgresql.ds.common.BaseDataSource.readBaseObject(BaseDataSource.java:1582)
        at org.postgresql.ds.common.BaseDataSource.initializeFrom(BaseDataSource.java:1592)
        at org.postgresql.ds.PGPoolingDataSource.initialize(PGPoolingDataSource.java:273)
        ... 3 more

java.util.Properties クラスのデシリアライズができないことが原因です。

解決方法

シリアライズの設定を追加しましょう。シリアライズの設定は META-INF/native-image/serialization-config.json ファイルに記載します。

場所は以下の位置に置きましょう。

META-INF/native-image/serialization-config.json のファイルの中身は以下の通りです。java.util.Properties が継承している java.util.Hashtablejava.util.Dictionary も記載します。

[
  {
    "name":"java.util.Properties"
  },
  {
    "name":"java.util.Hashtable"
  },
  {
    "name":"java.util.Dictionary"
  }
]

クラスが足りないと以下のメッセージが出力されます。

Exception in thread "main" com.oracle.svm.core.jdk.UnsupportedFeatureError: SerializationConstructorAccessor class not found for declaringClass: java.util.Hashtable (targetConstructorClass: java.util.Dictionary). Usually adding java.util.Hashtable to serialization-config.json fixes the problem.

記載が終わったら再び native-image 化すればOKです。