🚀 健康檢查:在 .NET 微服務模板中優雅配置 Health Checks
📚 目錄
- 🚀 健康檢查:在 .NET 微服務模板中優雅配置 Health Checks
- 一、背景與意義 🔍
- 二、核心配置 🔧
- 2.1 引入必要的 NuGet 依賴 📦
- 2.2 注冊 Health Checks 服務 ??
- 2.3 集成 HealthChecks UI 🖥?
- 2.4 自定義健康端點響應 📋
- 三、實戰演示 🚀
- 3.1 Gateway 端整合示例 🔗
- 3.2 Health Check 整體流程示意圖 📈
- 四、Kubernetes 集成 🐳
- 4.1 Kubernetes 整體流程示意圖 🌐
- 五、安全性與健壯性 🔐
- 六、性能與可用性考量 🚀
- 完整示例項目結構概覽(摘錄) 📂
一、背景與意義 🔍
在微服務架構下,各組件、服務分散部署,互相依賴性增加,單純依靠人工巡檢難以及時發現故障。健康檢查(Health Check)作為監控與自愈機制的基石,能夠讓我們:
- 實時檢測關鍵依賴(如數據庫、緩存、中間件、外部 API)是否“在線” ?
- 向 Kubernetes 或服務網關暴露健康端點,自動觸發重啟或流量調整 🔄
- 為運維團隊提供統一可視化界面,便于快速定位問題 🎛?
本節要點:為什么微服務需要健康檢查?Health Checks 在分布式系統里有哪些主要用途?
二、核心配置 🔧
在 .NET 中,Health Checks 基于 Microsoft.Extensions.Diagnostics.HealthChecks
擴展,由 ASP.NET Core 提供一整套健康檢查接口與中間件。下面我們依次展示如何引入依賴、注冊各類檢查、集成 HealthChecks UI、并自定義響應格式。
2.1 引入必要的 NuGet 依賴 📦
要使用 SQL Server、Redis、外部 HTTP 等檢查,需要先安裝對應的 NuGet 包。例如,在項目根目錄執行:
# 核心健康檢查包
dotnet add package Microsoft.Extensions.Diagnostics.HealthChecks# SQL Server 健康檢查擴展
dotnet add package AspNetCore.HealthChecks.SqlServer# Redis 健康檢查擴展
dotnet add package AspNetCore.HealthChecks.Redis# HTTP/URL 健康檢查擴展
dotnet add package AspNetCore.HealthChecks.System# HealthChecks UI(可視化界面)
dotnet add package AspNetCore.HealthChecks.UI
dotnet add package AspNetCore.HealthChecks.UI.InMemory.Storage # 演示或開發環境
# 若需持久化存儲,可替換為 SqlServer/PostgreSQL 存儲包
提示:在生產環境,建議將 HealthChecks UI 的存儲改為持久化存儲(如 SQL Server、PostgreSQL),否則重啟后歷史記錄會丟失。💾
2.2 注冊 Health Checks 服務 ??
在 Program.cs
(或 Startup.cs
)中,先將 IConfiguration
拿到本地,然后通過 AddHealthChecks()
注冊各類檢查項。示例代碼如下:
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
var services = builder.Services;// 1. 注冊基本健康檢查
services.AddHealthChecks()// 自檢項:確保應用啟動后至少返回 Healthy.AddCheck("Self", () => HealthCheckResult.Healthy("I'm alive"))// SQL Server 檢查:超時 3s,失敗返回 Unhealthy,帶上 tags 便于篩選.AddSqlServer(configuration["ConnectionStrings:Default"], name: "SqlServer",failureStatus: HealthStatus.Unhealthy,tags: new[] { "db", "sql" },timeout: TimeSpan.FromSeconds(3))// Redis 檢查:超時 2s,失敗返回 Degraded.AddRedis(configuration["ConnectionStrings:Redis"], name: "Redis",failureStatus: HealthStatus.Degraded,tags: new[] { "cache", "redis" },timeout: TimeSpan.FromSeconds(2))// 外部 HTTP/URL 檢查:超時 1s,失敗返回 Unhealthy.AddUrlGroup(new Uri(configuration["ExternalServices:PingUrl"]), name: "ExternalAPI",failureStatus: HealthStatus.Unhealthy,tags: new[] { "http", "external" },timeout: TimeSpan.FromSeconds(1));
要點:
- 🔖 AddCheck(“Self”, …):自檢項,保持應用啟動后能返回健康;
- 🏷? tags:為每個檢查項打標簽,后續在 UI 或 Gateway 可以根據標簽篩選;
- ?? timeout:超過該時長視為檢查失敗。
2.3 集成 HealthChecks UI 🖥?
HealthChecks UI 提供可視化界面,幫助運維團隊查看各個端點歷史狀態。示例注冊如下:
// 2.3.1 注冊 UI 服務
services.AddHealthChecksUI(setup =>
{setup.SetEvaluationTimeInSeconds(60); // 每 60s 重新評估一次setup.MaximumHistoryEntriesPerEndpoint(50); // 每個端點保留 50 條歷史記錄// 僅監控 /health-status 這個端點setup.AddHealthCheckEndpoint("MicroservicesHealth", "/health-status");
})
// 開發環境或演示環境使用內存存儲
.AddInMemoryStorage();// 若要生產環境使用 SQL Server 存儲,請替換為:
// .AddSqlServerStorage(configuration["ConnectionStrings:HealthChecksUI"]);
安全性提示:
- 建議對 UI 界面添加授權策略(如“AdminOnly”),否則任何人都能查看或篡改數據。🛡?
2.4 自定義健康端點響應 📋
默認情況下,MapHealthChecks
只會返回 HTTP 200 和簡單的“Healthy/Unhealthy”文本。通常我們希望輸出更豐富的 JSON,并根據總體健康狀態設置 HTTP 狀態碼,還要對異常進行日志告警。示例如下:
var app = builder.Build();
var logger = app.Services.GetRequiredService<ILogger<Program>>();// 將 Health Checks 映射到 /health-status
app.MapHealthChecks("/health-status", new HealthCheckOptions
{// 允許在端點調用失敗時返回 503ResultStatusCodes ={[HealthStatus.Healthy] = StatusCodes.Status200OK,[HealthStatus.Degraded] = StatusCodes.Status503ServiceUnavailable,[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable},ResponseWriter = async (context, report) =>{// 若請求取消,提前返回context.RequestAborted.ThrowIfCancellationRequested();// 遍歷所有檢查結果,如果有非 Healthy,寫警告日志foreach (var entry in report.Entries){if (entry.Value.Status != HealthStatus.Healthy){logger.LogWarning("Health Check '{Name}' status: {Status}. Error: {Error}",entry.Key,entry.Value.Status,entry.Value.Exception?.Message);}}// 自定義 JSON 格式var response = new{status = report.Status.ToString(),totalDuration = report.TotalDuration.TotalMilliseconds + " ms",results = report.Entries.Select(e => new{name = e.Key,status = e.Value.Status.ToString(),duration = e.Value.Duration.TotalMilliseconds + " ms",error = e.Value.Exception != null ? "Error occurred, see logs for details" : null})};context.Response.ContentType = "application/json; charset=utf-8";var options = new JsonSerializerOptions{WriteIndented = true,PropertyNamingPolicy = JsonNamingPolicy.CamelCase};await context.Response.WriteAsync(JsonSerializer.Serialize(response, options));}
})
.RequireAuthorization("HealthCheckPolicy"); // 僅允許特定角色訪問
要點:
- 將 Degraded/Unhealthy 狀態映射為 HTTP 503 ??;
- 在
ResponseWriter
中遍歷每個檢查項,若非 Healthy,就寫警告日志 📝;- 不直接將
Exception.Message
返回客戶端,避免泄露內部實現 🔒;- 使用
JsonSerializerOptions
美化 JSON 🎨。
三、實戰演示 🚀
下面以一個 API Gateway 為例,演示如何聚合多個微服務的 /health-status
,并在本地提供一個“聚合健康狀態”端點。
3.1 Gateway 端整合示例 🔗
// 在 Program.cs 中繼續配置
builder.Services.AddAuthorization(options =>
{options.AddPolicy("GatewayHealthPolicy", policy =>policy.RequireRole("GatewayAdmin"));
});var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();// 注入 HttpClient,用于調用下游服務
builder.Services.AddHttpClient("HealthClient").ConfigureHttpClient(client =>{client.Timeout = TimeSpan.FromSeconds(2); // 每個請求超時 2s});app.MapGet("/aggregate-health", async (IHttpClientFactory httpFactory) =>
{var urls = new[]{"http://serviceA/health-status","http://serviceB/health-status","http://serviceC/health-status"};var client = httpFactory.CreateClient("HealthClient");var tasks = urls.Select(async url =>{try{var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));var resp = await client.GetStringAsync(url, cts.Token);// 簡單判斷下游狀態是否包含 "Unhealthy"var isUnhealthy = resp.Contains("\"status\":\"Unhealthy\"");return new { Url = url, IsUnhealthy = isUnhealthy };}catch{// 調用失敗,視為 Unhealthyreturn new { Url = url, IsUnhealthy = true };}});var results = await Task.WhenAll(tasks);// 若任一服務不可用,則聚合狀態為 Unhealthyvar aggregateStatus = results.Any(r => r.IsUnhealthy) ? "Unhealthy" : "Healthy";var response = new{aggregateStatus,details = results.Select(r => new{service = r.Url,status = r.IsUnhealthy ? "Unhealthy" : "Healthy"})};return Results.Json(response);
})
.RequireAuthorization("GatewayHealthPolicy");
說明:
- 使用
IHttpClientFactory
創建帶超時設置的 HttpClient ??;- 并發調用各下游
/health-status
,若任一返回中包含"Unhealthy"
,則認為該服務不可用 🚫;- 最后再聚合結果,返回一個整體狀態及每個服務的健康情況。
3.2 Health Check 整體流程示意圖 📈
四、Kubernetes 集成 🐳
在 Kubernetes 環境中,我們通常要配置兩個探針:
- livenessProbe:檢測應用進程是否“活著”,若失敗則重啟 Pod 🔄
- readinessProbe:檢測服務是否已“準備就緒”并能正常對外提供流量 ?
因為 /health-status
包含了對數據庫/Redis/外部 API 的檢查,不建議直接當作 livenessProbe,否則只要依賴短暫不可用就會不斷重啟。最佳實踐如下:
apiVersion: apps/v1
kind: Deployment
metadata:name: my-microservice
spec:replicas: 3selector:matchLabels:app: my-microservicetemplate:metadata:labels:app: my-microservicespec:containers:- name: webimage: myregistry/my-microservice:latestports:- containerPort: 80# livenessProbe:只檢查應用自檢 ping 接口livenessProbe:httpGet:path: /health/pingport: 80initialDelaySeconds: 30periodSeconds: 10timeoutSeconds: 2failureThreshold: 3# readinessProbe:檢查完整的 /health-statusreadinessProbe:httpGet:path: /health-statusport: 80initialDelaySeconds: 15periodSeconds: 20timeoutSeconds: 5failureThreshold: 2# 環境變量或 ConfigMap 掛載可靈活配置連接字符串env:- name: ConnectionStrings__DefaultvalueFrom:secretKeyRef:name: my-secretskey: SqlConnectionString- name: ConnectionStrings__RedisvalueFrom:secretKeyRef:name: my-secretskey: RedisConnectionString- name: ExternalServices__PingUrlvalue: "https://api.external.com/ping"
要點:
- 📌
/health/ping
端點僅檢查自檢項(見 2.2 中.AddCheck("Self", …)
),保證應用運行即可;- 📌
/health-status
端點同時檢查數據庫、Redis、外部 API,僅當所有依賴都可用時才返回 Healthy;- 📌 合理設置
initialDelaySeconds
、periodSeconds
、timeoutSeconds
與failureThreshold
,避免頻繁誤判;- 🔒 如果探針端點公開在公網上,一定要在 Ingress 或 Service 層面添加 IP 白名單或身份驗證。
4.1 Kubernetes 整體流程示意圖 🌐
五、安全性與健壯性 🔐
-
不要泄露異常細節
- 在自定義響應時,將
Exception.Message
只寫入日志,不直接返回給客戶端,防止敏感信息泄露。
- 在自定義響應時,將
-
鑒權與授權
- 對
/health-status
和/hc-ui
端點均要加上授權策略。例如在Program.cs
中:
builder.Services.AddAuthorization(options => {options.AddPolicy("HealthCheckPolicy", policy =>policy.RequireRole("HealthAdmin"));options.AddPolicy("UIAccessPolicy", policy =>policy.RequireRole("OpsUser")); });app.UseAuthentication(); app.UseAuthorization();app.MapHealthChecks("/health-status", new HealthCheckOptions { … }).RequireAuthorization("HealthCheckPolicy");app.MapHealthChecksUI(options => {options.UIPath = "/hc-ui"; }).RequireAuthorization("UIAccessPolicy");
說明:🔒 只有擁有對應角色的用戶才可訪問健康檢查端點和 UI 界面。
- 對
-
日志告警與追蹤 📣
- 將非 Healthy 檢查項的異常寫入告警日志,并結合 Prometheus、Grafana 等工具配置告警規則。例如:
foreach (var entry in report.Entries) {if (entry.Value.Status != HealthStatus.Healthy){logger.LogError("【HealthCheck告警】{Name} 狀態: {Status},詳細: {Exception}",entry.Key,entry.Value.Status,entry.Value.Exception?.ToString());} }
- 配合
ILogger
將日志發送到 Elasticsearch / Seq / Kibana 等日志分析平臺。
- 將非 Healthy 檢查項的異常寫入告警日志,并結合 Prometheus、Grafana 等工具配置告警規則。例如:
-
Graceful Shutdown(優雅下線) 🌅
- 在程序關閉時,將 Pod 狀態先置為 NotReady,再執行清理邏輯。可以在
Program.cs
中訂閱ApplicationStopping
事件:var lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>(); lifetime.ApplicationStopping.Register(() => {// 將自檢狀態置為 Unhealthy,通知 Kubernetes 不再發流量// 具體實現可將“Self”檢查改為動態返回 Unhealthy });
- 在程序關閉時,將 Pod 狀態先置為 NotReady,再執行清理邏輯。可以在
六、性能與可用性考量 🚀
-
監控健康檢查耗時 ??
- 使用 Prometheus Client 庫,將每個健康檢查項的耗時上報到 Prometheus:
// 在自定義 ResponseWriter 中添加 var gauge = Metrics.CreateGauge("health_check_duration_seconds", "健康檢查耗時(秒)", "dependency"); foreach (var entry in report.Entries) {gauge.WithLabels(entry.Key).Set(entry.Value.Duration.TotalSeconds); }
- Grafana 可據此畫出每個依賴的響應曲線,幫助發現性能瓶頸。📊
- 使用 Prometheus Client 庫,將每個健康檢查項的耗時上報到 Prometheus:
-
抖動過濾與熔斷 ?
- 若數據庫偶發抖動,會在
/health-status
上觸發短暫 Unhealthy,造成 Pod 頻繁重啟。可使用“熔斷+滑動窗口”策略,將檢查結果在內存中緩存一定時長:- 使用 Polly 的 CircuitBreaker 在檢測到連續 3 次失敗后,將后續 30s 直接判定為 Unhealthy,避免頻繁觸發探針。
- 示例:
using Polly; using Polly.CircuitBreaker;// 定義熔斷策略 var breakerPolicy = Policy.Handle<Exception>().CircuitBreakerAsync(exceptionsAllowedBeforeBreaking: 3,durationOfBreak: TimeSpan.FromSeconds(30));services.AddHealthChecks().AddCheck("SqlServerWithBreaker", async () =>{return await breakerPolicy.ExecuteAsync(async () =>{// 這里實際調用 SQL Server 做檢查// …return HealthCheckResult.Healthy();});});
- 這樣在連續失敗后,短時間內不再實際調用數據庫,只把健康狀態返回為 Unhealthy,等熔斷解除后再恢復正常調用。🔄
- 若數據庫偶發抖動,會在
-
水平擴展與緩存 ??
- 當微服務水平擴展后,如果每個實例都直接去訪問數據庫檢查,容易造成短時壓力;可考慮:
- 統一在一個“側車”(Sidecar)或中間層進行數據庫探測,主實例直接從側車獲取結果。
- 將某些常駐的健康檢查(如 Redis)結果緩存在內存 30 秒,每次請求
/health-status
時先讀取緩存。
- 當微服務水平擴展后,如果每個實例都直接去訪問數據庫檢查,容易造成短時壓力;可考慮:
完整示例項目結構概覽(摘錄) 📂
MicroserviceDemo
├─ Program.cs
├─ appsettings.json
├─ Controllers
│ └─ WeatherController.cs
├─ HealthChecks
│ └─ CustomHealthChecks.cs # 可放置自定義熔斷/滑動窗口檢查邏輯
├─ Properties
│ └─ launchSettings.json
└─ Dockerfile
- Program.cs:包含上述健康檢查與 UI 的注冊、映射、授權邏輯。
- appsettings.json:存儲連接字符串與配置項,例如:
{"ConnectionStrings": {"Default": "Server=.;Database=MyDb;User Id=sa;Password=Your_password;","Redis": "localhost:6379"},"ExternalServices": {"PingUrl": "https://api.external.com/ping"},"HealthChecksUI": {"HealthChecks-UI": [{"Name": "MicroservicesHealth","Uri": "/health-status"}],"EvaluationTimeOnSeconds": 60,"MinimumSecondsBetweenFailureNotifications": 50},"Logging": {"LogLevel": {"Default": "Information","Microsoft.AspNetCore": "Warning"}} }
注意:示例僅供參考,請根據實際項目需求調整配置項及命名規范。