8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

App Service における Tomcat アプリケーションへの war ファイルのデプロイについて

Last updated at Posted at 2022-10-28

App Service 上で稼働する、Tomcat アプリケーションへのデプロイ時に使用する Kudu API がよくわからない!

App Service に war ファイルをデプロイする API として /api/wardeploy/api/publish の2種類があり、/api/wardeploy のほうがおそらく歴史が古い。
2022年10月現在では、公式ドキュメントでは/api/publishを使うように紹介されており、AZ CLI も内部的には、/api/publish を使うようになっている。1
なお、/api/wardeploy については、Kudu のリポジトリに WiKiページ が存在するが、/api/publish については存在しない。
どちらを使うべきかわからない、違いがわからないといったことが多いので
本稿では実際に試して見えてきた内容を記載します。

TL;DR

  • /api/publish を使いましょう
  • /api/publish を使うとネットワークドライブではなく、インスタンス固有のローカル領域にコピーされる。ファイル I/O のレイテンシの観点や、複数インスタンスの競合を避けるなどのメリットが有る
  • ここでいうローカルコピーは Java スタック独自の機能で、App Service の ローカルキャッシュとはべつもの
  • ただし ROOT コンテキストとなる。
  • ローカルコピーを有効にした状態で任意のコンテキストを用いたい場合は、スタートアップスクリプトで対応する必要あり

検証環境

OS: Windows
SKU: B1
Java: 11.0.13
Tomcat: 9.0.542
image.png

/api/wardeploy/api/publish の違い

ここでの api 実行時には、純粋に Kudu の API の挙動を確認するため、WebApp としては停止状態で行っています。
WebApp が稼働状態の場合、API 実行をトリガーに、アプリケーションの再起動が行われ、初期化処理内でいろいろ行われる(後述)ためです。

/api/wardeploy とは

/api/wardeploy については、WiKi にあるとおり、warファイルの展開をTomcatに実行させずに予め展開した状態で %HOME%\site\wwwroot\webapps (Linux の場合は$HOME/site/wwwroot/webapps)に配置が行われる。

In simplest terms, it takes over the unpacking of the WAR file from Tomcat by unpacking it by itself. The WAR file is unpacked to a temporary location and the unpacked contents are then copied over to the appropriate directory in /home/site/wwwroot/webapps

この動作の目的としては、
%HOME% あるいは$HOMEは、ネットワークドライブゆえに、複数インスタンスにスケールアウトした状態で、それぞれのインスタンスで動作する Tomcat が war を展開しようとする動作を防ぐことにある。

However, when you scale out the web app, multiple Tomcat instances race to unpack the WAR file leading to race conditions that may land one or more workers / instances in a bad state. This issue manifests in different ways, most common being a 404 status code when an incoming request is routed to any of the bad workers.

実行例

curl -X POST -u ${basicauth} "https://${myappName}.scm.azurewebsites.net/api/wardeploy" --data-binary @helloworld.war

image.png

dir /b /s
D:\home\site\wwwroot>dir /b /s
D:\home\site\wwwroot\webapps
D:\home\site\wwwroot\webapps\ROOT
D:\home\site\wwwroot\webapps\ROOT\META-INF
D:\home\site\wwwroot\webapps\ROOT\WEB-INF
D:\home\site\wwwroot\webapps\ROOT\index.jsp
D:\home\site\wwwroot\webapps\ROOT\META-INF\maven
省略

上記の通り、ROOT コンテキストで展開されるため、helloworld.war 内の index.jsp は https://<appname>.azurewebsites.net/ でアクセスできるようになる。

実行例 name(context) 指定

API実行時に name パラメータを指定することで、指定のコンテキストに展開することもできる

curl -X POST -u ${basicauth} "https://${myappName}.scm.azurewebsites.net/api/wardeploy?name=mycontext" --data-binary @helloworld.war

image.png

上記の通り、name で指定した コンテキストで展開されるため、helloworld.war 内の index.jsp は https://<appname>.azurewebsites.net/mycontext/ でアクセスできるようになる。

wardeploy 利用時の懸念点

warファイルを事前に展開することで、Tomcat 起動時の複数インスタンスによる競合を避けるメリットはあるものの、あくまでも %HOME% 配下はネットワークドライブとなる。ネットワークドライブである限り、何かしらの接続エラーや、File I/O の遅延の発生の可能性は避けられない。

/api/publish とは

/api/publish については、公式ドキュメント上 Kudu publish API reference にあるとおり、デフォルトでは、warファイルは、app.war として %HOME%\site\wwwroot (Linuxの場合は $HOME/site/wwwroot)に配置が行われる。
このとき、app.war の展開は行われない。

 - type=war: Deploy a WAR package. By default, the WAR package is deployed to /home/site/wwwroot/app.war. The target path can be specified with path.

実行例

※事前にwardeployを実行した際のファイルは削除した後に確認しています。

curl -X POST -u ${basicauth} "https://${myappName}.scm.azurewebsites.net/api/publish?type=war" --data-binary @helloworld.war

image.png

実行例 path(context) 指定

path を指定した場合は、そのディレクトリに展開されることになる。(wardeployでnameを指定した場合と同じ挙動)

curl -X POST -u ${basicauth} "https://${myappName}.scm.azurewebsites.net/api/publish?type=war&path=/home/site/wwwroot/webapps/mycontext" --data-binary @helloworld.war

image.png

D:\home\site\wwwroot>dir /b /s
D:\home\site\wwwroot\webapps
D:\home\site\wwwroot\webapps\mycontext
D:\home\site\wwwroot\webapps\mycontext\META-INF
D:\home\site\wwwroot\webapps\mycontext\WEB-INF
D:\home\site\wwwroot\webapps\mycontext\index.jsp
D:\home\site\wwwroot\webapps\mycontext\META-INF\maven
省略

/api/publish で配置された app.war はどうなるのか?

ここまで /api/wardeploy/api/publish そのものの動作の違いを確認してきました。
/api/publish を用いると、warファイルは展開されずに %HOME%\site\wwwroot\app.war として配置されることがわかりました。

アプリケーション起動処理

%HOME%\site\wwwroot 以下に app.war あるいは 展開済みの webapps/mycontext ディレクトリが存在する場合に、アプリケーション起動時の動きを見ていきます。

App Service にて Java が言語スタックの場合、Windows 環境では w3wp.exe のプロセスの配下に、JavaBootstrappercmdjava とプロセスが存在することがわかります。

image.png

cmd はプロセスの Handle からなんとなく catalina.bat と考えられますが、その前段の JavaBootStrapper は何をしているのかは残念ながら公開情報はありません。

そんなときは、Linux 版を見てみます。なにかヒントがあるかもしれません。

Linux 版の動作

Linux 環境で Java tomcat スタックのコンテナはどう動いているかを ps で見てみると java の他には、bash /bin/init_container.sh がいます。名前的に、コンテナのエントリポイントですね。

image.png

これを見てみると、ありました。なにやら app.war をコピーするぞって感じの分岐があります。
おそらく、JavaBootstrapper も似たようなことをやっているはずです。

/bin/init_container.sh抜粋
    78  # This is the precedence:
    79  # 1. /home/site/wwwroot/app.war
    80  # 2. /home/site/wwwroot/webapps
    81  # 3. Parking page app
    82  # Note: If /home/site/wwwroot/webapps is used, we copy the app to a local location before using it, unless,
    83  # - Local copy is turned on, in which case /home is pointing to a local share already (so local copy is unnecessary)
    84  # OR
    85  # - WEBSITE_SKIP_LOCAL_COPY is defined to 1 or TRUE.
    86  if [ -f /home/site/wwwroot/app.war ]
    87  then
    88      SITEROOT=/usr/local/tomcat/webapps 
    89      cp /home/site/wwwroot/app.war $SITEROOT/ROOT.war
    90      echo "Using app.war with SITEROOT=$SITEROOT"
    91
    92      # Enable WAR unpacking as we are sure only one app is present at this location
    93      # So we don't have to worry about locking issues. On the other hand, this will provide some performance improvements.
    94      TOMCAT_UNPACK_WARS=true
    95  elif [ ! -d /home/site/wwwroot/webapps ]
    96  then
    97      SITEROOT=/usr/local/appservice/parkingpage
    98      echo "Using parking page app with SITEROOT=$SITEROOT"
    99
   100      # Enable WAR unpacking as we are sure only one app is present at this location
   101      # So we don't have to worry about locking issues. On the other hand, this will provide some performance improvements.
   102      TOMCAT_UNPACK_WARS=true
   103  elif [[ ! -z "$WEBSITE_LOCALCACHE_READY" || "$WEBSITE_SKIP_LOCAL_COPY" = "1"  || "$WEBSITE_SKIP_LOCAL_COPY" = "true" ]]
   104  then
   105      SITEROOT=/home/site/wwwroot/webapps
   106      echo "No local copy needed. SITEROOT=$SITEROOT"
   107  else
   108      SITEROOT=/usr/local/tomcat/webapps
   109      cp -r /home/site/wwwroot/webapps/* $SITEROOT/
   110      echo "Made a local copy of the app and using SITEROOT=$SITEROOT"
   111  fi

catalina.log からの確認

%HOME%\site\wwwroot\app.war が配置されている場合(/api/publish を使った場合)

-Dsite.appbaseC:/DWASFiles/Sites/アプリ名/Temp/tomcatFiles で始まるディレクトリが指定されていることがわかります。
すなわち Tomcat は、%HOME% はもはや見ていません。
app.war は、ローカル領域(C:/DWASFiles/Sites/アプリ名/Temp/tomcatFiles)にすでにコピーされているものと考えられます。

catalina.log抜粋
CATALINA_BASE:         D:\Program Files\apache-tomcat-9.0.54
CATALINA_HOME:         D:\Program Files\apache-tomcat-9.0.54
Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
Command line argument: -Djava.util.logging.config.file=D:\Program Files\apache-tomcat-9.0.54\conf\logging.properties
Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
Command line argument: -Dfile.encoding=UTF-8
Command line argument: -Dcatalina.valves.showReport=False
Command line argument: -Dcatalina.valves.showServerInfo=False
Command line argument: -Dsite.logdir=D:/home/LogFiles
Command line argument: -Dsite.home=C:/DWASFiles/Sites/<アプリ名>/Temp/tomcatFiles/845e1301-9b9c-4834-a578-6db4e85825da
Command line argument: -Dsite.libs=D:/home/site/libs
Command line argument: -Dsite.appbase=C:/DWASFiles/Sites/<アプリ名>/Temp/tomcatFiles/845e1301-9b9c-4834-a578-6db4e85825da/site/wwwroot/webapps
Command line argument: -Dsite.xmlbase=C:/DWASFiles/Sites/<アプリ名>/Temp/tomcatFiles/845e1301-9b9c-4834-a578-6db4e85825da/site/wwwroot
Command line argument: -Dsite.unpackwars=true
Command line argument: -Dsite.tempdir=D:\local\Temp

この、インスタンスのローカル領域にファイルをコピーしてくれる機能については、ローカルキャッシュのドキュメントにひっそりと書かれています。(なお、ローカルキャッシュとは別の機能)
https://learn.microsoft.com/en-us/azure/app-service/overview-local-cache

If you are using Java (Java SE, Tomcat, or JBoss EAP), then by default the Java artifacts--.jar, .war, and .ear files--are copied locally to the worker.

ローカルにコピーするメリットとしては、ネットワークストレージである%HOME%($HOME) に比べて、File I/O の向上や、予期せぬネットワークエラーの回避などが考えられます。

%HOME%\site\wwwroot\webapps\mycontext 以下に展開済みファイルが配置されている場合(/api/wardeployを利用した場合 、 /api/publishでpathを指定した場合)

一方で従来の wardeploy によってすでに war ファイルが %HOME%\site\wwwroot\webapps 配下に展開されている場合、
-Dsite.appbase が、Dsite.appbase=D:/home/site/wwwroot/webapps を指しています。
Tomcat は、ネットワークドライブである、%HOME% を見ており、インスタンスのローカル領域にはアプリケーションファイルはコピーされていないという状況です。

catalina.log抜粋
CATALINA_BASE:         D:\Program Files\apache-tomcat-9.0.54
CATALINA_HOME:         D:\Program Files\apache-tomcat-9.0.54
Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
Command line argument: -Djava.util.logging.config.file=D:\Program Files\apache-tomcat-9.0.54\conf\logging.properties
Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
Command line argument: -Dcatalina.valves.showReport=False
Command line argument: -Dcatalina.valves.showServerInfo=False
Command line argument: -Dsite.logdir=D:/home/LogFiles
Command line argument: -Dsite.home=D:/home
Command line argument: -Dsite.libs=D:/home/site/libs
Command line argument: -Dsite.appbase=D:/home/site/wwwroot/webapps
Command line argument: -Dsite.xmlbase=D:/home/site/wwwroot
Command line argument: -Dsite.unpackwars=true

ローカル領域とはなんなのか

残念ながら Kudu UI の DebugConsole からでは、C:/DWASFiles/Sites/<アプリ名>/Temp/tomcatFiles/%LOCAL_EXPANDED% の詳細は確認できません。(Linux 版の場合コピー先は /usr/local/tomcatとなっており、SSH 接続して自由に見ることができます)

そんなときは、スタートアップスクリプト startup.cmd (or startup.ps1) を利用することでわかります。
スタートアップスクリプトは 以下に説明がある通り、Tomcat が起動する前に(つまり、JavaBootStrapper によって) 実行されます。

For Windows sites, create a file named startup.cmd or startup.ps1 in the wwwroot directory. This will automatically be executed before the Tomcat server starts.

スタートアップスクリプトによる検証

AZURE_JAVA_APP_PATHAZURE_SITE_HOME が使えそうです。

Setting name Description
AZURE_JAVA_APP_PATH Environment variable can be used by custom scripts So they have a location for app.jar after it's copied to loca
AZURE_SITE_HOME The value added to the Java args as -Dsite.home. The default is the value of HOME.

下記の内容の startup.cmd%HOME%\site\wwwroot\ に配置してアプリケーションを起動してみます。

%HOME%\site\wwwroot\startup.cmd
set CURRENT_DATE=%date:~10,4%%date:~4,2%%date:~7,2%
set logfile=%HOME%\LogFiles\startup_%CURRENT_DATE%.log

echo "==== startup.cmd start ===" >> "%logfile%" 2>&1
date /t >> "%logfile%" 2>&1
time /t >> "%logfile%" 2>&1

echo "AZURE_JAVA_APP_PATH  = %AZURE_JAVA_APP_PATH%">> "%logfile%" 2>&1
echo "AZURE_SITE_HOME = %AZURE_SITE_HOME%">> "%logfile%" 2>&1

echo "dir %AZURE_SITE_HOME%\site\wwwroot\" >> "%logfile%" 2>&1
dir %AZURE_SITE_HOME%\site\wwwroot >> "%logfile%" 2>&1

echo "dir %AZURE_SITE_HOME%\site\wwwroot\webapps" >> "%logfile%" 2>&1
dir %AZURE_SITE_HOME%\site\wwwroot\webapps >> "%logfile%" 2>&1


echo "==== startup.cmd end ===" >> "%logfile%" 2>&1

残念ながら、%AZURE_JAVA_APP_PATH%はドキュメントに記載の内容は取得できませんが(追記: JavaSE スタック Jarデプロイの場合に設定されます。 https://qiita.com/georgeOsdDev@github/items/436b21ffad30f7ca31d8 )、%AZURE_SITE_HOME% が catalina.log に出力された C:/DWASFiles/Sites/<アプリ名>/Temp/tomcatFiles/<ランダム文字列> に一致します。
そして、
C:\DWASFiles\Sites\<アプリ名>\Temp\tomcatFiles\211caf6a-7176-48f4-af7f-1b3d3a462d6d\site\wwwroot 配下に、
app.warROOT.xml と、空の webapps ディレクトリが存在していることがわかります。
Tomcat起動前なので、app.war はまだ展開されていませんね。

startup.cmdの実行結果
"==== startup.cmd start ===" 
Fri 10/28/2022 
01:41 PM
"AZURE_JAVA_APP_PATH  = "
"AZURE_SITE_HOME = C:\DWASFiles\Sites\<アプリ名>\Temp\tomcatFiles\211caf6a-7176-48f4-af7f-1b3d3a462d6d"
"dir C:\DWASFiles\Sites\<アプリ名>\Temp\tomcatFiles\211caf6a-7176-48f4-af7f-1b3d3a462d6d\site\wwwroot\" 
 Volume in drive C is Temporary Storage
 Volume Serial Number is 484F-9096

 Directory of C:\DWASFiles\Sites\<アプリ名>\Temp\tomcatFiles\211caf6a-7176-48f4-af7f-1b3d3a462d6d\site\wwwroot

10/28/2022  01:41 PM    <DIR>          .
10/28/2022  01:41 PM    <DIR>          ..
10/28/2022  12:58 PM             6,771 app.war
10/28/2022  01:41 PM               156 ROOT.xml
10/28/2022  01:41 PM    <DIR>          webapps
               2 File(s)          6,927 bytes
               3 Dir(s)  11,725,500,416 bytes free
"dir C:\DWASFiles\Sites\<アプリ名>\Temp\tomcatFiles\211caf6a-7176-48f4-af7f-1b3d3a462d6d\site\wwwroot\webapps" 
 Volume in drive C is Temporary Storage
 Volume Serial Number is 484F-9096

 Directory of C:\DWASFiles\Sites\<アプリ名>\Temp\tomcatFiles\211caf6a-7176-48f4-af7f-1b3d3a462d6d\site\wwwroot\webapps

10/28/2022  01:41 PM    <DIR>          .
10/28/2022  01:41 PM    <DIR>          ..
               0 File(s)              0 bytes
               2 Dir(s)  11,725,500,416 bytes free
"==== startup.cmd end ===" 

ちなみに ROOT.xml は以下のような内容が記載されていました。

<Context displayName="app" docBase="C:\DWASFiles\Sites\<アプリ名>\Temp\tomcatFiles\3c42584b-db11-45cf-acc9-3cf0c0ade9fa\site\wwwroot\app.war" path="" />

ローカルコピー機能は app.war(= ROOTコンテキスト) 以外は利用できるか

以下のように、startup.cmd の処理内で、app.war を 利用したいコンテキスト.war という名称で %AZURE_SITE_HOME%\site\wwwroot\webapps\ 配下に移動することでOKでした。
試してないですが、app.war 以外にも、%HOME% のどこかに予め用意しておいた war ファイルを適宜%AZURE_SITE_HOME%\site\wwwroot\webapps\ 以下にコピーすることで複数コンテキストを動かすことも可能と思われますが、1 WebAppに対しては 1 コンテキストが望ましい。3

startup.cmd
set CURRENT_DATE=%date:~10,4%%date:~4,2%%date:~7,2%
set logfile=%HOME%\LogFiles\startup_%CURRENT_DATE%.log

echo "==== startup.cmd start ===" >> "%logfile%" 2>&1
date /t >> "%logfile%" 2>&1
time /t >> "%logfile%" 2>&1

echo "AZURE_JAVA_APP_PATH  = %AZURE_JAVA_APP_PATH%">> "%logfile%" 2>&1
echo "AZURE_SITE_HOME = %AZURE_SITE_HOME%">> "%logfile%" 2>&1

echo "move %AZURE_SITE_HOME%\site\wwwroot\app.war %AZURE_SITE_HOME%\site\wwwroot\webapps\mycontext.war" >> "%logfile%" 2>&1
move %AZURE_SITE_HOME%\site\wwwroot\app.war %AZURE_SITE_HOME%\site\wwwroot\webapps\mycontext.war >> "%logfile%" 2>&1

echo "delete unused wwwroot\ROOT.xml" >> "%logfile%" 2>&1
del %AZURE_SITE_HOME%\site\wwwroot\ROOT.xml

echo "check %AZURE_SITE_HOME%\site\wwwroot" >> "%logfile%" 2>&1
dir %AZURE_SITE_HOME%\site\wwwroot >> "%logfile%" 2>&1

echo "check %AZURE_SITE_HOME%\site\wwwroot\webapps" >> "%logfile%" 2>&1
dir %AZURE_SITE_HOME%\site\wwwroot\webapps >> "%logfile%" 2>&1

echo "==== startup.cmd end ===" >> "%logfile%" 2>&1
catalina.log抜粋
[main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
[main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/9.0.54]
[main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [C:\DWASFiles\Sites\<アプリ名>\Temp\tomcatFiles\fdcceda6-132c-48ff-9ae2-090dfbb775c3\site\wwwroot\webapps\mycontext.war]
[main] com.microsoft.azure.appservice.logging.AppServiceLogger.info Log clean up task initializing. Log expiry period = 90 days
[main] com.microsoft.azure.appservice.logging.AppServiceLogger.info Log clean up task initialization succeeded
[main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [C:\DWASFiles\Sites\<アプリ名>\Temp\tomcatFiles\fdcceda6-132c-48ff-9ae2-090dfbb775c3\site\wwwroot\webapps\mycontext.war] has finished in [6,328] ms
[main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-127.0.0.1-24588"]
[main] org.apache.catalina.startup.Catalina.start Server startup in [8222] milliseconds

なお

  1. az webapp deploy --debug で確認可能

  2. App Service で利用可能なTomcat 9.x 系の最新版(9.0.62)の1世代前

  3. https://learn.microsoft.com/ja-jp/azure/developer/java/migration/migrate-tomcat-to-tomcat-app-service#create-and-deploy-web-apps

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?