Déploiement d’applications ASP.NET Core avec Docker – Partie 1

Dans cet article, nous comprendrons ce qu’est Docker, comment il fonctionne et comment créer un environnement complet pour déployer des applications ASP.NET Core dans des conteneurs Docker.
Faire fonctionner une application Web avec toutes ses dépendances implique plusieurs configurations et ajustements, ce qui peut s’avérer difficile. Pour nous aider dans ce processus, nous pouvons utiliser Docker, un formidable outil qui encapsule l’application, ses dépendances et ses configurations dans des conteneurs standardisés.
Dans cet article, nous comprendrons comment fonctionne Docker et comment commencer à intégrer ses fonctionnalités dans une application ASP.NET Core.
Introduction à Docker
Docker est une plateforme open source conçue pour faciliter la création, le test et le déploiement d’applications dans des environnements isolés appelés conteneurs.
Les conteneurs Docker sont des environnements isolés qui vous permettent d’exécuter des applications et leurs dépendances indépendamment du système d’exploitation. Ils agissent comme des « boîtes » contenant tout ce dont une application a besoin pour fonctionner, comme les bibliothèques, les variables d’environnement, les configurations et le code lui-même. Cela permet à l’application de s’exécuter dans n’importe quel environnement, que ce soit sur un ordinateur de bureau, sur des serveurs de production ou dans le cloud, sans complications ni configurations complexes, ce qui constitue son principal avantage.
Les développeurs peuvent utiliser Docker pour créer des images d’application, qui sont des instantanés prêts à être exécutés et les partager facilement avec d’autres développeurs ou équipes d’exploitation. Cela améliore la cohérence, réduit les problèmes de compatibilité et simplifie le processus de déploiement et de maintenance d’applications complexes.
De plus, Docker s’intègre bien aux systèmes CI/CD (Continuous Integration and Delivery), ce qui en fait un outil populaire parmi les équipes travaillant avec des microservices et des environnements hautement évolutifs. Pour ces raisons, il est devenu un choix important pour les développeurs et les professionnels DevOps à la recherche d’agilité et d’efficacité dans le développement et la livraison de logiciels.
Conteneurs vs machines virtuelles
Les conteneurs et les machines virtuelles (VM) sont deux technologies qui isolent les applications et leurs dépendances. Les deux offrent des avantages similaires, mais de manière différente.
Les conteneurs forment une abstraction au niveau de la couche application qui regroupe le code et ses dépendances. Une seule machine peut contenir plusieurs conteneurs et partager le noyau du système d’exploitation, permettant à chacun de s’exécuter comme un processus isolé. Contrairement aux machines virtuelles, les conteneurs occupent très peu d’espace disque (généralement des dizaines de Mo) et peuvent gérer plus d’applications en même temps.
En revanche, les machines virtuelles fournissent une abstraction du matériel physique. L’hyperviseur permet à plusieurs VM de s’exécuter sur une seule machine. Chaque VM est composée d’une copie complète d’un système d’exploitation, comprenant l’application, les binaires et les bibliothèques, occupant des dizaines de Go. L’ensemble de ce processus peut ralentir le démarrage de la VM.
Comme le montre l’image ci-dessus, contrairement aux machines virtuelles, les conteneurs n’ont pas besoin d’hyperviseur pour virtualiser le système d’exploitation, car ils partagent directement le noyau du système d’exploitation hôte.
L’absence d’hyperviseur réduit les frais généraux, car il n’est pas nécessaire de simuler le matériel ou de charger un système d’exploitation complet dans chaque instance. Cette structure plus légère permet aux conteneurs de démarrer plus rapidement et d’occuper moins d’espace disque et de mémoire par rapport aux machines virtuelles.
Images Docker
Une image Docker, également connue sous le nom d’image de conteneur, est un « package » qui contient tout ce dont une application a besoin pour s’exécuter dans un conteneur.
Ils incluent le code source de l’application, les bibliothèques, les dépendances, les variables d’environnement et toutes les configurations nécessaires au bon fonctionnement de l’application. Ces images sont créées via un fichier exécutable autonome appelé Dockerfile. En termes simples, une image Docker est un modèle ou un plan qui définit ce que le conteneur doit exécuter.
Chaque image Docker est construite à travers des calques. Ces couches sont constituées d’instructions écrites dans le fichier Dockerfile, qui décrivent chaque étape nécessaire à la création de l’application. Chaque commande du Dockerfile crée un nouveau calque.
Les calques permettent de créer et d’exécuter efficacement des images car Docker stocke uniquement les différences entre chaque calque. Ainsi, si plusieurs images partagent le même calque, comme une bibliothèque de base par exemple, Docker ne stocke ce calque qu’une seule fois, évitant ainsi une surcharge inutile.
Vous trouverez ci-dessous un exemple de Dockerfile pour une application ASP.NET Core. Notez que chaque commande crée un nouveau calque.
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /app
COPY *.csproj ./
RUN dotnet restore
COPY . ./
RUN dotnet publish -c Release -o /app/out
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build-env /app/out .
EXPOSE 80
ENTRYPOINT ["dotnet", "ApplicationName.dll"]
Images vs conteneurs
Une image Docker est un plan statique, tandis qu’un conteneur est une instance active de cette image. En termes simples, le conteneur est l’exécution de l’image. En ce sens, plusieurs conteneurs peuvent être créés à partir de la même image.
Par exemple, une image peut contenir une application Web telle qu’une API ou un service de travail, et vous pouvez lancer autant de conteneurs que nécessaire à partir de cette image, chaque conteneur fonctionnant comme une instance indépendante du serveur.
Pratique avec Docker
Pour mettre Docker en pratique, nous allons créer une application web dans ASP.NET Core puis nous configurerons les images et les conteneurs Docker. La première étape consiste donc à installer Docker sur votre ordinateur.
Installation de Docker Bureau
Vous pouvez utiliser l’outil Docker Desktop qui installe et configure ce dont vous avez besoin pour avoir le serveur Docker local sur votre machine. Pour ce faire, accédez simplement au Page de téléchargement de Docker Desktop et installez la version compatible avec votre système d’exploitation. Actuellement disponible pour Windows, Mac (macOS) et Linux.
Important : Docker Desktop comporte des restrictions pour une utilisation commerciale. Par conséquent, avant de l’installer, vérifiez si vous avez besoin ou non d’une licence pour l’utiliser.
Création de l’application
Pour nous entraîner à utiliser Docker, nous allons créer une application ASP.NET Core à l’aide du modèle MVC (Model, View, Controller) pour gérer une planification de tâches.
Pour rester concentrés sur les configurations Docker, dans cette première partie, nous utiliserons une base de données en mémoire, donc aucune configuration supplémentaire ne sera nécessaire pour que l’application fonctionne.
Vous pouvez accéder au code source du projet dans ce référentiel GitHub : Liste de rappel.
Pour créer l’application de base, vous pouvez utiliser la commande .NET suivante :
dotnet new mvc -o ReminderList -au none
Cette commande créera une application à l’aide du modèle MVC. De plus, le -au none
La commande empêche l’ajout de ressources d’authentification, car elles ne sont pas pertinentes dans ce contexte.
Ouvrez maintenant un terminal à la racine de l’application et exécutez les commandes ci-dessous pour télécharger les packages NuGet qui seront nécessaires.
dotnet add package Microsoft.EntityFrameworkCore. InMemory
dotnet add package Microsoft. EntityFrameworkCore
Ensuite, dans le dossier « Modèles », créez la classe suivante :
namespace ReminderList.Models;
public class TodoItem
{
public int Id { get; set; }
public string Title { get; set; }
public bool IsCompleted { get; set; } = false;
}
Ensuite, créez un nouveau dossier appelé « Data » et, à l’intérieur, créez la classe suivante :
using Microsoft.EntityFrameworkCore;
using ReminderList.Models;
namespace ReminderList.Data;
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options) : base(options) { }
public DbSet<TodoItem> TodoItems { get; set; }
}
Dans le dossier « Contrôleur », ajoutez le contrôleur suivant :
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ReminderList.Data;
using ReminderList.Models;
namespace ReminderList.Controllers
{
public class TodoController : Controller
{
private readonly TodoContext _context;
public TodoController(TodoContext context)
{
_context = context;
}
public async Task<IActionResult> Index()
{
return View(await _context.TodoItems.ToListAsync());
}
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(TodoItem todoItem)
{
if (ModelState.IsValid)
{
_context.Add(todoItem);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(todoItem);
}
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return View(todoItem);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, TodoItem todoItem)
{
if (id != todoItem.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(todoItem);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TodoItemExists(todoItem.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(todoItem);
}
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var todoItem = await _context.TodoItems
.FirstOrDefaultAsync(m => m.Id == id);
if (todoItem == null)
{
return NotFound();
}
return View(todoItem);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool TodoItemExists(int id)
{
return _context.TodoItems.Any(e => e.Id == id);
}
}
}
Maintenant, créons les fichiers de vues. Dans le dossier « Vues », créez un nouveau dossier appelé « Todo » et, à l’intérieur, ajoutez les vues suivantes :
@model IEnumerable<ReminderList.Models.TodoItem>
<h2>Todo List</h2>
<p>
<a asp-action="Create">Add New Task</a>
</p>
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Is Completed</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>@item.Title</td>
<td>@item.IsCompleted</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@model ReminderList.Models.TodoItem
<h2>Create New Task</h2>
<form asp-action="Create">
<div class="form-group">
<label asp-for="Title"></label>
<input asp-for="Title" class="form-control" />
</div>
<div class="form-group">
<label asp-for="IsCompleted"></label>
<input asp-for="IsCompleted" class="form-check-input" type="checkbox" />
</div>
<button type="submit" class="btn btn-primary">Create</button>
</form>
@model ReminderList.Models.TodoItem
<h2>Edit Task</h2>
<form asp-action="Edit">
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title"></label>
<input asp-for="Title" class="form-control" />
</div>
<div class="form-group">
<label asp-for="IsCompleted"></label>
<input asp-for="IsCompleted" class="form-check-input" type="checkbox" />
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
@model ReminderList.Models.TodoItem
<h2>Delete Task</h2>
<div>
<h4>Are you sure you want to delete this task?</h4>
<div>
<p>
<strong>Title:</strong> @Model.Title
</p>
<p>
<strong>Is Completed:</strong> @Model.IsCompleted
</p>
</div>
<form asp-action="DeleteConfirmed">
<input type="hidden" asp-for="Id" />
<button type="submit" class="btn btn-danger">Delete</button>
<a asp-action="Index" class="btn btn-secondary">Cancel</a>
</form>
</div>
Ensuite, dans le fichier Program.cs, remplacez le code par ce qui suit :
using Microsoft.EntityFrameworkCore;
using ReminderList.Data;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls("http://*:80");
builder.Services.AddControllersWithViews();
// Configure the in-memory database
builder.Services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoList"));
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Le code builder.WebHost.UseUrls("http://*:80");
définit l’URL et le port sur lesquels le serveur ASP.NET Core écoutera les requêtes HTTP lors de son exécution dans le conteneur Docker.
Création du fichier Docker
L’étape suivante consiste à créer le fichier qui contiendra les étapes de création de l’image Docker.
Dans le répertoire racine de l’application, créez un fichier appelé Dockerfile
. Il n’a pas besoin d’extension, utilisez simplement exactement ce nom.
Ensuite, ouvrez le fichier avec un éditeur de texte et ajoutez-y le code suivant :
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /app
COPY *.csproj ./
RUN dotnet restore
COPY . ./
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
ENV ASPNETCORE_ENVIRONMENT=Development
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 80
ENTRYPOINT ["dotnet", "ReminderList.dll"]
Analysons maintenant chaque partie de ce code.
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
: définit l’image de base (SDK .NET 9.0) pour l’étape de construction, y compris les outils de compilation, la restauration des dépendances et la publication d’applications.WORKDIR /app
: Définit le répertoire de travail sur /app à l’intérieur du conteneur, où toutes les commandes ci-dessous seront exécutées.COPY *.csproj ./
: copie le fichier .csproj (projet) de l’application dans le fichier/app
répertoire de travail. Cela permet de copier initialement uniquement les fichiers nécessaires à la restauration des dépendances, évitant ainsi d’avoir à reconstruire les dépendances à chaque fois qu’un fichier de code est modifié.RUN dotnet restore
: restaure les dépendances du projet, en téléchargeant les packages NuGet requis pour l’application. Ce processus permet de tirer parti du cache Docker pour la restauration.COPY . ./
: Copie l’intégralité du contenu du répertoire actuel dans le conteneur, y compris le code source.RUN dotnet publish -c Release -o /app/publish
: compile et publie l’application en mode Release dans le répertoire /app/publish, créant ainsi une version optimisée et prête pour la production.FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
: définit l’image de base pour l’étape finale, qui utilise le runtime ASP.NET Core 9.0.ENV ASPNETCORE_ENVIRONMENT=Development
: définit la variable d’environnement ASPNETCORE_ENVIRONMENT sur Développement, indiquant que l’application s’exécutera en mode développement.WORKDIR /app
: Définit le répertoire de travail de l’étape finale sur/app
.COPY --from=build /app/publish .
: copie le dossier publié (/app/publish
) de l’étape de construction à l’étape actuelle/app
répertoire à l’étape finale.EXPOSE 80
: expose le port 80 du conteneur afin qu’il puisse recevoir des connexions HTTP.ENTRYPOINT ["dotnet", "ReminderList.dll"]
: Définit le point d’entrée de l’application. Ceci spécifie que lorsque le conteneur démarre, il exécutera ledotnet ReminderList.dll
commande, qui lancera l’exécutable de l’application ASP.NET Core.
Création de l’image Docker
Maintenant que nous comprenons ce que signifie chaque étape du Dockerfile, nous pouvons exécuter la commande qui utilisera ce fichier et créer l’image Docker.
Ainsi, à la racine de l’application, ouvrez un terminal et exécutez la commande suivante :
docker build -t todoapp .
docker build
: Cette commande construit une image Docker basée sur le Dockerfile trouvé dans le répertoire courant représenté par le point (.).
Le -t flag
(abréviation de « tag ») permet de donner un nom à l’image qui sera construite, qui dans ce cas est todoapp. Cela permet d’identifier l’image dans le référentiel Docker local.
Ci-dessous vous pouvez voir le résultat de l’exécution et si tout s’est bien passé.
Vous pouvez maintenant vérifier l’image nouvellement créée dans Docker Desktop.
Création du conteneur Docker
Une fois l’image prête, nous pouvons créer le conteneur Docker qui utilisera l’image pour créer une instance de notre application.
Ainsi, dans le répertoire racine de l’application, exécutez la commande suivante :
docker run -d -p 8080:80 --name todoapp-container todoapp
Analysons chacune de ces commandes en détail :
docker run
: démarre un nouveau conteneur basé sur une image Docker.-d
: Signifie le mode détaché. Le conteneur s’exécutera en arrière-plan, vous permettant de continuer à utiliser le terminal pendant l’exécution du conteneur.-p 8080:80
: Mappe un port hôte au port du conteneur, où 8080 est le port de votre ordinateur (hôte) qui sera utilisé pour accéder à l’application, tandis que 80 est le port exposé par le conteneur (comme spécifié dans le Dockerfile par la commande EXPOSE 80 ).--name todoapp-container
: Donne un nom personnalisé au conteneur.todoapp
: C’est le nom de l’image Docker créée précédemment. Cette image sera utilisée pour créer le conteneur.
Le résultat des commandes est visible ci-dessous, ainsi que le conteneur dans Docker Desktop :
Notez que cette commande génère un ID. Il s’agit de l’ID du conteneur créé et démarré par le docker run
commande. Il s’agit d’un identifiant unique généré par Docker pour le conteneur nouvellement créé.
Maintenant que le conteneur est créé, nous pouvons accéder à l’adresse http://localhost:8080/Todo pour voir la candidature en ligne.
Conclusion
Docker est un outil qui facilite le déploiement d’applications et présente de nombreux avantages par rapport aux machines virtuelles traditionnelles puisque chaque conteneur Docker peut partager le même système d’exploitation que l’hôte. De plus, les mécanismes de Docker sont extrêmement efficaces, ne nécessitant que ce qui est nécessaire pour que les applications soient opérationnelles, ce qui rend le processus de déploiement extrêmement agile.
Dans cet article, nous avons créé une application simple pour gérer une liste de tâches à l’aide du modèle ASP.NET Core MVC. Ensuite, nous avons créé un Dockerfile et exécuté les commandes pour que l’application s’exécute dans un conteneur Docker.
Dans la partie suivante, nous ajouterons une base de données à l’application, créerons un conteneur individuel pour celle-ci et verrons comment vérifier que tout fonctionne correctement.
Source link