原文:
Health checks in ASP.NET Core | Microsoft Learn
var builder = WebApplication.CreateBuilder(args); builder.Services.AddHealthChecks(); var app = builder.Build(); app.MapHealthChecks("/healthz"); app.Run();
docker
HEALTHCHECK CMD curl --fail http://localhost:5000/healthz || exit
自定义health check、
public class SampleHealthCheck : IHealthCheck
实战参考:Health Checks in ASP.Net Core - .Net Core Central (dotnetcorecentral.com)
Health checks are a critical part of any distributed system. Especially in the era of microservices, it is extremely important to understand if the service is running healthy or not. In this blog, I will walk through how to implement Health Checks in ASP.Net Core.
ASP.Net Core provides support for health checks out of the box through middleware and extension methods.
What are the different aspects of an application that we need through health checks?
- Firstly, finding out dependencies such as a database or other services are alive or not
- Secondly, the usage of physical resources on the machine
We usually use the health checks for monitoring applications and to see how the applications are behaving. The health check results are also in use for scaling out applications based on how the service is degrading in response time.
To further dig deeper into the topic let us start by creating an ASP.Net Core Web application. And let us demonstrate the feature.
Creating a new ASP.Net Core Web Application
First of all, we will create a new ASP.Net Core web application. To achieve that, I will open up Visual Studio 2019, and select the menu option File -> New -> Project.
Once the new project creation window pops up, I will select the ASP.Net Core Web Application. And then I will click on the Next button.
Secondly, on the next page of the pop-up, I will provide the name of the project in the Project Name field as HealthCheck.Demo. And then click on the Create button.
Finally, on the final page, I will select the API template option. And I will keep other values default (ASP.Net Core 3.1) and click on the Create button.
Setting up Health Checks in ASP.Net Core
Once the project is created, I will set up a basic health check. For that, I will start with the Startup class.
Change in ConfigureServices method
In the ConfigureServices
of the Startup
class, I will add the health check system into the dependency injections container. For that, I will call the AddHealthChecks
extension method on the IServiceCollection
instance. The method will return an instance of IHealthChecksBuilder
.
As the name suggests the IHealthChecksBuilder
provides methods to set up and chain health checks.
As a first step, I will call the AddCheck
method on the IHealthChecksBuilder
instance returned by the AddHealthChecks
.
The AddCheck
takes two parameters, the first one is a name for the health check. And the second one is a Func
delegate which returns the HealthCheckResult
struct.
For the time being, I will just return HealthCheckResult.Healthy
as the return.
1 2 |
services.AddHealthChecks()
.AddCheck( "Test Health Check" , () => HealthCheckResult.Healthy());
|
Change in Configure method
Once the dependency injection container setup is complete, it is time to set up the health check middleware. For that, I will update the implementation of the UseEndpoints
extension method call to the IApplicationBuilder
instance.
Inside of the delegate call for the UseEndpoints
method parameter; I will call the MapHealthChecks
extension method on the IEndpointRouteBuilder
instance.
The MapHealthChecks
method takes the URL pattern as the parameter. I will pass api/health as the parameter value. You can use what suits your application.
Microsoft uses /health as the endpoint in all the examples of their documentation here.
1 2 3 4 5 |
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks( "api/health" );
endpoints.MapControllers();
});
|
The complete change in code inside of the Startup
class is below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Hosting;
namespace HealthCheck.Demo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get ; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHealthChecks()
.AddCheck( "Test Health Check" , () => HealthCheckResult.Healthy());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks( "api/health" );
endpoints.MapControllers();
});
}
}
}
|
Running the application
Once the code is complete, I will run the application to test the health check endpoint.
After running the application, if I go to the /api/health endpoint I will see the response as Healthy.
Healthy Response
Using Multiple Health Checks Providers
Now, in a real-life scenario, we will never return a HealthCheckResult.Healthy
from the health checks return and call it a day. For health checks, we will try to validate multiple aspects of the application to see if they are working as expected.
For example, let us say we want to check the state of our database. And make sure that we are able to successfully connect to the database.
For that, we will create a new class, DbHealthCheckProvider
. The only responsibility of this class is to connect to the database. And make sure that the connection was successful.
I will create this class as a static class and it will have a single static method Check
. The method Check will take the connection string of the database. Now since that class will be called from the Startup
class, and will not be used anywhere, it is fair to do this. Also, we will never have to test this class, for obvious reasons; it does not add any value.
For this to work, I will install the NuGet package System.Data.SqlClient.
Inside of the Check
method, I will open a connection to my local SQL Server And if the connection was successful, I will return HealthCheckResult.Healthy
. Otherwise, I will return HealthCheckResult.Unhealthy
from the method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Data.SqlClient;
namespace HealthCheck.Demo
{
public static class DbHealthCheckProvider
{
public static HealthCheckResult Check( string connectionString)
{
// Code to check if DB is running
try
{
using var connection = new SqlConnection(connectionString);
connection.Open();
return HealthCheckResult.Healthy();
}
catch
{
return HealthCheckResult.Unhealthy();
}
}
}
}
|
Setting up Startup class
Once the DbHealthCheckProvider
class is ready, I will set up the Startup
to use this class.
For that, I will update the ConfigureServices
method. And I will change the delegate implementation inside the AddCheck
method. Instead of calling an inline delegate to return HealthCheckResult.Healthy
. Now, I will call the DbHealthCheckProvider.Check
.
And for the database connection string, I will pass an empty string. This should result in an Unhealthy
response when we run the application.
1 2 3 4 5 6 7 8 |
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHealthChecks()
.AddCheck( "DB Health Check" , () =>
DbHealthCheckProvider.Check( "" ));
}
|
Once the code inside of Startup
is configured to use the new DbHealthCheckProvider
class, I will run the application.
Unhealthy Response
As expected, when we navigate to the /api/health endpoint, we can see an Unhealthy response back.
Console Response
In the above console response, we can see that the Health Checks system logs a fail in the console. This log can be used to identify what exactly failed. As you can see it prints the name of the health checks “DB Health Check”.
The HealthCheckResult.Unhealthy
or HealthCheckResult.Healthy
method takes an optional string description parameter. This is very handy when it comes to logging.
To demonstrate that I will update the implementation of the HealthCheckResult.Unhealthy
method inside of the DbHealthCheckProvider
. And I will add a description to the Unhealthy
method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Data.SqlClient;
namespace HealthCheck.Demo
{
public static class DbHealthCheckProvider
{
public static HealthCheckResult Check( string connectionString)
{
// Code to check if DB is running
try
{
using var connection = new SqlConnection(connectionString);
connection.Open();
return HealthCheckResult.Healthy();
}
catch
{
return HealthCheckResult.Unhealthy( "Could not connect to database!" );
}
}
}
}
|
Now if I run the application and navigate to the /api/health endpoint. I will see the description text “Could not connect to database!” will print out in the console.
Unhealthy with description
Queue Health Checks Provider
Now, let us say we our application also connects to a RabbitMQ server for processing messages. Now to check if we are able to connect to RabbitMQ server, we will need to write a health check provider.
Putting all health checks inside of a single class makes things difficult to manage. As well as harder to find out what was the cause of the issue.
Hence, I will create a new class, MqHealthCheckProvider
. This will also be a static class with a single static method Check
. For this class, we will just return HealthCheckResult.Healthy
from the method, we will not implement any real RabbitMQ connectivity for the demo.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace HealthCheck.Demo
{
public static class MqHealthCheckProvider
{
public static HealthCheckResult Check( string mqUri)
{
// Code to check if MQ is running
return HealthCheckResult.Healthy();
}
}
}
|
Once the MqHealthCheckProvider
is ready, I will update the Startup
class to configure the MqHealthCheckProvider
.
Since the AddCheck
method also returns IHealthChecksBuilder
instance, I will chain another AddCheck
to configure the MqHealthCheckProvider
.
And for this case as well when we call the Check
method of the MqHealthCheckProvider
class, we will pass the MQ Server string as an empty string.
1 2 3 4 5 6 7 8 9 10 |
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHealthChecks()
.AddCheck( "DB Health Check" , () =>
DbHealthCheckProvider.Check( "" ))
.AddCheck( "MQ Health Check" , () =>
MqHealthCheckProvider.Check( "" ));
}
|
Now when I run the application, I will still see the response as Unhealthy. This is because the health check is the aggregation of all the health checkpoints configured.
Hence, I will update the DbHealthCheckProvider.Check
call to pass a valid connection string to make that call as healthy as well.
I will get the connection string from the appsettings.json
file.
1 2 3 4 5 6 7 8 9 10 |
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHealthChecks()
.AddCheck( "DB Health Check" , () =>
DbHealthCheckProvider.Check(Configuration[ "Connection" ]))
.AddCheck( "MQ Health Check" , () =>
MqHealthCheckProvider.Check( "" ));
}
|
Now if I run the application, I will see Healthy in response.
Using Generic AddCheck method
The next thing I am going to walk through is how to use the generic AddCheck
method. For that let us consider our application is interacting with third part SAAS software like Sendgrid through an HTTP connection.
Firstly, I will create a new class that is responsible for checking if an HTTP request to Sendgrid is successful or not. I will name the class as SendgridHealthCheckProvider
.
This class will implement the interface IHealthCheck
, which is part of the namespace Microsoft.Extensions.Diagnostics.HealthChecks
. I will implement the method CheckHealthAsync
from the interface.
For the time being for the demo, I will return HealthCheckResult.Healthy
from the method. I will not make a real HTTP call outside.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Threading;
using System.Threading.Tasks;
namespace HealthCheck.Demo
{
public class SendgridHealthCheckProvider : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default )
{
return Task.FromResult(HealthCheckResult.Healthy());
}
}
}
|
Once the SendgridHealthCheckProvider
is ready, I will go and update the Startup
class to configure it.
For the configuration, I will use the generic version of the AddCheck
method.
1 2 3 4 5 6 7 8 9 |
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHealthChecks()
.AddCheck( "DB Health Check" , () => DbHealthCheckProvider.Check(Configuration[ "Connection" ]))
.AddCheck( "MQ Health Check" , () => MqHealthCheckProvider.Check( "" ))
.AddCheck<SendgridHealthCheckProvider>( "Sendgrid Health Check" );
}
|
Finally, I will run the application. And as expected, I will get a Healthy response from the API call.
Implementing Degraded option
One of the options of the HealthCheckResult
struct that we have not tried so far is the Degraded
. Now the question is when we will use this option?
Whenever a database or some external service is taking more than the SLA, we can send a Degraded response. This can be achieved by using a timer to check the time taken by the external dependency.
To demonstrate that, let us update the MqHealthCheckProvider
class. Now in the Check
method, it will return HealthCheckResult.Degraded
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace HealthCheck.Demo
{
public static class MqHealthCheckProvider
{
public static HealthCheckResult Check( string mqUri)
{
// Code to check if MQ is running
return HealthCheckResult.Degraded( "MQ is running slow!" );
}
}
}
|
Now if we run the application, we can see that the response is Degraded.
Degraded response
And also as expected the console log will print a warning, along with the same of the health check. And the description message “MQ is running slow!”.
Degraded warning in console
Health Checks in ASP.Net Core through Postman
Now, the question is, how the monitoring tools will interpret this response. Well, the best way to implement that will be through checking the HTTP status code. And if needed further investigation then checking the response body.
Let us run the requests through the postman and see how it all works. So to test that, we will just run the application as it is running now. Which means it will return a Degraded response.
If we navigate to the /api/health, we will see the HTTP Status code is still 200, but the response body is Degraded.
Degraded in Postman
Now let us see how an Unhealthy response will look like in postman. Specifically what status code will it return.
For doing that, let us just update the Startup
classes ConfigureServices
method. And for the AddCheck
of the DbHealthCheckProvider
, let us pass an empty connection string. This will create an Unhealthy response.
1 2 3 4 5 6 7 8 9 |
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHealthChecks()
.AddCheck( "DB Health Check" , () => DbHealthCheckProvider.Check( "" ))
.AddCheck( "MQ Health Check" , () => MqHealthCheckProvider.Check( "" ))
.AddCheck<SendgridHealthCheckProvider>( "Sendgrid Health Check" );
}
|
Once I make this change, let us run the application and check the postman response.
Unhealthy response in Postman
In the response, we can see that the HTTP status code is 503 Service Unavailable. And the response string is Unhealthy. Based on the HTTP status code any motoring tool can find out if the service is up or down.
For a Healthy response, we will get an HTTP Status Code of 200 and Healthy in the response string.
Conclusion
The Health Checks in ASP.Net Core implementation is really awesome in my opinion. It is super simple to implement and integrate with individual implementation classes.
I have done a YouTube video going through the concepts here.
The source code is available in Github here.
参考:
Health monitoring | Microsoft Learn
Docker-HealthCheck指令探测ASP.NET Core容器健康状态-阿里云开发者社区 (aliyun.com)
使用ASP.NET Core实现Docker的HealthCheck指令 - 程序员大本营 (pianshen.com)
ASP.NET Core Health Checks Explained (elmah.io)
(142条消息) .NET Core 3.0之深入源码理解HealthCheck(一)_dotNET跨平台的博客-CSDN博客
(142条消息) .NET Core 3.1之深入源码理解HealthCheck(二)_dotNET跨平台的博客-CSDN博客
在.NET Core 中实现健康检查 - 腾讯云开发者社区-腾讯云 (tencent.com)
ASP.NET CORE在docker中的健康检查(healthcheck) - 波多尔斯基 - 博客园 (cnblogs.com)
HealthCheck 不仅是对应用程序内运行情况、数据流通情况进行检查,还包括应用程序对外部服务或依赖资源的健康检查。
健康检查通常是以暴露应用程序的HTTP端点
的形式实施,可用于配置健康探测的的场景有 :
- 容器或负载均衡器 探测应用状态,执行既定策略,例如:容器探测到应用unhealthy可终止后续的滚动部署或者重启容器;负载均衡器探测到实例unhealthy能将请求路由到健康的运行实例。
- 对应用程序种依赖的第三方服务进行健康探测,比如redis、database、外部服务接口
- 内存、硬盘、网络等物理依赖资源的探测
HealthCheck提供对外暴露程序运行状态的机制。
容器HEALTHCHECK指令
一般情况下我们很容易知道容器正在运行running
, 但容器作为相对独立的应用执行环境,有时候并不知道容器是否以预期方式正确运作working
Dockerfile HEALTHCHECK指令提供了探测容器以预期工作的轮询机制,轮询内容可由应用自身决定。
具体而言:通过在容器内运行shell命令来探测容器健康状态,以Shell命令的退出码表示容器健康状态:
0 指示容器健康
1 指示容器不健康
2 指示不使用这个退出码
// 可定义轮询interval、探测超时timeout、 重试retries参数轮询探测 HEALTHCHECK [OPTIONS] CMD command
Every Linux or Unix command executed by the shell script or user has an exit status. Exit status is an integer number. 0 exit status means the command was successful without any errors. A non-zero (1-255 values) exit status means command was a failure.
linux shell执行成功,返回0;为对接Docker-HealcthCheck失败退出码1,要对Shell执行失败返回退出码1
对Web应用,自然会联想到使用curl命令访问端点
去探测容器应用:
curl web端点成功,命令返回0(真);curl web端点失败,命令返回非0(假)
// curl -f 表示请求失败返静默输出 HEALTHCHECK --interval=5m --timeout=3s --retries=3 CMD curl -f http://localhost:5000/healthz || exit 1
探测命令在stdout或stderr输出的任何内容会在容器Health Status中存储,可通过docker inspect [ContainerId] 查看HealthCheck状态。
下面渐进式演示使用Docker平台的HEALTHCHECK指令对接 ASP.NET Core程序的健康检查能力。
ASP.NET Core实现HealthCheck端点
ASPNET Core在2.2版本内置了健康检查的能力:终端中间件(满足该路径的url请求,将会被该中间件处理)。
public void ConfigureServices(IServiceCollection services) { services.AddHealthChecks(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseHealthChecks("/healthcheck"); }
Asp.NetCore 3.1将健康检查集成在 EndPoints,请自行修改。
请求/healthcheck端点, 程序会进行健康检查逻辑并响应输出, 默认的行为:
① 对healthy、degraded状态返回200 OK响应码;对于unhealthy返回503 Service Unavailable响应码
② 响应体只会包含简单的HealthStatus枚举字符串
③ 将每次健康检查的结果写入HealthReport对象。
作为企业级项目,存在对Web项目物理资源和服务依赖的健康检查需求, 这里我们为避免重复造轮子,引入了开源的力量。
开源社区对HealthCheck的支持
开源的企业级AspNetCore.Diagnostics.HealthChecks系列组件,该系列组件支持多种物理资源和服务依赖的健康检查,支持报告推送,支持友好的检查报告UI(支持后台轮询检查)、支持webhook通知。
下面的步骤演示了对web程序HTTP请求、Redis、Sqlite等服务进行健康检查的端点配置
① 引入AspNetCore.HealthChecks.Redis 、 AspNetCore.HealthChecks.Sqlite nuget库
② Startup.cs配置并启用健康检查
// 以下代码截取自 Startup.ConfigureServices方法,对swagger服务地址、redis、sqlte进行健康检查 services.AddHealthChecks().AddAsyncCheck("Http", async () => { using (HttpClient client = new HttpClient()) { try { var response = await client.GetAsync("http://localhost:5000/swagger"); if (!response.IsSuccessStatusCode) { throw new Exception("Url not responding with 200 OK"); } } catch (Exception) { return await Task.FromResult(HealthCheckResult.Unhealthy()); } } return await Task.FromResult(HealthCheckResult.Healthy()); }) .AddSqlite( sqliteConnectionString: Configuration.GetConnectionString("sqlite"), healthQuery: "select count(*) as count from ProfileUsageCounters;", name: "sqlite", failureStatus: HealthStatus.Degraded, tags: new string[] { "db", "sqlite", "sqlite" } ) .AddRedis(Configuration.GetConnectionString("redis"), "redis", HealthStatus.Unhealthy, new string[] { "redis", "redis" }) .Services .AddMvc(); // 以下代码截取自Startup.Configure方法:启用/healthz作为检查端点 app.UseHealthChecks("/healthz").UseMvcWithDefaultRoute(); // 这里仍然只会响应 200/503状态码+简单的HealthStatus枚举值
再次强调,容器HealthCheck指令不关注Shell命令的执行过程,只关注shell命令的执行结果
// docker-compose.yml文件健康检查 参考如下配置: healthcheck: test: curl -f http://localhost/healthcheck || exit 1 interval: 1m30s timeout: 10s retries: 3
HealthChecks-UI 了解一下
抛开Docker的HEALTHCHECK指令、负载均衡器的轮询机制不谈,我们的Web自身也可以进行 轮询健康检查并给出告警。
就我们上面的Web 实例来说,我们只对外提供的是一个 /healthcheck 检查端点,引入HealthChecks.UI.dll 将会在前端生成友好的HealthReport 界面, 该库支持后台轮询检查、支持webhook 通知。
这里就不展开说明,自行前往AspNetCore.Diagnostics.HealthChecks查看相应文档,效果如下
至此,本文内容完毕:
- 使用ASP.NET Core框架实现一个稍复杂的HealthCheck端点 /healthz
- 使用docker的HEALTHCHECK指令对接Web应用健康检查端点
Health Checks in ASP.Net Core - .Net Core Central (dotnetcorecentral.com)
标签:CORE,class,method,will,health,Health,NET,Check From: https://www.cnblogs.com/panpanwelcome/p/16918652.html