Tutoriel : Conteneuriser une application .NET

Ce didacticiel explique comment conteneuriser une application .NET avec Docker. Les conteneurs ont de nombreuses fonctionnalités et divers avantages : une infrastructure immuable qui fournit une architecture portable et permet la scalabilité. L’image vous permettra de créer des conteneurs pour votre environnement de développement local, votre cloud privé ou votre cloud public.

Dans ce tutoriel, vous allez :

  • Créer et publier une application .NET simple
  • Créer et configurer un Dockerfile pour .NET
  • Générer une image Docker
  • Créer et exécuter un conteneur Docker

Vous découvrirez la création d’un conteneur Docker et les tâches de déploiement pour une application .NET. La plateforme Docker utilise le moteur Docker pour générer et empaqueter rapidement des applications comme images Docker. Ces images sont écrites au format Dockerfile pour être déployées et exécutées dans un conteneur en couches.

Notes

Ce didacticiel n’est pas destiné aux applications ASP.NET Core. Si vous utilisez ASP.NET Core, consultez le didacticiel Apprendre à conteneuriser une application ASP.NET Core.

Prérequis

Installez les éléments requis suivants :

  • SDK .NET 8+
    Si .NET est installé, utilisez la commande dotnet --info pour identifier le Kit SDK que vous utilisez.
  • Docker Community Edition
  • Un dossier de travail temporaire pour Dockerfile et un exemple d’application .NET. Dans ce tutoriel, le nom docker-working est utilisé comme dossier de travail.
  • SDK .NET 7+
    Si .NET est installé, utilisez la commande dotnet --info pour identifier le Kit SDK que vous utilisez.
  • Docker Community Edition
  • Un dossier de travail temporaire pour Dockerfile et un exemple d’application .NET. Dans ce tutoriel, le nom docker-working est utilisé comme dossier de travail.

Créer une application .NET

Vous avez besoin d’une application .NET que le conteneur Docker exécute. Ouvrez votre terminal, créez un dossier de travail si ce n’est déjà fait, et accédez-y. Dans le dossier de travail, exécutez la commande suivante pour créer un projet dans un sous-répertoire nommé App :

dotnet new console -o App -n DotNet.Docker

Votre arborescence de dossiers doit ressembler à ceci :

📁 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

La commande dotnet new crée un dossier nommé App et génère une application de console « Hello World ». Modifiez les répertoires et accédez au dossier App à partir de votre session de terminal. Utilisez la commande dotnet run pour démarrer l’application. L’application s’exécute et imprime Hello World! sous la commande :

cd App
dotnet run
Hello World!

Le modèle par défaut crée une application qui affiche les données dans le terminal et se termine immédiatement. Pour ce didacticiel, vous utilisez une application qui effectue une boucle indéfiniment. Ouvrez le fichier Program.cs dans un éditeur de texte.

Conseil

Si vous utilisez Visual Studio Code, à partir du type de session de terminal précédente, tapez la commande suivante :

code .

Cela ouvre le dossier App qui contient le projet dans Visual Studio Code.

Le fichier Program.cs doit ressembler au code C# suivant :

Console.WriteLine("Hello World!");

Remplacez le fichier par le code suivant qui compte les nombres chaque seconde :

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));
}

Enregistrez le fichier et effectuez un nouveau avec dotnet run. N’oubliez pas que cette application s’exécute indéfiniment. Utilisez la commande Annuler Ctrl+C pour l’arrêter. Voici un exemple de sortie :

dotnet run
Counter: 1
Counter: 2
Counter: 3
Counter: 4
^C

Si vous envoyez un nombre à l’application sur la ligne de commande, l’application comptera uniquement jusqu’à ce montant puis se fermera. Effectuez un test avec dotnet run -- 5 pour compter jusqu’à cinq.

Important

Tous les paramètres après -- sont transmis à votre application au lieu d’être transmis à la commande dotnet run.

Publier une application .NET

Avant d’ajouter l’application .NET à l’image Docker, elle doit d’abord être publiée. Il est préférable que le conteneur exécute la version publiée de l’application. Pour publier l’app, exécutez la commande suivante :

dotnet publish -c Release

Cette commande compile votre application dans le dossier publish. Le chemin du dossier publish à partir du dossier de travail doit être .\App\bin\Release\net8.0\publish\.

Cette commande compile votre application dans le dossier publish. Le chemin du dossier publish à partir du dossier de travail doit être .\App\bin\Release\net7.0\publish\.

Dans le dossier Application, obtenez une liste de répertoires du dossier de publication pour vérifier que le fichier DotNet.Docker.dll a été créé.

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

Créer le Dockerfile

Le fichier Dockerfile est utilisé par la commande docker build pour créer une image de conteneur. Ce fichier est un fichier texte nommé Dockerfile, sans extension.

Créez un fichier nommé Dockerfile dans votre répertoire contenant le fichier .csproj et ouvrez-le dans un éditeur de texte. Ce didacticiel utilise l’image du runtime ASP.NET Core (qui contient l’image du runtime .NET) et correspond à l’application console .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"]

Remarque

L’image du runtime ASP.NET Core est utilisée intentionnellement ici, bien que l’image mcr.microsoft.com/dotnet/runtime:8.0 ait pu être utilisée.

Conseil

Ce fichier Dockerfile utilise des builds à plusieurs étapes, qui optimisent la taille finale de l’image en superposant le build et en laissant uniquement les artefacts requis. Pour plus d’informations, consultez Docker Docs: multi-stage builds.

Le mot clé FROM nécessite un nom complet d’image de conteneur Docker. Microsoft Container Registry (MCR, mcr.microsoft.com) est un syndicat de Docker Hub qui héberge des conteneurs accessibles publiquement. Le segment dotnet est le référentiel de conteneurs, tandis que le segment sdk ou aspnet est le nom de l’image conteneur. L’image est marquée avec 8.0, qui est utilisé pour le contrôle de version. Par conséquent, mcr.microsoft.com/dotnet/aspnet:8.0 correspond au runtime .NET 8.0. Veillez à extraire la version du runtime correspondante au runtime ciblé par votre kit de développement logiciel SDK. Par exemple, l’application créée dans la section précédente utilisait le SDK .NET 8.0. L’image de base à laquelle le fichier Dockerfile fait référence est marquée de l’étiquette 8.0.

Important

Lorsque vous utilisez des images conteneur Windows, vous devez spécifier la balise d’image au-delà du simple 8.0, par exemple, mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-1809 au lieu de mcr.microsoft.com/dotnet/aspnet:8.0. Sélectionnez un nom d’image en fonction de quel serveur vous utilisez, soit Windows Server Core ou Nano Server et quelle version du système d’exploitation. Vous trouverez la liste complète de toutes les balises prises en charge .NET sur Page de Docker Hub.

Enregistrez le fichier Dockerfile. La structure de répertoires du dossier de travail doit ressembler à ce qui suit. Certains des fichiers et des dossiers de niveau plus profond ont été omis pour économiser de l’espace dans l’article :

📁 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"]

Remarque

L’image du runtime ASP.NET Core est utilisée intentionnellement ici, bien que l’image mcr.microsoft.com/dotnet/runtime:7.0 ait pu être utilisée.

Conseil

Ce fichier Dockerfile utilise des builds à plusieurs étapes, qui optimisent la taille finale de l’image en superposant le build et en laissant uniquement les artefacts requis. Pour plus d’informations, consultez Docker Docs: multi-stage builds.

Le mot clé FROM nécessite un nom complet d’image de conteneur Docker. Microsoft Container Registry (MCR, mcr.microsoft.com) est un syndicat de Docker Hub qui héberge des conteneurs accessibles publiquement. Le segment dotnet est le référentiel de conteneurs, tandis que le segment sdk ou aspnet est le nom de l’image conteneur. L’image est marquée avec 7.0, qui est utilisé pour le contrôle de version. Par conséquent, mcr.microsoft.com/dotnet/aspnet:7.0 correspond au runtime .NET 7.0. Veillez à extraire la version du runtime correspondante au runtime ciblé par votre kit de développement logiciel SDK. Par exemple, l’application créée dans la section précédente utilisait le kit de développement logiciel (SDK) .NET 7.0. L’image de base à laquelle le fichier Dockerfile fait référence est marquée de l’étiquette 7.0.

Enregistrez le fichier Dockerfile. La structure de répertoires du dossier de travail doit ressembler à ce qui suit. Certains des fichiers et des dossiers de niveau plus profond ont été omis pour économiser de l’espace dans l’article :

📁 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
            └──...

Dans votre terminal, exécutez la commande suivante :

docker build -t counter-image -f Dockerfile .

Docker traitera chaque ligne du Dockerfile. . dans la commande docker build définit le contexte de build de l’image. Le commutateur -f est le chemin d’accès au fichier Dockerfile. Cette commande génère l’image et crée un référentiel local nommé counter-image qui pointe vers cette image. Une fois cette commande terminée, exécutez docker images pour afficher une liste des images installées :

docker images
REPOSITORY                         TAG       IMAGE ID       CREATED          SIZE
counter-image                      latest    2f15637dc1f6   10 minutes ago   217MB

Le référentiel counter-image est le nom de l’image. La balise latest est la balise utilisée pour identifier l’image. 2f15637dc1f6 est l’ID d’image. 10 minutes ago est l'heure de création de l'image. 217MB est la taille de l’image. Les dernières étapes du fichier Dockerfile sont de créer un conteneur à partir de l’image et d’exécuter l’application, de copier l’application publiée dans le conteneur et de définir le point d’entrée.

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

Le référentiel counter-image est le nom de l’image. La balise latest est la balise utilisée pour identifier l’image. 2f15637dc1f6 est l’ID d’image. 10 minutes ago est l'heure de création de l'image. 208MB est la taille de l’image. Les dernières étapes du fichier Dockerfile sont de créer un conteneur à partir de l’image et d’exécuter l’application, de copier l’application publiée dans le conteneur et de définir le point d’entrée.

FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]

La commande COPY indique à Docker de copier le dossier spécifié sur votre ordinateur, dans un dossier du conteneur. Dans cet exemple, le dossier publish est copié vers un dossier nommé App/out dans le conteneur.

La commande WORKDIR remplace le répertoire actif à l’intérieur du conteneur par App.

La commande suivante, ENTRYPOINT, indique à Docker de configurer le conteneur afin de l’exécuter comme un fichier exécutable. Au démarrage du conteneur, la commande ENTRYPOINT s’exécute. Lorsque cette commande se termine, le conteneur s’arrête automatiquement.

Conseil

Avant .NET 8, les conteneurs configurés pour s’exécuter en lecture seule peuvent échouer avec Failed to create CoreCLR, HRESULT: 0x8007000E. Pour régler ce problème, spécifiez une variable d’environnement DOTNET_EnableDiagnostics comme 0 (juste avant l’étape ENTRYPOINT) :

ENV DOTNET_EnableDiagnostics=0

Pour plus d’informations sur les variables d’environnement NET, consultez Variables d’environnement NET.

Notes

.NET 6 se normalise sur le préfixe DOTNET_ au lieu de COMPlus_ pour les variables d’environnement qui configurent le comportement au moment de l’exécution de .NET. Toutefois, le préfixe COMPlus_ continuera à fonctionner. Si vous utilisez une version précédente du runtime .NET, vous devez tout de même utiliser le préfixe COMPlus_.

Créez un conteneur.

Maintenant que vous disposez d’une image qui contient votre application, vous pouvez créer un conteneur. Vous pouvez créer un conteneur de deux manières. Tout d’abord, créez un conteneur arrêté.

docker create --name core-counter counter-image

Cette commande docker create crée un conteneur basé sur l’image counter-image. Le résultat de cette commande affiche la valeur CONTAINER ID (la vôtre sera différente) du conteneur créé :

d0be06126f7db6dd1cee369d911262a353c9b7fb4829a0c11b4b2eb7b2d429cf

Pour afficher une liste de tous les conteneurs, utilisez la commande 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

Gérer le conteneur

Le conteneur a été créé avec un nom core-counterspécifique qui est utilisé pour gérer le conteneur. L’exemple suivant utilise la commande docker start pour démarrer le conteneur, puis la commande docker ps pour afficher uniquement les conteneurs en cours d’exécution :

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

De même, la commande docker stop arrête le conteneur. L’exemple suivant utilise la commande docker stop pour arrêter le conteneur, puis la commande docker ps pour indiquer qu’aucun conteneur n’est en cours d’exécution :

docker stop core-counter
core-counter

docker ps
CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES

Se connecter à un conteneur

Lorsqu’un conteneur est en cours d’exécution, vous pouvez vous connecter à ce dernier pour afficher le résultat. Utilisez les commandes docker start et docker attach pour démarrer le conteneur et observer le flux de sortie. Dans cet exemple, les touches CTRL + C permettent de se déconnecter du conteneur en cours d’exécution. Sauf indication contraire, cette séquence de touches met fin au processus dans le conteneur, ce qui arrête le conteneur. Le paramètre --sig-proxy=false garantit que la commande Ctrl + C n’arrêtera pas le processus dans le conteneur.

Après vous être déconnecté du conteneur, reconnectez-vous pour vérifier qu’il est toujours en cours d’exécution et de comptage.

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

Supprimer un conteneur

Pour cet article, vous ne souhaitez pas de conteneurs présents qui ne servent à rien. Supprimez le conteneur que vous avez créé précédemment. Si le conteneur est en cours d’exécution, arrêtez-le.

docker stop core-counter

L’exemple suivant répertorie tous les conteneurs. Il utilise ensuite la commande docker rm pour supprimer le conteneur, puis recherche une deuxième fois si des conteneurs sont en cours d’exécution.

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

Exécution unique

Docker fournit la commande docker run pour créer et exécuter le conteneur comme une commande unique. Cette commande vous évite de devoir exécuter docker create , puis docker start. Vous pouvez également définir cette commande pour supprimer automatiquement le conteneur lorsque le conteneur s’arrête. Par exemple, utilisez docker run -it --rm pour effectuer deux opérations : utiliser automatiquement le terminal actuel pour se connecter au conteneur, puis supprimer ce conteneur une fois qu’il est terminé :

docker run -it --rm counter-image
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
^C

Le conteneur transmet également des paramètres dans l’exécution de l’application .NET. Pour indiquer à l’application .NET de compter uniquement sur trois transmis dans 3.

docker run -it --rm counter-image 3
Counter: 1
Counter: 2
Counter: 3

Avec docker run -it, la commande Ctrl + C interrompt le processus en cours d’exécution dans le conteneur, qui à son tour, arrête le conteneur. Comme le paramètre --rm a été fourni, le conteneur est automatiquement supprimé lorsque le processus est arrêté. Vérifiez qu’il n’existe pas :

docker ps -a
CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES

Modifier la commande ENTRYPOINT

La commande docker run vous permet également de modifier la commande ENTRYPOINT depuis le Dockerfile , puis d’exécuter un autre élément, mais uniquement pour ce conteneur. Par exemple, utilisez la commande suivante pour exécuter bash ou cmd.exe. Modifiez la commande selon vos besoins.

Dans cet exemple, ENTRYPOINT est remplacé par cmd.exe. CTRL + C interrompt le processus et arrête le conteneur.

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

Commandes essentielles

Docker possède de nombreuses commandes différentes qui créent, gèrent et interagissent avec des conteneurs et des images. Ces commandes Docker sont essentielles à la gestion de vos conteneurs :

Nettoyer les ressources

Au cours de ce didacticiel, vous avez créé des conteneurs et des images. Si vous le souhaitez, supprimez ces ressources. Utilisez les commandes suivantes pour

  1. Lister tous les conteneurs

    docker ps -a
    
  2. Arrêtez les conteneurs qui s’exécutent par leur nom.

    docker stop core-counter
    
  3. Supprimer un conteneur

    docker rm core-counter
    

Supprimez ensuite toutes les images que vous ne souhaitez plus conserver sur votre ordinateur. Supprimez l’image créée par votre Dockerfile, puis l’image .NET sur laquelle le fichier Dockerfile était basé. Vous pouvez utiliser la valeur IMAGE ID ou la chaîne mise en forme 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

Utilisez la commande docker images pour afficher la liste des images installées.

Conseil

Les fichiers image peuvent être volumineux. En règle générale, vous supprimez les conteneurs temporaires que vous avez créés lors des tests et du développement de votre app. Vous conservez normalement les images de base avec le runtime installé si vous envisagez de créer d’autres images basées sur ce runtime.

Étapes suivantes