こんにちは、Red Hat Middleware Technical Account Managerのイアンです。
Technical Account Manager (TAM)として、お客様のシステムの安定稼働をさせるために私は、日々お客様からのサポートケースに対応しています。 その中で、お客様の環境、アプリ、現象などを速やかに再現して原因と対応方法を提供することを心がけています。
物理サーバーや仮想マシン上のミドルウェアをご利用している場合は、自分のローカル環境で割と簡単に再現環境を立ち上げられますが、最近はOpenShift上のアプリの問い合わせが増えてきました。 OpenShift Local (旧名: CodeReady Containers)を用いて検証用のOpenShift環境を立ち上げることができますが、起動するのに時間かかり、PCのリソースも結構必要です。 ただアプリをイメージ化し、コンテナを起動したいだけなのですが、もっといいやり方がないでしょうか?
OpenShift環境上でアプリケーションをデプロイする時は、アプリをソースからビルドするSource-To-Image (s2i)の仕組みがよく使用されるかと思います。 中々いい仕組みで、OpenShift環境がなくても、ローカルで利用することができます!
目次
推奨環境
Source-To-ImageをローカルPCで実行するためには、コンテナエンジンが必要です。 DockerとPodmanがよく使われています。 当記事では、Fedora 35上の Podman を使用します。
なお、PodmanのコマンドラインツールはDockerと互換性がありますので、Dockerで実行する場合はpodman
をdocker
に置き換えて頂ければ使えると思います。
Podmanについて
当記事では、Podmanの詳細には触れませんが、ご興味がある方には、弊社が提供しているトレーニングの受講をご検討頂ければ幸いです。 また、Red Hat Developerアカウントをお持ちの方は、developers.redhat.comから電子書籍を無償でダウンロードできます。 Podman入門としてPodman In Actionをおすすめします。
s2iでのアプリイメージ作成
まずは、最新のs2i
バイナリをSource-To-Imageのリポジトリからダウンロードし、展開します。
また、サンプルアプリとして、JBoss Enterprise Application Platform (EAP)のQuickstartsの中のhelloworld-rs(単純なRESTサービス)を利用するので、 jboss-eap-quickstarts リポジトリをローカルにclone
し、 jboss-eap-quickstarts ディレクトリに移動します。
$ git clone https://github.com/jboss-developer/jboss-eap-quickstarts.git ... $ cd jboss-eap-quickstarts
s2i
を使うためには、ビルダーイメージが必要です。
OpenShiftと同じRed Hatが提供しているJBoss EAP 7.4ビルダーイメージを使います。
$ podman pull registry.redhat.io/jboss-eap-7/eap74-openjdk11-openshift-rhel8:latest
NOTE: registry.redhat.ioからダウンロードするためには、podman login registry.redhat.io
を使ってRed Hatアカウントにログインする必要があります。
準備ができたら、以下のs2i
コマンドを使って、 helloworld-rs のアプリをビルドし、イメージを作成します。
最終イメージ名は localhost/eap-app:latest
になります。
s2i build . registry.redhat.io/jboss-eap-7/eap74-openjdk11-openshift-rhel8 eap-app \ --copy \ --pull-policy never \ -e GALLEON_PROVISION_LAYERS=jaxrs-server \ -e GALLEON_PROVISION_DEFAULT_FAT_SERVER=true \ -e MAVEN_ARGS_APPEND="-f helloworld-rs/pom.xml" \ -e MAVEN_S2I_ARTIFACT_DIRS=helloworld-rs/target
NOTE: OpenShiftと違って、いくつかの$MAVEN_...
の環境変数を指定しています。
Quickstartsのソースのサブディレクトリの中の helloworld-rs をビルドするために必要です。
アプリのソースコードはソースディレクトリ直下にある場合は、別ディレクトリの pom.xml などを指定しなくても良いです。
ビルダーイメージからコンテナが起動され、Mavenのビルドが始まります。
Checking if image "registry.redhat.io/jboss-eap-7/eap74-openjdk11-openshift-rhel8" is available locally ... Checking if image "registry.redhat.io/jboss-eap-7/eap74-openjdk11-openshift-rhel8" is available locally ... Provisioning WildFly server... INFO Performing Maven build in /opt/jboss/container/wildfly/s2i/galleon/provisioning/generic_layers INFO Using MAVEN_OPTS -XX:+UseParallelOldGC -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -XX:+ExitOnOutOfMemoryError ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.713 s [INFO] Finished at: 2023-01-22T12:27:45Z [INFO] ------------------------------------------------------------------------ INFO Copying deployments from helloworld-rs/target to /deployments... '/tmp/src/helloworld-rs/target/ROOT.war' -> '/deployments/ROOT.war' INFO Cleaning up source directory (/tmp/src) INFO Copying server to /s2i-output INFO Linking /opt/eap to /s2i-output Build completed successfully
s2i
のビルドが完了したら、新しい eap-app イメージがローカルのリポジトリに作成されます。
$ podman images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/eap-app latest 6c2779ffb971 43 seconds ago 1.15 GB registry.redhat.io/jboss-eap-7/eap74-openjdk11-openshift-rhel8 latest a34c07e25cbf 6 weeks ago 1.01 GB
このイメージはそのまま実行できます。
$ podman run --rm \ --name eap \ -p 18080:8080 \ -e ENABLE_ACCESS_LOG=true \ localhost/eap-app:latest \ /opt/eap/bin/openshift-launch.sh
アプリの動作を確認します。
$ curl -w "\n" http://localhost:18080/rest.json {"result":"Hello World!"}
コンテナ起動時に$ENABLE_ACCESS_LOG
の環境変数を指定したので、コンテナの標準出力に以下のようなアクセスログも出ます。
$ podman logs --tail=10 eap ... 12:29:14,076 INFO [io.undertow.accesslog] (default task-1) 10.0.2.100 - - [22/Jan/2023:12:29:14 +0000] - "GET /rest/json HTTP/1.1" 200 25
これで、OpenShiftと同様のs2i
仕組みを使って、アプリのイメージを作成し、起動できました。
でも、これだけではありません。
ランタイムイメージの作成
先に作ったアプリのイメージのサイズをご覧になりましたでしょうか? 1GBほどとなってしまい、ビルダーイメージとほぼ同じサイズです。
ビルダーイメージの上にアプリを載せただけですので、アプリに必要ではないビルド関連のファイル(Maven, s2i)が含まれています。 ローカルでの検証だけなので、サイズがそんなに問題ではないかもしれませんが、OpenShiftと同様のランタイムイメージを使ってイメージを作成できます。
通常のOpenShift上のビルドは、ビルドから作成したアーティファクトを自動的にランタイムイメージにコピーしてくれますが、ローカルの場合は自動的にできません。
jboss-eap-quickstarts ディレクトリに .s2i/bin ディレクトリを作って、中の assemble-runtime ファイルを作って、中身を以下のように設定します。
#!/usr/bin/env bash mkdir /opt/eap cp -r /home/jboss/s2i/s2i-output/server/* /opt/eap chown -R jboss:root /opt/eap && chmod -R ug+rwX /opt/eap
NOTE: assemble-runtimeのコマンドはOpenShiftのeap74-basic-s2i.jsonテンプレートを参照して作成しました。
chmod u+x .s2i/bin/assemble-runtime
で実行可能にします。
また、ランタイムイメージをpull
しましょう。
$ podman pull registry.redhat.io/jboss-eap-7/eap74-openjdk11-runtime-openshift-rhel8
これができたら、前回のs2i
コマンドにruntime
イメージ関連のオプションを足して実行します。
$ s2i build /path/to/jboss-eap-quickstarts registry.redhat.io/jboss-eap-7/eap74-openjdk11-openshift-rhel8 eap-app \ --runtime-artifact /s2i-output:s2i \ --runtime-image registry.redhat.io/jboss-eap-7/eap74-openjdk11-runtime-openshift-rhel8 \ --runtime-pull-policy never --copy \ --pull-policy never \ -e GALLEON_PROVISION_LAYERS=jaxrs-server \ -e GALLEON_PROVISION_DEFAULT_FAT_SERVER=true \ -e MAVEN_ARGS_APPEND="-f helloworld-rs/pom.xml" \ -e MAVEN_S2I_ARTIFACT_DIRS=helloworld-rs/target
実行すると、s2i
は前回と同様にアプリをビルドしますが、ビルドのアーティファクトが含まれている /s2i-output ディレクトリをランタイムイメージの /home/jboss/s2i ディレクトリにマウントします。
その後、用意した assemble-runtime スクリプトが動き、ビルド結果を /opt/eap にコピーします。
最後にランタイムイメージを eap-app としてローカルリポジトリにプッシュします。
出来上がった eap-app イメージにはMaven, s2iなどのビルド関連のファイルがないので、ビルダーイメージの場合よりサイズを小さくできます。
$ podman images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/eap-app latest 352389a94bf3 15 seconds ago 814 MB registry.redhat.io/jboss-eap-7/eap74-openjdk11-openshift-rhel8 latest a34c07e25cbf 6 weeks ago 1.01 GB registry.redhat.io/jboss-eap-7/eap74-openjdk11-runtime-openshift-rhel8 latest 164d988e5c07 6 weeks ago 532 MB
Stupid s2i/container Tricks
ビルドの加速化
ここまで来て、数回s2i
でアプリをビルドして気づいたかと思いますが、毎回依存モジュールのダウンロードが必要となり、時間がかかっています。
サポートエンジニアとして、現象を再現する時は、コードを修正し、実行することを何回も繰り返すので、s2i
のビルドを加速させたらビルドに要している時間を省いて早く再現することができます。
ビルダーイメージは /tmp/artifacts/m2 をローカルMavenリポジトリとして使用しています。
そのため、コンテナ用のボリュームを /tmp/artifacts にマウントすれば、ダウンロードした依存モジュールを今後のビルドでも再利用できます。
s2i
の-v
オプションでボリュームマウントを指定できます。
$ podman volume create eap-build-repo $ s2i build . registry.redhat.io/jboss-eap-7/eap74-openjdk11-openshift-rhel8 eap-app \ --copy \ --pull-policy never \ -e GALLEON_PROVISION_LAYERS=jaxrs-server \ -e GALLEON_PROVISION_DEFAULT_FAT_SERVER=true \ -e MAVEN_ARGS_APPEND="-f helloworld-rs/pom.xml" \ -e MAVEN_S2I_ARTIFACT_DIRS=helloworld-rs/target \ -v "eap-build-repo:/tmp/artifacts"
もう一度s2i build
コマンドを実行すると、アプリのビルドがすぐに終わります。
latest以外のイメージの利用方法
当記事を書いた時のeap74-openjdk11-openshift-rhel8イメージはJBoss EAP 7.4.8を使用していますが、 例えば、お客様の環境に合わせて、7.4.2を使いたい場合はどうすればいいでしょうか?
残念ながら、以下のようなコマンドを使っても、ビルダーイメージのlatest
が使用されてしまいます。
$ s2i build . registry.redhat.io/jboss-eap-7/eap74-openjdk11-openshift-rhel8:7.4.2 eap-app \ ...
ここでイメージのタグを使えます!
以下のコマンドでjboss-eap-7/eap74-openjdk11-openshift-rhel8:7.4.2
イメージをlocalhost/eap74-openjdk11-openshift-rhel8:latest
としてタグ付けします。
$ podman pull registry.redhat.io/jboss-eap-7/eap74-openjdk11-openshift-rhel8:7.4.2 $ podman tag registry.redhat.io/jboss-eap-7/eap74-openjdk11-openshift-rhel8:7.4.2 localhost/eap74-openjdk11-openshift-rhel8:latest
タグ付けしたら、s2i build
コマンドで新規にタグ付けしたイメージを使います。
$ s2i build . localhost/eap74-openjdk11-openshift-rhel8 eap-app \ ...
Source-To-Imageはlocalhost/eap74-openjdk11-openshift-rhel8:latest
を使いますが、そのタグは実はjboss-eap-7/eap74-openjdk11-openshift-rhel8:7.4.2
を指していますので、JBoss EAP 7.4.2のイメージが出来上がります。
このやり方はランタイムイメージにも適用できます。
データベースドライバ導入
OpenShiftと同じs2i仕組みですので、環境変数を使って、データベースドライバの導入と設定を行えます。
今回のサンプルアプリはデータベースを使っていませんが、もしお持ちのアプリがデータベースを使っている場合の設定方法を紹介します。
NOTE: 以下の手順はGetting Started with JBoss EAP for OpenShift Container Platform Red Hat JBoss Enterprise Application Platform 7.4のDatasources章を参照して作成しました。
まずは、 jboss-eap-quickstarts ディレクトリに extensions ディレクトリを作成します。 その中に、 modules/org/postgresql/main ディレクトリを作成します。
extensions/modules/org/postgresql/main の中にPostgreSQL 14のJDBCドライバー(記事作成当時は postgresql-42.5.1.jar)を置きます。 同じディレクトリに module.xml を作成し、中身を以下のように設定します。
NOTE: resource-root
のpath
属性をダウンロードしたJDBCドライバーのファイル名に設定します。
<?xml version="1.0" ?> <module xmlns="urn:jboss:module:1.1" name="org.postgresql"> <resources> <resource-root path="postgresql-42.5.1.jar"/> </resources> <dependencies> <module name="wildflyee.api"/> <module name="sun.jdk"/> <module name="ibm.jdk"/> <module name="javax.api"/> <module name="javax.transaction.api"/> </dependencies> </module>
extensions/drivers.env ファイルを作って、中身を以下にします。 このファイルはEAP起動時のデータベースドライバーの登録に使用されます。
DRIVER DRIVERS=POSTGRES POSTGRES_DRIVER_NAME=postgresql POSTGRES_DRIVER_MODULE=org.postgresql POSTGRES_DRIVER_CLASS=org.postgresql.Driver POSTGRES_XA_DATASOURCE_CLASS=org.postgresql.xa.PGXADataSource
最後に extensions/install.sh を作ります。 このファイルはSource-To-Buildの中で実行され、 modules サブディレクトリを /opt/eap/modules にコピーし、 drivers.env を /opt/eap/bin/openshift にコピーします。
NOTE: install.sh
の詳細はModules, Drivers, and Generic Deploymentsをご参照ください。
#!/bin/bash injected_dir=$1 source /usr/local/s2i/install-common.sh install_modules ${injected_dir}/modules configure_drivers ${injected_dir}/drivers.env
最終的に extensions ディレクトリの構成は以下のようになります。
extensions ├── drivers.env ├── install.sh └── modules └── org └── postgresql └── main ├── module.xml └── postgresql-42.5.1.jar
ここでs2i build
を実行して、アプリのイメージを作成します。
NOTE: 今回は$CUSTOM_INSTALL_DIRECTORIES
の環境変数を指定しています。
$ s2i build /path/to/jboss-eap-quickstarts registry.redhat.io/jboss-eap-7/eap74-openjdk11-openshift-rhel8 eap-app \ --copy \ --pull-policy never \ -e GALLEON_PROVISION_LAYERS=jaxrs-server \ -e GALLEON_PROVISION_DEFAULT_FAT_SERVER=true \ -e MAVEN_ARGS_APPEND="-f helloworld-rs/pom.xml" \ -e MAVEN_S2I_ARTIFACT_DIRS=helloworld-rs/target \ -e CUSTOM_INSTALL_DIRECTORIES=extensions ... INFO Copying deployments from helloworld-rs/target to /deployments... '/tmp/src/helloworld-rs/target/ROOT.war' -> '/deployments/ROOT.war' INFO Processing ImageSource mounts: extensions INFO Processing ImageSource from /tmp/src/extensions INFO Cleaning up source directory (/tmp/src) INFO Copying server to /s2i-output INFO Linking /opt/eap to /s2i-output Build completed successfully
s2i
は無事に extensions ディレクトリの install.sh を実行できました。
起動する時は、環境変数を用いてデータソースを設定します。
$ podman run \ --rm \ --name eap \ -p 18080:8080 \ -e ENABLE_ACCESS_LOG=true \ -e DB_SERVICE_PREFIX_MAPPING=test-postgresql=TEST \ -e TEST_NONXA=true \ -e TEST_CONNECTION_CHECKER=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker \ -e TEST_EXCEPTION_SORTER=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter \ -e TEST_USERNAME=postgres \ -e TEST_PASSWORD=changeme \ -e TEST_POSTGRESQL_SERVICE_HOST=localhost \ -e TEST_POSTGRESQL_SERVICE_PORT=5432 \ -e TEST_DATABASE=test \ localhost/eap-app:latest \ /opt/eap/bin/openshift-launch.sh
EAPのCLIを使って、データソースが作成されたことを確認できますが、実際のデータベースがないため、接続テストを行うとエラーになります。
$ podman exec eap /opt/eap/bin/jboss-cli.sh -c "/subsystem=datasources/data-source=test_postgresql-TEST:test-connection-in-pool" { "outcome" => "failed", "failure-description" => "WFLYJCA0040: failed to invoke operation: WFLYJCA0047: Connection is not valid", "rolled-back" => true }
ローカル環境でもEAPとデータベースを試せたらいいですね。
EAPとデータベースのPod作成
Podmanの機能の一つとして、複数コンテナを一つPodとして管理することが可能です(Kubernetesと同じような概念です)。 この機能を用いて、PostgreSQLとEAPの2つのコンテナを起動し、お互いに接続できるようにします。
3つのコマンドを使って、Podを作成して、PostgreSQLとEAPのコンテナをPodの中に作成します。
$ podman pod create -n eap \ -p 18080:8080 $ podman create \ --pod eap \ --name postgres \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=changeme \ -e POSTGRES_DB=test \ postgres:14 $ podman create \ --pod eap \ --name eap-app \ -e ENABLE_ACCESS_LOG=true \ -e DB_SERVICE_PREFIX_MAPPING=test-postgresql=TEST \ -e TEST_NONXA=true \ -e TEST_CONNECTION_CHECKER=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker \ -e TEST_EXCEPTION_SORTER=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter \ -e TEST_USERNAME=postgres \ -e TEST_PASSWORD=changeme \ -e TEST_POSTGRESQL_SERVICE_HOST=localhost \ -e TEST_POSTGRESQL_SERVICE_PORT=5432 \ -e TEST_DATABASE=test \ localhost/eap-app:latest \ /opt/eap/bin/openshift-launch.sh
作成したPodをpodman pod start eap
で起動します。
2つコンテナが起動したことを確認できます。
NOTE: pause
のコンテナは cgroups の名前空間を管理するためのもので、Podmanが自動的に起動/停止します。
$ podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d27be4db5138 k8s.gcr.io/pause:3.5 39 seconds ago Up 27 seconds ago 0.0.0.0:18080->8080/tcp d15a4ef315f2-infra 2e7170d72f23 docker.io/library/postgres:14 postgres 39 seconds ago Up 26 seconds ago 0.0.0.0:18080->8080/tcp postgres 205e1e9c1691 localhost/eap-app:latest /opt/eap/bin/open... 39 seconds ago Up 26 seconds ago 0.0.0.0:18080->8080/tcp eap-app
今回は、データベースへの接続テストを行うと、成功します。
$ podman exec eap-app \ /opt/eap/bin/jboss-cli.sh -c "/subsystem=datasources/data-source=test_postgresql-TEST:test-connection-in-pool" { "outcome" => "success", "result" => [true] }
Podを停止したい場合はpodman pod stop eap
を実行します。
まとめ
OpenShift環境を使わくても、s2iの仕組みやコンテナ技術を使って、ローカルで色々検証することが可能です。 お客様の環境や現象をすぐに再現し、原因究明を行うことはTAMとして誇りに思います。