.NET 5 Kubernetes 中的后台服务不退出

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

我能够在 Ubuntu 18.04 上的 Azure Kubernetes 集群中使用

BackgroundService
成功运行 .NET 5 控制台应用程序。事实上,
BackgroundService
才是真正运行的:只是从队列中抓取消息,执行一些操作,然后在 Kubernetes 告诉它停止时终止,或者偶尔出现异常。

最后一个场景给我带来了问题。当

BackgroundService
遇到不可恢复的异常时,我希望容器停止(完成,或者任何状态将导致 Kubernetes 重新启动或销毁/重新创建容器)。

不幸的是,每当遇到异常时,

BackgroundService
出现都会点击
StopAsync()
函数(从我在日志和控制台输出中看到的),但容器保持运行状态并且永远不会重新启动。我的 Main() 如下所示:

        public static async Task Main(string[] args)
        {
            // Build service host and execute.
            var host = CreateHostBuilder(args)
                .UseConsoleLifetime()
                .Build();

            // Attach application event handlers.
            AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledException);

            try
            {
                Console.WriteLine("Beginning WebSec.Scanner.");
                await host.StartAsync();
                await host.WaitForShutdownAsync();
                Console.WriteLine("WebSec.Scanner has completed.");
            }
            finally
            {
                Console.WriteLine("Cleaning up...");

                // Ensure host is properly disposed.
                if (host is IAsyncDisposable ad)
                {
                    await ad.DisposeAsync();
                }
                else if (host is IDisposable d)
                {
                    d.Dispose();
                }
            }
        }

如果相关,

ProcessExit
UnhandledException
的事件处理程序存在用于刷新 AppInsights 遥测通道(也许这会阻塞它?):

        private static void OnProcessExit(object sender, EventArgs e)
        {
            // Ensure AppInsights logs are submitted upstream.
            Console.WriteLine("Flushing logs to AppInsights");
            TelemetryChannel.Flush();
        }

        private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            var thrownException = (Exception)e.ExceptionObject;
            Console.WriteLine("Unhandled exception thrown: {0}", thrownException.Message);

            // Ensure AppInsights logs are submitted upstream.
            Console.WriteLine("Flushing logs to AppInsights");
            TelemetryChannel.Flush();
        }

我只是在

ExecuteAsync()
中压倒
BackgroundService

        protected async override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            this.logger.LogInformation(
                "Service started.");

            try
            {
                // Loop until the service is terminated.
                while (!stoppingToken.IsCancellationRequested)
                {
                    // Do some work...
                }
            }
            catch (Exception ex)
            {
                this.logger.LogWarning(
                    ex,
                    "Terminating due to exception.");
            }

            this.logger.LogInformation(
                "Service ending.",
        }

我的 Dockerfile 很简单,有这一行来运行服务:

ENTRYPOINT ["dotnet", "MyService.dll"]

我遗漏了一些明显的东西吗?我觉得为了让它正常运行,我忘记了一些关于将其作为 Linux 容器运行的事情。

谢谢!

c# .net docker kubernetes background-service
2个回答
2
投票

这是如何使用

IHostApplicationLifetime.StopApplication()
的完整示例。

void Main()
{
    var host = Host.CreateDefaultBuilder()
        .ConfigureServices((context, services) =>
        {
            services.AddHostedService<MyService>();
        })
        .Build();
    
    Console.WriteLine("Starting service");

    host.Run();
    
    Console.WriteLine("Ended service");
}

// You can define other methods, fields, classes and namespaces here

public class MyService : BackgroundService
{
    private readonly IHostApplicationLifetime _lifetime;
    
    private readonly Random _rnd = new Random();
    
    public MyService(IHostApplicationLifetime lifetime)
    {
        _lifetime = lifetime;
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            while (true)
            {
                stoppingToken.ThrowIfCancellationRequested();
                
                var nextNumber = _rnd.Next(10);
                if (nextNumber < 8)
                {
                    Console.WriteLine($"We have number {nextNumber}");
                }
                else
                {
                    throw new Exception("Number too high");
                }
                
                await Task.Delay(1000);
            }
        }
        // If the application is shutting down, ignore it
        catch (OperationCanceledException e) when (e.CancellationToken == stoppingToken)
        {
            Console.WriteLine("Application is shutting itself down");
        }
        // Otherwise, we have a real exception, so must ask the application
        // to shut itself down.
        catch (Exception e)
        {
            Console.WriteLine("Oh dear. We have an exception. Let's end the process.");
            // Signal to the OS that this was an error condition by 
            // setting the exit code.
            Environment.ExitCode = 1;
            _lifetime.StopApplication();
        }
    }
}

该程序的典型输出如下所示:

Starting service
We have number 0
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Users\rowla\AppData\Local\Temp\LINQPad6\_spgznchd\shadow-1
We have number 2
Oh dear. We have an exception. Let's end the process.
info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
Ended service

0
投票

我看到这篇文章,对我来说,RB 的上述答案并不能解决我的问题,所以只是想扩展他的答案,以防对其他人有所帮助。我在 kubernetes 中使用 .net7 和 .net8,遇到两个问题,1) 我的后台服务不会退出,2) 如果抛出错误,进程将退出,并显示代码 0,表明 kubernetes 成功。

经过大量研究和测试后,您需要注入

IHostApplicationLifetime
并停止您的应用程序,因为后台服务将运行直到被取消。捕获异常并设置
Environment.ExitCode = 1
让 kubernetes 知道发生了故障。

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    try
    {
        // your long-running work
    }
    catch (OperationCanceledException)
    {
        // log or ignore
    }
    catch (Exception exception)
    {
        // BackgroundService always exits with code 0 which does not let kubernetes know
        // if there was an error. Explicitly setting exit code to 1 to relay a failure.
        Environment.ExitCode = 1;
    }
    finally
    {
        _lifetime.StopApplication();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.