.NET 일반 호스트
이 문서에서는 Microsoft.Extensions.Hosting NuGet 패키지에서 사용할 수 있는 .NET 제네릭 호스트를 구성하고 빌드하기 위한 다양한 패턴에 대해 알아봅니다. .NET 제네릭 호스트는 앱 시작 및 수명 관리를 담당합니다. 작업자 서비스 템플릿은 .NET 제네릭 호스트 HostApplicationBuilder를 만듭니다. 제네릭 호스트는 콘솔 앱과 같은 다른 유형의 .NET 애플리케이션과 함께 사용할 수 있습니다.
호스트는 앱의 리소스 및 수명 기능을 캡슐화하는 객체입니다:
- DI(종속성 주입)
- 로깅
- 구성
- 앱 종료
-
IHostedService
구현
호스트가 시작될 때 서비스 컨테이너의 호스티드 서비스 컬렉션에 등록된 IHostedService.StartAsync의 각 구현에서 IHostedService을 호출합니다. 워커 서비스 앱에서 IHostedService
인스턴스를 포함하는 모든 BackgroundService 구현에는 BackgroundService.ExecuteAsync 메서드가 호출됩니다.
하나의 개체에 앱의 모든 상호 종속적 리소스를 포함하는 주요 원인은 수명 관리 즉, 앱 시작 및 종료에 대한 제어 때문입니다.
호스트 설정
호스트는 일반적으로 Program
클래스의 코드로 구성, 빌드 및 실행됩니다.
Main
메서드는 다음 작업을 수행합니다.
- CreateApplicationBuilder 메서드를 호출하여 작성기 개체를 만들고 구성합니다.
- Build()를 호출하여 IHost 인스턴스를 만듭니다.
- 호스트 개체에 대해 Run 또는 RunAsync 메서드를 호출합니다.
.NET 작업자 서비스 템플릿은 다음과 같은 코드를 생성하여 제네릭 호스트를 만듭니다.
using Example.WorkerService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
IHost host = builder.Build();
host.Run();
작업자 서비스에 대한 자세한 내용은 .NET의 작업자 서비스를 참조하세요.
호스트 작성기 설정
CreateApplicationBuilder 메서드는 다음 작업을 수행합니다.
- 콘텐츠 루트를 GetCurrentDirectory()에서 반환된 경로로 설정합니다.
- 다음에서 호스트 구성을 로드합니다.
- 접두사가
DOTNET_
인 환경 변수. - 명령줄 인수.
- 접두사가
- 다음에서 앱 구성을 로드합니다.
- appsettings.json.
- appsettings.{Environment}.json.
- 비밀 관리자: 앱이
Development
환경에서 실행되는 경우 - 환경 변수입니다.
- 명령줄 인수.
- 다음 로깅 공급자를 추가합니다.
- 콘솔
- 디버그
- EventSource
- EventLog(Windows에서 실행 중인 경우에만)
- 환경이 일 때 범위 유효성 검사 및
Development
를 활성화합니다.
HostApplicationBuilder.Services는 Microsoft.Extensions.DependencyInjection.IServiceCollection 인스턴스입니다. 이러한 서비스는 등록된 서비스를 확인하기 위해 종속성 주입과 함께 사용되는 IServiceProvider를 빌드하는 데 사용됩니다.
프레임워크에서 제공하는 서비스
IHostBuilder.Build() 또는 HostApplicationBuilder.Build()를 호출하면 다음 서비스가 자동으로 등록됩니다.
추가 시나리오 기반 호스트 작성기
웹용으로 빌드하거나 분산 애플리케이션을 빌드하는 경우 다른 호스트 작성기를 사용해야 할 수도 있습니다. 다음 추가 호스트 작성기 목록을 고려합니다.
- DistributedApplicationBuilder: 분산 앱을 만들기 위한 작성기입니다. 자세한 내용은 .NET Aspire를 참조하세요.
- WebApplicationBuilder: 웹 애플리케이션 및 서비스용 작성기입니다. 자세한 내용은 ASP.NET Core 을 참조하세요.
-
WebHostBuilder:
IWebHost
에 대한 작성기입니다. 자세한 내용은 ASP.NET Core 웹 호스트를 참조하세요.
IHostApplicationLifetime
IHostApplicationLifetime 서비스를 모든 클래스에 주입하여 시작 후 및 정상 종료 작업을 처리합니다. 인터페이스의 세 가지 속성은 앱 시작 및 앱 중지 이벤트 처리기 메서드를 등록하는 데 사용되는 취소 토큰입니다. 인터페이스에는 StopApplication() 메서드도 포함됩니다.
다음 예시는 IHostedService 이벤트를 등록하는 IHostedLifecycleService 및 IHostApplicationLifetime
구현입니다:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace AppLifetime.Example;
public sealed class ExampleHostedService : IHostedService, IHostedLifecycleService
{
private readonly ILogger _logger;
public ExampleHostedService(
ILogger<ExampleHostedService> logger,
IHostApplicationLifetime appLifetime)
{
_logger = logger;
appLifetime.ApplicationStarted.Register(OnStarted);
appLifetime.ApplicationStopping.Register(OnStopping);
appLifetime.ApplicationStopped.Register(OnStopped);
}
Task IHostedLifecycleService.StartingAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("1. StartingAsync has been called.");
return Task.CompletedTask;
}
Task IHostedService.StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("2. StartAsync has been called.");
return Task.CompletedTask;
}
Task IHostedLifecycleService.StartedAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("3. StartedAsync has been called.");
return Task.CompletedTask;
}
private void OnStarted()
{
_logger.LogInformation("4. OnStarted has been called.");
}
private void OnStopping()
{
_logger.LogInformation("5. OnStopping has been called.");
}
Task IHostedLifecycleService.StoppingAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("6. StoppingAsync has been called.");
return Task.CompletedTask;
}
Task IHostedService.StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("7. StopAsync has been called.");
return Task.CompletedTask;
}
Task IHostedLifecycleService.StoppedAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("8. StoppedAsync has been called.");
return Task.CompletedTask;
}
private void OnStopped()
{
_logger.LogInformation("9. OnStopped has been called.");
}
}
ExampleHostedService
구현을 추가하도록 작업자 서비스 템플릿을 수정할 수 있습니다.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using AppLifetime.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<ExampleHostedService>();
using IHost host = builder.Build();
await host.RunAsync();
애플리케이션은 다음 샘플 출력을 작성합니다.
// Sample output:
// info: AppLifetime.Example.ExampleHostedService[0]
// 1.StartingAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 2.StartAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 3.StartedAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 4.OnStarted has been called.
// 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: ..\app-lifetime\bin\Debug\net8.0
// info: AppLifetime.Example.ExampleHostedService[0]
// 5.OnStopping has been called.
// info: Microsoft.Hosting.Lifetime[0]
// Application is shutting down...
// info: AppLifetime.Example.ExampleHostedService[0]
// 6.StoppingAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 7.StopAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 8.StoppedAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 9.OnStopped has been called.
출력에는 다양한 수명 주기 이벤트의 순서가 모두 표시됩니다.
IHostedLifecycleService.StartingAsync
IHostedService.StartAsync
IHostedLifecycleService.StartedAsync
IHostApplicationLifetime.ApplicationStarted
예를 들어, Ctrl+C를 눌러 애플리케이션이 중지되면 다음 이벤트가 발생합니다.
IHostApplicationLifetime.ApplicationStopping
IHostedLifecycleService.StoppingAsync
IHostedService.StopAsync
IHostedLifecycleService.StoppedAsync
IHostApplicationLifetime.ApplicationStopped
IHostLifetime
IHostLifetime 구현은 호스트가 시작될 때와 중지될 때를 제어합니다. 등록된 마지막 구현이 사용됩니다.
Microsoft.Extensions.Hosting.Internal.ConsoleLifetime
은 기본 IHostLifetime
구현입니다. 종료의 수명 메커니즘에 대한 자세한 내용은 호스트 종료를 참조하세요.
IHostLifetime
인터페이스는 IHostLifetime.WaitForStartAsync 메서드를 노출하며, IHost.StartAsync
이 시작될 때 호출되어 완료될 때까지 기다렸다가 계속 진행합니다. 이는 외부 이벤트에서 신호를 보낼 때까지 시작을 지연시키는 데 사용할 수 있습니다.
또한 IHostLifetime
인터페이스는 IHostLifetime.StopAsync에서 호출되어 호스트가 중지 중이며 종료할 때가 되었음을 나타내는 IHost.StopAsync
메서드를 노출합니다.
IHostEnvironment
IHostEnvironment 서비스를 클래스에 삽입하여 다음 설정에 대한 정보를 가져옵니다.
- IHostEnvironment.ApplicationName
- IHostEnvironment.ContentRootFileProvider
- IHostEnvironment.ContentRootPath
- IHostEnvironment.EnvironmentName
또한 IHostEnvironment
서비스는 다음 확장 메서드를 사용하여 환경을 평가하는 기능을 제공합니다.
- HostingEnvironmentExtensions.IsDevelopment
- HostingEnvironmentExtensions.IsEnvironment
- HostingEnvironmentExtensions.IsProduction
- HostingEnvironmentExtensions.IsStaging
호스트 구성
호스트 구성은 IHostEnvironment 구현의 속성을 구성하는 데 사용됩니다.
호스트 구성은 HostApplicationBuilderSettings.Configuration 속성에서 사용할 수 있으며 환경 구현은 IHostApplicationBuilder.Environment 속성에서 사용할 수 있습니다. 호스트를 구성하려면 Configuration
속성에 액세스하고 사용 가능한 확장 메서드를 호출합니다.
호스트 구성을 추가하려면 다음 예를 고려합니다.
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
HostApplicationBuilderSettings settings = new()
{
Args = args,
Configuration = new ConfigurationManager(),
ContentRootPath = Directory.GetCurrentDirectory(),
};
settings.Configuration.AddJsonFile("hostsettings.json", optional: true);
settings.Configuration.AddEnvironmentVariables(prefix: "PREFIX_");
settings.Configuration.AddCommandLine(args);
HostApplicationBuilder builder = Host.CreateApplicationBuilder(settings);
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();
앞의 코드가 하는 역할은 다음과 같습니다.
- 콘텐츠 루트를 GetCurrentDirectory()에서 반환된 경로로 설정합니다.
- 다음에서 호스트 구성을 로드합니다.
- hostsettings.json.
- 접두사가
PREFIX_
인 환경 변수. - 명령줄 인수.
앱 구성
앱 구성은 ConfigureAppConfiguration에서 IHostApplicationBuilder을 호출하여 생성됩니다. 공용 IHostApplicationBuilder.Configuration 속성을 사용하면 소비자가 사용 가능한 확장 메서드를 사용하여 기존 구성을 읽거나 변경할 수 있습니다.
자세한 내용은 .NET의 구성을 참조하세요.
호스트 종료
호스트된 프로세스를 중지하는 방법에는 여러 가지가 있습니다. 가장 일반적으로 호스트된 프로세스는 다음과 같은 방법으로 중지할 수 있습니다.
- 누군가 Run 또는 HostingAbstractionsHostExtensions.WaitForShutdown을 호출하지 않을 경우 앱은 보통
Main
을 완료하고 종료됩니다. - 앱이 크래시되는 경우
- 앱이 SIGKILL (또는 Ctrl+Z)를 사용하여 강제로 종료되는 경우.
호스팅 코드는 이러한 시나리오를 처리할 책임이 없습니다. 프로세스 소유자는 다른 앱과 동일하게 이를 처리해야 합니다. 호스티드 서비스 프로세스를 중지할 수 있는 다른 방법은 여러 가지가 있습니다.
-
ConsoleLifetime
을 사용하는 경우(UseConsoleLifetime), 다음 신호를 수신하고 호스트를 정상적으로 중지하려고 시도합니다. - 앱이 Environment.Exit을 호출할 경우
기본 제공된 호스팅 논리는 이러한 시나리오, 특히 ConsoleLifetime
클래스를 처리합니다.
ConsoleLifetime
은 애플리케이션의 정상 종료를 위해 ‘종료’ 신호인 SIGINT, SIGQUIT 및 SIGTERM을 처리하려고 합니다.
.NET 6 이전에는 .NET 코드에서 SIGTERM을 정상적으로 처리할 수 없었습니다. 이 제한을 해결하기 위해 ConsoleLifetime
은 System.AppDomain.ProcessExit을 구독합니다.
ProcessExit
가 발생하면 ConsoleLifetime
은 ProcessExit
스레드를 중지하고 차단하도록 호스트에 신호를 보내 호스트가 중지되기를 기다립니다.
프로세스 종료 핸들러는 애플리케이션의 정리 코드(예: IHost.StopAsync과 HostingAbstractionsHostExtensions.Run 메서드에서 Main
이후의 코드)를 실행할 수 있도록 허용합니다.
그러나 SIGTERM이 ProcessExit
가 발생하는 유일한 방법이 아니었기 때문에 이 방식에는 다른 문제가 있었습니다. SIGTERM은 앱 코드가 Environment.Exit
를 호출할 때도 발생합니다.
Environment.Exit
은 Microsoft.Extensions.Hosting
앱 모델에서 프로세스를 종료하는 정상적인 방법이 아닙니다.
ProcessExit
이벤트를 발생시키고 프로세스를 종료합니다.
Main
메서드의 끝이 실행되지 않습니다. 백그라운드 및 포그라운드 스레드가 종료되고 finally
블록이 실행되지 않습니다.
ConsoleLifetime
이 호스트가 종료되기를 기다리는 동안 ProcessExit
를 차단했기 때문에 이 동작으로 인해 의 Environment.Exit
이 ProcessExit
에 대한 호출 대기도 차단됩니다. 또한, 시그텀 처리가 프로세스를 정상적으로 종료하려고 시도했기 때문에 ConsoleLifetime
은 ExitCode를 0
으로 설정하고, 를 차단했습니다.
.NET 6에서는 POSIX 신호가 지원 및 처리됩니다.
ConsoleLifetime
은 SIGTERM을 적절하게 처리하고 Environment.Exit
가 호출될 때 더 이상 관련되지 않습니다.
팁
.NET 6 이상에서는 ConsoleLifetime
이 더 이상 Environment.Exit
시나리오를 처리하는 논리를 포함하지 않습니다.
Environment.Exit
을 호출하고 정리 논리를 수행해야 하는 앱은 ProcessExit
을 구독할 수 있습니다. 이러한 시나리오에서는 호스팅이 더 이상 호스트를 정상적으로 중지하려고 시도하지 않습니다.
애플리케이션에서 호스팅을 사용하는 경우 호스트를 정상적으로 중지하려면 IHostApplicationLifetime.StopApplication 대신 Environment.Exit
을 호출하면 됩니다.
호스팅 종료 프로세스
다음 시퀀스 다이어그램은 호스팅 코드에서 신호를 내부적으로 처리하는 방법을 보여 줍니다. 대부분의 사용자는 이 프로세스를 이해할 필요가 없습니다. 그러나 깊은 이해가 필요한 개발자의 경우 좋은 시각적 자료가 시작하는 데 도움이 될 수 있습니다.
호스트가 시작된 후 사용자가 Run
또는 WaitForShutdown
을 호출하면 처리기가 IApplicationLifetime.ApplicationStopping에 등록됩니다. 실행이 WaitForShutdown
에서 일시 중지되어 ApplicationStopping
이벤트가 발생할 때까지 기다립니다.
Main
메서드는 즉시 반환되지 않으며 앱은 Run
또는 WaitForShutdown
이 반환될 때까지 계속 실행됩니다.
프로세스에 신호를 보내면 다음 시퀀스가 시작됩니다.
- 제어가
ConsoleLifetime
에서ApplicationLifetime
으로 흘러서ApplicationStopping
이벤트를 발생시킵니다. 이는WaitForShutdownAsync
신호를 보내서Main
실행 코드의 차단을 해제합니다. 그동안 이 POSIX 신호가 처리되었으므로 POSIX 신호 처리기는Cancel = true
를 반환합니다. -
Main
실행 코드가 다시 실행되기 시작하고 호스트에StopAsync()
를 알리면 호스트되는 모든 서비스가 중지되고 다른 중지된 이벤트를 발생시킵니다. - 마지막으로
WaitForShutdown
이 존재하므로 모든 애플리케이션이 코드 정리를 실행하고Main
메서드를 정상적으로 종료합니다.
웹 서버 시나리오의 호스트 종료
HTTP/1.1 및 HTTP/2 프로토콜 모두에 대해 Kestrel에서 정상 종료가 작동하는 다양한 일반적인 시나리오와 트래픽을 원활하게 드레이닝하기 위해 부하 분산 장치를 사용하여 다양한 환경에서 이를 구성하는 방법이 있습니다. 웹 서버 구성은 이 문서의 범위를 벗어나지만 ASP.NET Core Kestrel 웹 서버에 대한 옵션 구성 설명서에서 자세한 내용을 확인할 수 있습니다.
호스트가 종료 신호(예: Ctrl+C 또는 StopAsync
)를 수신하면, ApplicationStopping신호를 통해 애플리케이션에 알립니다. 정상적으로 완료해야 하는 장기 실행 작업이 있는 경우 이 이벤트를 구독해야 합니다.
다음으로 호스트는 구성할 수 있는 종료 시간 제한(기본값 30초)을 사용하여 IServer.StopAsync를 호출합니다. Kestrel(및 Http.Sys)은 포트 바인딩을 닫고 새 연결 수락을 중지합니다. 또한 현재 연결에 새 요청 처리를 중지하라고 지시합니다. HTTP/2 및 HTTP/3의 경우 예비 GOAWAY
메시지가 클라이언트로 전송됩니다. HTTP/1.1의 경우 요청이 순서대로 처리되므로 연결 루프를 중지합니다. IIS는 503 상태 코드가 있는 새 요청을 거부하여 다르게 동작합니다.
활성 요청은 종료 시간 제한까지 완료되어야 합니다. 시간 제한 전에 모두 완료되면 서버는 더 빨리 제어권을 호스트에 반환합니다. 제한 시간이 만료되면 보류 중인 연결과 요청이 강제로 중단되어 로그와 클라이언트에 오류가 발생할 수 있습니다.
부하 분산 장치 고려 사항
부하 분산 장치 작업 시 클라이언트를 새 대상으로 원활하게 전환하려면 다음 단계를 따릅니다.
- 새 인스턴스를 가져와 트래픽 밸런싱을 시작합니다(크기 조정 목적으로 이미 여러 인스턴스가 있을 수 있음).
- 부하 분산 장치 구성에서 이전 인스턴스를 사용하지 않도록 설정하거나 제거하여 새 트래픽 수신을 중지합니다.
- 이전 인스턴스에 종료 신호를 보냅니다.
- 드레이닝되거나 시간이 초과될 때까지 기다립니다.
참고 항목
- .NET에서 종속성 주입
- .NET의 로깅
- .NET의 구성
- .NET의 Worker Services
- ASP.NET Core 웹 호스트
- ASP.NET Core Kestrel 웹 서버 구성
- 제네릭 호스트 버그는 github.com/dotnet/runtime 리포지토리에 만들어져야 합니다.
.NET