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


Azure Functions の Premium プランでは ウォームアップトリガー を用いてインスタンスのスケールアウト時に任意のウォームアップ処理を実行できます。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

以下はドキュメントからの抜粋です。
トリガー - 例 のコードをコピーして自身の Java プログラムに組み込み、run メソッドの中身を任意の処理に書き換える必要があります。

トリガー - 属性 に記載の通り、WarmupTrigger 属性は現状 C# のみサポートされており、C# スクリプト / JavaScript / Python / Java ではサポートされていません。

どういうことかと言うと、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

一方で、WarmupTrigger は Java ではサポートされていないため、必要な情報を持つ function.json はビルド時に生成されません。必要な情報というのは トリガー - 構成 に記載された以下の項目群のことです。

ワークアラウンド

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 メソッドを作成

トリガー - 例 をそのまま流用する形で実装しています。run メソッドの中身を任意の処理に書き換えてご利用ください。
https://github.com/nakazax/azure-functions-premium-warmup-java/blob/master/src/main/java/com/functionp/Warmup.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 を作成

トリガー - 構成 を参考に必要な情報を定義した function.json を作成しておきます。scriptFileentryPoint は自身のアプリケーションに合わせて書き換えて利用ください。
https://github.com/nakazax/azure-functions-premium-warmup-java/blob/master/src/main/resources/Warmup/function.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.jsonazure-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

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

ビルド & デプロイともに成功

デプロイの詳細でエラーメッセージは出ない

Azure Functions

デプロイ先の Azure Functions

[デプロイ センター] に「production に正常にデプロイされました」と表示される

[関数] に Warmup トリガーが表示される

[関数] > [Warmup] をクリックすると以下のような詳細が表示される

[ログ] > [traces] を見ると Warmup が実行された旨、表示される

(参考) 必要な情報を持つ function.json が存在しない場合の挙動

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

Azure Pipelines

ビルド & デプロイともに成功

デプロイの詳細でエラーメッセージは出ない

Azure Functions

[デプロイ センター] に「production にデプロイできませんでした」と表示される

[関数] に Warmup トリガーが表示されない

以上です。