ASP.NET Core의 Kestrel 웹 서버 구현 살펴보기

등록일시: 2017-10-02 08:00,  수정일시: 2017-09-15 00:50
조회수: 11,679
이 문서는 ASP.NET Core 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
본문에서는 크로스 플랫폼 비동기 I/O 라이브러리인 libuv 기반의 ASP.NET Core 용 크로스 플랫폼 웹 서버, Kestrel에 관해서 살펴봅니다.

Kestrel은 크로스 플랫폼 비동기 I/O 라이브러리인 libuv 기반의 ASP.NET Core 용 크로스 플랫폼 웹 서버입니다. 또한 Kestrel은 ASP.NET Core 프로젝트 템플릿에 기본으로 포함되는 웹 서버입니다.

Kestrel은 다음과 같은 기능들을 지원합니다:

  • HTTPS
  • WebSockets의 활성화에 사용되는 불투명 업그레이드 (Opaque Upgrade)
  • 내부적으로 Nginx의 고성능을 지원하기 위한 유닉스 소켓

Kestrel은 .NET Core가 지원하는 모든 플랫폼 및 버전에서 지원됩니다.

Kestrel을 역방향 프록시와 함께 사용하는 경우

단독으로 Kestrel만 사용할 수도 있고, IIS, Nginx 또는 Apache 같은 역방향 프록시 서버(Reverse Proxy Server)와 함께 사용할 수도 있습니다. 역방향 프록시 서버는 인터넷에서 HTTP 요청을 수신해서 사전 처리를 수행한 다음 Kestrel에 전달합니다.

Kestrel communicates directly with the Internet without a reverse proxy server

Kestrel communicates indirectly with the Internet through a reverse proxy server, such as IIS, Nginx, or Apache

내부 네트워크에서만 Kestrel이 노출될 경우, 역방향 프록시 서버를 사용하는 구성과 사용하지 않는 구성을 모두 적용할 수 있습니다.

역방향 프록시가 필요한 시나리오로의 한 가지 예로, 단일 서버에서 실행되는 동일한 IP 및 포트를 공유하는 다수의 응용 프로그램이 존재하는 경우를 들 수 있습니다. Kestrel은 복수의 프로세스가 동일한 IP 및 포트를 공유하는 기능을 지원하지 않기 때문에, Kestrel 자체만으로는 이런 시나리오를 구성할 수 없습니다. 만약 특정 포트를 수신하도록 Kestrel을 구성한다면, 호스트 헤더와 상관 없이 해당 포트의 모든 트래픽을 처리하게 됩니다. 따라서 포트를 공유할 수 있는 역방향 프록시가 고유한 IP 및 포트를 사용하는 Kestrel에 트래픽을 전달해줘야만 합니다.

굳이 역방향 프록시 서버가 필요하지 않은 경우에도 다음과 같은 이유로 역방향 프록시를 사용하는 것이 좋습니다:

  • 외부로 노출되는 영역을 제한할 수 있습니다.
  • 구성 및 방어를 위한 선택적인 추가 계층을 제공합니다.
  • 기존 인프라와 보다 잘 통합시킬 수 있습니다.
  • 로드 밸런싱 및 SSL 설정을 단순화시킵니다. 역방향 프록시 서버에만 SSL 인증서가 필요하며, 역방향 프록시 서버 자체는 일반적인 HTTP를 사용해서 내부 네트워크의 응용 프로그램 서버와 통신할 수 있습니다.

ASP.NET Core 응용 프로그램에서 Kestrel을 사용하는 방법

Microsoft.AspNetCore.Server.Kestrel 패키지는 Microsoft.AspNetCore.All 메타패키지에 포함되어 있습니다.

기본적으로 ASP.NET Core 프로젝트 템플릿은 Kestrel을 사용합니다. Program.cs 파일에 작성된 템플릿 코드는 CreateDefaultBuilder 메서드를 호출하는데, 이때 내부적으로 UseKestrel 메서드가 호출됩니다.

public static void Main(string[] args)
{
    BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseKestrel(options =>
        {
            options.Listen(IPAddress.Loopback, 5000);
            options.Listen(IPAddress.Loopback, 5001, listenOptions =>
            {
                listenOptions.UseHttps("testCert.pfx", "testPassword");
            });
        })
        .Build();

만약 Kestrel의 옵션을 구성하고 싶다면, 다음 예제와 같이 Program.cs 파일에서 UseKestrel 메서드를 호출하면 됩니다:

public static void Main(string[] args)
{
    BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseKestrel(options =>
        {
            options.Listen(IPAddress.Loopback, 5000);
            options.Listen(IPAddress.Loopback, 5001, listenOptions =>
            {
                listenOptions.UseHttps("testCert.pfx", "testPassword");
            });
        })
        .Build();

Kestrel 옵션

Kestrel 웹 서버는 인터넷 노출 배포 시 특히 유용한 제약 구성 옵션이 갖고 있습니다. 다음은 설정 가능한 몇 가지 제한 사항입니다:

  • 최대 클라이언트 연결 수
  • 최대 요청 본문 크기
  • 최소 요청 본문 데이터 통신 속도

이런 제약 조건과 기타 제약 조건들은 KestrelServerOptions 클래스의 Limits 속성을 이용해서 설정합니다. 이 Limits 속성은 KestrelServerLimits 클래스의 인스턴스를 담고 있습니다.

최대 클라이언트 연결 수

다음 코드를 이용해서 전체 응용 프로그램이 동시에 열 수 있는 TCP 연결의 최대 수를 설정할 수 있습니다:

.UseKestrel(options =>
{
    options.Limits.MaxConcurrentConnections = 100;
    options.Limits.MaxConcurrentUpgradedConnections = 100;
    options.Limits.MaxRequestBodySize = 10 * 1024;
    options.Limits.MinRequestBodyDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Limits.MinResponseDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Listen(IPAddress.Loopback, 5000);
    options.Listen(IPAddress.Loopback, 5001, listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
    });
})

HTTP나 HTTPS에서 다른 프로토콜로 업그레이드 된 연결에는 (예, WebSockets 요청) 별도의 제한이 존재합니다. 연결이 업그레이드 되고 난 이후에는 MaxConcurrentConnections 제한 갯수에 포함되지 않습니다.

최대 연결 수의 기본값은 무제한 (null) 입니다.

최대 요청 본문 크기

기본 최대 요청 본문 크기는 30,000,000 바이트로, 이는 약 28.6MB 입니다.

ASP.NET Core MVC 응용 프로그램에서 이 제한을 재정의 할 때 권장하는 방법은 액션 메서드에 RequestSizeLimit 어트리뷰트를 지정하는 것입니다:

[RequestSizeLimit(100000000)]
public IActionResult MyActionMethod()

반면 다음 예제는 전체 응용 프로그램, 모든 요청에 대한 제약 조건을 구성하는 방법을 보여줍니다:

.UseKestrel(options =>
{
    options.Limits.MaxConcurrentConnections = 100;
    options.Limits.MaxConcurrentUpgradedConnections = 100;
    options.Limits.MaxRequestBodySize = 10 * 1024;
    options.Limits.MinRequestBodyDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Limits.MinResponseDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Listen(IPAddress.Loopback, 5000);
    options.Listen(IPAddress.Loopback, 5001, listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
    });
})

미들웨어에서 특정 요청에 대한 설정을 재정의 할 수도 있습니다:

app.Run(async (context) =>
{
    context.Features.Get<IHttpMaxRequestBodySizeFeature>()
        .MaxRequestBodySize = 10 * 1024;
    context.Features.Get<IHttpMinRequestBodyDataRateFeature>()
        .MinDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    context.Features.Get<IHttpMinResponseDataRateFeature>()
        .MinDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));

응용 프로그램이 이미 요청을 읽기 시작한 다음에 요청의 제한 사항을 구성하려고 시도하면 예외가 발생합니다. 참고로 MaxRequestBodySize 속성이 읽기 전용 상태인지, 즉, 제한을 구성하기에는 너무 늦었는지 여부를 알려주는 IsReadOnly 속성도 지원됩니다.

최소 요청 본문 데이터 통신 속도

Kestrel은 데이터가 지정된 속도(바이트/초)로 들어오는지 매 초마다 확인합니다. 그리고 속도가 최소값 이하로 떨어지면 연결이 시간 만료됩니다. 유예 기간(Grace Period)은 Kestrel이 클라이언트가 전송 속도를 최소 속도까지 올릴 수 있도록 허용하는 시간을 말하며, 그 동안에는 속도가 확인되지 않습니다. 유예 기간은 TCP의 느린 시작(TCP Slow-Start)으로 인해서 초기에 느린 속도로 데이터를 전송하는 연결이 끊어지지 않도록 도와줍니다.

기본 최소 속도는 초당 240 바이트이고 5 초의 유예 기간이 주어집니다.

최소 속도는 응답에도 적용됩니다. 요청 제한이나 응답 제한을 설정하는 코드는 속성과 인터페이스 이름에 RequestBodyResponse가 포함되어 있다는 점만 제외하면 동일합니다.

다음 예제는 Program.cs 파일에서 최소 데이터 전송 속도를 구성하는 방법을 보여줍니다:

.UseKestrel(options =>
{
    options.Limits.MaxConcurrentConnections = 100;
    options.Limits.MaxConcurrentUpgradedConnections = 100;
    options.Limits.MaxRequestBodySize = 10 * 1024;
    options.Limits.MinRequestBodyDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Limits.MinResponseDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Listen(IPAddress.Loopback, 5000);
    options.Listen(IPAddress.Loopback, 5001, listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
    });
})

미들웨어에서 요청 별 속도를 구성할 수도 있습니다:

app.Run(async (context) =>
{
    context.Features.Get<IHttpMaxRequestBodySizeFeature>()
        .MaxRequestBodySize = 10 * 1024;
    context.Features.Get<IHttpMinRequestBodyDataRateFeature>()
        .MinDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    context.Features.Get<IHttpMinResponseDataRateFeature>()
        .MinDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));

다른 Kestrel 옵션들에 대한 정보는 다음 클래스들을 참고하시기 바랍니다:

끝점 구성

기본적으로 ASP.NET Core는 http://localhost:5000에 바인딩됩니다. KestrelServerOptionsListen 메서드나 ListenUnixSocket 메서드를 호출해서 Kestrel이 수신 대기할 URL 접두사와 포트를 구성할 수 있습니다. (UseUrls 메서드, urls 명령줄 인수 또는 ASPNETCORE_URLS 환경 변수를 사용할 수도 있지만, 잠시 후에 설명할 제약 사항이 존재합니다.)

TCP 소켓 바인딩

Listen 메서드는 TCP 소켓에 바인딩하며, 옵션 람다를 전달해서 SSL 인증서를 구성할 수 있습니다:

public static void Main(string[] args)
{
    BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseKestrel(options =>
        {
            options.Listen(IPAddress.Loopback, 5000);
            options.Listen(IPAddress.Loopback, 5001, listenOptions =>
            {
                listenOptions.UseHttps("testCert.pfx", "testPassword");
            });
        })
        .Build();

이 예제에서 ListenOptions를 이용해서 특정 끝점에 SSL을 구성하고 있는 방식에 유의하시기 바랍니다. 동일한 API를 사용해서 특정 끝점에 대한 Kestrel의 다른 설정들을 구성할 수 있습니다.

Windows에서는 자체 서명 SSL 인증서를 생성하기 위해서 PowerShell의 New-SelfSignedCertificate 커맨드릿(cmdlet)을 사용할 수 있습니다. 또는 자체 서명 인증서를 손쉽게 생성할 수 있게 도와주는 서드 파티 도구들도 존재합니다:

macOS 및 Linux에서는 OpenSSL을 사용해서 자체 서명 인증서를 만들 수 있습니다.

보다 자세한 내용은 Setting up HTTPS for development 문서를 참고하시기 바랍니다.

Unix 소켓 바인딩

다음 예제에서 볼 수 있는 것처럼, Unix 소켓을 사용해서 Nginx의 성능을 개선할 수도 있습니다:

.UseKestrel(options =>
{
    options.ListenUnixSocket("/tmp/kestrel-test.sock");
    options.ListenUnixSocket("/tmp/kestrel-test.sock", listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testpassword");
    });
})

포트 0

포트 번호를 0 으로 지정하면, Kestrel이 사용 가능한 포트에 동적으로 바인딩됩니다. 다음 예제는 런타임에 실제로 Kestrel이 바인딩 한 포트를 확인하는 방법을 보여줍니다:

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
    var serverAddressesFeature = app.ServerFeatures.Get<IServerAddressesFeature>();

    app.UseStaticFiles();

    app.Run(async (context) =>
    {
        context.Response.ContentType = "text/html";
        await context.Response
            .WriteAsync("<p>Hosted by Kestrel</p>");

        if (serverAddressesFeature != null)
        {
            await context.Response
                .WriteAsync("<p>Listening on the following addresses: " +
                    string.Join(", ", serverAddressesFeature.Addresses) +
                    "</p>");
        }

        await context.Response.WriteAsync($"<p>Request URL: {context.Request.GetDisplayUrl()}<p>");
    });
}

UseUrls 제한 사항

UseUrls 메서드를 호출하거나 urls 명령줄 인수 또는 ASPNETCORE_URLS 환경 변수를 이용해서 끝점을 구성할 수도 있습니다. 이 방식은 Kestrel 대신 다른 서버를 사용하고자 하는 경우에 유용합니다. 그러나 다음과 같은 제한 사항들을 감안해야 합니다:

  • 이 방식들로는 SSL을 사용할 수 없습니다.
  • Listen 메서드와 UseUrls 메서드를 둘 다 사용할 경우, Listen 메서드의 끝점이 UseUrls 메서드의 끝점을 재정의 합니다.

IIS의 끝점 구성

IIS를 사용할 경우, Listen 메서드나 UseUrls 메서드 호출로 설정한 바인딩을 IIS의 URL 바인딩이 모두 덮어씁니다. 보다 자세한 정보는 ASP.NET Core 모듈 살펴보기 문서를 참고하시기 바랍니다.

URL 접두사

UseUrls 메서드를 호출하거나 urls 명령줄 인수 또는 ASPNETCORE_URLS 환경 변수를 사용할 경우, 다음 중 한 가지 형식으로 URL 접두사를 지정할 수 있습니다.

오직 HTTP URL 접두사만 유효하며, UseUrls 메서드를 이용해서 URL 바인딩을 구성할 경우 Kestrel은 SSL을 지원하지 않습니다.

  • 포트 번호가 지정된 IPv4 주소

    http://65.55.39.10:80/

    0.0.0.0은 모든 IPv4 주소에 바인딩되는 특수한 주소입니다.

  • 포트 번호가 지정된 IPv6

    http://[0:0:0:0:0:ffff:4137:270a]:80/

    IPv6에서 [::]는 IPv4의 0.0.0.0와 같습니다.

  • 포트 번호가 지정된 호스트 이름

    http://contoso.com:80/
    http://*:80/

    호스트 이름, * 및 + 는 특별하게 취급되지 않습니다. IP 주소로 인식되지 않은 모든 항목과 "localhost"는 모든 IPv4 및 IPv6 IP에 바인딩 됩니다. 동일한 포트에서 서로 다른 ASP.NET Core 응용 프로그램에 서로 다른 호스트 이름을 바인딩해야 한다면, HTTP.sys를 사용하거나 IIS, Nginx 또는 Apache 같은 역방향 프록시 서버를 사용해야 합니다.

  • 포트 번호가 지정된 "localhost" 이름 또는 포트 번호가 지정된 루프백 IP

    http://localhost:5000/
    http://127.0.0.1:5000/
    http://[::1]:5000/

    Kestrel은 localhost가 지정되면 IPv4 및 IPv6 루프백 인터페이스 모두에 바인딩을 시도합니다. 만약 요청된 포트가 두 루프백 인터페이스 모두 다른 서비스에서 사용 중이면 Kestrel의 시작이 실패합니다. 어떤 이유로든 둘 중 한 루프백 인터페이스라도 사용할 수 없는 경우 (가장 일반적으로 IPv6가 지원되지 않는 경우), Kestrel은 경고 로그를 남깁니다.

다음 단계

보다 자세한 내용은 다음 문서를 참고하시기 바랍니다: