본문 바로가기

.NET/C#

Optimizing ASP.NET Core Docker Image sizes

출처 : https://www.hanselman.com/blog/OptimizingASPNETCoreDockerImageSizes.aspx


ASP.NET Docker 이미지 크기 최적화에 대한 2016 년 Steve Laster 의 훌륭한 글이 있습니다 이후 Docker는 다단계 빌드 파일을 추가하여 하나의 Dockerfile에서 더 많은 작업을 수행 할 수 있습니다. 컨테이너는 쉽고 신뢰할 수있는 배포 방법이며 밀도에 관한 것입니다. 가능한 한 적은 메모리를 사용하고 싶지만 가능한 한 작게 만들어서 네트워크를 이동하는 데 시간을 낭비하지 않는 것이 좋습니다. 이미지 파일의 크기는 컨테이너 시작 시간에도 영향을 줄 수 있습니다. 게다가 그것은 단지 깔끔합니다.

저는 제 책상에 약간의 6 노드 Raspberry Pi (ARM) Kubenetes Cluster를 구축 했습니다. - 이번 주에, 당신이하는 것처럼, 이미지 크기가 내가 원하는 것보다 약간 더 크다는 것을 알았습니다. 이것은 상대적으로 저전력 시스템이기 때문에 더 큰 문제이지만, 다시 말해서 불필요한 x 메가 바이트를 가지고 다니지 않아도되는 이유는 무엇입니까?

Alex Ellis 는 YouTube 비디오와 함께 Raspberry Pi 용 .NET Core 앱을 만드는 데 유용한 블로그 를 갖고 있습니다. 그의 비디오 및 블로그에서 그는 OpenFaas (오픈 소스 서버리스 플랫폼) 에 적합한 "Console.WriteLine ()"콘솔 앱을 제작 했지만, 내 라즈베리 파이 k8 클러스터 에도 ASP.NET 코어 앱을 갖고 싶었습니다 그는 이것을 자신의 블로그에 "도전"으로 포함 시켰으므로 도전을 받아 들였습니다! 모든 도움과 지원에 감사드립니다, 알렉스!

DOCKER의 ASP.NET 코어 (ARM)

먼저 기본 ASP.NET Core app을 만듭니다. 나는 웹 API를 할 수 있지만, 이번에는 Razor Pages로 MVC를 할 것입니다. 분명히하기 위해서, 그들은 출발점이 다를 때와 똑같습니다. 언제든지 페이지를 추가하거나 둘 중 하나에 JSON을 추가 할 수 있습니다.

나는 "dotnet new mvc"(또는 dotnet new razor 등)로 시작한다. Kuberenetes가 관리하는 Docker에서 이것을 실행하려고 합니다. 그리고 Kestrel 웹 서버가 다음과 같이 시작되는 방식을 변경하기 위해 Program.cs의 WebHost를 항상 변경할 수는 있습니다 .

WebHost.CreateDefaultBuilder(args)
    .UseUrls(<a href="http://*:5000;http://localhost:5001;https://hostname:5002">http://*:5000;http://localhost:5001;https://hostname:5002</a>)

Docker 사용 사례의 경우 환경 변수를 사용하여 수신 대기 URL을 변경하는 것이 더 쉽습니다. 물론 80 일 수도 있지만 5000을 원합니다. Dockerfile을 만들 때 ASPNETCORE_URLS 환경 변수를 http : // + : 5000으로 설정합니다.

ASP.NET을위한 최적화 된 MULTISTAGE DOCKERFILE

이를 수행하는 "올바른"방법이 많이 있으므로 시나리오에 대해 생각하고 싶을 것입니다. 아래에서 ARM (Raspberry Pi)을 사용하고 있으므로 "qemu : Unsupported syscall : 345"와 같이 컨테이너를 실행하는 중에 오류가 발생 하면 x86 / x64에서 ARM 이미지를 실행하려고합니다. Windows에서 ARM 컨테이너를 만들 예정이지만 여기서 실행할 수는 없습니다. 나는 그것을 컨테이너 레지스트리에 밀어 넣고 내 Raspberry Pi 클러스터에 그것을 내려 놓으라고 명령해야한다.

여기 내가 지금까지 가지고있는 것이있다. 주석 처리 된 것이 있으므로주의하십시오. 이것은 / 나를위한 학습 운동이었습니다. 무슨 일이 일어나고 있는지 모르는 경우 복사 / 붙여 넣기를하지 마십시오! 실수가 있다면, Dockerfile의 GitHub Gist를 보시면 개선하고 개선 할 수 있습니다.

.NET Core에는 빌드 도구 및 개발 키트와 컴파일러 등이 포함 된 SDK가 있으며 런타임이 있음을 이해하는 것이 중요합니다. 런타임에는 "응용 프로그램 만들기"항목이 없으며 "응용 프로그램 실행"기능 만 있습니다. 현재 ARM 용 SDK가 없으므로 (다소 우아하게) 다단계 빌드 파일로 작업하는 데 한계가 있습니다. 그러나 ARM 용 SDK가 있었더라도 공간이 더 효율적이고 작은 이미지를 만들기 때문에 Docker 파일을 계속 사용하고 싶습니다.

이걸 부셔 버리자. 두 단계가 있습니다. 첫 번째 FROM은 코드를 작성하는 SDK 이미지입니다. Docker 내부에서 빌드를 수행하고 있습니다. 이는 멋지고 믿을만한 빌드 작업입니다.

PRO TIP : Docker는 중간 이미지를 만들고 최소한의 작업을하는 것이 현명 하지만 우리 (저자)가 올바른 작업을 수행하는 데 도움이됩니다.

예를 들어 .csproj를 복사 한 곳을 확인한 다음 "dotnet 복원"을 수행 하시겠습니까? 종종 사람들은 "COPY." 그런 다음 복원을 수행하십시오. 그건 Docker가 무엇이 바뀌 었는지 탐지하지 못하게하고, 결국 당신은 모든 것을 복원하는데 돈을 지불하게 될 것입니다.

이 두 단계를 수행하여 프로젝트를 복사하고 복원하고 코드를 복사하면 Docker가 중간 단계를 "도트 넷 복원"하여 캐시 할 수 있다는 것을 의미합니다.

빌드 한 후에는 게시를 수행합니다. 내가 (리눅스 - 팔)처럼 목적지를 알고 있다면 -r 리눅스 - 팔 (또는 데비안, 또는 무엇이든)으로 자체 포함 된 RID (런타임 id) 퍼블리시를 할 수 있으며, 포함 된 버전의 앱.

그렇지 않으면 앱 코드를 게시하고 .NET Core 런타임 이미지를 사용하여 실행할 수 있습니다. 이 이미지에 완전한 독립형 빌드를 사용하고 있기 때문에 .NET 런타임을 포함하는 것은 너무 과한 일입니다. 당신이 경우 마이크로 소프트 / DOTNET의 부두 노동자 허브 보면 당신이 "deps"라는 이미지를 볼 수 있습니다 "종속." 그것들은 .NET이 실행해야하는 것들을 포함하는 데비안 상단에있는 이미지입니다.하지만 .NET 자체는 아닙니다.

이미지 스택은 일반적으로 다음과 같이 보입니다 (예 :).

  • 데비안에서 : 스트레치
  • FROM microsoft / dotnet : 2.0-runtime-deps
  • FROM microsoft / dotnet : 2.0-runtime

따라서 기본 이미지, 종속성 및 .NET 런타임이 있습니다. SDK 이미지에는 코드를 작성해야하기 때문에 훨씬 더 많은 내용이 포함됩니다. 다시 말하자면, 우리 는 이것을 "빌더로서"이미지 에 사용하고 컴파일 결과 를 복사하여다른 런타임 이미지에 넣는 이유 입니다. 당신은 모든 세계에서 최고의 것을 얻습니다.

FROM microsoft / dotnet : 2.0-sdk 빌더로   

실행 mkdir -p / root / src / app / aspnetcoreapp
WORKDIR / root / src / app / aspnetcoreapp #이

프로젝트 파일 만 복사
하면 추가로 불필요한 복원을 방지
할 수 있으며 # -use intermediate layer
# 이것은 csproj를 변경하면 다시 발생합니다.
# 이것은 빠른 빌드를 의미합니다!
COPY aspnetcoreapp.csproj.
# 사용자 지정 nuget.config가 있으므로
COPY nuget.config 복사하십시오.
.net 네트워크 복구 ./aspnetcoreapp.csproj를 실행하십시오

. .
RUN dotnet publish -c release -o published -r linux-arm

#Smaller - 독립 실행 형 .NET이 설치된 응용 프로그램에 가장 적합합니다.
# .NET 응용 프로그램을 실행하려면 * 종속성 *이 있습니다. .NET 런타임 이미지는
FROM microsoft / dotnet에

위치 합니다. 2.0.0-runtime-deps-stretch-arm32v7 #Bigger - 자체 포함되지 않은 응용 프로그램에 가장 적합합니다.
#FROM microsoft / dotnet : 2.0.0-runtime-stretch-arm32v7

# 이들은 비 ARM 이미지입니다.
#FROM microsoft / dotnet : 2.0.0-runtime-deps
#FROM microsoft / dotnet : 2.0.0-runtime

WORKDIR / root /
COPY --from = 빌더 / 루트 / src / app / aspnetcoreapp / 게시.
ENV ASPNETCORE_URLS = http : // + : 5000
EXPOSE 5000 / tcp
# 런타임 또는 SDK에 포함 된
dotnet exe로 응용 프로그램을 실행합니다. #CMD [ "dotnet", "./aspnetcoreapp.dll"]
# .NET 핵심 애플 리케이션이 포함되어 있습니다. 이것을 얻기 위해 -r을 사용하여 빌드했습니다.
CMD [ "./aspnetcoreapp"]

또한 커스텀 nuget.config를 가지고 있습니다. 그래서 당신이한다면, 모든 패키지를 픽업하기 위해 dotnet restore를 빌드 할 때 사용할 수 있어야합니다.

나는 두 번째 단계에서 많은 FROM을 주석 처리하여 설명했습니다. 저는 ARM을 사용하고 있습니다 만 다른 것들을 보길 원했습니다.

우리가 작성한 코드를 런타임 이미지에 복사하고 나면 환경 변수를 설정하여 우리 모두가 포트 5000에서 내부적으로 청취하도록합니다 (위의 내용을 기억하십니까?) 그런 다음 우리는 app을 실행합니다. 런타임이있는 경우 "dotnet foo.dll"을 사용하여 실행할 수 있지만 나처럼 독창적 인 빌드를 사용하면 "foo"를 실행하게됩니다.

요약하면 다음과 같습니다.

  • 빌더로 FROM microsoft / dotnet : 2.0-SDK로 빌드
  • 결과를 런타임에 복사
  • 적절한 런타임 FROM을 사용하십시오.
    • 오른쪽 CPU 아키텍처?
    • .NET 런타임 (일반)을 사용하거나 자체 포함 된 빌드 (덜 사용)
  • 올바른 포트에서 듣기 (웹 앱이라면)?
  • 앱을 성공적으로 올바르게 실행하고 있습니까?
  • .dockerignore 있어요? 당신이 / obj, / bin 등을 통해 복사하고 싶지 않기 때문에 .NET 빌드에 매우 중요합니다.하지만 당신은 원한다 / 발행하고 싶습니다. 
    obj / 
    bin / 
    ! published /

좀 더 최적화하기

귀하의 앱을보고 전화하지 않는 코드와 바이너리를 제거 할 수 있는 몇 가지 시험판 "트리 트리밍"도구 가 있습니다. 필자는 Microsoft.Packaging.Tools.Trims.Trims.Trims.Trims.Trims.Trims.Timings뿐만 아니라 그것을 시도하고 내 프로젝트에 패키지를 추가하여 내 최종 이미지에서 더 많은 사용되지 않는 코드를 얻을 수 있습니다.

8/14 단계 : RUN dotnet 게시 -c 릴리스 -o 게시 -r linux-arm / p : LinkDuringPublish = true 
--->
39404479945f .NET 코어 용 Microsoft (R) Build Engine 버전 15.4.8.50001
Copyright (C) Microsoft Corporation. 판권 소유.

347 개 파일 중 152 개를 잘라서 20.54 MB를 절약 할 수 있습니다.
최종 응용 프로그램 크기는 33.56 MB
aspnetcoreapp -> /root/src/app/aspnetcoreapp/bin/release/netcoreapp2.0/linux-arm/aspnetcoreapp.dll
347 개 중 152 개 20.54 MB 절감을위한 파일
최종 응용 프로그램 크기는 33.56 MB입니다.

최종 이미지에서 도커 기록을 실행하면 크기의 출처를 정확하게 볼 수 있습니다. Microsoft가 데비안 기본 이미지에서 알파인 이미지로 전환하면이 크기가 더 작아집니다.

C : \ 사용자 \ 스캇 \ 바탕 화면 \ K8S 파이 \의 aspnetcoreapp> 고정 표시기 역사 C60에 대한 
SIZE 주석에 의해 등재 IMAGE
분 전 / 빈 / SH -c # (NOP) CMD [ "DOTNET" "./aspnet c6094ca46c3b ... 0B
b7dfcf137587 1 분 전 / bin / sh -c # (nop)
ENOS ASPNETCORE_URLS = htt ... 0B
8742269735bc 분 전에 / bin / sh / c # (nop) EXPOSE 5000 / tcp 0B a5ba51b91d9d 3 분 전 / bin / sh -c # -c # (nop) COPY dir : cc64bd3b9bacaeb ... 56.5MB
28c008e38973 / bin / sh -c # (nop) WORKDIR / root / 0B
4bafd6e2811a 4 시간 전 / bin / sh -c apt-get 업데이트 && apt ~ 할 ... 45.4MB
<missing> 3 주전 / bin / sh -c # (nop) CMD [ "bash"] 0B
<missing> 3 주전 / bin / sh -c # (nop) ADD 파일 : 8b7cf813a113aa2 ... 85.7MB

변경 사항을 적용한 결과 Dockerfile의 진화가 나타나고 최종 결과는 점점 작아졌습니다. 약간의 작업으로 약 45 메가 또는 약 20 % 작게 보입니다.

pi \ aspnetcoreapp> docker 이미지의 경우 C : \ Users \ scott \ Desktop \ k8s | find / i "aspnetcoreapp" 
shanselman / aspnetcoreapp 0.5 c6094ca46c3b 약 1 분 전에 188MB
shanselman / aspnetcoreapp 0.4 083bfbdc4e01 196MB
shanselman / aspnetcoreapp 0.3 fa053b4ee2b4 약 한 시간 전에 199MB
shanselman / aspnetcoreapp 0.2 ba73f14e29aa 4 시간 전 207MB
shanselman / aspnetcoreapp 0.1 cac2f0e3826c 3 몇 시간 전 233MB

나중에이 YAML 설명을 사용하여 Kubernetes에 표준 ASP.NET Core 웹 응용 프로그램을 넣고 Raspberry Pi에서 크기를 조정 하는 블로그 게시물 을 작성합니다. 나는 많은 것을 배우고있다! Alex Ellis 와 Glenn Condron 및 Jessie Frazelle 에게 감사드립니다 .