Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 3개 있습니다.)
(시리즈 글이 7개 있습니다.)
개발 환경 구성: 401. .NET Core 콘솔 응용 프로그램을 배포(publish) 시 docker image 자동 생성
; https://www.sysnet.pe.kr/2/0/11708

개발 환경 구성: 402. .NET Core 콘솔 응용 프로그램을 docker로 실행/디버깅하는 방법
; https://www.sysnet.pe.kr/2/0/11709

VS.NET IDE: 143. Visual Studio - ASP.NET Core Web Application의 "Enable Docker Support" 옵션으로 달라지는 점
; https://www.sysnet.pe.kr/2/0/12171

VS.NET IDE: 144. .NET Core 콘솔 응용 프로그램을 배포(publish) 시 docker image 자동 생성 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/12197

VS.NET IDE: 185. Visual Studio - 원격 Docker container 내에 실행 중인 응용 프로그램에 대한 디버깅
; https://www.sysnet.pe.kr/2/0/13351

닷넷: 2156. .NET 7 이상의 콘솔 프로그램을 (dockerfile 없이) 로컬 docker에 배포하는 방법
; https://www.sysnet.pe.kr/2/0/13437

개발 환경 구성: 704. Visual Studio - .NET 8 프로젝트부터 dockerfile에 추가된 "USER app" 설정
; https://www.sysnet.pe.kr/2/0/13547




Visual Studio - ASP.NET Core Web Application의 "Enable Docker Support" 옵션으로 달라지는 점

비주얼 스튜디오에서 ASP.NET Core Web Application 프로젝트 생성 시 "(Trying out Container Tools in Visual Studio 2019) Enable Docker Support" 옵션을 (이 글에서는 Linux 모드로) 설정하고 생성하면 어떤 면들이 달라지는 걸까요?

가장 큰 차이점은, 우선 프로젝트에 다음과 같은 내용의 "Dockerfile"이 포함됩니다.

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

# .NET 5: FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80

# .NET 5: FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["WebApplication1/WebApplication1.csproj", "WebApplication1/"]
RUN dotnet restore "WebApplication1/WebApplication1.csproj"
COPY . .
WORKDIR "/src/WebApplication1"
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"]

그리고 "Properties" 폴더에 "launchSettings.json" 파일에는 docker 프로파일이 하나 추가되고,

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:31909",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "WebApplication1": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:5000"
    },
    "Docker": {
      "commandName": "Docker",
      "launchBrowser": true,
      "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
      "publishAllPorts": true
    }
  }
}

비주얼 스튜디오에서 웹 애플리케이션 실행을 위한 환경을 기본적으로 "Docker" profile 모드로 선택하게 됩니다.

docker_vs2019_0.png




이 상태에서 "F5" 키를 눌러 디버깅을 실행하면 비주얼 스튜디오는 Dockerfile을 빌드해 이미지를 만드는 작업을 추가합니다.

docker build -f "D:\temp\WebApplication1\Dockerfile" --force-rm -t webapplication1:dev --target base --label "com.microsoft.created-by=visual-studio" --label "com.microsoft.visual-studio.project-name=WebApplication1" "D:\temp"


여기서 중요한 것은 "--target base"입니다. 이 때문에 Dockerfile의 내용 중에 사실상 "F5"로 만들어지는 docker 이미지는 다음의 명령어로만 이뤄집니다.

# Use multi-stage builds
# https://docs.docker.com/develop/develop-images/multistage-build/

# 비주얼 스튜디오에서 개발 중에 사용될 목적의 이미지 생성
# https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80

# 이하 나머지 명령어는 사용되지 않음
...[생략]...

물론 저 이미지에는 ASP.NET Core 웹 애플리케이션의 빌드 파일들이 없습니다. 그래도 괜찮습니다. 어차피 나중에 "docker run"으로 실행할 때 다음과 같이 "-v" 옵션들을 이용해 프로젝트 폴더를 컨테이너에 공유하기 때문입니다.

docker run -dt -v "%USERPROFILE%\vsdbg\vs2017u5:/remote_debugger:rw" -v "D:\temp\WebApplication1:/app" -v "D:\temp:/src" -v "F:\nuget_root:/root/.nuget/fallbackpackages" -v "c:\program files\dotnet\sdk\NuGetFallbackFolder:/root/.nuget/fallbackpackages2" -e "DOTNET_USE_POLLING_FILE_WATCHER=1" -e "ASPNETCORE_ENVIRONMENT=Development" -e "NUGET_PACKAGES=/root/.nuget/fallbackpackages" -e "NUGET_FALLBACK_PACKAGES=/root/.nuget/fallbackpackages;/root/.nuget/fallbackpackages2" -P --name WebApplication1 --entrypoint tail webapplication1:dev -f /dev/null


-v "%USERPROFILE%\vsdbg\vs2017u5:/remote_debugger:rw"
-v "D:\temp\WebApplication1:/app" 
-v "D:\temp:/src" 
-v "F:\nuget_root:/root/.nuget/fallbackpackages" 
-v "c:\program files\dotnet\sdk\NuGetFallbackFolder:/root/.nuget/fallbackpackages2"

위와 같이 공유되는 관계로, Docker Desktop에서는 해당 드라이브들을 공유시켜둬야 합니다. (제 경우에는 그냥 모두 체크해 두었습니다.)

docker_vs2019_1.png

여기서 끝이 아닙니다. 비주얼 스튜디오는 실행 중인 docker 컨테이너에 다음과 같이 "dotnet" 프로세스를 시작해 웹 애플리케이션을 구동합니다.

docker exec -itd f71a5003400e dotnet /app/bin/Debug/netcoreapp3.1/WebApplication1.dll




F5 디버깅을 끝내고 나면, 비주얼 스튜디오는 해당 컨테이너를 실행 중인 상태로 두고 내부의 "dotnet" 프로세스만 종료시킵니다. 그리고, 다음번 F5 디버깅을 시작하게 되면, 컨테이너에 기존 실행 중인 닷넷 프로세스가 있다면 확실하게 종료시키는 명령어를 수행하고,

// PowerShell에서 수행

docker exec -i f71a5003400e /bin/sh -c "if PID=$(pidof dotnet); then kill $PID; fi"

다시 dotnet 프로세스를 띄우는 작업만 반복합니다.

docker exec -itd f71a5003400e dotnet /app/bin/Debug/netcoreapp3.1/WebApplication1.dll

정리해 보면, 비주얼 스튜디오는 docker 지원 프로젝트에 대해 (최초 한 번 이미지를 빌드하는 시간과 컨테이너 실행 시간을 제외하고) 아무런 지연 현상 없이, 따라서 기존과 동일한 성능의 개발 환경을 제공하는 것입니다.




이렇게 자동으로 생성된 이미지를,

C:\temp> docker images
REPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE
webapplication1                         dev                 54e29ea338cc        3 hours ago         207MB

직접 실행하고 싶다면 비주얼 스튜디오가 해준 동작을 그대로 재연하기만 하면 됩니다.

docker run -dt -v "%USERPROFILE%\vsdbg\vs2017u5:/remote_debugger:rw" -v "D:\temp\WebApplication1:/app" -v "D:\temp:/src" -v "F:\nuget_root:/root/.nuget/fallbackpackages" -v "c:\program files\dotnet\sdk\NuGetFallbackFolder:/root/.nuget/fallbackpackages2" -e "DOTNET_USE_POLLING_FILE_WATCHER=1" -e "ASPNETCORE_ENVIRONMENT=Development" -e "NUGET_PACKAGES=/root/.nuget/fallbackpackages" -e "NUGET_FALLBACK_PACKAGES=/root/.nuget/fallbackpackages;/root/.nuget/fallbackpackages2" -P --name WebApplication1 --entrypoint tail webapplication1:dev -f /dev/null

docker exec -itd f71a5003400e dotnet /app/bin/Debug/netcoreapp3.1/WebApplication1.dll

하지만, Kitematic 도구에서는 단순히 이미지를 "CREATE"로 하게 되면 VOLUME 매핑이 없으므로 그냥 빈 깡통에 불과한 "aspnet:3.1-buster-slim" 이미지의 컨테이너만 실행됩니다. Kitematic에서도 "CREATE"로 실행 가능한 유형의 이미지를 만들려면 Dockerfile의 "base"가 아닌 "final"을 target으로 지정하면 됩니다. 그리고 이 작업을 간단하게 할 수 있는 것이, 비주얼 스튜디오에서 Dockerfile을 마우스 우 클릭해 "Build Docker Image" 메뉴를 선택하는 것입니다.

docker_vs2019_2.png

Dockerfile의 final 타깃을 선택하면 우선, 다음의 작업이 선택됩니다.

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

하지만 final의 선행 작업으로 base 타깃이 지정되었기에 그에 앞서 base 먼저 구성됩니다.

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80

이후, final 타깃의 "COPY --from=publish /app/publish ."로 인해 "publish" 타깃이 다시 구성되고,

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

이보다 앞서 "FROM build ..."로 인해 build 타깃까지 올라가게 됩니다.

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

결국, Dockerfile의 final 타깃은 다음과 같은 작업을 순차적으로 진행하는 것입니다.

[build 타깃]
.NET Core SDK 이미지를 다운로드한 임시 이미지를 만들고,
그 이미지 내부에 csproj 파일을 복사,
csproj 파일의 정보를 기반으로 "dotnet restore",
모든 소스 코드 파일을 복사,
"dotnet build" 수행

[publish 타깃]
"dotnet publish" 수행

[final 타깃]
dotnet/core/aspnet 이미지에 [publish 타깃]에서 생성된 이미지로부터 "dotnet publish"의 결과물을 복사
컨테이너로 인스턴스화 될 때 실행될 ENTRYPOINT를 "dotnet WebApplication1.dll"로 등록.

물론 Visual Studio의 "Build Docker Image" 메뉴가 아닌, 직접 명령행을 통해 "docker build"로 다음과 같이 실행해 final 이미지를 만들어도 됩니다.

docker build -f "D:\temp\WebApplication1\Dockerfile" --force-rm -t webapplication1:dev --target final --label "com.microsoft.created-by=visual-studio" --label "com.microsoft.visual-studio.project-name=WebApplication1" "D:\temp"


이렇게 생성한 final 이미지를 Kitematic에서 선택해 "CREATE" 버튼을 누르면 컨테이너가 인스턴스화되고 "dotnet WebApplication1.dll" ENTRYPOINT로 인해 ASP.NET 웹 응용 프로그램이 내부에서 실행까지 됩니다.




Visual Studio는 기본적으로 빌드 시에 Dockerfile의 "base" 타깃을 사용한다고 했는데, 이것을 바꾸는 것도 가능합니다.

Container Tools build properties
; https://learn.microsoft.com/en-us/visualstudio/containers/container-msbuild-properties?view=vs-2019

이를 위해 csproj에 DockerfileFastModeStage 속성을 추가해 Dockerfile에 있는 target을 정해줄 수 있습니다. 가령, 아래와 같이 "final"로 지정하면,

<DockerfileFastModeStage>final</DockerfileFastModeStage>

비주얼 스튜디오는 빌드 시마다 위에서 설명한 "final" 이미지를 구축하게 됩니다. (물론 이렇게 하면 개발 시 꽤나 불편한 사용자 경험을 하게 되므로, 대개의 경우 약간의 사용자 정의 작업을 추가한 타깃을 지정할 때 쓰게 될 것입니다.)




자, 그럼 빌드 오류를 하나씩 살펴볼까요? ^^

우선, 다음과 같은 식으로 오류가 발생한다면?

...[생략]...
1>SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.
1>CTC1008 : Downloading the debugger vsdbg failed. Please ensure that your computer has an internet connection.
1>F:\nuget_root\microsoft.visualstudio.azure.containers.tools.targets\1.9.10\build\Container.targets(198,5): error CTC1001: Volume sharing is not enabled. On the Settings screen in Docker Desktop, click Shared Drives, and select the drive(s) containing your project files. 1>Done building project "docker2webapp.csproj" -- FAILED.


"%USERPROFILE%\vsdbg\vs2017u5:/remote_debugger:rw"에 해당하는 드라이브가 Docker Desktop에 공유가 안 되었기 때문입니다. 참고로, 비주얼 스튜디오는 다음과 같은 경로를 컨테이너의 볼륨으로 공유합니다.

  • (%userprofile% 폴더가 있는) C 드라이브
  • 프로젝트가 있는 드라이브
  • nuget 보관 폴더 (기본값인 경우 "%USERPROFILE%\.nuget": 참고 https://www.sysnet.pe.kr/2/0/12135)

마찬가지로 위의 목록들에 대한 드라이브 공유가 안 되어 있으면 이런 식의 오류도 발생합니다.

1>------ Build started: Project: docker2webapp, Configuration: Debug Any CPU ------
...[생략]...
1>docker run -dt -v "%USERPROFILE%\vsdbg\vs2017u5:/remote_debugger:rw" -v "D:\workshop2\JenniferV\gitlab\UnitTest\AzureWebApps\AzureWebTest\docker2webapp:/app" -v "D:\workshop2\JenniferV\gitlab\UnitTest\AzureWebApps\AzureWebTest:/src" -v "F:\nuget_root:/root/.nuget/fallbackpackages" -v "c:\program files\dotnet\sdk\NuGetFallbackFolder:/root/.nuget/fallbackpackages2" -e "DOTNET_USE_POLLING_FILE_WATCHER=1" -e "ASPNETCORE_ENVIRONMENT=Development" -e "NUGET_PACKAGES=/root/.nuget/fallbackpackages" -e "NUGET_FALLBACK_PACKAGES=/root/.nuget/fallbackpackages;/root/.nuget/fallbackpackages2" -P --name docker2webapp --entrypoint tail docker2webapp:dev -f /dev/null
1>docker: Error response from daemon: status code not OK but 500: {"Message":"Unhandled exception: Drive has not been shared"}. 1>See 'docker run --help'.
1>F:\nuget_root\microsoft.visualstudio.azure.containers.tools.targets\1.9.10\build\Container.targets(198,5): error CTC1015: Docker command failed with exit code 125.
1>F:\nuget_root\microsoft.visualstudio.azure.containers.tools.targets\1.9.10\build\Container.targets(198,5): error CTC1015: docker: Error response from daemon: status code not OK but 500: {"Message":"Unhandled exception: Drive has not been shared"}.
1>F:\nuget_root\microsoft.visualstudio.azure.containers.tools.targets\1.9.10\build\Container.targets(198,5): error CTC1015: See 'docker run --help'.
1>F:\nuget_root\microsoft.visualstudio.azure.containers.tools.targets\1.9.10\build\Container.targets(198,5): error CTC1015: If the error persists, try restarting Docker Desktop.
1>Done building project "docker2webapp.csproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/10/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2022-08-26 01시52분
정성태

1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13524정성태1/12/20242063오류 유형: 890. 한국투자증권 KIS Developers OpenAPI - GW라우팅 중 오류가 발생했습니다.
13523정성태1/12/20241887오류 유형: 889. Visual Studio - error : A project with that name is already opened in the solution.
13522정성태1/11/20242019닷넷: 2200. C# - HttpClient.PostAsJsonAsync 호출 시 "Transfer-Encoding: chunked" 대신 "Content-Length" 헤더 처리
13521정성태1/11/20242082닷넷: 2199. C# - 한국투자증권 KIS Developers OpenAPI의 WebSocket Ping, Pong 처리
13520정성태1/10/20241855오류 유형: 888. C# - Unable to resolve service for type 'Microsoft.Extensions.ObjectPool.ObjectPool`....'
13519정성태1/10/20241935닷넷: 2198. C# - Reflection을 이용한 ClientWebSocket의 Ping 호출파일 다운로드1
13518정성태1/9/20242162닷넷: 2197. C# - ClientWebSocket의 Ping, Pong 처리
13517정성태1/8/20242014스크립트: 63. Python - 공개 패키지를 이용한 위성 이미지 생성 (pystac_client, odc.stac)
13516정성태1/7/20242101닷넷: 2196. IIS - AppPool의 "Disable Overlapped Recycle" 옵션의 부작용
13515정성태1/6/20242372닷넷: 2195. async 메서드 내에서 C# 7의 discard 구문 활용 사례 [1]
13514정성태1/5/20242064개발 환경 구성: 702. IIS - AppPool의 "Disable Overlapped Recycle" 옵션
13513정성태1/5/20242002닷넷: 2194. C# - WebActivatorEx / System.Web의 PreApplicationStartMethod 특성
13512정성태1/4/20241975개발 환경 구성: 701. IIS - w3wp.exe 프로세스의 ASP.NET 런타임을 항상 Warmup 모드로 유지하는 preload Enabled 설정
13511정성태1/4/20241994닷넷: 2193. C# - ASP.NET Web Application + OpenAPI(Swashbuckle) 스펙 제공
13510정성태1/3/20241933닷넷: 2192. C# - 특정 실행 파일이 있는지 확인하는 방법 (Linux)
13509정성태1/3/20241965오류 유형: 887. .NET Core 2 이하의 프로젝트에서 System.Runtime.CompilerServices.Unsafe doesn't support netcoreapp2.0.
13508정성태1/3/20241998오류 유형: 886. ORA-28000: the account is locked
13507정성태1/2/20242692닷넷: 2191. C# - IPGlobalProperties를 이용해 netstat처럼 사용 중인 Socket 목록 구하는 방법파일 다운로드1
13506정성태12/29/20232186닷넷: 2190. C# - 닷넷 코어/5+에서 달라지는 System.Text.Encoding 지원
13505정성태12/27/20232673닷넷: 2189. C# - WebSocket 클라이언트를 닷넷으로 구현하는 예제 (System.Net.WebSockets)파일 다운로드1
13504정성태12/27/20232313닷넷: 2188. C# - ASP.NET Core SignalR로 구현하는 채팅 서비스 예제파일 다운로드1
13503정성태12/27/20232184Linux: 67. WSL 환경 + mlocate(locate) 도구의 /mnt 디렉터리 검색 문제
13502정성태12/26/20232285닷넷: 2187. C# - 다른 프로세스의 환경변수 읽는 예제파일 다운로드1
13501정성태12/25/20232082개발 환경 구성: 700. WSL + uwsgi - IPv6로 바인딩하는 방법
13500정성태12/24/20232172디버깅 기술: 194. Windbg - x64 가상 주소를 물리 주소로 변환
13498정성태12/23/20232810닷넷: 2186. 한국투자증권 KIS Developers OpenAPI의 C# 래퍼 버전 - eFriendOpenAPI NuGet 패키지
1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...