Alfresco SDKでホットデプロイを試してみた - Alfresco SDKでホットデプロイを試してみた - aegif Labo Blog Alfresco
null Alfresco SDKでホットデプロイを試してみた
Alfresco all-in-one SDK(AIO SDK)を利用したカスタマイズ開発の際、繰返し新しいモジュールをデプロイする場合が多いです。ただし、AIO SDKはDockerベースなので、さすかに毎回Dockerイメージをリビルドするのはしんどいです。そのため、新規作成のJARファイルをホットデプロイする方法を調べてみました。
- この記事はAlfresco all-in-one SDK v4.0とJava 11に基づいています
- xxxx-platform、xxxx-platform-docker中のxxxxはAIO SDKのプロジェクト名を指します
- 例:プロジェクト名がcustom-alfrescoの場合、xxxx-platformはcustom-alfresco-platformになる
1. そのままDocker中のTomcatのjarファイルを更新する
AIO SDKのデフォルト設定により、Docker中のTomcatではauto deployが有効になっています(server.xml):
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
ただし、JVMキャッシュの影響で、jarファイルを更新するだけではJavaクラスの更新を反映できません。一方、リソースファイルはJVMにキャッシュされていないので、リソースのみの変更であれば、jarファイルをDockerコンテナにコピーするだけで更新を反映できます。
例えば、AlfrescoのWebscript Extension Pointのjavascriptファイルだけを更新したい場合、AIO-SDKプロジェクトのxxxxx-platform
フォルダーで以下のコマンドを実行すればDockerコンテナを再起動せずにjavascriptへの編集を反映できます。
$ mvn package
$ docker cp target/*-SNAPSHOT.jar __container__:/usr/local/tomcat/webapps/alfresco/WEB-INF/lib/
2. DockerイメージをビルドせずTomcatのみを再起動する
Tomcatでのjarファイルリロードは主にTomcatマネージャー方式、AutoDeploy+WatchedResource方式と手作業でリロードする方式があります。ただし、AIO SDKのDockerイメージ中のTomcatはデフォルトでマネージャーが有効化されていません。そのため、alfresco webアプリの設定ファイルを修正し、TomcatをリロードさせてWatchedResourceを有効にするのが一番簡単な方法です(Tomcatの仕様上、設定用xmlファイルを変更すると自動的にリロードします)。
- Dockerコンテイナーの
tomcat/webapps/alfresco/META-INF/context.xml
に以下の項目を追加し保存する(context.xmlファイルを編集しdocker cp
をする方がより早い) -
<WatchedResource>WEB-INF/lib</WatchedResource>
- context.xmlを更新するとTomcatは自動的にリロードされる
- その後、
tomcat/webapps/alfresco/WEB-INF/lib
にjarファイルをアップロードするだけでTomcatがリロードされ、jarファイル中のJavaクラスの更新が反映される
この方法の問題点は、tomcatにキャッシュされた定時実行タスクがWebアプリインスタンスにつながっていることです。WebアプリがリロードされるとインスタンスIDが変更されるため定時タスクが正しく実行されなくなり、以下のエラーメッセージが出力されます。
org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading Illegal access: this web application instance has been stopped already. Could not load [org.alfresco.error.ExceptionStackUtil].
また、コンテナ中のtomcatの再起動とalfresco-acsコンテナのリロードの所要時間には大差がないため、上記の問題点を含めて考えるとこの方法を使うより run.sh reload_acs
を使った方がよさそうです。
3. Hotswap AgentでJavaクラスを差し替える
ここまででわかることは、本気にホットデプロイをしたいならJVM中のクラスを差し替えないといけないということです。Javaクラスの更新と言えばJRebelあたりが考えられますが、有料のため敷居が高いです。幸い、公式ドキュメントによるとAlfresco AIO SDKはJRebelの他にGPLv2.0のHotswap-Agentにも対応しています。
Alfresco SDKの公式ドキュメントに従って、Hotswap-AgentをAIO SDKのDockerイメージに導入してみましょう(最新のDcevm-11.0.11を利用するため手順は少し異なります)。
- HotSwapAgentには特別な(Java Hotspot VMベースの)JDK(jdk for Dcevm-11.0.11)が必要なので、AIO SDKのDocker OS(Linux-x64)に対応するものをダウンロードする
- 上記JDKのtar.gzファイルを
xxxx-platform-docker/src/main/docker/
の下に置き、同じフォルダーの中のDockerfileに以下のコマンドを追加し、SDKのDockerイメージ中のJDKを上書きするCOPY Openjdk11u-dcevm-linux-x64.tar.gz $TOMCAT_DIR RUN tar -xvf $TOMCAT_DIR/Openjdk11u-dcevm-linux-x64.tar.gz -C /usr/java/ && \ rm $TOMCAT_DIR/Openjdk11u-dcevm-linux-x64.tar.gz && \ alternatives --install /usr/bin/java java /usr/java/dcevm-11.0.11+1/bin/java 40000 && \ alternatives --install /usr/bin/javac javac /usr/java/dcevm-11.0.11+1/bin/javac 40000 && \ alternatives --install /usr/bin/jar jar /usr/java/dcevm-11.0.11+1/bin/jar 40000 && \ alternatives --set java /usr/java/dcevm-11.0.11+1/bin/java && \ alternatives --set javac /usr/java/dcevm-11.0.11+1/bin/javac && \ alternatives --set jar /usr/java/dcevm-11.0.11+1/bin/jar && \ ln -sfn /usr/java/dcevm-11.0.11+1 /usr/java/latest && \ ln -sfn /usr/java/dcevm-11.0.11+1 /usr/java/default
- JDKのtar.gzファイルをDockerイメージの/usr/javaに解凍する
- alternativesコマンドで、Dockerイメージのjava、javacとjarコマンドをjdk-Dcevmに繋がる
-
Dcevm JDKのファイル名(
Openjdk11u-dcevm-linux-x64.tar.gz
)と解凍後のDcevm jdkフォルダー名(dcevm-11.0.11+1
)はバージョン依存しているため使用するバージョンに合わせて適宜修正する
- AIO SDKの
xxxx-platform-docker/src/main/docker/
の下に元々hotswap-agent.propertiesが存在するので、Dockerfileに以下のコマンドを追加し、hotswap-agent設定ファイルをDockerイメージのtomcatに導入する -
COPY hotswap-agent.properties $TOMCAT_DIR/webapps/alfresco/WEB-INF/classes
- こうすると、DcevmJDK中のhotswap-agentがalfrescoアプリを認識し、TomcatPluginをプリロードする
- AIO SDKのDockerイメージのデフォルト起動コマンドは
CMD: ["catalina", "run" "-security"]
なので、Dockerfileに以下のコマンドを追加し、-securityモードをオフにする -
CMD ["catalina", "run"]
-
デフォルト起動コマンドは
docker image inspect
で確認できる
-
- Dcevm-jdk-11.0.9からはHotSwapAgentがデフォルトでOFFに設定されているため、Dockerイメージの環境変数に
JAVA_OPTS: "-XX:HotswapAgent=fatjar"
を設定するdocker/docker-compose.yml
のxxxx-acs項目のenviroment
にJAVA_OPTS: "-XX:HotswapAgent=fatjar"
を追加する- Dcevm-11.0.09からhotswap-agentをONにするための設定は
=fatjar
と=core
両方が存在するが、=fatjar
の場合のみTomcatプラグインが有効になる
-
毎回ビルド後の
docker cp
を避けるため、プロジェクトのclassファイル出力フォルダをDockerイメージにマッピングする-
docker/docker-compose.yml
のxxxx-acs項目のvolumnes
に- ../../../xxxx-platform/target/classes:/usr/local/tomcat/hotswap-agent/xxxx-platform/target/classes
を追加する -
こうすると、hotswap-agentが上記フォルダー中のクラスファイルの変動を監視し、自動的にコンパイルされたクラスファイルがJVMにロードされるようになる
-
監視フォルダ(
/usr/local/tomat/hotswap-agent
)は上記hotswap-agent.properties
ファイルのextraClasspath
項目に記入されている
-
以上の設定を用いて、AIO SDKのDockerコンテナを起動すると、alfresco_acsコンテナのログ出力に以下の内容が確認できます。
- JVMの起動
-
Java Home: /usr/java/dcevm-11.0.11+1 JVM Version: 11.0.11+1-202105021744 Command line argument: -XX:HotswapAgent=fatjar
- hotswap-agentのTomcatプラグインが正しく
webapps/alfresco
中のhotswap-agent.propertiesを検知し、extraClassPathを監視フォルダーに設定する -
HOTSWAP AGENT: 12:52:15.356 DEBUG (org.hotswap.agent.util.classloader.URLClassLoaderHelper) - Added extraClassPath URLs [file:/usr/local/tomcat/hotswap-agent/] to classLoader ParallelWebappClassLoader
では、早速AlfrescoのサンプルAPIで試してみましょう(/alfresco/s/sample/helloworld
)。このAPIのjavaクラスの中身はこうなります。
public class HelloWorldWebScript extends DeclarativeWebScript {
...
protected Map<String, Object> executeImpl(...) {
...
model.put("fromJava", "HelloFromJava!");
return model;
}
}
http://localhost:8080/alfresco/s/sample/helloworld
にアクセスすると、戻り値の${fromJava}
はHelloFromJava
になります。-
$ curl -u http://localhost:8080/alfresco/s/sample/helloworld Message: 'Hello from JS!' 'HelloFromJava'
HelloWorldWebScript.java
の中身をmodel.put("fromJava", "HelloFromJava Modified!");
に変更し、mvn package
をすると、alfresco-acsのDockerコンテナにhotswap-agentのログからHelloWorldWebScript.java
がリロードされたことが確認できる-
Flushing HelloWorldWebScript from introspector HOTSWAP AGENT: 14:41:07.414 DEBUG (org.hotswap.agent.plugin.jdk.JdkPlugin) - Flushing HelloWorldWebScript from com.sun.beans.introspect.ClassInfo cache HOTSWAP AGENT: 14:41:07.415 DEBUG (org.hotswap.agent.plugin.jdk.JdkPlugin) - Flushing HelloWorldWebScript from ObjectStreamClass caches ... DEBUG (org.hotswap.agent.config.PluginManager) - ... reloaded classes [..., HelloWorldWebScript, ...] (autoHotswap)
http://localhost:8080/alfresco/s/sample/helloworld
をアクセスすると、上記変更が反映される-
$ curl http://localhost:8080/alfresco/s/sample/helloworld Message: 'Hello from JS!' 'HelloFromJava Modified!'
以上で、AlfrescoのDockerイメージを再ビルドせずに変更されたJavaクラスを反映できました!
4. Hotswap-Agentは新規クラスに対応していないようです
AIO SDKのHelloWorldWebScriptサンプルを真似て、AnotherHelloWorld.javaを作成してみましょう。
- javaクラス:AnotherHelloWorld.java(HelloWorldWebScriptのコピー)
- webscript定義:anotherhelloworld.get.desc.xml、html.ftlとjs(helloworld定義のコピー)
- webscript-context.xml:AnotherHelloWorldクラスをbeanに登録する
これでmvn package
をすると、alfresco-acsコンテナのログにクラスの新規登録を確認できますが、/alfresco/s/sample/anotherhelloworld
にアクセスすると、Web Script Status 404 - Not Found
というエラーが出ます。
$ curl http://localhost:8080/alfresco/s/sample/anotherhelloworld
...
<title>Web Script Status 404 - Not Found</title>
...
alfresco-acsコンテナを再ビルドすると正しくanotherhelloworld APIを利用できます。
この理由は、Hotswap-Agentが現在SpringのComponentScanのみをサポートしているためです。AlfrescoのクラスはXMLベースのbean方式で定義されているため、hotswap-agentのサポート対象外になります。
さすがに有料のJRebelと比べると機能が少ないですが、公式ドキュメントにはAt least XML-based bean definition will be available in a near future as well と書かれているので、いつかその機能を実装する予定はあるようです。
まとめ
Alfresco AIO SDKでのホットデプロイについて、以下のことが分かりました。
- Javaクラスの開発が安定し(更新があまり発生しない)、Webscriptのjavascript、出力テンプレートなどのリソースのみを更新することが多いケースでは、通常のalfrsco-acsコンテナの
/webapps/alfresco/WEB-INF/lib/
にmvnビルド成果物を置くだけで更新が反映される方法が便利です - Javaクラスを更新する頻度が少ない場合は、alfresco-acsコンテナをリロードする方法を使うのがよいと思います
- Javaクラスの更新頻度が高い場合は、JRebelまたはHotswap-Agentを利用できます。その場合は両方とも独自のJVMまたはJVMプラグインを利用しているため、AIO SDKのDocker設定の変更が必要です。また、Hotswap-Agentは現時点でXMLベースのbean定義をサポートしていないため、大量のJavaクラスを作成する大規模な開発であればJRebelの方が使いやすいかもしれません。