ASP.NET Docker イメージ サイズの最適化に関する 2016 年の Steve Laster による素晴らしい投稿があります。それ以来、Docker はマルチステージ ビルド ファイルを追加して、1 つの Dockerfile でより多くのことを実行できるようにしました。コンテナーは、簡単で信頼性の高い展開が重要であり、密度も重要です。メモリの使用をできるだけ少なくしたいのは確かですが、メモリをネットワーク上で移動するのに時間を費やさないように、メモリをできるだけ小さくするのも良いことです。イメージ ファイルのサイズも、コンテナーの起動時間に影響を与える可能性があります。さらに、きちんと整頓されています。
私は今週、あなたと同じように私の机の上に小さな 6 ノードの Raspberry Pi (ARM) Kubenetes Cluster を構築してきましたが、イメージのサイズが思ったよりも少し大きいことに気付きました。比較的低電力のシステムであるため、これはより大きな問題ですが、繰り返しになりますが、必要がないのに、不要な x メガバイトを持ち歩く必要はありません。
Alex Ellis は、Raspberry Pi 用の .NET Core アプリの構築に関するすばらしいブログと、YouTube ビデオを公開しています。彼のビデオとブログで、彼は "Console.WriteLine()" コンソール アプリを作成しています。これは OpenFaas (オープン ソース サーバーレス プラットフォーム) に最適ですが、Raspberry Pi k8s クラスターに ASP.NET Core アプリも入れたいと考えていました。彼はこれを彼のブログに「挑戦」として含めたので、挑戦は受け入れられました!助けてくれてありがとう、アレックス!
Docker 上の ASP.NET Core (ARM 上)
まず、基本的な ASP.NET Core アプリを作成します。 Web API を行うこともできますが、今回は Razor Pages を使用して MVC を行います。明確にするために、それらは出発点が異なるだけで同じものです。後でいつでもページを追加したり、JSON を追加したりできます。
「dotnet new mvc」(または dotnet new razor など) から始めます。 Kuberenetes が管理する Docker でこれを実行しますが、いつでも Program.cs の WebHost を変更して、Kestrel Web サーバーの起動方法を次のように変更できます。
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:5000;http://localhost:5001;https://hostname:5002")
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 があったとしても、このような Dockerfile を使用したいと考えています。これは、スペースを効率的に使用し、イメージを小さくするためです。
これを分解しましょう。 2 つの段階があります。最初の FROM は、コードをビルドする SDK イメージです。私たちは Docker 内でビルドを行っています - これは素晴らしく信頼性の高いビルド方法です。
<ブロック引用>プロからのヒント: Docker は中間イメージの作成と最小限の作業に優れています 、しかし、私たち (作者) がそれを助けるために正しいことをするなら、それは役に立ちます.
たとえば、.csproj をコピーしてから "dotnet restore" を実行する場所を参照してください。多くの場合、人々が「COPY . .」を行うのを目にします。そして復元を行います。これにより、Docker は何が変更されたかを検出できず、ビルドごとに復元の料金を支払うことになります。
この 2 つのステップ (プロジェクトのコピー、復元、コードのコピー) を行うことで、「dotnet 復元」の中間ステップが Docker によってキャッシュされ、作業が大幅に高速化されます。
ビルドしたら、公開します。私のように宛先がわかっている場合 (linux-arm)、-r linux-arm (または debian など) を使用して自己完結型の RID (ランタイム ID) パブリッシュを行うことができ、完全な自己を得ることができます。
それ以外の場合は、アプリのコードを公開し、.NET Core ランタイム イメージを使用してそれを実行できます。このイメージには完全な自己完結型のビルドを使用しているため、.NET ランタイムも含めるのはやり過ぎです。 Microsoft/dotnet の Docker ハブを見ると、「依存関係」の「deps」と呼ばれるイメージが表示されます。これらは、.NET が実行する必要があるものを含む debian の上にあるイメージですが、.NET 自体は含まれません。
画像のスタックは一般的に次のようになります (例)
- debian:stretch から
- microsoft/dotnet:2.0-runtime-deps から
- microsoft/dotnet:2.0-runtime から
これで、ベース イメージ、依存関係、および .NET ランタイムが完成しました。 SDK イメージには、コードをビルドする必要があるため、さらに多くのものが含まれます。繰り返しますが、これが "as builder" イメージに使用し、コンパイルの結果をコピーする理由です。 それらを別のランタイム イメージに配置します。すべての世界で最高のものを手に入れることができます。
FROM microsoft/dotnet:2.0-sdk as builder
RUN mkdir -p /root/src/app/aspnetcoreapp
WORKDIR /root/src/app/aspnetcoreapp
#copy just the project file over
# this prevents additional extraneous restores
# and allows us to re-use the intermediate layer
# This only happens again if we change the csproj.
# This means WAY faster builds!
COPY aspnetcoreapp.csproj .
#Because we have a custom nuget.config, copy it in
COPY nuget.config .
RUN dotnet restore ./aspnetcoreapp.csproj
COPY . .
RUN dotnet publish -c release -o published -r linux-arm
#Smaller - Best for apps with self-contained .NETs, as it doesn't include the runtime
# It has the *dependencies* to run .NET Apps. The .NET runtime image sits on this
FROM microsoft/dotnet:2.0.0-runtime-deps-stretch-arm32v7
#Bigger - Best for apps .NETs that aren't self-contained.
#FROM microsoft/dotnet:2.0.0-runtime-stretch-arm32v7
# These are the non-ARM images.
#FROM microsoft/dotnet:2.0.0-runtime-deps
#FROM microsoft/dotnet:2.0.0-runtime
WORKDIR /root/
COPY --from=builder /root/src/app/aspnetcoreapp/published .
ENV ASPNETCORE_URLS=http://+:5000
EXPOSE 5000/tcp
# This runs your app with the dotnet exe included with the runtime or SDK
#CMD ["dotnet", "./aspnetcoreapp.dll"]
# This runs your self-contained .NET Core app. You built with -r to get this
CMD ["./aspnetcoreapp"]
また、カスタムの nuget.config があることにも注意してください。その場合は、ビルド時に dotnet restore がすべてのパッケージを取得できるようにする必要があります。
第 2 段階の FROM の束をコメントアウトして含めました。私は ARM のみを使用していますが、他のものも見てもらいたいと思います。
ビルドしたコードをランタイム イメージにコピーしたら、環境変数を設定して、すべてのポートが内部でポート 5000 をリッスンするようにします (上記のことを覚えていますか?)。次に、アプリを実行します。ランタイムがある場合は "dotnet foo.dll" で実行できますが、私のように自己完結型のビルドを使用している場合は、"foo" を実行するだけです。
要約すると:
- FROM microsoft/dotnet:2.0-sdk をビルダーとしてビルド
- 結果をランタイムにコピー
- 適切なランタイム FROM を使用する
- 適切な CPU アーキテクチャ?
- .NET ランタイムを使用する (標準) または自己完結型ビルドを使用する (それほどでもない)
- 適切なポートでリッスンしていますか (ウェブ アプリの場合)?
- アプリを正常に正しく実行していますか?
- .dockerignore はありますか? /obj、/bin などをコピーしたくないが、/published が必要なため、.NET ビルドにとって非常に重要です。
obj/
bin/
!published /
もう少し最適化
アプリを調べて、呼び出していないコードとバイナリを削除できるプレリリースの "ツリー トリミング" ツールがいくつかあります。 Microsoft.Packaging.Tools.Trimming も含めて試してみて、プロジェクトにパッケージを追加するだけで、最終的なイメージから未使用のコードをさらに取得しました。
Step 8/14 : RUN dotnet publish -c release -o published -r linux-arm /p:LinkDuringPublish=true
---> Running in 39404479945f
Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
Trimmed 152 out of 347 files for a savings of 20.54 MB
Final app size is 33.56 MB
aspnetcoreapp -> /root/src/app/aspnetcoreapp/bin/release/netcoreapp2.0/linux-arm/aspnetcoreapp.dll
Trimmed 152 out of 347 files for a savings of 20.54 MB
Final app size is 33.56 MB
最終的なイメージで docker history を実行すると、サイズがどこから来ているかを正確に確認できます。 Microsoft が Debian ベース イメージから Alpine イメージに切り替えた場合、これはさらに小さくなるはずです。
C:\Users\scott\Desktop\k8s for pi\aspnetcoreapp>docker history c60
IMAGE CREATED CREATED BY SIZE COMMENT
c6094ca46c3b 3 minutes ago /bin/sh -c #(nop) CMD ["dotnet" "./aspnet... 0B
b7dfcf137587 3 minutes ago /bin/sh -c #(nop) EXPOSE 5000/tcp 0B
a5ba51b91d9d 3 minutes ago /bin/sh -c #(nop) ENV ASPNETCORE_URLS=htt... 0B
8742269735bc 3 minutes ago /bin/sh -c #(nop) COPY dir:cc64bd3b9bacaeb... 56.5MB
28c008e38973 3 minutes ago /bin/sh -c #(nop) WORKDIR /root/ 0B
4bafd6e2811a 4 hours ago /bin/sh -c apt-get update && apt-get i... 45.4MB
<missing> 3 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:8b7cf813a113aa2... 85.7MB
変更を加えたときの Dockerfile の進化を次に示します。最終結果はどんどん小さくなっていきます。少し手を加えて 45 メガバイト、または約 20% 縮小したように見えます。
C:\Users\scott\Desktop\k8s for pi\aspnetcoreapp>docker images | find /i "aspnetcoreapp"
shanselman/aspnetcoreapp 0.5 c6094ca46c3b About a minute ago 188MB
shanselman/aspnetcoreapp 0.4 083bfbdc4e01 12 minutes ago 196MB
shanselman/aspnetcoreapp 0.3 fa053b4ee2b4 About an hour ago 199MB
shanselman/aspnetcoreapp 0.2 ba73f14e29aa 4 hours ago 207MB
shanselman/aspnetcoreapp 0.1 cac2f0e3826c 3 hours ago 233MB
後で、この YAML の説明を使用してこの標準の ASP.NET Core Web アプリを Kubernetes に配置し、Raspberry Pi でスケールアウトするブログ投稿を行う予定です。私はたくさんのことを学んでいます!時間を割いてくれた Alex Ellis、Glenn Condron、Jessie Frazelle に感謝します!
スポンサー: DocuVieware HTML5 Viewer と Document Management Kit を使用して、ドキュメントのライフ サイクルの各ステップを管理する強力な Web アプリケーションを作成します。デモをチェックして、100 以上のフォーマットを取得、スキャン、編集、注釈付けし、UI をカスタマイズしてください!