しばやん雑記

Azure Serverless とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure App Service (Windows / Linux) に Java アプリケーションをデプロイして動かす簡単な方法

最近 Spring Boot なアプリケーションを Azure App Service にデプロイしてみたら、いつの間にかに Java アプリケーションを動かすのがかなり簡単になっていたのでメモとして残しておきます。今回は Docker Image ではなく JAR をデプロイする方法を使います。

大学の授業で Eclipse と Swing を使った以来なのでかなり知識は古かったのですが、最近は Java に特化した Visual Studio Code のインストーラーを使うとサクッと環境が作れるので楽でした。

このインストーラーで入る Java は PATH が通ってない状態だったのが少し罠でした。実体のパスは JAVA_HOME から辿れるので、適当に追加しておくと良さそうです。

Spring Boot プロジェクトの作成は Extension として提供されている Spring Initializr を使うと、コマンドパレットから対話式で作成できるのであっさり終わりました。

f:id:shiba-yan:20200202225828p:plain

今回は Maven を使いましたが、Gradle も使うことが出来るようです。使用する言語や Spring のバージョン、そして追加する依存関係の選択まで一通りコマンドパレットで行えるのはかなり良いです。

f:id:shiba-yan:20200202225837p:plain

Visual Studio Code なのでプロジェクトを作成すれば、後は F5 を押せばデバッグ実行されます。大学時代には Eclipse を使ったデバッグ実行で四苦八苦したので、このあたりでモチベーションが高まりました。

今回はサンプルなので適当なコントローラーを追加して、プロジェクトを Azure Repos に入れました。

f:id:shiba-yan:20200202204945p:plain

Visual Studio Code からデプロイする機能はありますが、最初からちゃんと CI を組んでおくことが重要だと思っているので、今回もデプロイには Azure Pipelines を使います。

Java 向けの App Service を作成する

App Service を新規作成する際には Java SE / Tomcat / WildFly の 3 つが選べるようになっていますが、Windows と Linux で選べる項目に結構差があるのと、App Service の設定とは微妙に差異があります。

特に Java SE を選ぼうとすると Linux 固定になってしまいますが、実は Windows でも使えるようになっています。後から変えればよいので、Windows の場合は ASP.NET V4.7 あたりを選んでおくのが安パイです。

f:id:shiba-yan:20200128234237p:plain

Linux では使用する Docker Image の選択になるので重要ですが、Windows の場合は VM Image にランタイムが予めインストールされているので、設定を切り替えるぐらいの意味しか持ってません。*1

Windows 向けの Java 設定

一応ドキュメントは用意されていますが、App Service 側の設定というよりは Java のオプション回りの話が多かったです。一応 Jetty もインストールされているはずですが、なかったことにされてる気がします。

Azure Portal から Stack として Java を選択するといろんな項目が追加で表示されます。

Java version と Java container は好きなやつを選べばよいと思いますが、今回はシンプルに最新の Java 11 と素の JAR をデプロイする設定にしました。

f:id:shiba-yan:20200128234245p:plain

昔は Auto-update の選択肢がなかったはずなので、このあたり楽になったなと思います。全く関係ないですが Node.js では ~12 のような指定をすると v12 の最新を自動で使うようにできます。

後は Always on を有効化しておいて、コールドスタートを出来るだけ避けるようにしましょう。

Linux 向けの Java 設定

Linux に関してもドキュメントは Java のオプション回りの話が多いです。一応目を通しておきましょう。

Windows とはまた設定が異なり、Linux ではバージョンを細かくは指定できないようになってます。

f:id:shiba-yan:20200128234254p:plain

基本的には App Service が提供している latest の Docker Image を使う仕組みなので、バージョンを細かく固定したい場合には独自の Docker Image を使うように設定することになります。

とはいえ、そこまでやる場合は Docker Image でのデプロイにした方が楽なので、カスタムな Runtime Stack を作るのではなく Docker Image を CI で作るようにしましょう。

アプリケーションのデプロイ

最初に App Service が Java に対応した際には HttpPlatformHandler を使うために Web.config を用意する必要がありましたが、そういう時代は既に終わっていたようです。

ドキュメントにも書いてあるように app.jar というファイル名でデプロイすると、App Service が自動的に適切な設定でアプリケーションを立ち上げてくれるようになってました。

ちなみにこの挙動は Windows と Linux で共通となっています。

By default, App Service expects your JAR application to be named app.jar. If it has this name, it will be run automatically.

Configure Linux Java apps - Azure App Service | Microsoft Docs

何時から変更されたのかは分からないですが、Stack として Java を選択すると HttpPlatformHandler の設定は自動的に行われ、起動スクリプトまで用意されるという手の込みようです。

実際に app.jarwwwroot 直下に置いただけの Process Explorer の情報ですが、IIS は用意された起動スクリプトを使って Java アプリケーションを実行しています。

f:id:shiba-yan:20200202213314p:plain

起動時のパラメータを調整したい場合には startup.cmd というファイルを用意しておけば、それが優先して使われる仕組みになっていました。

デフォルトのスクリプトは要約すると以下のコマンドを実行しているだけなので、カスタマイズも簡単です。

"%JAVA_HOME%\bin\java.exe" -noverify -Djava.net.preferIPv4Stack=true -Dserver.port=%HTTP_PLATFORM_PORT% -jar "%_WEBAPP_DIR%\app.jar"

メモリ周りのオプションを設定したい場合は、このコマンドに追加して startup.cmd を作成すればよいはずです。そして app.jar とセットでデプロイすると自動的に使われます。

ちなみに Linux の場合は JAVA_OPTS を App Settings に追加すれば良さそうでした。

Azure Pipelines を使ってビルドとデプロイを行う

最後は Azure Pipelines でビルドを行い、App Service へのデプロイまで行います。長くなってきて説明が面倒なので、先に Azure Pipelines の定義を出しておきます。

やっていることは簡単で Maven を使って app.jar を作成し、Zip Deploy で App Service にデプロイしているだけです。今回は単一ファイルなので Run From Package ではなく Zip Deploy にしました。

# Maven package Java project Web App to Linux on Azure
# Build your Java project and deploy it to Azure as a Linux web app
# Add steps that analyze code, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/java

trigger:
- master

variables:
  vmImageName: 'ubuntu-latest'
  MAVEN_CACHE_FOLDER: $(Pipeline.Workspace)/.m2/repository

stages:
- stage: Build
  displayName: Build stage
  jobs:
  - job: MavenPackageAndPublishArtifacts
    displayName: Maven Package and Publish Artifacts
    pool:
      vmImage: $(vmImageName)
    
    steps:
    - task: Cache@2
      inputs:
        key: 'maven | "$(Agent.OS)" | **/pom.xml'
        restoreKeys: |
           maven | "$(Agent.OS)"
           maven
        path: $(MAVEN_CACHE_FOLDER)
      displayName: Cache Maven local repo

    - task: Maven@3
      displayName: 'Maven Package'
      inputs:
        mavenPomFile: 'pom.xml'
        options: '-Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'

    - publish: target
      artifact: webapp
      displayName: 'Publish artifacts'

- stage: Deploy
  displayName: Deploy stage
  jobs:
  - job: DeployWebApp
    displayName: Deploy Web App
    pool: 
      vmImage: $(vmImageName)
    steps:
    - checkout: none
    - download: current
      artifact: webapp

    - task: ArchiveFiles@2
      inputs:
        rootFolderOrFile: '$(Pipeline.Workspace)/webapp/'
        includeRootFolder: false
        archiveType: 'zip'
        archiveFile: '$(Pipeline.Workspace)/$(Build.BuildId).zip'

    - task: AzureWebApp@1
      inputs:
        azureSubscription: 'Azure Sponsorship'
        appType: 'webApp'
        appName: 'java11test-1'
        package: '$(Pipeline.Workspace)/*.zip'
        deploymentMethod: 'zipDeploy'

Maven は Pipeline Caching を使っておくとかなり処理時間を短縮できました。まだプレビュー扱いですが Task のバージョンも順調に上がっているのと、効果が劇的だったので使う価値があります。

ちなみに Pipeline Caching なしの場合、Maven の実行は 2 分 30 秒ぐらいかかってました。

f:id:shiba-yan:20200202205023p:plain

それが Pipeline Caching を有効にすると 15 秒ぐらいまでに短縮されました。Cache に必要な処理時間を考慮しても 30 秒ぐらいで完了しています。

f:id:shiba-yan:20200202205008p:plain

上の Pipeline 定義では Windows 向けのデプロイ定義しか書いてないですが、Linux も全く同じように Zip Deploy が使えるので、ほとんど同じ定義が使えるようになってます。

実際に Windows と Linux の App Service に Azure Pipelines からデプロイしましたが、問題なく同一の JAR が実行されていることが確認できました。

f:id:shiba-yan:20200202225500p:plain

単純に app.jar という名前のファイルをデプロイするだけで済むので、Web.config といった IIS 固有の知識も必要なくなり、かなり使い勝手が良くなったのではないかと感じます。

欲を言うと実体は zip な JAR を一度 zip にしてからデプロイというのは手間かなと思いました。

Zip Deploy 後に変更が反映されない場合

今回 Windows と Linux の両方で JAR のデプロイを試していたのですが、Windows 側だけデプロイしてもアプリケーションが更新されなかった現象に遭遇しました。App Service を再起動すれば反映されるので、単純に JAR をデプロイしたタイミングで IIS の Worker Process がリサイクルされていないようです。

Web.config が不要になって、JAR のみデプロイする形になったので IIS が変更を追跡できていないのかもしれません。調べてみると特殊なオプションが追加されていたので、以下の 2 つを設定すると Zip Deploy のタイミングでリサイクルが走るようになりました。

  • SCM_RESTART_APP_CONTAINER_AFTER_DEPLOYMENT
  • WEBSITE_RECYCLE_PREVIEW_ENABLED

上の設定は元々 Linux だけで利用可能なものでしたが、ちょっと前に WEBSITE_RECYCLE_PREVIEW_ENABLED と組み合わせることで Windows でも利用可能になったようです。

f:id:shiba-yan:20200202213454p:plain

これで Azure Pipelines からのデプロイ後に、ちゃんとデプロイしたアプリケーションが立ち上がるようになりました。本番で運用する場合にはちゃんと Deployment Slot を使って Swap する運用にしましょう。

Azure Spring Cloud は設計がかなりいけてる

今回の内容を書こうと思ったきっかけは、Ignite Tour Osaka で寺田さんに Java と App Service についていろいろ聞いたからなんですが、その時に紹介された Azure Spring Cloud がかなりいけてたので紹介します。

詳細は寺田さんのブログと動画を見てもらえれば理解できるはずです。特に動画は必見ですね。

App Service のようなマネージドサービスですが、裏側は AKS になっています。非常に良いと感じたのが利用者に裏側で Kubernetes が動いていることを意識させることなく、Java のアプリケーションを実行できるという点です。今の技術で App Service を再構成すると同じようなアーキテクチャになるでしょう。

個人的には Kubernetes は利用者が直接触るようなものではなく、Azure Cloud Services のようにサービスを構築するためのサービス、そういった足回りを支えるものだと感じています。なので Azure Spring Cloud の方向性は最高だと思いました。

*1:Framework JIT という機能があるようだけど、特に気にしなくてもよい