Время работы управляющего приложения .NET Core, размещенного в докере

Отказ от ответственности - это почти тот же вопрос, что и контейнер докеры немедленно выходит даже с Console.ReadLine() в консольном приложении .net. - но я не думаю, что принятый ответ на этот вопрос удовлетворительный.

Что я пытаюсь достичь
Я создаю консольное приложение (это HTTP-сервис с использованием ServiceStack), который построен на ядре .NET(dnxcore50 - это консольное приложение, а не приложение ASP.NET). Я запускаю это приложение в контейнере докеров на машине Linux. Это я сделал, и HTTP-сервис работает.

Моя проблема
Сказав, что "моя служба работает" - и это так, есть проблема с размещением услуги в контейнере докеров. Я использую Console.ReadLine() после запуска моего HTTP-прослушивателя, но этот код не блокируется в контейнере докера, и контейнер выйдет сразу после запуска. Я могу запустить контейнер докера в "интерактивном" режиме, и служба будет сидеть там, слушая, пока я не убью интерактивный сеанс, а затем контейнер выйдет.

Код для репо
Код ниже - это полный список кодов для создания моего тестового консольного приложения .NET core.

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 для прослушивания сигнала kill следующим образом:

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!)

Решение, описанное в соответствующей статье (вверху этого сообщения), бесполезно для меня - в рабочей среде я не могу ожидать сохранения контейнера докеров, обеспечив наличие интерактивного сеанса, который будет поддерживать Console.ReadLine работает, потому что там есть поток STD-IN...

Есть ли другой способ сохранить мой контейнер докеров (используя параметр -d (отсоединенный) при вызове docker run) при размещении приложения .NET Core?

Рефакторинг кода как часть предложения 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();
            }
        }
    }

Окончательное решение!

Для Posterity - решение, с которым я столкнулся, - это код, который можно найти здесь (спасибо мифам за разъяснение): 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.

Ответ 1

Если вы собираетесь размещать приложения .NET Core в Docker, я бы рекомендовал просто следовать нормальному .NET Core Hosting API, где он вызывает IWebHost.Run(), чтобы заблокировать основной поток и сохранить приложение консоли.

AppHostSelfBase - это всего лишь оболочка вокруг .NET Core хостинг API, но вместо этого вызывает неблокирующий IWebHost.Start(). Чтобы получить поведение IWebHost.Run(), вы должны использовать один и тот же подход ManualResetEventSlim и Console.CancelKeyPress, который использует реализация WebHost.Run(), но лично просто проще использовать .NET Core Hosting API и вызвать Run() и просто зарегистрировать свой ServiceStack AppHost в качестве .NET Core-модуля.