재우니의 블로그

 

주의 : 번역기 및 약간의 수정으로 인해 좋은 번역이 되지 않을 수도 있으니 양해 바랍니다.
https://andrewlock.net/why-isnt-my-aspnetcore-app-in-docker-working/

 

 

 

Docker 에서 실행 중인 ASP.NET Core 앱으로 이동하려고 할 때, 응답하지 않는 이유는 기본적으로 ASP.NET Core가 포트에 바인딩하는 방식과 관련이 있습니다.

 

요전 날 CentOS 관련 이슈 리포트에 답변하다가 스스로 문제에 부딪혔습니다.  문제를 진단하기 위해 CentOS 에서 ASP.NET Core 애플리케이션을 실행해야 했습니다. 불행히도 ASP.NET Core는 CentOS 를 지원하지만 사전 설치된 Docker 이미지는 제공하지 않습니다. 현재 아래와 같이 3가지 Linux Docker 이미지를 제공합니다.

 

 

  • Debian
  • Ubuntu
  • Alpine

 

또한 WSL에 CentOS를 설치할  있지만, Microsoft Store 에서 직접 설치할 수 있는 Ubuntu 와 비교해보면 이는 훨씬 번거롭습니다.

 

 

그래서 자체적으로 CentOS Docker 이미지를 빌드하고 "수동으로"  ASP.NET Core 를 설치해 봅니다.

 

 

Dockerfile로 샘플 앱 만들기.

 

Visual Studio를 사용하여 샘플 웹 응용 프로그램을 만드는 것으로 시작했습니다. CLI를 사용하여 앱을 만들 수도 있었지만 Dockerfile 을 자동 생성하는 옵션도 제공한다는 것을 알고 있었기 때문에 Visual Studio 를 사용하기로 결정했습니다.  

 

ASP.NET Core Web API 를 선택하고, minimal APIs 를 사용, https 는 비활성화, Docker 지원(Linux)을 활성화 체크한 다음,  솔루션을 생성했습니다.

 

이렇게 하면 기본적으로 Debian 기반 dockerfile 이 생성되며( mcr.microsoft.com/dotnet/aspnetcore:6.0) 다른 태그를 선택하지 않는 한 이미지는 Debian 기반임) 다음과 같습니다.

 

 

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["WebApplication1.csproj", "."]
RUN dotnet restore "./WebApplication1.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "WebApplication1.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "WebApplication1.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebApplication1.dll"]

 

이 Dockerfile 은 여러 단계 빌드의 모범 사례를 사용하여, 런타임 이미지가 가능한 한 small 한지 확인합니다. 이는 4가지 다른 단계를 보여줍니다.

  • mcr.microsoft.com/dotnet/aspnetcore:6.0 AS base. 이 단계에서는 애플리케이션을 실행 하는 데 사용할 기본 image 를 정의합니다 . 여기에는 애플리케이션을 실행하기 위한 최소한의 종속성이 포함됩니다.
  • mcr.microsoft.com/dotnet/sdk:6.0 AS build. 이 단계에서는 애플리케이션을 빌드 하는 데 사용할 docker image 를 정의합니다 . 여기에는 전체 .NET SDK 와 다양한 기타 종속성 이 포함됩니다. 이 단계에서는 실제로 애플리케이션을 빌드합니다.
  • FROM build AS publish. 이 단계는 애플리케이션을 게시(배포) 하는 데 사용됩니다.
  • base AS final. 마지막 단계는 실제로 Production 에 배포하는 단계입니다. base image 를 기반으로 진행 하지만 publish assets 과 함께 복사된 상태에서 합니다.

 

여러 단계의 빌드는 Docker 에 배포할 때 항상 모범 사례이지만 이것은 일반적으로 필요한 것보다 더 복잡합니다. Visual Studio가 "fast model"를 사용하여 Docker 이미지 내부에서도 더 빠르게 개발할 수 있도록 하는 추가 단계가 있습니다. Docker에서 개발하지 않고 Docker 에만 배포 하는 경우 이 파일을 단순화할 수 있습니다.

 

 

CentOS 기반 ASP.NET Core 이미지 만들기

 

테스트를 위해 CentOS에서 응용 프로그램을 실행하기만 하면 되었고, CentOS 상에서 build 할 필요가 없었습니다. 따라서 Debian 에서 빌드하는 그대로 build stage 를 남겨 두었습니다 . CentOS 기반 이미지로 전환해야 하는 것은 기본 단계에 불과했습니다.

 

CentOS에 ASP.NET Core를 설치하는 방법에 대한 지침을 찾는 것으로 시작했습니다 . 각 Linux 배포판은 패키지 관리자를 사용하는 일부 버전, Snap 패키지를 사용하는 버전 등으로 약간 다릅니다. CentOS 의 경우 yum패키지 관리자 를 사용할 수 있습니다.

 

ASP.NET Core 설치는 고맙게도 매우 간단합니다. Microsoft 패키지 저장소를 추가하고 YUM을 사용하여 설치하기만 하면 됩니다. CentOS 버전 7 Docker 이미지부터 ASP.NET Core Docker 이미지를 빌드할 수 있습니다.

 

 

FROM centos:7 AS base

# Add Microsoft package repository and install ASP.NET Core
RUN rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm \
    && yum install -y aspnetcore-runtime-6.0

WORKDIR /app

# ... remainder of dockerfile as before

 

base image 를 변경함으로 인해, 이제 다음과 같은 명령을 사용하여 CentOS에서 샘플 ASP.NET Core 앱을 빌드하고 실행할 수 있습니다.

 

docker build -t centos-test .
docker run --rm -p 8000:5000 centos-test

 

이를 실행하고 http://localhost:8000/weatherforecast로 이동하면 다음과 같이 표시됩니다.

 

앱이 응답하지 않는 이유를 알기 위해 디버깅해 보기

 

그 결과를 예상하지 못했다.,ASP.NET Core  설치하는 간단한 케이스라고 생각했습니다, 앱이 그냥 작동할 것이라고 생각했다. 버그를 확인하기 위해  콘솔에 표기된 로그를 확인했습니다.

 

 

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000
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/

 

 

또한 앱이 올바른 포트인 포트 5000에서도 수신 대기하고 있음을 확인할 수 있었습니다. Docker 명령에서 Docker가 컨테이너 5000 내부의 포트를 컨테이너 8000 외부의 포트에 매핑하도록 지정하여 이 역시 올바르게 표시되도록 했습니다.

 

저는 이 시점에서 설명서를 다시 한 번 확인했는데, 8000:5000이 올바른 방식인지 확인했고, 형식은 host: container입니다.

 

이 모든 것이 오히려 이상하게 보였습니다. 아마도 응용 프로그램은 요청을 전혀 수신하지 않았지만 확실히 하기 위해 로깅 Debug 수준을 높이고 다시 시도했습니다.

 

docker run --rm -p 8000:5000 `
    -e Logging__Loglevel__Default=Debug `
    -e Logging__Loglevel__Microsoft.AspNetCore=Debug `
    centos-test

 

물론 로그가 더 장황했지만 요청이 완료되었다는 표시는 없었습니다.

dbug: Microsoft.Extensions.Hosting.Internal.Host[1]
      Hosting starting
info: Microsoft.AspNetCore.Server.Kestrel[0]
      Unable to bind to http://localhost:5000 on the IPv6 loopback interface: 'Cannot assign requested address'.
dbug: Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer[1]
      Unable to locate an appropriate development https certificate.
dbug: Microsoft.AspNetCore.Server.Kestrel[0]
      No listening endpoints were configured. Binding to http://localhost:5000 by default.
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000
dbug: Microsoft.AspNetCore.Hosting.Diagnostics[13]
      Loaded hosting startup assembly WebApplication1
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/
dbug: Microsoft.Extensions.Hosting.Internal.Host[2]
      Hosting started

 

그래서 이 시점에서 두 가지 가능한 시나리오가 있었습니다.

 

 

  • 앱이 전혀 작동하지 않는다.
  • 앱이 컨테이너 외부에 올바르게 노출(expose )되지 않는다.

 

 

첫 번째 사례를 테스트하기 위해 컨테이너 가 실행되는 동안 exec 컨테이너와 endpoint  에 연결하기로 결정했습니다.

curl 이것은 내가 예상한 포트에서 컨테이너 내부에서 앱이 올바르게 실행되고 있는지 여부를 알려줍니다. 

 docker exec 를 활용하여 cli 사용해서 이 작업을 수행할 수도 있었지만, 간단하게 하기 위해 Docker Desktop을 사용하여 컨테이너 내부  curl  endpoint 에 대한 명령 프롬프트(command prompt) 를 열었습니다.

 

 

 

물론 curl 컨테이너 내부의 endpoint 을 -ing( 컨테이너 포트 사용 5000)하면 내가 예상한 데이터가 반환되었습니다. 따라서 앱이 작동되고 올바른 port 에서 응답했습니다. 

이 시점에서 나는 옵션이 부족했습니다. 운 좋게도 응용 프로그램 로그의 한 단어가 갑자기 내 눈을 사로 잡고 올바른 방향을 가리켰습니다. loopback

 

 

 

ASP.NET Core URL: loopback vs IP 주소

 

내 블로그에서 가장 인기 있는 게시물 중 하나(작성한 지 2년 후)는 "ASP.NET Core 앱의 URL을 설정하는 5가지 방법" 입니다. 해당 게시물에서 ASP.NET Core 가 시작할 때 바인딩할 URL 을 제어할 수 있는 몇 가지 방법을 설명하지만 현재 관련 섹션의 제목은 "어떤 URL을 사용할 수 있습니까?" 입니다. . 이 섹션에서는 바인딩할 수 있는 기본적으로 3가지 유형의 URL이 있음을 언급합니다.

 

 

  • IPv4 및 IPv6에 대한 "loopback" 호스트 이름(예: http://localhost:5000), format :{scheme}://{loopbackAddress}:{port}
  • 컴퓨터에서 사용할 수 있는 특정 IP 주소(예: http://192.168.8.31:5005), format : {scheme}://{IPAddress}:{port}
  • http://*:6264 형식의 지정된 포트(예: )에 대한 "모든" IP 주소{scheme}://*:{port}

 

 

"loopback" 주소는 "현재 machine"을 참조하는 네트워크 주소입니다. 따라서 액세스 하면 현재 컴퓨터의 http://localhost:5000 포트에 액세스하려고 합니다 . 5000 이것은 일반적으로 개발할 때 원하는 것이며 ASP.NET Core 앱이 바인딩하는 기본 URL입니다. 따라서 ASP.NET Core 앱을 로컬로 실행하고 http://localhost:5000 브라우저에서 이동하면 모든 것이 동일한 시스템의 동일한 네트워크 인터페이스에서 제공되기 때문에 모든 것이 작동합니다.

 

그러나 Docker 컨테이너 내부에 있을 때 요청은 동일한 네트워크 인터페이스에서 오지 않습니다. 기본적으로 Docker 컨테이너는 별도의 시스템으로 생각할 수 있습니다. Docker 컨테이너 내부에 바인딩하면 localhost 앱이 컨테이너 외부에 노출되지 않아 오히려 쓸모 없게 됩니다.

이 문제를 해결하는 방법은 구문을 사용하여 앱이 모든 IP 주소에 바인딩되도록 하는 것입니다.{scheme}://*:{port}

 

이전 게시물에서 언급했듯이 이 패턴에서 * 를 사용할 필요 가 없습니다. IP 주소가 아닌 모든 것을 사용할 localhost 일 수 있으므로 http://*:5000, http://+:5000  또는  
http://example.com:5000 등을 사용할 수 있습니다. 이 모든 것은 동일하게 작동합니다.

 

ASP.NET Core 애플리케이션을 임의의 IP 주소에 바인딩하면 호스트에서 요청이 "통과" 되므로 앱에서 처리할 수 있습니다. 예를 들어 Docker 이미지를 실행할 때 런타임에 URL을 설정할 수 있습니다.

 

docker run --rm -p 8000:5000 ` -e DOTNET_URLS=http://+:5000 centos-test

 

아래 스크립트는 최종 Dockerfile입니다.

 

FROM centos:7 AS base

# Add Microsoft package repository and install ASP.NET Core
RUN rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm \
    && yum install -y aspnetcore-runtime-6.0

# Ensure we listen on any IP Address 
ENV DOTNET_URLS=http://+:5000

WORKDIR /app

# ... remainder of dockerfile as before
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["WebApplication1.csproj", "."]
RUN dotnet restore "./WebApplication1.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "WebApplication1.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "WebApplication1.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebApplication1.dll"]

 

 

이 변경으로 Docker 이미지를 다시 빌드하고 다음을 사용하여 앱을 다시 실행할 수 있습니다.

docker build -t centos-test .
docker run --rm -p 8000:5000 centos-test

 

마지막으로 브라우저에서  endpoint 를 호출할 수 있습니다.

 

따라서 여기서 중요한 사항은 다음과 같습니다.

 

ASP.NET Core Docker 이미지를 빌드할 때 localhost 뿐만 아니라 모든 IP 주소에 바인딩하도록 앱을 구성해야 합니다.

 

물론 공식 .NET Docker 이미지는 이미 ASPNETCORE_URLS=http://+:80을 설정하여 포트 80에 바인딩합니다.

 

요약

 

 

이 게시물에서는 ASP.NET Core 를 실행하기 위해 CentOS Docker 이미지를 빌드하려는 상황에 대해 설명했습니다. ASP.NET Core 설치 지침에 따라 이미지를 만든 방법을 설명했지만, 자체 만든 ASP.NET Core 앱이 요청에 응답하지 않았습니다. 디버깅 프로세스를 통해 문제의 근본 원인을 파악하였고, 확인한 결과 loopback 주소에 바인딩하고 있다는 것을 깨달았습니다. 

 

즉, 애플리케이션은 Docker 컨테이너 내부에서 액세스할 수 있지만, 외부에서는 액세스할 수 없습니다. 이 문제를 해결하기 위해 ASP.NET Core 앱을 .NET 뿐만 아니라 모든 IP 주소에 바인딩 localhost  작업을 했습니다.

 

 

 

 

번역 : https://andrewlock.net/why-isnt-my-aspnetcore-app-in-docker-working/

 

Why isn't my ASP.NET Core app in Docker working?

In this post I describe a problem where my ASP.NET Core app in Docker wasn't responding to requests. This post debugs and diagnoses the problem

andrewlock.net