Azure Functions & Java のウォームアップトリガー実装時の注意点

Azure Functions の Premium プランでは **[ウォームアップトリガー](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-warmup?tabs=java)** を用いてインスタンスのスケールアウト時に任意のウォームアップ処理を実行できます。Java で実装する際には注意するポイントがありますので、本記事で簡単にまとめたいと思います。

# tl;dr
Azure Functions の Java は現状では `WarmupTrigger` 属性をサポートしていないため、ウォームアップ用の `function.json` を自前で準備しておき、ビルド成果物のパッケージに同梱する必要があります。

# ドキュメントの解説
Azure Functions のウォームアップトリガーについて以下のドキュメントにまとまっています。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-warmup?tabs=java

以下はドキュメントからの抜粋です。
[トリガー - 例](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-warmup?tabs=java#trigger---example) のコードをコピーして自身の Java プログラムに組み込み、`run` メソッドの中身を任意の処理に書き換える必要があります。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/f97b448a-09c1-a8cd-7afb-69e2bcf5bef0.png)|
|:-:|

[トリガー - 属性](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-warmup?tabs=java#trigger---attributes) に記載の通り、`WarmupTrigger` 属性は現状 C# のみサポートされており、C# スクリプト / JavaScript / Python / Java ではサポートされていません。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/655c83c3-67a8-605c-5d1e-6eeee6fea334.png)|
|:-:|

どういうことかと言うと、`HttpTrigger` 属性を例に説明します。例えば HTTP トリガーの Function を実装する場合、以下ドキュメントのように `HttpTrigger` のアノテーションを Java のメソッドに付与します。azure-functions-maven-plugin でビルドを実施する際に、自動的に `function.json` が生成されます。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-reference-java?tabs=consumption#triggers-and-annotations

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/75a737be-6c22-81ee-f490-a69bfc5b8889.png)|
|:-:|

一方で、`WarmupTrigger` は Java ではサポートされていないため、必要な情報を持つ `function.json` はビルド時に生成されません。必要な情報というのは [トリガー - 構成](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-warmup?tabs=java#trigger---configuration) に記載された以下の項目群のことです。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/2fba7e97-78a0-0d07-2213-5d4c495a10e6.png)|
|:-:|

# ワークアラウンド
Java でウォームアップトリガーを利用するためには以下の対応が必要です。

1. `@FunctionName("Warmup")` アノテーションが付与された Java メソッドを作成
2. 必要な情報を持つ `function.json` を作成
3. `function.json` をビルド成果物に同梱するよう設定
4. アプリケーションをビルドして Azure Functions にデプロイ

以下のサンプルコードに沿って説明していきたいと思います。
https://github.com/nakazax/azure-functions-premium-warmup-java

## 1. `@FunctionName("Warmup")` アノテーションが付与された Java メソッドを作成
[トリガー - 例](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-warmup?tabs=java#trigger---example) をそのまま流用する形で実装しています。`run` メソッドの中身を任意の処理に書き換えてご利用ください。
https://github.com/nakazax/azure-functions-premium-warmup-java/blob/master/src/main/java/com/functionp/Warmup.java

```java:azure-functions-premium-warmup-java/src/main/java/com/functionp/Function.java
package com.functionp;

import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.annotation.FunctionName;

public class Warmup {

    @FunctionName("Warmup")
    public void run( ExecutionContext context) {
       context.getLogger().info("Function App instance is warm 🌞🌞🌞");
    }
}
```

## 2. 必要な情報を持つ `function.json` を作成
[トリガー - 構成](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-warmup?tabs=java#trigger---configuration) を参考に必要な情報を定義した `function.json` を作成しておきます。`scriptFile` や `entryPoint` は自身のアプリケーションに合わせて書き換えて利用ください。
https://github.com/nakazax/azure-functions-premium-warmup-java/blob/master/src/main/resources/Warmup/function.json

```json:azure-functions-premium-warmup-java/src/main/resources/Warmup/function.json
{
  "scriptFile": "../azure-functions-premium-warmup-java-1.0-SNAPSHOT.jar",
  "entryPoint": "com.functionp.Warmup.run",
  "bindings": [ {
    "type": "warmupTrigger",
    "direction": "in",
    "name": "Warmup"
  } ]
}
```

## 3. `function.json` をビルド成果物に同梱するよう設定
先に作成した `azure-functions-premium-warmup-java/src/main/resources/Warmup/function.json` を `azure-functions-premium-warmup-java/target/azure-functions/${applicatioName}/Warmup` に格納するように設定します。

ビルドツールとして Maven を利用している場合は `pom.xml` の中で当該処理を実装するのが手軽かと思います。以下は `pom.xml` の抜粋で、`<id>copy-warmup-function-json</id>` のブロックで当該処理を定義しています。 
https://github.com/nakazax/azure-functions-premium-warmup-java/blob/master/pom.xml

```xml:azure-functions-premium-warmup-java/pom.xml(抜粋)
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <executions>
        <execution>
            <id>copy-resources</id>
            <phase>package</phase>
            <goals>
                <goal>copy-resources</goal>
            </goals>
            <configuration>
                <overwrite>true</overwrite>
                <outputDirectory>${stagingDirectory}</outputDirectory>
                <resources>
                    <resource>
                        <directory>${project.basedir}</directory>
                        <includes>
                            <include>host.json</include>
                            <include>local.settings.json</include>
                        </includes>
                    </resource>
                </resources>
            </configuration>
        </execution>
        <execution>
            <id>copy-warmup-function-json</id>
            <phase>package</phase>
            <goals>
                <goal>copy-resources</goal>
            </goals>
            <configuration>
                <overwrite>true</overwrite>
                <outputDirectory>${stagingDirectory}/Warmup</outputDirectory>
                <resources>
                    <resource>
                        <directory>src/main/resources/Warmup</directory>
                        <includes>
                            <include>function.json</include>
                        </includes>
                    </resource>
                </resources>
            </configuration>
        </execution>
    </executions>
</plugin>
```

## 4. アプリケーションをビルドして Azure Functions にデプロイ
後はお好みの方法でアプリケーションのビルドと Azure Functions へのデプロイを行います。以下 URL は Azure Pipelines の定義のサンプルです。
https://github.com/nakazax/azure-functions-premium-warmup-java/blob/master/azure-pipelines.yml

## デプロイ後の挙動 (正常時)
上記の流れに沿ってデプロイを行った後の Azure リソースの状態は以下のようになるはずです。

### Azure Pipelines
ビルド & デプロイともに成功
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/37c0b304-9fc6-b6e0-4a1d-a291ff1f86fc.png)

デプロイの詳細でエラーメッセージは出ない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/ccd6e3b9-7707-6872-372a-8c297005ee92.png)

### Azure Functions
デプロイ先の Azure Functions
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/593fb754-730a-3654-e918-850ae4afc1e7.png)

[デプロイ センター] に「production に正常にデプロイされました」と表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/bc5aedaa-8b6a-e9c9-b582-ebab4ed2d716.png)

[関数] に Warmup トリガーが表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/699fda66-3918-7ec7-a360-77737e9dd4c0.png)

[関数] > [Warmup] をクリックすると以下のような詳細が表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/91456980-ab9c-efa4-c38e-a27826e9eb7c.png)

[ログ] > [traces] を見ると Warmup が実行された旨、表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/7cbdf01a-f5fc-4aee-018e-4df7281eb15a.png)

## (参考) 必要な情報を持つ `function.json` が存在しない場合の挙動
以下は Java のメソッドだけを実装して Azure Functions にデプロイした場合の挙動です。Azure Pipelines でのビルド & デプロイ自体は成功しますが、Azure Functions の [デプロイ センター] に「production にデプロイできませんでした」と表示され、デプロイに失敗します。
(最初、原因が分からずにハマりました。。。)

### Azure Pipelines
ビルド & デプロイともに成功
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/428f43b4-682b-8c56-984a-d90a293d6d8d.png)

デプロイの詳細でエラーメッセージは出ない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/772ecf63-bf46-da0d-94fe-78adae4da446.png)

### Azure Functions
[デプロイ センター] に「production にデプロイできませんでした」と表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/24c61a9a-a485-59cd-733b-15205a17dfe8.png)

[関数] に Warmup トリガーが表示されない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/5e5f6fdc-990d-8111-db33-f74cabc0f63e.png)

以上です。