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분
정성태

... 46  47  48  49  50  51  52  53  54  [55]  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12251정성태7/1/202011040.NET Framework: 920. C# - 파일의 비동기 처리 유무에 따른 스레드 상황 [1]파일 다운로드2
12250정성태6/30/202013672.NET Framework: 919. C# - 닷넷에서의 진정한 비동기 호출을 가능케 하는 I/O 스레드 사용법 [1]파일 다운로드1
12249정성태6/29/20209816오류 유형: 625. Microsoft SQL Server 2019 RC1 Setup - 설치 제거 시 Warning 26003 오류 발생
12248정성태6/29/20208235오류 유형: 624. SQL 서버 오류 - service-specific error code 17051
12247정성태6/29/20209765.NET Framework: 918. C# - 불린 형 상수를 반환값으로 포함하는 3항 연산자 사용 시 단축 표현 권장(IDE0075) [2]파일 다운로드1
12246정성태6/29/202010567.NET Framework: 917. C# - USB 관련 ETW(Event Tracing for Windows)를 이용한 키보드 입력을 감지하는 방법
12245정성태6/24/202011043.NET Framework: 916. C# - Task.Yield 사용법 (2) [2]파일 다운로드1
12244정성태6/24/202010873.NET Framework: 915. ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용 [1]파일 다운로드1
12243정성태6/23/20208447VS.NET IDE: 147. Visual C++ 프로젝트 - .NET Core EXE를 "Debugger Type"으로 지원하는 기능 추가
12242정성태6/23/20209175오류 유형: 623. AADSTS90072 - User account '...' from identity provider 'live.com' does not exist in tenant 'Microsoft Services'
12241정성태6/23/202012528.NET Framework: 914. C# - Task.Yield 사용법파일 다운로드1
12240정성태6/23/202013823오류 유형: 622. 소켓 바인딩 시 "System.Net.Sockets.SocketException: An attempt was made to access a socket in a way forbidden by its access permissions" 오류 발생
12239정성태6/21/202010223Linux: 30. (윈도우라면 DLL에 속하는) .so 파일이 텍스트로 구성된 사례 [1]
12238정성태6/21/202010188.NET Framework: 913. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 라이브러리
12237정성태6/20/20209959.NET Framework: 912. 리눅스 환경의 .NET Core에서 "test".IndexOf("\0")가 0을 반환
12236정성태6/19/202010351오류 유형: 621. .NET Standard 대상으로 빌드 시 dynamic 예약어에서 컴파일 오류 - error CS0656: Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'
12235정성태6/19/20209969오류 유형: 620. Windows 10 - Inaccessible boot device 블루 스크린
12234정성태6/19/20209693개발 환경 구성: 494. NuGet - nuspec의 패키지 스키마 버전(네임스페이스) 업데이트 방법
12233정성태6/19/20209374오류 유형: 619. SQL 서버 - The transaction log for database '...' is full due to 'LOG_BACKUP'. - 두 번째 이야기
12232정성태6/19/20208326오류 유형: 618. SharePoint - StoreBusyRetryLater 오류
12231정성태6/15/202010749.NET Framework: 911. Console/Service Application을 위한 SynchronizationContext - AsyncContext
12230정성태6/15/202010114오류 유형: 617. IMetaDataImport::GetMethodProps가 반환하는 IL 코드 주소(RVA) 문제
12229정성태6/13/202011983.NET Framework: 910. USB/IP PROJECT를 이용해 C#으로 USB Keyboard + Mouse 가상 장치 만들기 [1]
12228정성태6/12/202012080.NET Framework: 909. C# - Source Generator를 적용한 XmlCodeGenerator파일 다운로드1
12227정성태6/12/202016047오류 유형: 616. Visual Studio의 느린 업데이트 속도에 대한 원인 분석 [5]
12226정성태6/11/202013344개발 환경 구성: 493. OpenVPN의 네트워크 구성 [4]파일 다운로드1
... 46  47  48  49  50  51  52  53  54  [55]  56  57  58  59  60  ...