Azure WebAppsでMac/LinuxからJavaを無停止デプロイ

[Microsoft Azure Advent Calendar 2015 7日目](http://qiita.com/advent-calendar/2015/azure)の記事です。
遅刻しました。申し訳ありません...

UNIX環境(Win以外)からwarアプリを無停止デプロイするまでのポイントをおさらいします。
下記の構成で無停止デプロイを実現するにあたり、Standard以上のApp Serviceプランが必要です。

# アップロード

Azure WebAppsにはgithub連携でWebHookを受け取って自動的にデプロイする機構がありますが、コンパイル言語の場合、ビルド済バイナリがリポジトリに含まれていない限りはWebApps側でコンパイルを担保する必要があります。
カスタム・デプロイ・ロジックを使ってmavenなどを持ってきてビルドを挟ませることも可能ですが、`cmd`/PowerShellだったのと今回は既に外部でWebHook→ビルドするJenkinsジョブがあったのでそこからFTPSによるアップロードを行いました。

FTPSは実は`curl`コマンド一発でアップロードが完結します。
WebApps側でディレクトリ構造を修正していない場合は、`/site/wwwroot/webapps/ROOT.war`にアップロードすることで自動的に展開され、ルートにアプリが配備されます。
FTPSログイン用にデプロイ資格情報からユーザ名・パスワードをあらかじめ設定した上で、下記のコマンドを実行します。

```sh
curl -T /path/to/APP.war -u 'mywebapp\deployer':${FTPS_PASSWORD} --ftp-ssl ftp://waws-prod-ty1001.ftp.azurewebsites.windows.net/site/wwwroot/webapps/ROOT.war
```

# スワップ

上述のアップロードが検知され、warが展開されている間、アプリケーションはデタッチされダウンタイムが発生します。
今回はこのダウンタイム回避のために、デプロイメントスロットとそのスワップによる運用を行っています。
スケーリング(複数台)環境でのローリングデプロイが可能かどうか確認していないのですが、Microsoftのフォーラムなどではこのスワップによるデプロイが推奨されているようです。

デプロイメントスロットの有効化にはApp ServiceプランStandard以上が必要になります。
また、スワップした際には設定も同時に入れ替わるので、実運用アプリについてはすべて同じ値にしておくことが推奨されます。
なお、オプションとして提示されている自動スワップはgithub連携などの「継続的デプロイ」経由でしか発火しないため、今回は手動でのスワップが必須になります。

自分の場合はFTPSユーザ名の末尾に`__staging`がつく以外は全く同じ環境が出来上がりました。

1. `staging`にFTPSアップロード
2. `curl`でヘルスチェックURIを叩き、アプリケーションを配備
3. Azure CLIから`swap`コマンドを発行してデプロイメントスロットをスワップ

で無停止にアプリケーションを更新しています。


最終的にはJenkinsから下記のシェルを叩いてデプロイを行っています。

```sh
curl -T $WORKSPACE/target/*.war -u 'mywebapp__staging\deployer':${FTPS_PASSWORD} --ftp-ssl ftp://waws-prod-ty1-001.ftp.azurewebsites.windows.net/site/wwwroot/webapps/ROOT.war
curl -T $WORKSPACE/.azure/web.config -u 'mywebapp__staging\deployer':${FTPS_PASSWORD} --ftp-ssl ftp://waws-prod-ty1-001.ftp.azurewebsites.windows.net/site/wwwroot/web.config # 環境変数群
# すぐに下のシェルを実行すると展開前にcurlを叩いてしまうため、30秒スリープする
sleep 30
# アップロードしたwarが展開される（200が帰ってくる）までcurlで監視する
until [ "$(curl -XGET -sLI  ${HEALTHCHECK_URI} -o /dev/null -w '%{http_code}\n')" = "200" ]; do sleep 1; done
# 本番とデプロイスロットを交換する
azure site swap -q -s ${SUBSCRIPTION_ID} mywebapp
```

---

以下はAWS Elasitcbeanstalkから移行するにあたって気づいたところです。

## IP制限など

IP制限などSecurity Groupでやっていたような設定の一部は、`/site/wwwroot/web.config`にxmlファイルを配備することで指定します。
また`JAVA_OPTS`などの環境変数はポータルの「アプリケーション設定」で記述する他に、このファイルでも設定可能です。

デプロイメントスロットを使用している場合、`web.config`が一部のスロットにしか存在しない時にアプリケーションが配備されない現象がありました。

```xml:web.config
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <security>
            <ipSecurity allowUnlisted="false" denyAction="NotFound">
                <add allowed="true" ipAddress="123.123.123.123" subnetMask="255.255.255.255"/>
            </ipSecurity>
        </security>
        <httpPlatform>
            <environmentVariables>
                <environmentVariable name="JAVA_OPTS" value="-Dspring.profiles.active=live" />
            </environmentVariables>
        </httpPlatform>
    </system.webServer>
</configuration>
```

## 常時接続オプション

Azure WebAppsは複数のアプリがリソースを共有しており、リクエストを受けてからアプリケーションを配備します。配備してからしばらくリクエストが途切れると再度デタッチされます。
ブートに時間のかかるJavaアプリと上述のリクエストを受けてからの配備という仕様は絶妙に相性が悪く、今回テストしたアプリケーションでは初回アクセス時に60秒近く掛かる場合もありました。
App ServiceプランBasic以上で使える「常時接続」をオンにしておくことをおすすめします。