我能够在 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 容器运行的事情。
谢谢!
这是如何使用
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
我看到这篇文章,对我来说,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();
}
}