チュートリアル: .NET アプリのコンテナー化
このチュートリアルでは、Docker を使用して .NET アプリケーションをコンテナー化する方法について説明します。 コンテナーには、変更できないインフラストラクチャになったり、移植可能なアーキテクチャを提供したり、スケーラビリティを可能にしたりといった、さまざまな特徴とベネフィットがあります。 そのイメージを使って、ローカル開発環境、プライベート クラウド、またはパブリック クラウド用にコンテナーを作成することができます。
このチュートリアルでは、次の作業を行いました。
- 簡単な .NET アプリを作成して発行する
- .NET 用の Dockerfile を作成して構成する
- Docker イメージの構築
- Docker コンテナーを作成して実行する
.NET アプリケーション用に Docker コンテナーを構築してデプロイするタスクについて説明します。 "Docker プラットフォーム" では、"Docker エンジン" を使用して、すばやくアプリがビルドされ、"Docker イメージ" としてパッケージ化されます。 これらのイメージは、階層型コンテナーに展開されて実行される Dockerfile 形式で記述されています。
メモ
このチュートリアルは ASP.NET Core アプリ向けのものではありません。 ASP.NET Core を使用している場合は、ASP.NET Core アプリケーションをコンテナー化する方法に関するチュートリアルを参照してください。
必須コンポーネント
次の前提条件をインストールします。
- .NET 8+ SDK
.NET がインストールされている場合は、dotnet --info
コマンドを使用して、使用している SDK を特定します。 - Docker Community Edition
- Dockerfile と .NET サンプル アプリ用の一時作業フォルダー。 このチュートリアルでは、作業フォルダーに docker-working の名前が使用されます。
- .NET 7+ SDK
.NET がインストールされている場合は、dotnet --info
コマンドを使用して、使用している SDK を特定します。 - Docker Community Edition
- Dockerfile と .NET サンプル アプリ用の一時作業フォルダー。 このチュートリアルでは、作業フォルダーに docker-working の名前が使用されます。
.NET アプリの作成
Docker コンテナーが実行される .NET アプリが必要です。 ターミナルを開き、作業フォルダーがまだない場合は作成して、移動します。 作業フォルダーで次のコマンドを実行し、App という名前のサブディレクトリに新しいプロジェクトを作成します。
dotnet new console -o App -n DotNet.Docker
フォルダー ツリーは次のようになります:
📁 docker-working
└──📂 App
├──DotNet.Docker.csproj
├──Program.cs
└──📂 obj
├── DotNet.Docker.csproj.nuget.dgspec.json
├── DotNet.Docker.csproj.nuget.g.props
├── DotNet.Docker.csproj.nuget.g.targets
├── project.assets.json
└── project.nuget.cache
dotnet new
コマンドでは、"App" という名前の新しいフォルダーが作成され、"Hello World" コンソール アプリケーションが生成されます。 ディレクトリを変更し、ターミナル セッションから "App" フォルダーに移動します。 dotnet run
コマンドを使用してアプリを起動します。 アプリケーションが実行され、コマンドの下に Hello World!
出力されます:
cd App
dotnet run
Hello World!
既定のテンプレートでは、ターミナルに出力した後、すぐに終了するアプリが作成されます。 このチュートリアルでは、無限にループするアプリを使います。 テキスト エディターで Program.cs ファイルを開きます。
ヒント
Visual Studio Code を使用している場合は、前のターミナル セッションで次のコマンドを入力します。
code .
これにより、Visual Studio Code のプロジェクトを含む "App" フォルダーが開きます。
"Program.cs" は次のような C# コードになります。
Console.WriteLine("Hello World!");
1 秒ごとに数をカウントする次のコードにファイルを置き換えます。
var counter = 0;
var max = args.Length is not 0 ? Convert.ToInt32(args[0]) : -1;
while (max is -1 || counter < max)
{
Console.WriteLine($"Counter: {++counter}");
await Task.Delay(TimeSpan.FromMilliseconds(1_000));
}
var counter = 0;
var max = args.Length is not 0 ? Convert.ToInt32(args[0]) : -1;
while (max is -1 || counter < max)
{
Console.WriteLine($"Counter: {++counter}");
await Task.Delay(TimeSpan.FromMilliseconds(1_000));
}
ファイルを保存し、dotnet run
で再びプログラムをテストします。 このアプリは無限に実行されることに注意してください。 停止するにはキャンセル コマンド Ctrl + C を使用します。 次に出力例を示します。
dotnet run
Counter: 1
Counter: 2
Counter: 3
Counter: 4
^C
コマンド ラインでアプリに数値を渡すと、アプリはその値までカウント アップしただけで終了します。 dotnet run -- 5
で 5 までカウントしてみてください。
重要
--
より後のパラメーターは dotnet run
コマンドに渡されず、代わりにアプリケーションに渡されます。
.NET アプリを発行する
.NET アプリを Docker イメージに追加する前に、まず発行する必要があります。 アプリの発行済みバージョンをコンテナーで実行することをお勧めします。 アプリを発行するには、次のコマンドを実行します。
dotnet publish -c Release
このコマンドでは、publish フォルダーにアプリがコンパイルされます。 作業フォルダーから publish フォルダーへのパスは .\App\bin\Release\net8.0\publish\
です。
このコマンドでは、publish フォルダーにアプリがコンパイルされます。 作業フォルダーから publish フォルダーへのパスは .\App\bin\Release\net7.0\publish\
です。
App フォルダーから、publish フォルダーのディレクトリ一覧を表示し、DotNet.Docker.dll ファイルが作成されたことを確認します。
dir .\bin\Release\net8.0\publish\
Directory: C:\Users\default\App\bin\Release\net8.0\publish
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 9/22/2023 9:17 AM 431 DotNet.Docker.deps.json
-a--- 9/22/2023 9:17 AM 6144 DotNet.Docker.dll
-a--- 9/22/2023 9:17 AM 157696 DotNet.Docker.exe
-a--- 9/22/2023 9:17 AM 11688 DotNet.Docker.pdb
-a--- 9/22/2023 9:17 AM 353 DotNet.Docker.runtimeconfig.json
dir .\bin\Release\net7.0\publish\
Directory: C:\Users\default\App\bin\Release\net7.0\publish
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2/13/2023 1:52 PM 431 DotNet.Docker.deps.json
-a--- 2/13/2023 1:52 PM 6144 DotNet.Docker.dll
-a--- 2/13/2023 1:52 PM 153600 DotNet.Docker.exe
-a--- 2/13/2023 1:52 PM 11052 DotNet.Docker.pdb
-a--- 2/13/2023 1:52 PM 253 DotNet.Docker.runtimeconfig.json
Dockerfile を作成する
Dockerfile ファイルは、コンテナー イメージを作成するために docker build
コマンドによって使用されます。 このファイルは Dockerfile という名前のテキスト ファイルで、拡張子はありません。
.csproj が含まれるディレクトリに Dockerfile という名前のファイルを作成し、テキスト エディターで開きます。 このチュートリアルでは、ASP.NET Core ランタイム イメージ (.NET ランタイム イメージを含む) を使用して、.NET コンソール アプリケーションに対応します。
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /App
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
Note
mcr.microsoft.com/dotnet/runtime:8.0
イメージが使用されている可能性もありますが、ここでは ASP.NET Core ランタイム イメージを意図的に使用します。
ヒント
この Dockerfile はマルチステージ ビルドを使用します。これにより、ビルドをレイヤー化して必要な成果物のみを残すことで、イメージの最終的なサイズが最適化されます。 詳細については、「Docker Docs: マルチステージ ビルド」を参照してください。
FROM
キーワードには、完全修飾 Docker コンテナー イメージ名が必要です。 Microsoft Container Registry (MCR、mcr.microsoft.com) は、パブリック アクセスが可能なコンテナーをホストする Docker Hub のシンジケートです。 dotnet
セグメントはコンテナー リポジトリで、sdk
または aspnet
セグメントはコンテナー イメージの名前です。 イメージには 8.0
のタグが付けられ、バージョン管理に使用されます。 このため、mcr.microsoft.com/dotnet/aspnet:8.0
は .NET 8.0 ランタイムです。 必ず、SDK のターゲットのランタイムと一致するランタイム バージョンをプルしてください。 たとえば、前のセクションで作成したアプリでは .NET 8.0 SDK が使用されているため、Dockerfile で参照される基本イメージは 8.0 でタグ付けされます。
重要
Windows ベースのコンテナー イメージを使う場合は、単なる 8.0
ではないイメージ タグを指定する必要があります (たとえば、mcr.microsoft.com/dotnet/aspnet:8.0
ではなく mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-1809
)。 Nano Server または Windows Server Core のどちらを使用しているか、およびその OS のバージョンに基づいてイメージ名を選択します。 .NET の Docker Hub ページで、サポートされているすべてのタグの完全な一覧を確認できます。
Dockerfile ファイルを保存します。 作業フォルダーのディレクトリ構造は次のようになります。 一部のより深いレベルのファイルとフォルダーは、スペース削減のためこの記事では省略されています。
📁 docker-working
└──📂 App
├── Dockerfile
├── DotNet.Docker.csproj
├── Program.cs
├──📂 bin
│ └──📂 Release
│ └──📂 net8.0
│ └──📂 publish
│ ├── DotNet.Docker.deps.json
│ ├── DotNet.Docker.exe
│ ├── DotNet.Docker.dll
│ ├── DotNet.Docker.pdb
│ └── DotNet.Docker.runtimeconfig.json
└──📁 obj
└──...
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env
WORKDIR /App
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
Note
mcr.microsoft.com/dotnet/runtime:7.0
イメージが使用されている可能性もありますが、ここでは ASP.NET Core ランタイム イメージを意図的に使用します。
ヒント
この Dockerfile はマルチステージ ビルドを使用します。これにより、ビルドをレイヤー化して必要な成果物のみを残すことで、イメージの最終的なサイズが最適化されます。 詳細については、「Docker Docs: マルチステージ ビルド」を参照してください。
FROM
キーワードには、完全修飾 Docker コンテナー イメージ名が必要です。 Microsoft Container Registry (MCR、mcr.microsoft.com) は、パブリック アクセスが可能なコンテナーをホストする Docker Hub のシンジケートです。 dotnet
セグメントはコンテナー リポジトリで、sdk
または aspnet
セグメントはコンテナー イメージの名前です。 イメージには 7.0
のタグが付けられ、バージョン管理に使用されます。 このため、mcr.microsoft.com/dotnet/aspnet:7.0
は .NET 7.0 ランタイムです。 必ず、SDK のターゲットのランタイムと一致するランタイム バージョンをプルしてください。 たとえば、前のセクションで作成したアプリでは .NET 7.0 SDK が使用されているため、Dockerfile で参照される基本イメージは 7.0 でタグ付けされます。
Dockerfile ファイルを保存します。 作業フォルダーのディレクトリ構造は次のようになります。 一部のより深いレベルのファイルとフォルダーは、スペース削減のためこの記事では省略されています。
📁 docker-working
└──📂 App
├── Dockerfile
├── DotNet.Docker.csproj
├── Program.cs
├──📂 bin
│ └──📂 Release
│ └──📂 net7.0
│ └──📂 publish
│ ├── DotNet.Docker.deps.json
│ ├── DotNet.Docker.exe
│ ├── DotNet.Docker.dll
│ ├── DotNet.Docker.pdb
│ └── DotNet.Docker.runtimeconfig.json
└──📁 obj
└──...
ターミナルから、次のコマンドを実行します。
docker build -t counter-image -f Dockerfile .
Docker により Dockerfile 内の各行が処理されます。 docker build
コマンドの .
を使って、イメージのビルド コンテキストを設定します。 -f
スイッチは Dockerfile へのパスです。 このコマンドでは、イメージがビルドされて、そのイメージを指す counter-image という名前のローカル リポジトリが作成されます。 このコマンドが終了したら、docker images
を実行し、インストールされているイメージの一覧を表示します。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
counter-image latest 2f15637dc1f6 10 minutes ago 217MB
counter-image
リポジトリはイメージの名前です。 latest
タグは、イメージの識別に使われるタグです。 2f15637dc1f6
はイメージ ID です。 10 minutes ago
はイメージが作成された時間です。 217MB
はイメージのサイズです。 Dockerfile の最後の手順は、イメージからコンテナーを作成してアプリを実行し、発行されたアプリをコンテナーにコピーし、エントリ ポイントを定義することです。
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
counter-image latest 2f15637dc1f6 10 minutes ago 208MB
counter-image
リポジトリはイメージの名前です。 latest
タグは、イメージの識別に使われるタグです。 2f15637dc1f6
はイメージ ID です。 10 minutes ago
はイメージが作成された時間です。 208MB
はイメージのサイズです。 Dockerfile の最後の手順は、イメージからコンテナーを作成してアプリを実行し、発行されたアプリをコンテナーにコピーし、エントリ ポイントを定義することです。
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
COPY
コマンドは、自分のコンピューターの指定したフォルダーをコンテナー内のフォルダーにコピーするよう Docker に指示します。 この例では、publish フォルダーが、コンテナーの App/out という名前のフォルダーにコピーされています。
WORKDIR
コマンドでは、コンテナー内の現在のディレクトリが、"App" に変更されます。
次のコマンド ENTRYPOINT
は、実行可能ファイルとして実行するためにコンテナーを構成するよう Docker に指示します。 コンテナーの起動時に、ENTRYPOINT
コマンドが実行されます。 このコマンドが終了すると、コンテナーは自動的に停止します。
ヒント
.NET 8 より前で、読み取り専用として実行するように構成されたコンテナーは Failed to create CoreCLR, HRESULT: 0x8007000E
で失敗する可能性があります。 この問題に対処するには、(ENTRYPOINT
手順の直前に) DOTNET_EnableDiagnostics
環境変数に 0
を指定します。
ENV DOTNET_EnableDiagnostics=0
さまざまな .NET 環境変数の詳細については、「.NET 環境変数」を参照してください。
メモ
.NET 6 では、.NET の実行時の動作を構成する環境変数のプレフィックスが、COMPlus_
ではなく DOTNET_
に標準化されています。 ただし、プレフィックス COMPlus_
は引き続き機能します。 以前のバージョンの .NET ランタイムを使用している場合は、環境変数に COMPlus_
プレフィックスをまだ使用する必要があります。
コンテナーの作成
アプリを含むイメージができたので、コンテナーを作成することができます。 2 つの方法でコンテナーを作成することができます。 最初に、停止している新しいコンテナーを作成します。
docker create --name core-counter counter-image
この docker create
コマンドでは、counter-image イメージに基づくコンテナーが作成されます。 そのコマンドの出力に、作成されたコンテナーの CONTAINER ID (個々に異なります) が示されます。
d0be06126f7db6dd1cee369d911262a353c9b7fb4829a0c11b4b2eb7b2d429cf
"すべて" のコンテナーの一覧を表示するには、docker ps -a
コマンドを使います。
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d0be06126f7d counter-image "dotnet DotNet.Docke…" 12 seconds ago Created core-counter
コンテナーを管理する
コンテナーが特定の名前 core-counter
で作成されました。この名前を使用してコンテナーを管理します。 次の例では、docker start
コマンドを使ってコンテナーを起動した後、docker ps
コマンドを使って、実行されているコンテナーのみを表示します。
docker start core-counter
core-counter
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cf01364df453 counter-image "dotnet DotNet.Docke…" 53 seconds ago Up 10 seconds core-counter
同様に、docker stop
コマンドではコンテナーを停止します。 次の例では、docker stop
コマンドを使ってコンテナーを停止した後、docker ps
コマンドを使って、実行されているコンテナーがないことを示します。
docker stop core-counter
core-counter
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
コンテナーへの接続
コンテナーが実行状態になった後、それに接続して出力を確認できます。 docker start
コマンドを使ってコンテナーを起動し、docker attach
コマンドを使って出力ストリームを表示します。 この例では、Ctrl+C キー入力を使用して、実行中のコンテナーからデタッチします。 このキー入力では、コンテナーを停止するように指定されている場合を除き、コンテナー内のプロセスが終了します。 --sig-proxy=false
パラメーターを指定すると、Ctrl + C キーを押してもコンテナー内のプロセスが停止しないことが保証されます。
コンテナーからデタッチした後、再アタッチして、まだ実行されていてカウントが行われていることを確認します。
docker start core-counter
core-counter
docker attach --sig-proxy=false core-counter
Counter: 7
Counter: 8
Counter: 9
^C
docker attach --sig-proxy=false core-counter
Counter: 17
Counter: 18
Counter: 19
^C
コンテナーを削除する
この記事では、何も実行しないコンテナーを放置しないようにします。 前に作成したコンテナーを削除します。 コンテナーが実行されている場合は、それを停止します。
docker stop core-counter
次の例では、すべてのコンテナーを一覧表示します。 次に、docker rm
コマンドを使ってコンテナーを削除した後、もう一度実行中のコンテナーを確認します。
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2f6424a7ddce counter-image "dotnet DotNet.Dock…" 7 minutes ago Exited (143) 20 seconds ago core-counter
docker rm core-counter
core-counter
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
単一実行
Docker では、1 つのコマンドとしてコンテナーを作成して実行するための docker run
コマンドが提供されています。 このコマンドでは、docker create
を実行してから docker start
を実行する必要がありません。 コンテナーが停止したら自動的にコンテナーを削除するように、このコマンドを設定することもできます。 たとえば、docker run -it --rm
を使うと 2 つのことが行われます。つまり、最初に現在の端末を使ってコンテナーに自動的に接続し、次にコンテナーが終了したらそれを削除します。
docker run -it --rm counter-image
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
^C
また、コンテナーは .NET アプリの実行にパラメーターを渡します。 .NET アプリに対して 3 つまでしかカウントしないように指示するには、3 を渡します。
docker run -it --rm counter-image 3
Counter: 1
Counter: 2
Counter: 3
docker run -it
では、Ctrl+C キーを押すと、コンテナーで実行されているプロセスを停止し、さらにコンテナーを停止します。 --rm
パラメーターを指定したので、プロセスが停止するとコンテナーは自動的に削除されます。 それが存在しないことを確認します。
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ENTRYPOINT を変更する
docker run
コマンドでは、Dockerfile から ENTRYPOINT
コマンドを変更し、そのコンテナーに対してのみ何か他のことを実行することもできます。 たとえば、次のコマンドを使うと bash
または cmd.exe
を実行できます。 必要に応じて、コマンドを編集します。
この例では、ENTRYPOINT
が cmd.exe
に変更されています。 プロセスを終了してコンテナーを停止するには、Ctrl+C キーを押します。
docker run -it --rm --entrypoint "cmd.exe" counter-image
Microsoft Windows [Version 10.0.17763.379]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\>dir
Volume in drive C has no label.
Volume Serial Number is 3005-1E84
Directory of C:\
04/09/2019 08:46 AM <DIR> app
03/07/2019 10:25 AM 5,510 License.txt
04/02/2019 01:35 PM <DIR> Program Files
04/09/2019 01:06 PM <DIR> Users
04/02/2019 01:35 PM <DIR> Windows
1 File(s) 5,510 bytes
4 Dir(s) 21,246,517,248 bytes free
C:\>^C
重要なコマンド
Docker には、コンテナーとイメージを作成、管理、操作するさまざまなコマンドが用意されています。 コンテナーの管理に不可欠な Docker コマンドは次のとおりです。
リソースをクリーンアップする
このチュートリアルでは、コンテナーとイメージを作成しました。 必要な場合は、これらのリソースを削除します。 次のコマンドを使います
すべてのコンテナーを一覧表示します
docker ps -a
実行しているコンテナーを名前で選んで停止します。
docker stop core-counter
コンテナーを削除します
docker rm core-counter
次に、コンピューターに残しておきたくないイメージを削除します。 Dockerfile によって作成されたイメージを削除した後、Dockerfile が基にした .NET イメージを削除します。 IMAGE ID または REPOSITORY:TAG の書式に設定された文字列を使うことができます。
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:8.0
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:7.0
インストールされているイメージの一覧を表示するには、docker images
コマンドを使います。
ヒント
イメージ ファイルは大きくなることがあります。 普通、アプリのテスト中および開発中に作成した一時的なコンテナーは削除します。 通常、ランタイムがインストールされた基本イメージは、そのランタイムを基にして他のイメージをビルドする予定がある場合は、残しておきます。
次のステップ
.NET