目次
TL;DR
忙しい人向けに。
Cloud FoundryにPayara MicroProfileアプリをデプロイしたければ、
https://github.com/making/cf-payara-micro
のpom.xml
と.profile
とmanifest.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.MF
のMain-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.xml
とmanifest.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"をコピーするのが楽です。
毎回やるのは大変なので、
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があります。
- https://github.com/cloudfoundry-community/tomee-buildpack
- https://github.com/cloudfoundry/ibm-websphere-liberty-buildpack
今回のサンプルを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と同じ頻度でリリースされているように見えます。