Microsoft MVP성태의 닷넷 이야기
글쓴 사람
홈페이지
첨부 파일
 

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

비주얼 스튜디오에서 ASP.NET Core Web Application 프로젝트 생성 시 "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.

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

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 이미지는 다음의 명령어로만 이뤄집니다.

# 비주얼 스튜디오에서 개발 중에 사용될 목적의 이미지 생성
# 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://docs.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 ==========




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

[연관 글]





[최초 등록일: ]
[최종 수정일: 3/7/2020 ]

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

비밀번호

댓글 쓴 사람
 




... [16]  17  18  19  20  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
11889정성태5/8/20191251개발 환경 구성: 438. mstest, QTAgent의 로그 파일 설정 방법
11888정성태5/8/20192375.NET Framework: 830. C# - 비동기 호출을 취소하는 CancellationToken의 간단한 예제 코드파일 다운로드1
11887정성태5/8/20191568.NET Framework: 829. C# - yield 문을 사용할 수 있는 메서드의 조건
11886정성태5/8/20191589오류 유형: 534. mstest.exe 실행 시 "Visual Studio Enterprise is required to execute the test." 오류 [2]
11885정성태5/7/20191031오류 유형: 533. mstest.exe 실행 시 "File extension specified '.loadtest' is not a valid test extension." 오류 발생
11884정성태5/5/20191850.NET Framework: 828. C# DLL에서 Win32 C/C++처럼 dllexport 함수를 제공하는 방법 - 두 번째 이야기
11883정성태5/3/20192270.NET Framework: 827. C# - 인터넷 시간 서버로부터 받은 시간을 윈도우에 적용하는 방법파일 다운로드1
11882정성태5/9/20191220.NET Framework: 826. (번역글) .NET Internals Cookbook Part 11 - Various C# riddles파일 다운로드1
11881정성태4/28/20191809오류 유형: 532. .NET Core 프로젝트로 마이그레이션 시 "CS0579 Duplicate 'System.Reflection.AssemblyCompanyAttribute' attribute" 오류 발생
11880정성태4/25/20191108오류 유형: 531. 이벤트 로그 오류 - Task Scheduling Error: m->NextScheduledSPRetry 1547, m->NextScheduledEvent 1547
11879정성태1/27/20201531.NET Framework: 825. (번역글) .NET Internals Cookbook Part 10 - Threads, Tasks, asynchronous code and others파일 다운로드2
11878정성태5/9/20191404.NET Framework: 824. (번역글) .NET Internals Cookbook Part 9 - Finalizers, queues, card tables and other GC stuff파일 다운로드1
11877정성태5/9/20191471.NET Framework: 823. (번역글) .NET Internals Cookbook Part 8 - C# gotchas파일 다운로드1
11876정성태5/9/20191228.NET Framework: 822. (번역글) .NET Internals Cookbook Part 7 - Word tearing, locking and others파일 다운로드1
11875정성태4/21/20191112오류 유형: 530. Visual Studo에서 .NET Core 프로젝트를 열 때 "One or more errors occurred." 오류 발생
11874정성태5/9/20191267.NET Framework: 821. (번역글) .NET Internals Cookbook Part 6 - Object internals파일 다운로드1
11873정성태7/9/20201085.NET Framework: 820. (번역글) .NET Internals Cookbook Part 5 - Methods, parameters, modifiers파일 다운로드1
11872정성태5/9/20191333.NET Framework: 819. (번역글) .NET Internals Cookbook Part 4 - Type members파일 다운로드1
11871정성태5/9/20191596.NET Framework: 818. (번역글) .NET Internals Cookbook Part 3 - Initialization tricks [3]파일 다운로드1
11870정성태4/16/20191280.NET Framework: 817. Process.Start로 실행한 콘솔 프로그램의 출력 결과를 얻는 방법파일 다운로드1
11869정성태5/9/20191341.NET Framework: 816. (번역글) .NET Internals Cookbook Part 2 - GC-related things파일 다운로드1
11868정성태4/15/20191157.NET Framework: 815. CER(Constrained Execution Region)이란?파일 다운로드1
11867정성태4/15/20191240.NET Framework: 814. Critical Finalizer와 SafeHandle의 사용 의미파일 다운로드1
11866정성태4/9/20192525Windows: 159. 네트워크 공유 폴더(net use)에 대한 인증 정보는 언제까지 유효할까요?
11865정성태4/9/20191232오류 유형: 529. 제어판 - C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools is not accessible.
11864정성태4/9/20191377오류 유형: 528. '...' could be '0': this does not adhere to the specification for the function '...'
... [16]  17  18  19  20  21  22  23  24  25  26  27  28  29  30  ...