C# Advent CalendarとひとりAdvent Calendar 22日目です。
[追記]今日のエントリは昨日英語版C# Advent Calendarに投稿したエントリの日本語版です。
ぜひ英語版C#アドベントカレンダーでも興味のあるエントリを読んでみてください。
今回は、ASP.NET Core 2.2で導入されたHealthchecks機能をkubernetesのReadiness ProbeとLiveness ProbeといっしょにOpenShiftで試してみるという内容です。
Healthchecks 機能についてはPreview時点ですが、このブログに紹介があります。
詳細は公式ドキュメントに説明されています。
[追記]日本語ではしばやんせんせーのブログをどうぞ。
Healthchecks 機能は、アプリケーションのヘルスステータスを返すためのエンドポイントと、そのステータのチェック方法を定義するサービスに関するAPIを提供します。今迄だと、MVCなら適当なControllerを用意したりしていたと思いますが、それが専用になりました。アプリケーション固有のルーティングやUIから独立させることができ、より使いやすくなると思われます。
このHealthchecks機能、もちろんどんな環境でも使えます。OS問わず、セルフホストでもIISの上でも、ASP.NET Coreをサポートしている各種クラウドサービスの上で動きます。機能としては、レスポンスを返すだけなので、そのレスポンスを見てどう処理するかはHealthchecksを行う側の機能となります。kubernetesのLiveness ProbeとReadiness Probeともうまくあわせることができます。今回は、kubernetesの機能をそのまま使えるOpenShiftで試してみました。
Configure Liveness and Readiness Probes - Kubernetes
OpenShiftで.NET Core 2.2を有効にする方法はこちらをご覧ください。
ASP.NET Core での実装
今回の動くサンプルコードはここにおいています。
kubernetesのLiveness ProbeとReadiness Probeはそれぞれ、Liveness Probeがコンテナの生存確認(=失敗したらコンテナ再起動)に、Readiness Probeがトラフィックの送信可否(=失敗したらトラフィックを送信しない)に使われます。Healthchecks機能は複数のHealthcheckのサービスを登録することができ、1つのエンドポイントに対し任意の数のHealthchecksのサービスを対応させることができます。今回はLivenessとReadiness用にそれぞれ1つずつサービスを作成しパスに登録することにします。その場合、Startup.cs
クラスを次のように記述します。
public void ConfigureServices(IServiceCollection services) { ... services.AddHealthChecks() .AddCheck<LivenessHealthCheck>("Liveness", failureStatus: null) .AddCheck<ReadinessHealthCheck>("Readiness", failureStatus: null); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { ... app.UseHealthChecks("/health/live", new HealthCheckOptions() { Predicate = check => check.Name == "Liveness" }); app.UseHealthChecks("/health/ready", new HealthCheckOptions() { Predicate = check => check.Name == "Readiness", }); ... }
ConfigureServices
メソッドの中でHealthchecksのサービスを登録します。いくつかオーバーロードされたメソッドがあって、独自クラスでなくラムダ式で定義したりもできますが、今回は独自クラスであるLivenessHealthCheck
とReadinessHealthCheck
を使っています。AddCheck
メソッドでロジックを登録するときに第一引数で名前を指定しています。この名前は次のConfigure
メソッドで使います。
Configure
メソッドでは、Healthcheksを提供するエンドポイントと、そのエンドポイントに対してどのサービスを実行させるかをPredicate
で指定します。ConfigureServices
メソッド内で登録したロジックのうち、Predicate
でマッチしたロジックが実行されます。
ここでは、登録するときに使った名前でフィルタしていますが、公式ドキュメントのサンプルコードのようにタグを登録してフィルタに使うこともできます。1つのエンドポイントに複数のサービスを利用したい場合はタグの方が使いやすそうです。
using System.Threading.Tasks; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.Extensions.Diagnostics.HealthChecks; public void ConfigureServices(IServiceCollection services) { services.AddHealthChecks() .AddCheck("Foo", () => HealthCheckResult.Healthy("Foo is OK!"), tags: new[] { "foo_tag" }) .AddCheck("Bar", () => HealthCheckResult.Unhealthy("Bar is unhealthy!"), tags: new[] { "bar_tag" }) .AddCheck("Baz", () => HealthCheckResult.Healthy("Baz is OK!"), tags: new[] { "baz_tag" }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseHealthChecks("/health", new HealthCheckOptions() { // Filter out the 'Bar' health check. Only Foo and Baz execute. Predicate = (check) => check.Tags.Contains("foo_tag") || check.Tags.Contains("baz_tag") }); }
Health checks in ASP.NET Core | Microsoft Docs
LivenessHealthCheck
とReadinessHealthCheck
クラスは、今回はサンプルなので下のような単純なロジックを実装しています。ここはアプリの要件にあわせて変更してください。ステータスコードやレスポンスのコンテンツなど様々なカスタマイズが可能になっているので、ドキュメントを参考にしましょう。
internal class LivenessHealthCheck : IHealthCheck { private HealthStatusData data; public LivenessHealthCheck(HealthStatusData data) { this.data = data; } public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken)) { if (data.IsLiveness) { return Task.FromResult(HealthCheckResult.Healthy()); } else { return Task.FromResult(HealthCheckResult.Unhealthy("Error")); } } }
このサンプルではHealthStatusData
というのはDIによってinjectされますが、用意したボタンをclickするとfalseに設定できるようにしています。詳しくはサンプルコードを見てください。
ここまででASP.NET Core側の実装は完了です。
OpenShift (kubernetes)側での実装
サンプルアプリをそのままOpenShiftにデプロイする場合は、最初に紹介したリンクで.NET Core 2.2のイメージをインポートした後に次のコマンドを実行します。
$ oc new-project aspnetcore22 $ oc new-app --name=healthcheckexample 'dotnet:2.2~https://github.com/tanaka-takayoshi/HealthCheckExample.git' --build-env DOTNET_STARTUP_PROJECT=HealthCheckExample $ oc expose svc/healthcheckexample
アプリがデプロイされたら、OpenShiftであればWeb ConsoleからProbeを設定できます。
Deployment ConfigのYAMLを直接編集することもできます。
spec: ... template: ... spec: containers: - image: >- ... livenessProbe: failureThreshold: 3 httpGet: path: /health/live port: 8080 scheme: HTTP periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 readinessProbe: failureThreshold: 3 httpGet: path: /health/ready port: 8080 scheme: HTTP periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 ...
OpenShiftのダッシュボードでもProbeを確認できます。
それでは、用意したボタンからまずはReadiness Probe側をfalseに設定してみます。すると、ConsoleにReadiness checkに失敗したというメッセージが出て、oc get event
などにも表示されます。
Readiness checkに失敗したPodにはルーティングがされないので、先程のURLにアクセスするとルーティングできないというエラーが表示されます。
これは1つのPodしかない状態でそのPodのReadiness checkが失敗しているためです。ためしにPodを2つにスケールさせると、追加した1つは正常にReadiness checkをパスするので、そちらにルーティングされます。
それでは、再度Podを1つにスケールダウンして戻して、今度はLiveness Probeをfalseに設定してみます。いったんUnhealthの表示がされますが、
ダッシュボードやoc get event
を見ると、Liveness probeの失敗が閾値を超えるとすぐに再起動がかかっているのがわかります。
今回はkubernetes (OpenShift)のProbeにあわせたHealthchecksの使い方を紹介しましたが、デプロイする環境にあわせたHealthchecksのロジックが組めるような仕組みになっていました。ぜひ使ってみましょう。