Linux

AlpineLinuxでnetty-tcnative unavailableのエラーが出る時の対処法

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

Androidの月額課金でPub/Subからデベロッパー通知を受信する以前、Google の月額課金の状態はリアルタイムデベロッパー通知で制御すると楽だという話をしました。 https://itnek...

もちろん 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.(OpenSsl.java:93)
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’

Cloud Pub/Sub Client Libraries

上記は Gradle の例ですが、compile は非推奨なので implementation にしておいた方がいいでしょうか。

Gradleのcompileは非推奨なのでapiかimplementationを使う夏頃から携わっているプロジェクトで初めて SpringBoot を利用しています。 今回の開発言語は Kotlin でしたが、これまで...

どうやら、このライブラリの中で 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.

Docker – Problem with java netty_tcnative

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を実行しました。

netty-tcnative-alpine

要は libc6-compat を入れたら動くよという話です。早速やってみます。

Kotlin のアプリケーションを Docker で起動して状況を見守ります。

そして、見事にセグメンテーションエラーで SIGSEGV が返されます。ガックシ・・・。

他にも以下の情報がありましたが、まずは AplineLinux とは別のディストリビューションだと問題ないのかを切り分けるために、こちらは試していません。

仕方がないので,glibc をソース取ってきてコンパイルしようかと思ったんですが sgerrand/alpine-pkg-glibc に公開されていました

Alpine Linux に 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の要件の深さによっては、特定のソフトウェアで問題が発生する可能性があります。

openjdk

なるほどね。では、alpine ではない openjdk:8-jre を試してみます。

見事に動作しました。やはり glibc の有無だけの問題だったか。

Alpine にこだわる必要がなければ、コンテナイメージを変えるのも選択肢の一つですね。

Alpine じゃなきゃ困る場合は、少し上で触れた sgerrand/alpine-pkg-glibc を試してみるのもアリです。