Nguon: Microsoft Learn · .NET 8.0

Health checks (kiểm tra tình trạng sức khỏe) trong ASP.NET Core

Nguồn: Health checks in ASP.NET Core

ASP.NET Core cung cấp Health Checks Middleware (phần mềm trung gian kiểm tra sức khỏe) và các thư viện để báo cáo tình trạng sức khỏe của các components cơ sở hạ tầng ứng dụng. Health checks được expose bởi ứng dụng dưới dạng HTTP endpoints và có thể được cấu hình cho nhiều kịch bản monitoring theo thời gian thực:

Basic Health Probe (đầu dò sức khỏe cơ bản)

Đối với nhiều ứng dụng, cấu hình basic health probe báo cáo tính khả dụng của ứng dụng (liveness) là đủ.

Cài đặt

Đăng ký health check services với AddHealthChecks() trong Program.cs:

csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks();

var app = builder.Build();

app.MapHealthChecks("/healthz");

app.Run();

Theo mặc định, không có health checks cụ thể nào được đăng ký. Ứng dụng được coi là healthy nếu nó có thể phản hồi tại health endpoint. Response là trạng thái plaintext: Healthy, Degraded hoặc Unhealthy.

Docker HEALTHCHECK

dockerfile
HEALTHCHECK CMD curl --fail http://localhost:5000/healthz || exit 1

Tạo Custom Health Checks (kiểm tra sức khỏe tùy chỉnh)

Implement interface IHealthCheck:

csharp
public class SampleHealthCheck : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        var isHealthy = true;

        if (isHealthy)
        {
            return Task.FromResult(
                HealthCheckResult.Healthy("A healthy result."));
        }

        return Task.FromResult(
            new HealthCheckResult(
                context.Registration.FailureStatus, "An unhealthy result."));
    }
}

Đăng ký Health Checks

Đăng ký với AddCheck():

csharp
builder.Services.AddHealthChecks()
    .AddCheck<SampleHealthCheck>("Sample");

Với custom failure status và tags (nhãn):

csharp
builder.Services.AddHealthChecks()
    .AddCheck<SampleHealthCheck>(
        "Sample",
        failureStatus: HealthStatus.Degraded,
        tags: new[] { "sample" });

Lambda-based health checks (kiểm tra sức khỏe dùng lambda):

csharp
builder.Services.AddHealthChecks()
    .AddCheck("Sample", () => HealthCheckResult.Healthy("A healthy result."));

Health checks với constructor arguments (đối số constructor):

csharp
public class SampleHealthCheckWithArgs : IHealthCheck
{
    private readonly int _arg1;
    private readonly string _arg2;

    public SampleHealthCheckWithArgs(int arg1, string arg2)
        => (_arg1, _arg2) = (arg1, arg2);

    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        return Task.FromResult(HealthCheckResult.Healthy("A healthy result."));
    }
}

// Đăng ký với AddTypeActivatedCheck
builder.Services.AddHealthChecks()
    .AddTypeActivatedCheck<SampleHealthCheckWithArgs>(
        "Sample",
        failureStatus: HealthStatus.Degraded,
        tags: new[] { "sample" },
        args: new object[] { 1, "Arg" });

Health Checks Routing (định tuyến kiểm tra sức khỏe)

Định tuyến cơ bản

csharp
app.MapHealthChecks("/healthz");

Yêu cầu Host cụ thể

csharp
app.MapHealthChecks("/healthz")
    .RequireHost("www.contoso.com:5001");

Giới hạn theo port cụ thể:

csharp
app.MapHealthChecks("/healthz")
    .RequireHost("*:5001");

Yêu cầu Authorization (ủy quyền)

csharp
app.MapHealthChecks("/healthz")
    .RequireAuthorization();

Bật CORS (chia sẻ tài nguyên cross-origin)

csharp
app.MapHealthChecks("/healthz")
    .RequireCors();

Health Check Options (tùy chọn kiểm tra sức khỏe)

Cấu hình hành vi với HealthCheckOptions:

Lọc Health Checks

csharp
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
    Predicate = healthCheck => healthCheck.Tags.Contains("sample")
});

Tùy chỉnh HTTP Status Codes

csharp
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
    ResultStatusCodes =
    {
        [HealthStatus.Healthy] = StatusCodes.Status200OK,
        [HealthStatus.Degraded] = StatusCodes.Status200OK,
        [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
    }
});

Cho phép Cache Responses (lưu cache phản hồi)

csharp
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
    AllowCachingResponses = true
});

Custom Response Writer (bộ ghi phản hồi tùy chỉnh)

csharp
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
    ResponseWriter = WriteResponse
});

private static Task WriteResponse(HttpContext context, HealthReport healthReport)
{
    context.Response.ContentType = "application/json; charset=utf-8";

    var options = new JsonWriterOptions { Indented = true };

    using var memoryStream = new MemoryStream();
    using (var jsonWriter = new Utf8JsonWriter(memoryStream, options))
    {
        jsonWriter.WriteStartObject();
        jsonWriter.WriteString("status", healthReport.Status.ToString());
        jsonWriter.WriteStartObject("results");

        foreach (var healthReportEntry in healthReport.Entries)
        {
            jsonWriter.WriteStartObject(healthReportEntry.Key);
            jsonWriter.WriteString("status",
                healthReportEntry.Value.Status.ToString());
            jsonWriter.WriteString("description",
                healthReportEntry.Value.Description);
            jsonWriter.WriteStartObject("data");

            foreach (var item in healthReportEntry.Value.Data)
            {
                jsonWriter.WritePropertyName(item.Key);
                JsonSerializer.Serialize(jsonWriter, item.Value,
                    item.Value?.GetType() ?? typeof(object));
            }

            jsonWriter.WriteEndObject();
            jsonWriter.WriteEndObject();
        }

        jsonWriter.WriteEndObject();
        jsonWriter.WriteEndObject();
    }

    return context.Response.WriteAsync(
        Encoding.UTF8.GetString(memoryStream.ToArray()));
}

Database Health Checks (kiểm tra sức khỏe database)

SQL Server Probe

Dùng thư viện cộng đồng AspNetCore.Diagnostics.HealthChecks:

csharp
var conStr = builder.Configuration.GetConnectionString("DefaultConnection");
if (string.IsNullOrEmpty(conStr))
{
    throw new InvalidOperationException(
        "Could not find a connection string named 'DefaultConnection'.");
}

builder.Services.AddHealthChecks()
    .AddSqlServer(conStr);

Khi kiểm tra database connections, dùng query đơn giản như SELECT 1 hoặc tránh queries hoàn toàn. Các queries phức tạp có thể làm quá tải database.

Entity Framework Core DbContext Probe (đầu dò DbContext)

Đăng ký DbContext health check:

csharp
builder.Services.AddDbContext<SampleDbContext>(options =>
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddHealthChecks()
    .AddDbContextCheck<SampleDbContext>();

Theo mặc định, DbContextHealthCheck gọi phương thức CanConnectAsync của EF Core.

Readiness và Liveness Probes (đầu dò sẵn sàng và sống)

Phân biệt giữa các trạng thái ứng dụng bằng cách dùng các health checks riêng biệt.

Ví dụ: Startup Background Service (dịch vụ nền khởi động)

csharp
public class StartupBackgroundService : BackgroundService
{
    private readonly StartupHealthCheck _healthCheck;

    public StartupBackgroundService(StartupHealthCheck healthCheck)
        => _healthCheck = healthCheck;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken);
        _healthCheck.StartupCompleted = true;
    }
}

public class StartupHealthCheck : IHealthCheck
{
    private volatile bool _isReady;

    public bool StartupCompleted
    {
        get => _isReady;
        set => _isReady = value;
    }

    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        if (StartupCompleted)
        {
            return Task.FromResult(HealthCheckResult.Healthy("The startup task has completed."));
        }

        return Task.FromResult(HealthCheckResult.Unhealthy("That startup task is still running."));
    }
}

Đăng ký

csharp
builder.Services.AddHostedService<StartupBackgroundService>();
builder.Services.AddSingleton<StartupHealthCheck>();

builder.Services.AddHealthChecks()
    .AddCheck<StartupHealthCheck>(
        "Startup",
        tags: new[] { "ready" });

Các Endpoints riêng biệt

csharp
app.MapHealthChecks("/healthz/ready", new HealthCheckOptions
{
    Predicate = healthCheck => healthCheck.Tags.Contains("ready")
});

app.MapHealthChecks("/healthz/live", new HealthCheckOptions
{
    Predicate = _ => false
});

Ví dụ Kubernetes (bộ điều phối container)

yaml
spec:
  template:
  spec:
    readinessProbe:
      httpGet:
        path: /healthz/ready
        port: 80
      initialDelaySeconds: 30
      timeoutSeconds: 1
    ports:
      - containerPort: 80

Phân phối Health Check Libraries (thư viện kiểm tra sức khỏe)

Tạo health checks có thể tái sử dụng:

csharp
public static class SampleHealthCheckBuilderExtensions
{
    private const string DefaultName = "Sample";

    public static IHealthChecksBuilder AddSampleHealthCheck(
        this IHealthChecksBuilder healthChecksBuilder,
        int arg1,
        string arg2,
        string? name = null,
        HealthStatus? failureStatus = null,
        IEnumerable<string>? tags = default)
    {
        return healthChecksBuilder.Add(
            new HealthCheckRegistration(
                name ?? DefaultName,
                _ => new SampleHealthCheckWithArgs(arg1, arg2),
                failureStatus,
                tags));
    }
}

Health Check Publisher (bộ phát hành kết quả kiểm tra sức khỏe)

Dành cho các hệ thống monitoring dựa trên push:

csharp
public class SampleHealthCheckPublisher : IHealthCheckPublisher
{
    public Task PublishAsync(HealthReport report, CancellationToken cancellationToken)
    {
        if (report.Status == HealthStatus.Healthy)
        {
            // Xử lý trạng thái healthy
        }
        else
        {
            // Xử lý trạng thái unhealthy
        }

        return Task.CompletedTask;
    }
}

Đăng ký và cấu hình

csharp
builder.Services.Configure<HealthCheckPublisherOptions>(options =>
{
    options.Delay = TimeSpan.FromSeconds(2);
    options.Predicate = healthCheck => healthCheck.Tags.Contains("sample");
});

builder.Services.AddSingleton<IHealthCheckPublisher, SampleHealthCheckPublisher>();

HealthCheckPublisherOptions

Timing của từng Health Check

csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks()
   .Add(new HealthCheckRegistration(
       name: "SampleHealthCheck1",
       instance: new SampleHealthCheck(),
       failureStatus: null,
       tags: null,
       timeout: default)
   {
       Delay = TimeSpan.FromSeconds(40),
       Period = TimeSpan.FromSeconds(30)
   });

var app = builder.Build();
app.MapHealthChecks("/healthz");
app.Run();

Dependency Injection trong Health Checks

csharp
public class SampleHealthCheckWithDI : IHealthCheck
{
    private readonly SampleHealthCheckWithDiConfig _config;

    public SampleHealthCheckWithDI(SampleHealthCheckWithDiConfig config)
        => _config = config;

    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        var isHealthy = true;

        if (isHealthy)
        {
            return Task.FromResult(
                HealthCheckResult.Healthy("A healthy result."));
        }

        return Task.FromResult(
            new HealthCheckResult(
                context.Registration.FailureStatus, "An unhealthy result."));
    }
}

// Đăng ký
builder.Services.AddSingleton<SampleHealthCheckWithDiConfig>(new SampleHealthCheckWithDiConfig
{
    BaseUriToCheck = new Uri("https://sample.contoso.com/api/")
});

builder.Services.AddHealthChecks()
    .AddCheck<SampleHealthCheckWithDI>(
        "With Dependency Injection",
        tags: new[] { "inject" });

UseHealthChecks so với MapHealthChecks

MapHealthChecks

UseHealthChecks

Khuyến nghị: Dùng MapHealthChecks cho các ứng dụng ASP.NET Core hiện đại.