주의 : 번역기 및 약간의 수정으로 인해 좋은 번역이 되지 않을 수도 있으니 양해 바랍니다. 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 기반 이미지로 전환해야 하는 것은 기본 단계에 불과했습니다.
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 앱을 빌드하고 실행할 수 있습니다.
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
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 이미지를 다시 빌드하고 다음을 사용하여 앱을 다시 실행할 수 있습니다.
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 작업을 했습니다.