TL;DR
OpenCVのJavaバインディングをJBoss EAP 8(UBI8/OpenJDK 17)上で動かすには、cmakeによるソースビルドが必要になる。本記事ではTekton PipelineRun / BuildConfig でのビルドフローと、実際に踏んだ4つのハマりどころを記録する。
この記事が役に立つケース:
- OpenCV最新版(4.x)をJBoss EAPで使いたい
- JavaCVを検討したがRHELバージョン問題やバージョン遅れで断念した
- OpenShift(内部ネットワーク環境)でOpenCVをビルドしている
ハマりどころ早見表:
| # | 症状 | 原因 |
|---|---|---|
| 1 |
libopencv_java4130.so が maven package 後に消える |
ファイル名に java を含む .so が消失(詳細未特定) |
| 2 | 起動時に UnsatisfiedLinkError / GLIBCXX not found
|
ビルドイメージとランタイムイメージのRHELバージョン不一致 |
| 3 | 起動時 UnsatisfiedLinkError(libjpeg等) |
ランタイムイメージに画像ライブラリ未インストール |
| 4 | cmake構成フェーズが数十分かかる |
OpenCVDownload.cmake のタイムアウトがデフォルト600秒 |
背景:なぜcmakeでビルドするのか
JavaCVを使わない理由
OpenCVのJavaバインディングとして、ソースビルドの代わりに JavaCV(bytedeco/javacv)を使う選択肢がある。しかし今回の環境では以下の理由で断念した。
① 最新のOpenCVバージョンが使えない
JavaCV 1.5.12はOpenCV 4.8.0に対応しており、OpenCV 4.13.0には未対応だった(検証時点)。OpenCVの新バージョンを使うには対応するJavaCVのリリースを待つ必要がある。
② javacv-platform はOpenCV以外の大量のライブラリも同梱されている
javacv-platform を依存関係に追加すると、OpenCV・FFmpeg・OpenBLAS・Leptonica・Tesseractなど、コンピュータビジョン関連のネイティブライブラリが全プラットフォーム向けに一括ダウンロードされる。必要なのはOpenCVだけでも、依存ライブラリ込みで数百MBのJARが取り込まれる。
③ Ubuntu 24でビルドされているためRHEL 8では UnsatisfiedLinkError になる
JavaCVが配布するネイティブライブラリは、公式にはUbuntu 24.04(glibc 2.39)でビルドされている。JBoss EAP 8のランタイムイメージはUBI8ベース(glibc 2.28)のため、実行時にシンボルが見つからず UnsatisfiedLinkError が発生する。
java.lang.UnsatisfiedLinkError: .../libjnijavacpp.so:
/lib64/libstdc++.so.6: version `GLIBCXX_3.4.26' not found
これはJavaCVの問題ではなく、ビルド環境とランタイム環境のglibc世代が合っていないことが原因であり、JavaCPP Presetsをソースからビルドすれば解決する。ただし自前ビルドを選んだ場合、JavaCPP PresetsはOpenBLASなどの依存ライブラリも一緒にビルドするため、OpenBLASのビルドだけで数十分〜数時間かかることがある。ソースビルドの手間はOpenCVを直接cmakeでビルドする場合と変わらないため、今回はOpenCVを直接ビルドする方針をとった。
環境
| 項目 | バージョン |
|---|---|
| OpenCV | 4.13.0 |
| JBoss EAP | 8.x |
| ベースイメージ | UBI8 / OpenJDK 17 |
| CI | Tekton(PipelineRun) |
| アーティファクト管理 | Nexus |
| OpenShift | 4.x |
アーキテクチャ
ビルドは3つのTaskと1つのBuildConfigに分かれる。OpenCVバイナリはアプリケーションのビルドとは独立したライフサイクルを持つため、Task 1・2はOpenCVのバージョンアップ時にのみ実行し、成果物はNexusに保存して再利用する。
PipelineRun 構成
Task 1: cmakeビルド
steps:
- name: install-tools
image: ubi8
script: |
dnf install -y cmake \
java-17-openjdk-devel \
python3 \
gcc \
gcc-c++ \
make \
pkgconfig \
libjpeg-turbo-devel \
libpng-devel \
libtiff-devel \
--enablerepo=ubi-8-baseos-rpms \
--enablerepo=ubi-8-appstream-rpms
- name: patch-sources
image: ubi8
script: |
# タイムアウトを5秒に短縮(外部接続できなければ即fail)
sed -i \
-e 's/TIMEOUT[[:space:]]\+600/TIMEOUT 5/g' \
-e 's/INACTIVITY_TIMEOUT[[:space:]]\+60/INACTIVITY_TIMEOUT 5/g' \
opencv/cmake/OpenCVDownload.cmake
- name: cmake-build
image: ubi8
script: |
mkdir -p build && cd build
cmake ../opencv \
-DBUILD_SHARED_LIBS=ON \
-DBUILD_JAVA=ON \
-DBUILD_LIST=core,imgcodecs,imgproc,java,java_bindings_generator \
-DBUILD_TESTS=OFF \
-DBUILD_PERF_TESTS=OFF \
-DBUILD_EXAMPLES=OFF \
-DJAVA_HOME=/usr/lib/jvm/java-17-openjdk \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_RPATH='$ORIGIN'
make -j$(nproc)
- name: rename-java-binding
image: ubi8
script: |
# java を含むファイル名は maven package 中に消えるためリネーム(詳細はハマり1参照)
mv build/lib/libopencv_java4130.so build/lib/libopencv_jni4130.so
-DBUILD_LISTについて: ビルド対象モジュールをcore,imgcodecs,imgproc,java,java_bindings_generatorに限定している。これにより不要なモジュール(video, dnn, features2dなど)のビルドが省略され、ビルド時間の短縮と外部依存ライブラリのダウンロード対象の削減を同時に実現できる。javaとjava_bindings_generatorはJavaバインディング生成に必須。
Task 2: Nexusへプッシュ
steps:
- name: archive-and-push
image: ubi8
script: |
tar -czf opencv-natives-4.13.0.tgz --wildcards *.so*
# ネイティブライブラリ(tgz)
curl -u ${NEXUS_USER}:${NEXUS_PASS} \
--upload-file opencv-natives-4.13.0.tgz \
https://nexus.example.internal/repository/raw-hosted/opencv/opencv-natives-4.13.0.tgz
# Javaバインディング JAR
curl -u ${NEXUS_USER}:${NEXUS_PASS} \
--upload-file build/bin/opencv-4130.jar \
https://nexus.example.internal/repository/raw-hosted/opencv/opencv-4130.jar
Task 3: oc start-build(毎回)
BuildConfigを oc start-build で起動する。ビルド本体(Galleon provisioning・maven package・マルチステージDockerfile)はすべてBuildConfig内で実行され、完了後にImageStreamにpushされる。
steps:
- name: start-build
image: registry.redhat.io/openshift4/ose-cli
script: |
oc start-build ${BUILD_CONFIG_NAME} \
--namespace=${NAMESPACE} \
--follow \
--wait
BuildConfig:マルチステージDockerfile
EAP 8ではGalleon feature-packを使ったマルチステージビルドが標準となる。アーティファクトビルドステージでWARをビルドしつつNexusからOpenCVライブラリを取得し、ランタイムステージでEAPイメージに組み込む。BuildConfigの output にImageStreamを指定することで、ビルド完了後に自動的にpushされる。
# ── アーティファクトビルドステージ ──────────────────────────────
FROM registry.redhat.io/jboss-eap-8/eap8-openjdk17-builder-openshift-rhel8 AS builder
# Galleon provisioning + maven package ...
# Nexusからライブラリを取得・解凍
RUN curl -u ${NEXUS_USER}:${NEXUS_PASS} -L -o opencv-natives.tgz \
https://nexus.example.internal/repository/raw-hosted/opencv/opencv-natives-4.13.0.tgz && \
tar -xzf opencv-natives.tgz -C /tmp/opencv-natives/
# ── ランタイムステージ ──────────────────────────────────────────
FROM registry.redhat.io/jboss-eap-8/eap8-openjdk17-runtime-openshift-rhel8 AS runtime
# 画像ライブラリをインストール(-devel なしのランタイム用パッケージ)
# libopencv_imgcodecs.so がこれらに動的リンクしているため必要
RUN dnf install -y libjpeg-turbo libpng libtiff \
--enablerepo=ubi-8-baseos-rpms \
--enablerepo=ubi-8-appstream-rpms && \
dnf clean all
COPY --from=builder /tmp/opencv-natives/ ${JBOSS_HOME}/modules/com/opencv/main/lib/
COPY --from=builder /path/to/opencv-4130.jar ${JBOSS_HOME}/modules/com/opencv/main/
# Galleon extensionsでJBossモジュールとして登録(後述)
JBossモジュール登録
OpenCVのネイティブライブラリとJARをJBoss EAPから利用するには、JBossモジュールとして登録する必要がある。module.xml でJAR・ネイティブライブラリパス・JDK依存を宣言し、Galleon extensionsまたは手動で ${JBOSS_HOME}/modules/ 以下に配置する。
<!-- ${JBOSS_HOME}/modules/com/opencv/main/module.xml -->
<module xmlns="urn:jboss:module:1.9" name="com.opencv">
<resources>
<resource-root path="opencv-4130.jar"/>
</resources>
<dependencies>
<module name="javax.api"/>
</dependencies>
</module>
ネイティブライブラリ(.so)のロードはモジュールシステム経由ではなく、アプリケーションコード側で System.load() によるフルパス指定で行う(詳細はハマり1の対処を参照)。
ネイティブライブラリのロード方式
JNIライブラリのロードには System.loadLibrary() と System.load() の2種類がある。本構成では System.load() によるフルパス指定を採用している。
| 方式 | パス解決 | 本構成での採否 |
|---|---|---|
System.loadLibrary("opencv_jni4130") |
LD_LIBRARY_PATH / java.library.path から検索 |
❌ 不採用 |
System.load("/full/path/libopencv_jni4130.so") |
引数のフルパスを直接使用 | ✅ 採用 |
System.loadLibrary() を不採用にした理由: LD_LIBRARY_PATH はプロセス全体に影響するため、JBoss EAP内の他のネイティブライブラリ(Undertow等)のロードに干渉するリスクがある。System.load() であればパス解決がアプリケーション内で完結する。
さらに、cmakeビルド時に -DCMAKE_INSTALL_RPATH='$ORIGIN' を指定しているため、libopencv_jni4130.so から libopencv_core4130.so 等への依存解決は同一ディレクトリ内で行われ、LD_LIBRARY_PATH の設定は不要となる。
static {
// Galleon extensions が設定する環境変数からパスを取得
String libPath = System.getenv("OPENCV_NATIVE_LIB_PATH");
System.load(libPath + "/libopencv_jni4130.so");
}
ハマりどころ
1. libopencv_java4130.so が maven package 中に消える
症状
NexusからtgzをダウンロードしてBuildConfig内で解凍しても、libopencv_java4130.so だけが target ディレクトリ内に存在しない。他の .so ファイルは正常に存在する。
原因
消失するフェーズの詳細は未特定。調査した範囲では以下が候補として残っている。
-
maven-dependency-pluginのunpack設定にexcludesがある -
maven-resources-pluginのfilteringがバイナリを破損させる -
eap-maven-pluginのコンテンツコピー処理がjavaを含む名前を除外する
共通しているのはファイル名に java を含む .so だけが消えるという点。libopencv_jni4130.so と libopencv_core4130.so などは消えない。
対処
Task 1のビルド完了直後、Nexusプッシュ前にリネームする。
# NG: libopencv_java4130.so → maven package 中に消える
# OK: libopencv_jni4130.so → 消えない
mv build/lib/libopencv_java4130.so build/lib/libopencv_jni4130.so
2. ビルドイメージとランタイムイメージのRHELバージョンを一致させないと UnsatisfiedLinkError
症状
java.lang.UnsatisfiedLinkError: .../libopencv_jni4130.so:
/lib64/libstdc++.so.6: version `GLIBCXX_3.4.26' not found
ビルドは正常に完了しているにもかかわらず、起動時に初めて発覚する。
原因
JavaからC++のコードを呼び出すJNI(Java Native Interface)では、System.load() 呼び出し時にJVMがOS上の .so ファイルをロードし、glibc・libstdc++などのシステムライブラリを動的にリンクする。このとき、.so をコンパイルした環境のglibc・libstdc++バージョンが実行環境より新しいと UnsatisfiedLinkError が発生する。「ビルドできた」はコンパイル成功であり、実行環境との互換性は別問題。
対処
cmakeビルドのベースイメージを、EAPランタイムイメージと同じRHELバージョンのUBIに統一する。
| ビルドイメージ | ランタイムイメージ | 結果 | |
|---|---|---|---|
| ❌ | ubi9 |
eap8-openjdk17-...-rhel8(UBI8) |
起動時にfail |
| ✅ | ubi8 |
eap8-openjdk17-...-rhel8(UBI8) |
正常 |
この不一致はビルドが成功してもランタイムまで気づけない。ビルドパイプライン設計の最初にランタイムイメージのRHELバージョンを確定させてからベースイメージを選ぶのが確実。
なお、JavaCV公式バイナリがUbuntu 24(glibc 2.39)ベースでビルドされているためUBI8上で動かない問題も、この同じ構造から来ている。
3. 画像ライブラリが不足すると UnsatisfiedLinkError
ビルドイメージ側(気づきにくい)
cmakeビルド時に libjpeg-turbo-devel・libpng-devel・libtiff-devel が存在しない場合、OpenCVはJPEG/PNG/TIFFサポートなしでビルドされる。cmakeはエラーにならず警告のみで通過するため気づきにくい。
確認方法: cmake実行後の出力で Media I/O セクションを確認する。YES になっていない項目は無効化されている。
-- Media I/O:
-- ZLib: zlib (ver 1.2.11)
-- JPEG: libjpeg-turbo (ver 2.0.4-62) ← YES であること
-- PNG: libpng (ver 1.6.34) ← YES であること
-- TIFF: libtiff (ver 42 / 4.0.9) ← YES であること
ランタイムイメージ側
ランタイムイメージに libjpeg-turbo・libpng・libtiff(-devel なし)が存在しない場合、OpenCVの .so がこれらへの動的リンク解決に失敗し、起動時に UnsatisfiedLinkError が発生する。ハマり2と同じエラー種別だが原因が異なる。
UnsatisfiedLinkError: .../libopencv_jni4130.so:
libjpeg.so: cannot open shared object file: No such file or directory
ビルドイメージとランタイムイメージでのパッケージの使い分け
| パッケージ | ビルドイメージ | ランタイムイメージ |
|---|---|---|
libjpeg-turbo-devel |
✅ 必要(ヘッダファイル) | — |
libjpeg-turbo |
— | ✅ 必要(ランタイム) |
libpng-devel |
✅ | — |
libpng |
— | ✅ |
libtiff-devel |
✅ | — |
libtiff |
— | ✅ |
-devel パッケージはヘッダファイルを含むビルド用パッケージであり、実行時には不要。
4. cmake構成フェーズで外部ダウンロードが1件最大10分待ってからfailする
症状
外部ネットワーク接続がない環境でcmakeを実行すると、cmake構成フェーズ(cmake ../opencv ... の実行中、Makefile生成前)で長時間フリーズしたように見える。実際には接続できない外部URLへのダウンロードを延々と待っている。
原因
cmake/OpenCVDownload.cmake にデフォルトのタイムアウト値が定義されている。
# cmake/OpenCVDownload.cmake(抜粋)
set(OPENCV_DOWNLOAD_PARAMS INACTIVITY_TIMEOUT 60 TIMEOUT 600 ...)
# ^^ ^^^
# 無通信タイムアウト60秒 全体タイムアウト600秒(10分)
OpenCVはcmake構成フェーズでippicv・adeなど複数の依存ライブラリを個別に外部からダウンロードしようとする。外部接続できない環境では1件あたり最大600秒待ってからfailするため、ライブラリ数に比例して積み重なる。
対処
cmake実行前に sed で OpenCVDownload.cmake のタイムアウトを短縮する。
# タイムアウトを5秒に短縮(外部接続できなければ即fail)
sed -i \
-e 's/TIMEOUT[[:space:]]\+600/TIMEOUT 5/g' \
-e 's/INACTIVITY_TIMEOUT[[:space:]]\+60/INACTIVITY_TIMEOUT 5/g' \
opencv/cmake/OpenCVDownload.cmake
これにより外部接続できないライブラリは約5秒でfailし、cmake構成フェーズが即座に次の処理に進む。なお、ダウンロードが必要な依存ライブラリについては、-DBUILD_LIST でビルド対象モジュールを必要最小限に絞ることでダウンロード対象自体を減らすアプローチを併用している。
まとめと教訓
| ハマりどころ | 原因 | 対処 | 教訓 |
|---|---|---|---|
.so が消える |
maven package 中に消失 |
jni にリネーム |
ネイティブライブラリのファイル名は疑う |
GLIBCXX not found |
RHEL世代の不一致 | UBI8で統一 | ランタイムのRHELバージョンを起点に設計する |
libjpeg.so not found |
画像ライブラリ未インストール | dnf install |
cmake出力の Media I/O を必ず確認 |
| 構成フェーズが遅い |
TIMEOUT 600 / INACTIVITY_TIMEOUT 60
|
sed で5秒に短縮 |
内部NW環境ではタイムアウト設定を確認 |
最も見つけにくいのはRHELバージョン不一致(ハマり2)。 ビルドが通るため見逃しやすく、起動時に初めて発覚する。エラーメッセージも GLIBCXX の話であり、一見OpenCVとの関連が見えない。
JavaCVが避けられない場合も含め、ネイティブライブラリを扱うときの原則として「ビルド環境とランタイム環境のglibc世代を一致させる」を設計時点で決めておくと、後から追いかける必要がなくなる。
参考リンク
OpenCV
- OpenCV cmake設定オプションリファレンス
- OpenCV Linux インストール(ソースビルド)
- OpenCV Javaバインディング入門
- opencv/cmake/OpenCVDownload.cmake(タイムアウト設定ソース)
JavaCV / JavaCPP Presets
JNI・ネイティブライブラリ・glibc
- Java UnsatisfiedLinkError とは(JNI・ネイティブライブラリの基礎)
- JNI ネイティブライブラリのリンクエラーをデバッグする
- GLIBCXX version not found の原因と対処
JBoss EAP 8 / OpenShift
Tekton