Health checks (kiểm tra tình trạng sức khỏe) trong 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:
- Container orchestrators và load balancers (bộ điều phối container và cân bằng tải) có thể dùng health probes (đầu dò sức khỏe) để kiểm tra trạng thái ứng dụng và thực hiện hành động (khởi động lại container, định tuyến traffic khỏi các instances không healthy).
- Resource monitoring (giám sát tài nguyên) cho memory, disk và các tài nguyên máy chủ vật lý khác.
- Dependency testing (kiểm tra phụ thuộc) để xác nhận tính khả dụng và hoạt động bình thường của databases và external service endpoints.
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:
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
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:
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():
builder.Services.AddHealthChecks()
.AddCheck<SampleHealthCheck>("Sample");Với custom failure status và tags (nhãn):
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):
builder.Services.AddHealthChecks()
.AddCheck("Sample", () => HealthCheckResult.Healthy("A healthy result."));Health checks với constructor arguments (đối số constructor):
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
app.MapHealthChecks("/healthz");Yêu cầu Host cụ thể
app.MapHealthChecks("/healthz")
.RequireHost("www.contoso.com:5001");Giới hạn theo port cụ thể:
app.MapHealthChecks("/healthz")
.RequireHost("*:5001");Yêu cầu Authorization (ủy quyền)
app.MapHealthChecks("/healthz")
.RequireAuthorization();Bật CORS (chia sẻ tài nguyên cross-origin)
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
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
Predicate = healthCheck => healthCheck.Tags.Contains("sample")
});Tùy chỉnh HTTP Status Codes
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)
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
AllowCachingResponses = true
});Custom Response Writer (bộ ghi phản hồi tùy chỉnh)
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:
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:
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)
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ý
builder.Services.AddHostedService<StartupBackgroundService>();
builder.Services.AddSingleton<StartupHealthCheck>();
builder.Services.AddHealthChecks()
.AddCheck<StartupHealthCheck>(
"Startup",
tags: new[] { "ready" });Các Endpoints riêng biệt
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)
spec:
template:
spec:
readinessProbe:
httpGet:
path: /healthz/ready
port: 80
initialDelaySeconds: 30
timeoutSeconds: 1
ports:
- containerPort: 80Phâ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:
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:
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
builder.Services.Configure<HealthCheckPublisherOptions>(options =>
{
options.Delay = TimeSpan.FromSeconds(2);
options.Predicate = healthCheck => healthCheck.Tags.Contains("sample");
});
builder.Services.AddSingleton<IHealthCheckPublisher, SampleHealthCheckPublisher>();HealthCheckPublisherOptions
- Delay: Độ trễ ban đầu sau khi ứng dụng khởi động (mặc định: 5 giây)
- Period: Chu kỳ thực thi (mặc định: 30 giây)
- Predicate: Hàm lọc checks
- Timeout: Timeout thực thi (mặc định: 30 giây)
Timing của từng Health Check
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
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
- Map health checks endpoint vào routing system
- Cho phép dùng endpoint-aware middleware (authorization, CORS)
- Cung cấp kiểm soát chi tiết về matching policy
- Hỗ trợ nhiều endpoints với các cấu hình khác nhau
- Có thể short-circuit middleware sau routing
UseHealthChecks
- Đăng ký middleware trong pipeline
- Short-circuit pipeline khi khớp
- Kiểm soát vị trí chính xác trong middleware pipeline
- Có thể khớp bất kỳ path nào trên port cụ thể
Khuyến nghị: Dùng MapHealthChecks cho các ứng dụng ASP.NET Core hiện đại.