---
title: Payara MicroProfileで作ったJava EEアプリをCloud Foundryにデプロイする
tags: ["Payara", "Microprofile", "Java EE", "Java", "Cloud Foundry"]
categories: ["Middleware", "AppServer", "Payara", "Microprofile"]
date: 2017-06-27T03:30:32Z
updated: 2017-06-27T17:00:47Z
---

**目次**
<!-- toc -->

### 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)を作成できる](https://payara.gitbooks.io/payara-server/documentation/payara-micro/configuring/package-uberjar.html)ことで有名です。

実際に、

```
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](https://github.com/cloudfoundry/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](https://docs.cloudfoundry.org/devguide/deploy-apps/deploy-app.html#profile)です。
Cloud Foundryではアプリケーションフォルダの直下に`.profile`という名前のスクリプトファイルがある場合、起動前にこれを実行します。

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

``` sh
#!/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`の後に自動で行われます。

``` xml
<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>
```

全内容は[こちら](https://github.com/making/cf-payara-micro/blob/master/pom.xml)。

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

``` yaml
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](https://github.com/cloudfoundry/java-buildpack-memory-calculator)と言う、コンテナのメモリサイズに応じて自動でJVMの適切なメモリ設定をしてくれるツールが含まれています。これを使用してJVMのオプションを設定したい場合は、`manifest.yml`は次のようになります。


``` yaml
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](https://github.com/cloudfoundry/java-buildpack-memory-calculator)のバージョン)が変わる度に変更する必要があります。
> 特にJava Buildpack 4からは設定内容が[大きく変わります](https://www.cloudfoundry.org/just-released-java-buildpack-4-0/)。
>
> 実行コマンドを簡単に知るには、まずは`-c`をつけずに`cf push`して、[Apps Manager](https://console.run.pivotal.io)でアプリケーションのSettingタブに行き、"Start Command"をコピーするのが楽です。
> ![image](https://user-images.githubusercontent.com/106908/27568505-2ea26da0-5b2d-11e7-9c4d-329dd71f1a08.png)


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

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楽しい&#9996;&#9996;


TODO:

* Container to Container Networkingを使ったHazelcastのクラスタリング
* Backendサービス(データベース)の使い方 [[サンプルアプリ](https://github.com/making/cf-payara-micro/tree/db)]
* 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`を用意して、
>
> ``` yaml
> 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と同じ頻度でリリースされているように見えます。
