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

Я пытаюсь запустить консольное приложение .net core 1.0.0 внутри контейнера докеров.
Когда я запускаю команду dotnet run изнутри папки Demo на моей машине, она работает нормально; Но при запуске с помощью docker run -d --name demo Demo контейнер немедленно выйдет.

Я попробовал docker logs demo проверить журналы, и он просто показывает текст из Console.WriteLine:

Демо-приложение работает...

и ничего больше.

Я загрузил проект в https://github.com/learningdockerandnetcore/Demo

В проекте содержатся Programs.cs, Dockerfile, используемые для создания демонстрационного образа и project.json файла.

Ответ 1

Вы должны либо запустить свой контейнер в интерактивном режиме (с опцией -i). но обратите внимание, что фоновые процессы будут немедленно закрыты при запуске контейнера, поэтому убедитесь, что ваш script запущен на переднем плане или просто не будет работать.

Ответ 2

Если вы переключите свое приложение на целевой .net core 2.0, то вы можете использовать пакет Microsoft.Extensions.Hosting для размещения приложения основной консоли .net с помощью API HostBuilder для запуска/остановки вашего приложения. Его класс ConsoleLifetime будет обрабатывать общий метод запуска/остановки приложения.

Чтобы запустить приложение, вы должны реализовать свой собственный интерфейс IHostedService или наследовать от класса BackgroundService, а затем добавить его в контекст хоста в ConfigureServices.

namespace Microsoft.Extensions.Hosting
{
    //
    // Summary:
    //     Defines methods for objects that are managed by the host.
    public interface IHostedService
    {
        // Summary:
        // Triggered when the application host is ready to start the service.
        Task StartAsync(CancellationToken cancellationToken);

        // Summary:
        // Triggered when the application host is performing a graceful shutdown.
        Task StopAsync(CancellationToken cancellationToken);
    }
}

Вот пример размещенного сервиса:

public class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private Timer _timer;

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

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero, 
            TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        _logger.LogInformation("Timed Background Service is working.");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

Затем создайте HostBuilder и добавьте сервис и другие компоненты (регистрация, настройка).

public class Program
{
    public static async Task Main(string[] args)
    {
        var hostBuilder = new HostBuilder()
             // Add configuration, logging, ...
            .ConfigureServices((hostContext, services) =>
            {
                // Add your services with depedency injection.
            });

        await hostBuilder.RunConsoleAsync();
    }
}

Ответ 3

Единственный способ заставить Docker/Linux сохранить приложение .NET Core - это подделать ASP.NET для размещения его для меня... Это такой уродливый взлом!

Это будет выполняться в Docker с использованием опции docker run -d, поэтому вам не нужно иметь действующее соединение, чтобы поддерживать поток STDIN живым.

Я создал консольное приложение .NET Core (не приложение ASP.NET), и мой класс Program выглядит следующим образом:

public class Program
{
    public static ManualResetEventSlim Done = new ManualResetEventSlim(false);
    public static void Main(string[] args)
    {
        //This is unbelievably complex because .NET Core Console.ReadLine() does not block in a docker container...!
        var host = new WebHostBuilder().UseStartup(typeof(Startup)).Build();

        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;
            };

            host.Run(cts.Token);
            Done.Set();
        }
    }      
}

Класс Startup:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IServer, ConsoleAppRunner>();
    }


    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
    }
}

Класс ConsoleAppRunner

public class ConsoleAppRunner : IServer
{

    /// <summary>A collection of HTTP features of the server.</summary>
    public IFeatureCollection Features { get; }

    public ConsoleAppRunner(ILoggerFactory loggerFactory)
    {
        Features = new FeatureCollection();
    }

    /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
    public void Dispose()
    {

    }

    /// <summary>Start the server with an application.</summary>
    /// <param name="application">An instance of <see cref="T:Microsoft.AspNetCore.Hosting.Server.IHttpApplication'1" />.</param>
    /// <typeparam name="TContext">The context associated with the application.</typeparam>
    public void Start<TContext>(IHttpApplication<TContext> application)
    {
        //Actual program code starts here...
        Console.WriteLine("Demo app running...");

        Program.Done.Wait();        // <-- Keeps the program running - The Done property is a ManualResetEventSlim instance which gets set if someone terminates the program.

    }
}

Единственная приятная вещь в этом - это то, что вы можете использовать DI в вашем приложении (если хотите), поэтому в моем случае я использую ILoggingFactory для обработки моего журналирования.

Редактировать 30 октября 2018 г. Эта публикация все еще кажется популярной - я хотел бы просто указать всем, кто читает мой старый пост, что он сейчас довольно древний. Я основывал его на .NET core 1.1 (который был новым в то время). Вполне вероятно, что если вы используете более новую версию ядра .NET(2.0/2.1 или выше), то, вероятно, существует гораздо лучший способ решения этой проблемы сейчас. Пожалуйста, уделите время просмотру некоторых других постов в этой теме, которые могут быть не столь высоко оценены, как эта, но могут быть более новыми и более свежими -d.

Ответ 4

Я не уверен, почему Console.ReadLine(); не блокирует основной поток при запуске приложения консоли консоли dotnet в отдельном контейнере докеров, но лучшим решением является регистрация ConsoleCancelEventHandler с событием Console.CancelKeyPress.

Затем вместо этого вы можете блокировать основной поток с типом Threading WaitHandle и сигнализировать о выпуске основного потока, когда стрелка Console.CancelKeyPress запущена.

Хороший пример кода можно найти здесь: https://gist.github.com/kuznero/73acdadd8328383ea7d5

Ответ 6

еще один "грязный способ" - запустить вашу программу на экране с помощью

screen -dmS yourprogramm

Ответ 7

Я использую этот подход:

static async Task Main(string[] args)
{
   // run code ..

   await Task.Run(() => Thread.Sleep(Timeout.Infinite));
}

Ответ 8

Для тех, кто хочет запустить ваше консольное приложение .net 4.x в linux docker без необходимости -i и хотеть запускать его в фоновом режиме, лучшим решением является пакет mono.posix, который делает именно то, что мы ищем, слушайте сигналы Linux.

это также относится к WebApi2 с проектами Owin или к любому console app

для большинства из нас работают контейнеры в фоновом режиме, используя console.read или ManualResetEventSlim или AutoResetEvent обыкновения работали из - за отдельным режим по грузчику.

Лучшим решением является установка Install-Package Mono.Posix

вот пример:

using System;
using Microsoft.Owin.Hosting;
using Mono.Unix;
using Mono.Unix.Native;

public class Program
{
    public static void Main(string[] args)
    {
        string baseAddress = "http://localhost:9000/"; 

        // Start OWIN host 
        using (WebApp.Start<Startup>(url: baseAddress)) 
        { 
            Console.ReadLine(); 
        }

        if (IsRunningOnMono())
        {
            var terminationSignals = GetUnixTerminationSignals();
            UnixSignal.WaitAny(terminationSignals);
        }
        else
        {
            Console.ReadLine();
        }

        host.Stop();
    }

    public static bool IsRunningOnMono()
    {
        return Type.GetType("Mono.Runtime") != null;
    }

    public static UnixSignal[] GetUnixTerminationSignals()
    {
        return new[]
        {
            new UnixSignal(Signum.SIGINT),
            new UnixSignal(Signum.SIGTERM),
            new UnixSignal(Signum.SIGQUIT),
            new UnixSignal(Signum.SIGHUP)
        };
    }
}

сообщение в блоге с полным исходным кодом: https://dusted.codes/running-nancyfx-ina-docker-container-a-beginners-guide-to-build-and-run-dotnet-applications-in-docker

Ответ 9

Использование Console.ReadLine вместо этого, похоже, работает.

С#:

do
{
    Console.WriteLine($"Type: quit<Enter> to end {Process.GetCurrentProcess().ProcessName}");
}
while (!Console.ReadLine().Trim().Equals("quit",StringComparison.OrdinalIgnoreCase));

F #:

while not (Console.ReadLine().Trim().Equals("quit",StringComparison.OrdinalIgnoreCase)) do
    printfn "Type: quit<Enter> to end"