【问题标题】:.NET Core 3.1 - Worker Service in linux container: Clean Shutdown.NET Core 3.1 - linux 容器中的 Worker 服务:干净关闭
【发布时间】:2021-01-14 16:56:51
【问题描述】:

以下是 Visual Studio 2019 中用于创建工作器服务的基本模板的代码。将此服务部署到 linux 容器时,当容器被请求停止时,ExecuteAsync 方法中的代码 CancellationToken 永远不会被取消。以各种方式停止容器后,我看到另外 10 个“Worker running at”日志条目,然后容器停止。这与我读到的一致:docker 发送一个 SIGTERM 命令,等待 10 秒,然后如果容器没有自行停止,则发送一个 SIGKILL 命令。

我正在寻找的是当容器被请求停止时 CancellationToken 应该如何设置为 Canceled 的答案?我的最终目标是确保在容器突然停止之前完成或回滚任何正在运行的事务以及其他此类清理工作。

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<Worker>();
            });
}


public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1000, stoppingToken);
        }
    }
}

我已尝试订阅 AppDomain.CurrentDomain.ProcessExit 事件,因为这似乎是网络上大多数人所说的(没有任何示例),但是我实现它的方式不起作用或者只是这不是正确的做法。从 VS2019 运行容器,事件处理程序的代码永远不会执行。以下是我尝试实现的方法:

public static void Main(string[] args)
{
    using (CancellationTokenSource cts = new CancellationTokenSource())
    {
        AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) =>
        {
            cts.Cancel();
        };

        var host = CreateHostBuilder(args).Build();
        Task task;

        task = host.RunAsync(cts.Token);

        task.Wait(cts.Token);
    }
}

更新: 我猜这个问题与模板构建 docker 文件和启动容器的方式有关。 SIGTERM 被发送到 PID 1 并查看容器上 ps -ef 的结果,tail 的调用是 PID 1 而不是 .netcore 解决方案中的任何内容。我想下一个问题是如何解决这个问题?

更新(请求的 Docker 文件) docker 文件是 Visual Studio 为此模板创建的默认 docker 文件。

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/runtime:5.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY ["WorkerService2/WorkerService2.csproj", "WorkerService2/"]
RUN dotnet restore "WorkerService2/WorkerService2.csproj"
COPY . .
WORKDIR "/src/WorkerService2"
RUN dotnet build "WorkerService2.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "WorkerService2.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WorkerService2.dll"]

【问题讨论】:

  • 您是否尝试实现IDisposable,如docs 中所述?
  • 似乎 Dispose 方法对于围绕完成或回滚进行中事务的业务逻辑来说是错误的位置,因为您的服务已被要求停止。有一个 CancellationToken 传递给 ExecuteAsync 方法,应该有办法实际使用它。
  • 尝试将ConfigureAwait(false)添加到Task.Delay
  • Task.Delay(1000, stoppingToken).ConfigureAwait(false); 没有改变我能看到的任何东西。当我请求容器停止时,我仍然会再获得 10 秒的“Worker Running ...”日志条目,然后服务停止,而没有来自 StopAsync 的任何日志条目,或者 Worker 中的 Dispose 覆盖。
  • I'm guessing the problem has something to do with the way template is building 你的Dockerfile 是什么?

标签: c# .net-core containers


【解决方案1】:

这实际上是在调试时在 Docker for Windows 中从 Visual Studio 运行容器的问题(我猜 Docker for Mac 会类似)。

通过昨晚生成的dockerfile中的这个链接(https://aka.ms/containerfastmode)阅读后,当从Visual Studio运行图像进行测试时,入口点不会是netcore应用程序,所以PID1不会是.netcore应用程序,它不会收到 SIGTERM 信号。但是,如果不是通过 Visual Studio 启动,.netcore 应用程序应该是入口点。

入口点是tail -f /dev/null,就是无限等待 保持容器运行。当应用程序通过 调试器,它是负责运行应用程序的调试器(即 是,dotnet webapp.dll)。如果在没有调试的情况下启动,工具 运行 docker exec -i {containerId} dotnet webapp.dll 来运行应用程序。

在 Visual Studio 中进行构建后,我直接从 Docker for Windows 为我的应用程序测试了带有 :latest 标记的映像。当我这样做时,使用了 dockerfile 中的 ENTRYPOINT 并且 PID 1 是预期的 .netcore 应用程序。停止容器后,会触发 CancellationToken.Cancel() 并引发 OperationCancelledException。

以下是我将使用的模板的基本代码,以说明 CancellationToken 被取消并在它发生时引发异常。

{
    try
    {
        _logger.LogInformation($"WORKER STARTING at: {DateTimeOffset.Now}");

        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("$Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1000, stoppingToken);
        }

        _logger.LogInformation($"Exited While Loop: {DateTimeOffset.Now} (Will never get hit as is)");
    }
    catch(OperationCanceledException)
    {
        _logger.LogInformation("!!!OperationCancelled!!!  Start clean up from here");
    }
    catch(Exception ex)
    {
        _logger.LogCritical(ex, $"Exception Caught:  {ex.GetType().FullName}");
    }

    _logger.LogInformation($"WORKER STOPPING at: {DateTimeOffset.Now}");
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-02
    相关资源
    最近更新 更多