QuarkusでHealth Checkのエンドポイントを作成する

はじめに

KubernetesにはLiveness ProbeをReadiness Probeといった概念があり、アプリケーションにそれぞれを確認するためのエンドポイントを作成する場合があります。
Spring Boot(2.3以上)などではそのエンドポイントが用意されていますが、Quarkusにもそれぞれのエンドポイントを作るやりたか(正確にはMicroprofileの実装であるsmallryeのQuarkus拡張)があったので試してみようかと思います。
基本的に以下のドキュメントにしたがってやる形で試してみようと思います。

やってみる

環境

実行環境は以下の通り

$ java --version
openjdk 11.0.8 2020-07-14
OpenJDK Runtime Environment 18.9 (build 11.0.8+10)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.8+10, mixed mode)

$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: /snap/intellij-idea-ultimate/253/plugins/maven/lib/maven3
Java version: 11.0.8, vendor: N/A, runtime: /home/yuya-hirooka/.sdkman/candidates/java/11.0.8-open
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-48-generic", arch: "amd64", family: "unix"

プロジェクトはQuarkus - Start coding with code.quarkus.ioで作成します。 設定は以下の通り f:id:yuya_hirooka:20201012192020p:plain

デフォルトのエンドポイントを確認する

実は特になにもせずともデフォルトでRedinessとLivenessのエンドポイントは用意されています。

  • /health/live : アプリケーション本体が立ち上がっているかどうか
  • /health/ready : DBの接続チェックなどを含めたアプリケーションがアクセスを受け入れる準備できているかどうか
  • /health : RedinessとLiveness両方の情報を返す

アプリケーションを起動し以下のエンドポイントにアクセスしてみます。

$ curl -i http://localhost:8080/health
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
content-length: 46


{
    "status": "UP",
    "checks": [
    ]
}

$ curl -i http://localhost:8080/health/ready
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
content-length: 46


{
    "status": "UP",
    "checks": [
    ]
}


$ curl -i http://localhost:8080/health/live 
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
content-length: 46


{
    "status": "UP",
    "checks": [
    ]
}

ここで、UPがアプリケーションがアクティブな状態を示し、DOWNがアプリケーションがインアクティブであることを示します。

見ての通りこの状態だとアプリケーションはなにもチェックせず、自分自身が起動していることのみ確認することができます。
任意のチェックを行い結果を返す必要がある場合は、少し手をくわえてやる必要があります。

Health Cheackを作成する

例えば、Readinessのチェックではデータベースとの接続がうまく行っているか否かの確認を行いたい場合があります。そのような場合にはReadinessのHealth Checkを実装してやる必要があります。
まずは単にHealth Checkを実装します。

import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Readiness;

import javax.enterprise.context.ApplicationScoped;

@Readiness
@ApplicationScoped
public class ReadinessCheck implements HealthCheck {

    @Override
    public HealthCheckResponse call() {
        return HealthCheckResponse.up("test");
    }
}

Healthチェックを行なう際はorg.eclipse.microprofile.health.HealthCheckを実装したCDIのBeanとして作成します。この際、クラスに@Redinessをつけると/health/readyに対するHealth Cheackを実装でき、@Livenessをつけると/health/liveに対する実装を行えます。このブログでは@Redinessでの実装しか行いませんが、基本的には全く同じことが@Livenessでも行えるはずです。
UPの状態の結果を返す場合はHealthCheckResponse.up("任意の文字列")を利用します。

この状態で、cURLを叩くと以下のようなレスポンスを返します。

$ curl -i http://localhost:8080/health/ready 
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
content-length: 121


{
    "status": "UP",
    "checks": [
        {
            "name": "test",
            "status": "UP"
        }
    ]
}

実装をデータベースへの接続を確認するように修正します。
ここでは仮想的にDBの接続が失敗するような場合における実装をしてみます。

@Readiness
@ApplicationScoped
public class ReadinessCheck implements HealthCheck {

    @Override
    public HealthCheckResponse call() {
        try {
            checkDB();
            return HealthCheckResponse.up("test");
        } catch (Exception e) {
            e.printStackTrace();
            return HealthCheckResponse.down("db connection");
        }
    }

    private void checkDB(){
       throw new RuntimeException("db connection falied");
    }
}

ここでは、checkDB()というDBの接続を確認することを想定したメソッドを作成し、失敗を模擬的に表した、RuntimeExceptionを投げています。
もう一度、cURLを叩くと以下のようなレスポンスが返ってきます。

$ curl -i http://localhost:8080/health/ready 
HTTP/1.1 503 Service Unavailable
content-type: application/json; charset=UTF-8
content-length: 134


{
    "status": "DOWN",
    "checks": [
        {
            "name": "db connection",
            "status": "DOWN"
        }
    ]
}

任意の情報を付加する

Health Checkに任意の情報を更かしたい場合はHealthCheckResponseBuilderを使います。

@Readiness
@ApplicationScoped
public class ReadinessCheck implements HealthCheck {

    @Override
    public HealthCheckResponse call() {
        try {
            checkDB();
            return HealthCheckResponse.named("db connection")
                    .up()
                    .build();
        } catch (Exception e) {
            e.printStackTrace();
            return HealthCheckResponse
                    .named("db connection")
                    .withData("foo", "bar")
                    .down().build();
        }
    }

    private void checkDB(){
       throw new RuntimeException("db connection falied");
    }
}

ここで、HealthCheckResponse.named("db connection")HealthCheckResponseBuilderを返し、そのwithData("key", "value")メソッドを使って任意のデータを返します。

cURLを叩くと以下のようなレスポンスが返ってきます。

$ curl -i http://localhost:8080/health/ready 
HTTP/1.1 503 Service Unavailable
content-type: application/json; charset=UTF-8
content-length: 200


{
    "status": "DOWN",
    "checks": [
        {
            "name": "db connection",
            "status": "DOWN",
            "data": {
                "foo": "bar"
            }
        }
    ]
}

複数の条件を確認するようなチェックを行なう

例えば、DB接続の他に依存APIも立ち上がっていることが必要な場合、複数の状態を確認してHealth Checkの結果としてUPを返す必要があります。
この場合、単にHealth Checkクラスを増やしてやるだけでOKです。
新たなHealth CheckクラスであるReadinessAPICheck.javaを作成します。

@Readiness
@ApplicationScoped
public class ReadinessAPICheck implements HealthCheck {

    @Override
    public HealthCheckResponse call() {
        try {
            chekAPI();
            return HealthCheckResponse.up("dependent api");
        } catch (Exception e) {
            e.printStackTrace();
            return HealthCheckResponse.down("dependent api");
        }
    }

    private void chekAPI(){
    }
}

cURLを叩くと以下のような結果が返ってきます。

$ curl -i http://localhost:8080/health/ready 
HTTP/1.1 503 Service Unavailable
content-type: application/json; charset=UTF-8
content-length: 209


{
    "status": "DOWN",
    "checks": [
        {
            "name": "api",
            "status": "UP"
        },
        {
            "name": "db connection",
            "status": "DOWN"
        }
    ]
}

最終的なチェック結果は各チェックのAND条件で決まります。
この場合、DB接続のほうがDOWNとなっているので最終的な結果もDOWNとなります。

Health UI

QuarkusのHealth Checkでは簡易的なUIもデフォルトで用意してくれています。http://${APP_URL}//health-ui/にアクセスすることで、そのUIをみることができます。

f:id:yuya_hirooka:20201012211846p:plain

ヘッダーの歯車ボタンを押すと設定を行なうことができます。

f:id:yuya_hirooka:20201012212016p:plain

Pollの項目ではチェック結果更新のためのポーリングの時間を決められた間隔で設定することができます。

Configuration Property

application.proptertiesには以下のような設定ポイントが用意されています。

プロパティ名 説明 デフォルト値
quarkus.health.extensions.enabled Health Checkを有効にするか否かのフラグ true (Boolean)
quarkus.smallrye-health.root-path Health Checkのルートパス /health (String)
quarkus.smallrye-health.liveness-path Helth CheckのLivenessのパス /live (String)
quarkus.smallrye-health.readiness-path Helth CheckのReadinessのパス /ready (String)
quarkus.smallrye-health.ui.root-path Health UIへアクセするためのパス /health-ui
quarkus.smallrye-health.ui.always-include Health UIをビルド常にビルドに含めるかどうかの設定。デフォルトではtestモードとdevモードのときだけHealth UIをビルドに含める false (Boolean)
quarkus.smallrye-health.ui.enable Health UIを有効にするか否かのフラグ ture (Boolean)

参考