在 .NET Core Worker Service 中执行健康检查

问题描述 投票:0回答:7

如何在 .NET Core Worker Service 中实现运行状况检查?

该服务将在 Docker 内部运行,并且需要能够检查服务的运行状况。

c# docker .net-core service
7个回答
14
投票

另一种方法是实施

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;
  }
}

10
投票

我认为将 SDK 更改为 Microsoft.NET.Sdk.Web 不值得。您会仅仅因为一项健康检查而包含额外的中间件吗?不,谢谢...

您可以使用不同的协议,例如 TCP。

总体思路是:

  1. 创建一个单独的后台服务来创建 TCP 服务器(查看TcpListener.cs
  2. 当您收到请求时,您有两个选择:如果应用程序运行状况良好,则接受 TCP 连接,否则拒绝它。
  3. 如果您使用容器,您的编排器应该可以选择通过 TCP 调用它(在 k8s 中有一个属性 tcpSocket

如果您需要更详细的信息,您可以查看:在 Kubernetes 上使用 TCP 探针监控 ASP.NET Core 后台服务的运行状况

干杯!


7
投票

添加 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();
            }
        }
    }

5
投票

我认为您还应该考虑保留 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);
})

2
投票

我为实现此目的所做的是将 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 一样。


1
投票

避免 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
)。

这是最低限度的实施。可以添加错误处理,并将路径和周期提取到配置设置中,使其更加优雅。


0
投票

我在我的工作进程中运行以下代码。在我看来,与此处描述的其他一些解决方案相比,它具有以下优点:

  • 它包括完整的生产就绪代码。
  • 如果工作进程不健康,它会可靠地失败健康检查。
    • @Veikedo 的解决方案 似乎依赖于文件的存在。如果工作进程完全崩溃,该文件可能仍然存在。因此,即使该进程不再存在,健康检查也会通过。
  • 它完全依赖于
    Microsoft.Extensions.Diagnostics.HealthChecks
    基础设施,不使用任何自定义托管服务。
  • 因此,它包含了许多附加健康检查可能提供的健康信息。
  • 它不涉及任何开放的端口/TCP侦听器/HTTP服务器或其他网络交互。
  • 此答案包括如何在您的
    Dockerfile
    中设置健康检查的说明(学分@lonix)。

首先,创建一个

FileHealthCheckPublisher
类。

请注意,我正在使用 NodaTime 的 IClock 接口来使其可测试。您可以轻松删除 IClock 依赖项并改为编写

File.SetLastWriteTimeUtc(path, DateTime.UtcNow);
。另请参阅 @lonix 的 答案。

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的回答中得到了很多灵感,谢谢!

© www.soinside.com 2019 - 2024. All rights reserved.