控制 docker 中托管的 .NET Core 控制台应用程序的生命周期

Control lifetime of .NET Core console application hosted in docker

免责声明 - 这几乎与 相同的问题 - 但我认为这个问题的公认答案并不令人满意。

我想要达到的目标
我正在构建一个控制台应用程序(它是一个使用 ServiceStack 的 HTTP 服务),它是用 .NET 核心构建的(dnxcore50 - 这是一个控制台应用程序,而不是 ASP.NET 应用程序)。我 运行 这个应用程序在 Linux 机器上的 docker 容器中。我已经完成了,并且 HTTP 服务正常工作。

我的问题
话虽如此 'my service works' - 确实如此,但在 docker 容器中托管服务存在问题。我在启动我的 HTTP 侦听器后使用 Console.ReadLine(),但此代码不会在 docker 容器内阻塞,容器将在启动后立即退出。我可以在 'interactive' 模式下启动 docker 容器,服务将坐在那里监听,直到我终止交互式会话,然后容器将退出。

回购代码
下面的代码是用于创建我的测试 .NET 核心服务堆栈控制台应用程序的完整代码清单。

public class Program
{
    public static void Main(string[] args)
    {
        new AppHost().Init().Start("http://*:8088/");
        Console.WriteLine("listening on port 8088");
        Console.ReadLine();

    }
}

public class AppHost : AppSelfHostBase
{
    // Initializes your AppHost Instance, with the Service Name and assembly containing the Services
    public AppHost() : base("My Test Service", typeof(MyTestService).GetAssembly()) { }

    // Configure your AppHost with the necessary configuration and dependencies your App needs
    public override void Configure(Container container)
    {

    }
}

public class MyTestService: Service
{
    public TestResponse Any(TestRequest request)
    {
        string message = string.Format("Hello {0}", request.Name);
        Console.WriteLine(message);
        return new TestResponse {Message = message};
    }

}

[Api("Test method")]
[Route("/test/{Name}", "GET", Summary = "Get Message", Notes = "Gets a message incorporating the passed in name")]
public class TestRequest : IReturn<TestResponse>
{
    [ApiMember(Name = "Name", Description = "Your Name", ParameterType = "path", DataType = "string")]
    public string Name { get; set; }
}

public class TestResponse 
{
    [ApiMember(Name = "Message", Description = "A Message", ParameterType = "path", DataType = "string")]
    public string Message { get; set; }
}

老办法解决这个问题
因此,之前使用 Mono 托管(Mono 有严重的性能问题 - 因此切换到 .NET 核心) - 解决此行为的方法是使用 Mono.Posix 侦听这样的终止信号:

using Mono.Unix;
using Mono.Unix.Native;

...

static void Main(string[] args)
    {
        //Start your service here...

        // check if we're running on mono
        if (Type.GetType("Mono.Runtime") != null)
        {
            // on mono, processes will usually run as daemons - this allows you to listen
            // for termination signals (ctrl+c, shutdown, etc) and finalize correctly
            UnixSignal.WaitAny(new[] {
                new UnixSignal(Signum.SIGINT),
                new UnixSignal(Signum.SIGTERM),
                new UnixSignal(Signum.SIGQUIT),
                new UnixSignal(Signum.SIGHUP)
            });
        }
        else
        {
            Console.ReadLine();
        }
    }

现在 - 我知道这不适用于 .NET Core(显然是因为 Mono.Posix 适用于 Mono!)

相关文章(post 顶部)中概述的解决方案对我没有用 - 在生产环境中,我不能指望通过确保 docker 容器保持活动状态它有一个可用的交互式会话,它将使 Console.ReadLine 正常工作,因为那里有一个 STD-IN 流...

在托管 .NET Core 应用程序时,还有其他方法可以让我的 docker 容器保持活动状态(在调用 docker run 时使用 -d(分离)选项)吗?

代码重构作为 Mythz 建议的一部分

 public static void Main(string[] args)
    {
        Run(new AppHost().Init(), "http://*:8088/");
    }

    public static void Run(ServiceStackHost host, params string[] uris)
    {
        AppSelfHostBase appSelfHostBase = (AppSelfHostBase)host;

        using (IWebHost webHost = appSelfHostBase.ConfigureHost(new WebHostBuilder(), uris).Build())
        {
            ManualResetEventSlim done = new ManualResetEventSlim(false);
            using (CancellationTokenSource cts = new CancellationTokenSource())
            {
                Action shutdown = () =>
                {
                    if (!cts.IsCancellationRequested)
                    {
                        Console.WriteLine("Application is shutting down...");
                        cts.Cancel();
                    }

                    done.Wait();
                };

                Console.CancelKeyPress += (sender, eventArgs) =>
                {
                    shutdown();
                    // Don't terminate the process immediately, wait for the Main thread to exit gracefully.
                    eventArgs.Cancel = true;
                };

                Console.WriteLine("Application started. Press Ctrl+C to shut down.");
                webHost.Run(cts.Token);
                done.Set();
            }
        }
    }

最终解决方案!

为了后代——我采用的解决方案是可以在此处找到的代码(感谢 Myths 的澄清):https://github.com/NetCoreApps/Hello/blob/master/src/SelfHost/Program.cs

相关代码的回购:

public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseStartup<Startup>()
            .UseUrls("http://*:8088/")
            .Build();

        host.Run();
    }
}

public class Startup
{
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        // app.UseStaticFiles();

        app.UseServiceStack(new AppHost());

        app.Run(context =>
        {
            context.Response.Redirect("/metadata");
            return Task.FromResult(0);
        });
    }

在 NuGet 中,我安装了 Microsoft.NETCore.App、ServiceStack.Core 和 ServiceStack.Kestrel。

如果您要在 Docker 中托管 .NET Core 应用程序,我建议您按照正常的 .NET Core Hosting API 调用 IWebHost.Run() 来阻塞主线程并保持控制台应用程序还活着。

AppHostSelfBase 只是一个 wrapper around .NET Core's hosting API but calls the non-blocking IWebHost.Start() instead. To get the behavior of IWebHost.Run() you should be able to reuse the same approach of ManualResetEventSlim and Console.CancelKeyPress that WebHost.Run()'s implementation uses, but personally it's just easier to use .NET Core's Hosting API and call Run() and just register your ServiceStack AppHost as a .NET Core module.