IK.AM

@making's tech note


Payara MicroProfileで作ったJava EEアプリをCloud Foundryにデプロイする

🗃 {Middleware/AppServer/Payara/Microprofile}
🏷 Payara 🏷 Microprofile 🏷 Java EE 🏷 Java 🏷 Cloud Foundry 
🗓 Updated at 2017-06-27T17:00:47Z  🗓 Created at 2017-06-27T03:30:32Z   🌎 English Page

目次

TL;DR

忙しい人向けに。

Cloud FoundryにPayara MicroProfileアプリをデプロイしたければ、

https://github.com/making/cf-payara-micro

pom.xml.profilemanifest.ymlを参考にしてください。

サンプルアプリであれば、

git clone https://github.com/making/cf-payara-micro
mvn clean package
cf push <your-app-name>

でデプロイできます!簡単。

何が問題?

Payara Microは実行可能jar(uber jar)を作成できることで有名です。

実際に、

java -jar payara-microprofile-1.0-4.1.2.172.jar --deploy ROOT.war --outputUberJar ROOT.jar
java -jar ROOT.jar

でアプリケーションを実行可能です。

Cloud Foundryのjava buildpackは一般的には実行可能jarがあれば。

cf push myapp -p app.jar

でデプロイ可能です。なので、PayaraのアプリはCloud Foundryに素直にデプロイ可能かと思えます。

しかし、Payara Microで作ったuber jarでcf pushを実行すると次のエラーが発生します。

2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR Exception in thread "main" java.lang.IllegalStateException: java.io.FileNotFoundException: /home/vcap/app (Is a directory)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at fish.payara.micro.boot.loader.ExecutableArchiveLauncher.<init>(ExecutableArchiveLauncher.java:44)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at fish.payara.micro.boot.PayaraMicroLauncher.<init>(PayaraMicroLauncher.java:57)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at fish.payara.micro.boot.PayaraMicroLauncher.main(PayaraMicroLauncher.java:72)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at fish.payara.micro.PayaraMicro.main(PayaraMicro.java:358)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR Caused by: java.io.FileNotFoundException: /home/vcap/app (Is a directory)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at java.util.zip.ZipFile.open(Native Method)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at java.util.zip.ZipFile.<init>(ZipFile.java:219)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at java.util.zip.ZipFile.<init>(ZipFile.java:149)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at java.util.jar.JarFile.<init>(JarFile.java:166)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at java.util.jar.JarFile.<init>(JarFile.java:130)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at fish.payara.micro.boot.loader.jar.JarFile.<init>(JarFile.java:112)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at fish.payara.micro.boot.loader.jar.JarFile.<init>(JarFile.java:106)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at fish.payara.micro.boot.loader.jar.JarFile.<init>(JarFile.java:92)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at fish.payara.micro.boot.loader.archive.JarFileArchive.<init>(JarFileArchive.java:61)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at fish.payara.micro.boot.loader.Launcher.createArchive(Launcher.java:149)
2017-06-27T10:10:17.33+0900 [APP/PROC/WEB/0]ERR 	at fish.payara.micro.boot.loader.ExecutableArchiveLauncher.<init>(ExecutableArchiveLauncher.java:41)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at fish.payara.micro.boot.loader.jar.JarFile.<init>(JarFile.java:83)
2017-06-27T10:10:17.32+0900 [APP/PROC/WEB/0]ERR 	at fish.payara.micro.boot.loader.archive.JarFileArchive.<init>(JarFileArchive.java:57)
2017-06-27T10:10:17.33+0900 [APP/PROC/WEB/0]ERR 	... 3 more

java.io.FileNotFoundExceptionと出ます。Payaraがjarファイルであることを想定しているのに、それが存在していないというエラーのようです。

実はcf push -p app.jarでデプロイすると、app.jarがアップロードされるのではなく、app.jarが一度展開され、展開されたフォルダがアップロードされます。Cloud Foundry上ではjava -jar app.jarではなく、java <Main Class>が実行されます。(Spring Bootのjarでない場合は、META-INF/MANIFEST.MFMain-Classが使用されます)

そのため、Payaraの想定と異なり、実行時にエラーが発生しています。

Cloud FoundryでPayaraを動かす

根本的にはPayaraがjarが展開された状態でも実行可能になって欲しいのですが、workaroundとして今のままで動かせるように少しハックします。 この節はマニアックなので、興味のない人は読み飛ばしてください。

ちょっと強引ですが、Payaraがjarを想定しているので、実行前に展開されたフォルダを再度jarにパッケージングすれば動くでしょう。

ここでポイントとなるのがCloud FoundryのPre-Runtime Hooksです。 Cloud Foundryではアプリケーションフォルダの直下に.profileという名前のスクリプトファイルがある場合、起動前にこれを実行します。

なので、次のような.profileを作成します。

#!/bin/sh

zip -qr app.jar fish MICRO-INF META-INF

次に問題となるのが、cf push-pをつけない場合はカレントフォルダが丸ごとアップロードされますが、-pでjarファイルを指定した場合、展開したフォルダしかアップロードされないと言う点です。このままでは.profileを作ってもアップロードされません。このため、上記のドキュメントでは

Note: The Java buildpack does not support pre-runtime hooks.

と書かれています。

それでも、原理上は.profileをjarが展開されるフォルダに入れられれば、一緒にアップロードされ、アプリケーションの実行前にこのスクリプトが実行されるはずです。 そこでjarコマンドを使って、jarファイルの中に.profileを埋め込みます。

jar -uvf ROOT.jar .profile

これで、展開されたフォルダに.profileを混ぜ込むことができます。

あとはこのjarをcf pushすれば良いのですが、実行コマンドをjar -jar app.jarに変更する必要があります。これは-cで指定可能です。 javaコマンドはPATHに入っていないのでフルパスの.java-buildpack/open_jdk_jre/bin/javaを実行します。

コマンドは次のようになります。

cf push myapp -p ROOT.jar -b java_buildpack -c '.java-buildpack/open_jdk_jre/bin/java -jar app.jar'

以上の操作を毎回叩くのは面倒なので、pom.xmlmanifest.ymlに設定します。

uberjarの作成とjar -uvfはmaven-antrun-pluginを使って、次のように設定するとmvn packageの後に自動で行われます。

<plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.8</version>
    <executions>
        <execution>
            <phase>package</phase>
            <configuration>
                <target>
                    <echo message="Build uber jar"/>
                    <java fork="true"
                          jar="${settings.localRepository}/fish/payara/extras/payara-microprofile/${payara-microprofile.version}/payara-microprofile-${payara-microprofile.version}.jar">
                        <arg value="--deploy"/>
                        <arg value="${basedir}/target/${build.finalName}.war"/>
                        <arg value="--outputUberJar"/>
                        <arg value="${basedir}/target/${build.finalName}.jar"/>
                    </java>
                    <echo message="Add .profile into uber jar"/>
                    <jar update="true" destfile="${basedir}/target/${build.finalName}.jar">
                        <fileset dir="${basedir}" includes=".profile"/>
                    </jar>
                </target>
            </configuration>
            <goals>
                <goal>run</goal>
            </goals>
        </execution>
    </executions>
</plugin>

全内容はこちら

manifest.ymlは次のようになります。

applications:
- name: myapp
  memory: 1G
  buildpack: java_buildpack
  command: '.java-buildpack/open_jdk_jre/bin/java -jar app.jar'
  path: target/ROOT.jar

これで、

mvn clean package
cf push

の2コマンドだけでアプリケーションのビルド -> Cloud Foundryへのデプロイができます。超お手軽です。

ちなみにJava BuildpackではMemory Calculatorと言う、コンテナのメモリサイズに応じて自動でJVMの適切なメモリ設定をしてくれるツールが含まれています。これを使用してJVMのオプションを設定したい場合は、manifest.ymlは次のようになります。

applications:
- name: myapp
  memory: 1G
  buildpack: java_buildpack
  command: 'CALCULATED_MEMORY=$($PWD/.java-buildpack/open_jdk_jre/bin/java-buildpack-memory-calculator-2.0.2_RELEASE -memorySizes=metaspace:64m..,stack:228k.. -memoryWeights=heap:65,metaspace:10,native:15,stack:10 -memoryInitials=heap:100%,metaspace:100% -stackThreads=300 -totMemory=$MEMORY_LIMIT) && JAVA_OPTS="-Djava.io.tmpdir=$TMPDIR -XX:OnOutOfMemoryError=$PWD/.java-buildpack/open_jdk_jre/bin/killjava.sh $CALCULATED_MEMORY" && .java-buildpack/open_jdk_jre/bin/java $JAVA_OPTS -jar app.jar'
  path: target/ROOT.jar

【注意】

command: 'CALCULATED_MEMORY=$($PWD/.java-buildpack/open_jdk_jre/bin/java-buildpack-memory-calculator-2.0.2_RELEASE -memorySizes=metaspace:64m..,stack:228k.. -memoryWeights=heap:65,metaspace:10,native:15,stack:10 -memoryInitials=heap:100%,metaspace:100% -stackThreads=300 -totMemory=$MEMORY_LIMIT) && JAVA_OPTS="-Djava.io.tmpdir=$TMPDIR -XX:OnOutOfMemoryError=$PWD/.java-buildpack/open_jdk_jre/bin/killjava.sh $CALCULATED_MEMORY" && .java-buildpack/open_jdk_jre/bin/java $JAVA_OPTS -jar app.jar'

この部分は、Java Buildpackのバージョン(Memory Calculatorのバージョン)が変わる度に変更する必要があります。 特にJava Buildpack 4からは設定内容が大きく変わります

実行コマンドを簡単に知るには、まずは-cをつけずにcf pushして、Apps ManagerでアプリケーションのSettingタブに行き、"Start Command"をコピーするのが楽です。 image

毎回やるのは大変なので、

https://github.com/making/cf-payara-micro

をコピーすると良いです。

何が嬉しい?

これまでJava EEアプリケーションをCloud Foundryにデプロイするにはアプリケーションサーバー毎にbuildpackが必要でした。(Payara用のbuildpackはありません!) これらのbuildpackは通常Java Buildpackからフォークされて作られるので、アップストームへの追従が必要です。コミュニティベースのbuildpackは更新が止まることが多々あるため、アプリケーションだけをケアすれば良いというbuildpackのメリットが失われてしまいます。 また、Java EEサーバーをそのままクラウド上でコンテナとして動かすのがちょっと重く、管理機能面など逆にJava EEサーバーのメリット失う可能性もあります。

今回の方法は、OfficialなJava Buildpackを使っているので、最新のBuildpackに追従でき、セキュアなランタイムを維持できます。Microprofileを使っているのでフットプリントも小さく、コンテナ上でも普通に使えそうです。

BuildpackはCVEが出たら48時間以内にリリースされる予定です。

これでParaya Microprofileで作ったJava EEアプリでもCloud Foundryのスケーラビリティ、自動復旧、ログ・メトリクスの集約などなどを享受できます。

ぜひ、Payara Microprofile on Cloud FoundryでJava EEでもCloud Native Lifeをお楽しみください。

Cloud Foundry楽しい✌✌

TODO:

  • Container to Container Networkingを使ったHazelcastのクラスタリング
  • Backendサービス(データベース)の使い方 [サンプルアプリ]
  • Workaroundを直す

【余談】

ちなみにメンテナンスされているように見えるJava EEのBuildpackとしてはApache TomEEとIBM WebSphere Application Server Libertyがあります。

今回のサンプルをTomEEで動かしたい場合は、次のようなmanifest.ymlを用意して、

applications:
- name: hello-my-tomee
  memory: 1G
  buildpack: https://github.com/cloudfoundry-community/tomee-buildpack#v4.2
  path: target/ROOT.war # jarじゃなくてwar

cf push可能です。TomEE BuildpackはJava Buildpackと同じ頻度でリリースされているように見えます。


✒️️ Edit  ⏰ History  🗑 Delete