如何在 .NET Core Worker Service 中实现运行状况检查?
该服务将在 Docker 内部运行,并且需要能够检查服务的运行状况。
另一种方法是实施
IHealthCheckPublisher
。
这种方法的好处是能够重用现有的
IHealthCheck
或与依赖于IHealthCheck
接口的第三方库集成(如这个)。
虽然您仍然将
Microsoft.NET.Sdk.Web
作为 SDK,但您不需要添加任何 asp.net 细节。
这是一个例子:
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host
.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services
.AddHealthChecks()
.AddCheck<RedisHealthCheck>("redis_health_check")
.AddCheck<RfaHealthCheck>("rfa_health_check");
services.AddSingleton<IHealthCheckPublisher, HealthCheckPublisher>();
services.Configure<HealthCheckPublisherOptions>(options =>
{
options.Delay = TimeSpan.FromSeconds(5);
options.Period = TimeSpan.FromSeconds(5);
});
});
}
public class HealthCheckPublisher : IHealthCheckPublisher
{
private readonly string _fileName;
private HealthStatus _prevStatus = HealthStatus.Unhealthy;
public HealthCheckPublisher()
{
_fileName = Environment.GetEnvironmentVariable(EnvVariableNames.DOCKER_HEALTHCHECK_FILEPATH) ??
Path.GetTempFileName();
}
public Task PublishAsync(HealthReport report, CancellationToken cancellationToken)
{
// AWS will check if the file exists inside of the container with the command
// test -f $DOCKER_HEALTH_CHECK_FILEPATH
var fileExists = _prevStatus == HealthStatus.Healthy;
if (report.Status == HealthStatus.Healthy)
{
if (!fileExists)
{
using var _ = File.Create(_fileName);
}
}
else if (fileExists)
{
File.Delete(_fileName);
}
_prevStatus = report.Status;
return Task.CompletedTask;
}
}
我认为将 SDK 更改为 Microsoft.NET.Sdk.Web 不值得。您会仅仅因为一项健康检查而包含额外的中间件吗?不,谢谢...
您可以使用不同的协议,例如 TCP。
总体思路是:
如果您需要更详细的信息,您可以查看:在 Kubernetes 上使用 TCP 探针监控 ASP.NET Core 后台服务的运行状况
干杯!
添加 HTTPListener 并公开健康检查端点。
使用HTTPListener不需要添加Microsoft.NET.Sdk.Web SDK。
程序.cs
using Consumer;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<Worker>();
services.AddHostedService<HttpHealthcheck>();
})
.Build();
await host.RunAsync();
HttpHealthcheck.cs
using System.Net;
using System.Text;
namespace Consumer;
public class HttpHealthcheck : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly HttpListener _httpListener;
private readonly IConfiguration _configuration;
public HealthcheckHttpListener(ILogger<Worker> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
_httpListener = new HttpListener();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_httpListener.Prefixes.Add($"http://*:5001/healthz/live/");
_httpListener.Prefixes.Add($"http://*:5001/healthz/ready/");
_httpListener.Start();
_logger.LogInformation($"Healthcheck listening...");
while (!stoppingToken.IsCancellationRequested)
{
HttpListenerContext ctx = null;
try
{
ctx = await _httpListener.GetContextAsync();
}
catch (HttpListenerException ex)
{
if (ex.ErrorCode == 995) return;
}
if (ctx == null) continue;
var response = ctx.Response;
response.ContentType = "text/plain";
response.Headers.Add(HttpResponseHeader.CacheControl, "no-store, no-cache");
response.StatusCode = (int)HttpStatusCode.OK;
var messageBytes = Encoding.UTF8.GetBytes("Healthy");
response.ContentLength64 = messageBytes.Length;
await response.OutputStream.WriteAsync(messageBytes, 0, messageBytes.Length);
response.OutputStream.Close();
response.Close();
}
}
}
我认为您还应该考虑保留 Microsoft.NET.Sdk.Worker。
不要因为健康检查而改变整个sdk。
然后您可以创建一个后台服务(就像主工作人员一样),以便更新文件以写入例如当前时间戳。后台健康检查工作人员的一个示例是:
public class HealthCheckWorker : BackgroundService
{
private readonly int _intervalSec;
private readonly string _healthCheckFileName;
public HealthCheckWorker(string healthCheckFileName, int intervalSec)
{
this._intervalSec = intervalSec;
this._healthCheckFileName = healthCheckFileName;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (true)
{
File.WriteAllText(this._healthCheckFileName, DateTime.UtcNow.ToString());
await Task.Delay(this._intervalSec * 1000, stoppingToken);
}
}
}
然后你可以添加这样的扩展方法:
public static class HealthCheckWorkerExtensions
{
public static void AddHealthCheck(this IServiceCollection services,
string healthCheckFileName, int intervalSec)
{
services.AddHostedService<HealthCheckWorker>(x => new HealthCheckWorker(healthCheckFileName, intervalSec));
}
}
通过此您可以在服务中添加健康检查支持
.ConfigureServices(services =>
{
services.AddHealthCheck("hc.txt", 5);
})
我为实现此目的所做的是将 Microsoft.NET.Sdk.Web 添加到我的 Worker,然后配置一个 Web 主机与该 Worker 一起运行:
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>();
})
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
services.AddLogging(builder =>
builder
.AddDebug()
.AddConsole()
);
});
完成后,剩下要做的就是映射运行状况检查端点,就像通常使用 ASP.NET Core 一样。
避免 HTTP 方法;只需
touch
容器内的一个文件,然后运行基于文件的运行状况检查 - 这与普通 docker 或协调器配合得很好。
创建托管服务以定期触摸文件
/tmp/myapp-healthcheck
:
public class FileBasedHealthCheckGenerator : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Touch("/tmp/myapp-healthcheck");
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
}
}
private void Touch(string path)
{
using var fileStream = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
File.SetLastWriteTimeUtc(path, DateTime.UtcNow);
}
}
在
Program.cs
中注册托管服务:
services.AddHostedService<FileBasedHealthCheckGenerator>();
更新
Dockerfile
:
HEALTHCHECK --start-period=10s --interval=30s --timeout=10s --retries=3 \
CMD [ $(find '/tmp/myapp-healthcheck' -mmin 0.5 | wc -l) -eq 1 ] || exit 1
或更新
docker-compose.yml
:
healthcheck:
start_period: 10s
interval: 30s
timeout: 10s
retries: 3
test: '[ $(find '/tmp/myapp-healthcheck' -mmin 0.5 | wc -l) -eq 1 ] || exit 1'
或者更新 Orchestrator 配置。
每 30 秒就会触摸
/tmp/myapp-healthcheck
(TimeSpan.FromSeconds(30)
)。当 docker healthcheck 运行时,它会尝试查找过去 30 秒内创建/更新的匹配文件 (-mmin 0.5
)。
这是最低限度的实施。可以添加错误处理,并将路径和周期提取到配置设置中,使其更加优雅。
我在我的工作进程中运行以下代码。在我看来,与此处描述的其他一些解决方案相比,它具有以下优点:
Microsoft.Extensions.Diagnostics.HealthChecks
基础设施,不使用任何自定义托管服务。Dockerfile
中设置健康检查的说明(学分@lonix)。首先,创建一个
FileHealthCheckPublisher
类。
请注意,我正在使用 NodaTime 的 IClock 接口来使其可测试。您可以轻松删除 IClock 依赖项并改为编写
。另请参阅 @lonix 的 答案。File.SetLastWriteTimeUtc(path, DateTime.UtcNow);
internal sealed class FileHealthCheckPublisher : IHealthCheckPublisher
{
private readonly IClock _clock;
private readonly string _healthCheckFilePath = "/healthz";
public FileHealthCheckPublisher(IClock clock)
{
_clock = clock;
}
public Task PublishAsync(HealthReport report, CancellationToken cancellationToken)
{
if (report.Status == HealthStatus.Healthy)
{
Touch(_healthCheckFilePath);
}
else
{
Delete(_healthCheckFilePath);
}
return Task.CompletedTask;
}
private void Touch(string path)
{
using var fileStream = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
var now = _clock.GetCurrentInstant();
File.SetLastWriteTimeUtc(path, now.ToDateTimeUtc());
}
private void Delete(string path)
{
try
{
File.Delete(path);
}
catch
{
// best effort delete; might not exist in the first place
}
}
}
然后,将此行添加到您的 Dockerfile 中
我过滤在最后 0.6 秒而不是 0.5 秒内触及的文件,就像 @lonix 的答案一样,以避免检查成功但恰好是 30 秒前的可能边缘情况。
HEALTHCHECK --start-period=10s --interval=30s --timeout=10s --retries=3 \
CMD [ $(find '/healthz' -mmin 0.6 | wc -l) -eq 1 ] || exit 1
最后,在您的
Program.cs
/Startup.cs
/DI 配置中注册服务。添加与您的用例相关的检查。我在这里使用 Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore 。社区集合 AspNetCore.Diagnostics.HealthChecks 可能也很有趣。
builder.Services.AddHealthChecks()
.AddDbContextCheck<AppDbContext>();
builder.Services.AddSingleton<IHealthCheckPublisher, FileHealthCheckPublisher>();
另请参阅:有关 ASP.NET Core 中的运行状况检查的 Microsoft 文档
我从@Veikedo's和@lonix's的回答中得到了很多灵感,谢谢!