null Alfresco SDKでホットデプロイを試してみた

Alfresco all-in-one SDK(AIO SDK)を利用したカスタマイズ開発の際、繰返し新しいモジュールをデプロイする場合が多いです。ただし、AIO SDKはDockerベースなので、さすかに毎回Dockerイメージをリビルドするのはしんどいです。そのため、新規作成のJARファイルをホットデプロイする方法を調べてみました。

  1. この記事はAlfresco all-in-one SDK v4.0とJava 11に基づいています
  2. 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項目のenviromentJAVA_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の方が使いやすいかもしれません。
関連記事
customize

RANKING
2020.10.12
2020.11.19
2020.12.23
2020.10.05
2020.11.25