Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 5개 있습니다.)
(시리즈 글이 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 ==========




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/15/2024]

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

비밀번호

댓글 작성자
 



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

... 76  77  [78]  79  80  81  82  83  84  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
11860정성태4/5/201918666오류 유형: 527. Visual C++ 컴파일 오류 - error C2220: warning treated as error - no 'object' file generated
11859정성태4/4/201915957디버깅 기술: 125. WinDbg로 EXE의 EntryPoint에서 BP 거는 방법
11858정성태3/27/201916721VC++: 129. EXE를 LoadLibrary로 로딩해 PE 헤더에 있는 EntryPoint를 직접 호출하는 방법파일 다운로드1
11857정성태3/26/201915295VC++: 128. strncpy 사용 시 주의 사항(Linux / Windows)
11856정성태3/25/201915186VS.NET IDE: 134. 마이크로소프트의 CoreCLR 프로파일러 리눅스 예제를 Visual Studio F5 원격 디버깅하는 방법 [1]파일 다운로드1
11855정성태3/25/201917435개발 환경 구성: 436. 페이스북 HTTPS 인증을 localhost에서 테스트하는 방법
11854정성태3/25/201913044VS.NET IDE: 133. IIS Express로 호스팅하는 사이트를 https로 접근하는 방법
11853정성태3/24/201915538개발 환경 구성: 435. 존재하지 않는 IP 주소에 대한 Dns.GetHostByAddress/gethostbyaddr/GetNameInfoW 실행이 느리다면? - 두 번째 이야기 [1]
11852정성태3/20/201915707개발 환경 구성: 434. 존재하지 않는 IP 주소에 대한 Dns.GetHostByAddress/gethostbyaddr/GetNameInfoW 실행이 느리다면?파일 다운로드1
11851정성태3/19/201919149Linux: 8. C# - 리눅스 환경에서 DllImport 대신 라이브러리 동적 로드 처리 [2]
11850정성태3/18/201917327.NET Framework: 813. C# async 메서드에서 out/ref/in 유형의 인자를 사용하지 못하는 이유
11849정성태3/18/201917136.NET Framework: 812. pscp.exe 기능을 C#으로 제어하는 방법파일 다운로드1
11848정성태3/17/201913997스크립트: 14. 윈도우 CMD - 파일이 변경된 경우 파일명을 변경해 복사하고 싶다면?
11847정성태3/17/201918356Linux: 7. 리눅스 C/C++ - 공유 라이브러리 동적 로딩 후 export 함수 사용 방법파일 다운로드1
11846정성태3/15/201916807Linux: 6. getenv, setenv가 언어/운영체제마다 호환이 안 되는 문제
11845정성태3/15/201917460Linux: 5. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) [3]
11844정성태3/14/201918508개발 환경 구성: 434. Visual Studio 2019 - 리눅스 프로젝트를 이용한 공유/실행(so/out) 프로그램 개발 환경 설정 [1]파일 다운로드1
11843정성태3/14/201913705기타: 75. MSDN 웹 사이트를 기본으로 영문 페이지로 열고 싶다면?
11842정성태3/13/201913204개발 환경 구성: 433. 마이크로소프트의 CoreCLR 프로파일러 예제를 Visual Studio CMake로 빌드하는 방법 [1]파일 다운로드1
11841정성태3/13/201913311VS.NET IDE: 132. Visual Studio 2019 - CMake의 컴파일러를 기본 g++에서 clang++로 변경
11840정성태3/13/201914141오류 유형: 526. 윈도우 10 Ubuntu App 환경에서는 USB 외장 하드 접근 불가
11839정성태3/12/201917112디버깅 기술: 124. .NET Core 웹 앱을 호스팅하는 Azure App Services의 프로세스 메모리 덤프 및 windbg 분석 개요 [3]
11838정성태3/7/201920612.NET Framework: 811. (번역글) .NET Internals Cookbook Part 1 - Exceptions, filters and corrupted processes [1]파일 다운로드1
11837정성태3/6/201933871기타: 74. 도서: 시작하세요! C# 7.3 프로그래밍 [10]
11836정성태3/5/201918079오류 유형: 525. Visual Studio 2019 Preview 4/RC - C# 8.0 Missing compiler required member 'System.Range..ctor' [1]
11835정성태3/5/201917529.NET Framework: 810. C# 8.0의 Index/Range 연산자를 .NET Framework에서 사용하는 방법 및 비동기 스트림의 컴파일 방법 [3]파일 다운로드1
... 76  77  [78]  79  80  81  82  83  84  85  86  87  88  89  90  ...