以前、GCP の Pub/Sub へ接続するために Cloud Pub/Sub Client Libraries の Java 版を使用しました。

もちろん Kotlin でも利用可能ですし、このライブラリで Pub/Sub のサブスクリプションからのメッセージ受信(プル)が実現できていました。
しかし、Docker コンテナに乗せて動作させたところ以下のようなエラーが出て失敗します。
netty-tcnative unavailable (this may be normal)
java.lang.IllegalArgumentException: Failed to load any of the given libraries: [netty_tcnative_linux_x86_64, netty_tcnative_linux_x86_64_fedora, netty_tcnative_x86_64, netty_tcnative]
io.grpc.netty.shaded.io.netty.util.internal.NativeLibraryLoader.loadFirstAvailable(NativeLibraryLoader.java:104)
io.grpc.netty.shaded.io.netty.handler.ssl.OpenSsl.loadTcNative(OpenSsl.java:526)
io.grpc.netty.shaded.io.netty.handler.ssl.OpenSsl.
io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts.defaultSslProvider(GrpcSslContexts.java:244)
io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts.configure(GrpcSslContexts.java:171)
io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts.forClient(GrpcSslContexts.java:120)
今回は、このエラーの原因と対策について調査しましたので紹介していきます。
エラーの原因
エラーメッセージを確認すると、「Failed to load any of the given libraries」ですので、一部のライブラリがロードできないことが原因だとわかります。
しかし、ローカル環境(Mac)では問題がないので、Docker コンテナの OS(Linux)かライブラリの指定が悪いと想像ができそうです。
ちなみに、読み込んでいるライブラリは以下の通り。
compile ‘com.google.cloud:google-cloud-pubsub:1.61.0’
上記は Gradle の例ですが、compile は非推奨なので implementation にしておいた方がいいでしょうか。

どうやら、このライブラリの中で netty や grpc が使われているようなので、これらのモジュールかモジュールを使った SSL 通信の部分が怪しいですね。
Alpineのglibc
調査をしていく中で、原因は AlpineLinux と netty-tcnative 周りのライブラリとの兼ね合いということがわかってきました。
特に以下の記事では似たような環境で状況が似ています。
OpenSSLのハマりポイントがありalpineをコンテナイメージのベースにしていたが上手く起動できなかった。ubuntuベースにするとサクッと起動した。 alpineで起動すると次のようなエラーに遭遇する。netty-tcnativeを使うためにalpineで何かが足りていないのだろう。
KotlinでgRPC。grpc-javaのTLS with JDKとTLS with OpenSSLの使い方をまとめた
StackOverflow でも同様の投稿があり、ここにヒントがありました。
After some googling I found out that: GRPC Java examples are not working on Alpine Linux since required libnetty-tcnative-boringssl-static depends on glibc. Alpine is using musl libc and application startup will fail with message similar to mine.
AlpineLinux では glibc ではなく musl-libc が採用されているようで、netty-tcnative は OS の glibc の中にある機能を使っているのが今回の問題の原因のようです。
netty-tcnative-alpine
そんな中、「netty-tcnative-alpine」というライブラリに行き当たりました。
そこでは以下の記載があります。一部抜粋して Google 翻訳で日本語に訳してみます。
This is just FYI, but I did some recent testing on Alpine and I discovered that netty-tcnative-boringssl-static works without issue on standard Alpine.For openjdk:8-jre-alpine, I ran apk update && apk add libc6-compat while generating the docker image.
私は標準アルパインで問題なくnetty-tcnative-boringssl-staticが動くことを発見しました。openjdk:8-jre-alpineの場合、dockerイメージの生成中にapk update && apk add libc6-compatを実行しました。
要は libc6-compat を入れたら動くよという話です。早速やってみます。
1 2 3 | FROM openjdk:8-jre-alpine apk update && apk add libc6-compat |
Kotlin のアプリケーションを Docker で起動して状況を見守ります。
そして、見事にセグメンテーションエラーで SIGSEGV が返されます。ガックシ・・・。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x000000000001f776, pid=1, tid=0x00007fd366505b10 # # JRE version: OpenJDK Runtime Environment (8.0_212-b04) (build 1.8.0_212-b04) # Java VM: OpenJDK 64-Bit Server VM (25.212-b04 mixed mode linux-amd64 compressed oops) # Derivative: IcedTea 3.12.0 # Distribution: Custom build (Sat May 4 17:33:35 UTC 2019) # Problematic frame: # C 0x000000000001f776 # # Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" # # An error report file with more information is saved as: # //hs_err_pid1.log # # If you would like to submit a bug report, please include # instructions on how to reproduce the bug and visit: # https://icedtea.classpath.org/bugzilla # |
他にも以下の情報がありましたが、まずは AplineLinux とは別のディストリビューションだと問題ないのかを切り分けるために、こちらは試していません。
仕方がないので,glibc をソース取ってきてコンパイルしようかと思ったんですが sgerrand/alpine-pkg-glibc に公開されていました
Debianで動かす
では openjdk を使った Alpine じゃないコンテナイメージを探してみましょう。
本家にも Alpine については以下の記載がありました。
The main caveat to note is that it does use musl libc instead of glibc and friends, so certain software might run into issues depending on the depth of their libc requirements.
注意すべき主な注意点は、glibcやその仲間の代わりにmusl libcを使用していることです。そのため、libcの要件の深さによっては、特定のソフトウェアで問題が発生する可能性があります。
なるほどね。では、alpine ではない openjdk:8-jre を試してみます。
1 2 3 4 5 6 | FROM openjdk:8-jre RUN apt-get update \ && apt-get -y install --no-install-recommends \ && openssl \ && rm –rf /var/lib/apt/lists/* |
見事に動作しました。やはり glibc の有無だけの問題だったか。
Alpine にこだわる必要がなければ、コンテナイメージを変えるのも選択肢の一つですね。
Alpine じゃなきゃ困る場合は、少し上で触れた sgerrand/alpine-pkg-glibc を試してみるのもアリです。