Architecture de l'application console
L'utilisation de Docker pour héberger des applications console nécessite une configuration rigoureuse, particulièrement pour la gestion des flux d'entrée/sortie standard et la persistance des fichiers journaux. L'application cible est un calculateur interactif en ligne de commaned. À chaque opération, les saisies et les résultats sont enregistrés dans un fichier texte local, créant un environnement propice au test des montages de volumes Docker.
Le code source suivant implémente cette logique en utilisant des blocs d'instruction optimisés pour la gestion des ressources, une séparation des responsabilités et les fonctionnalités modernes du langage C#.
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
namespace DockerConsoleLogger
{
public class Program
{
private static readonly string LogDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
public static void Main(string[] args)
{
EnsureLogDirectoryExists();
Logger.Write("Initialisation du service de calcul.");
Console.WriteLine("Service de calcul conteneurisé actif. Démarrer une session ? (O/N)");
while (Console.ReadLine()?.Trim().ToUpper() == "O")
{
ExecuteCalculationCycle();
Console.WriteLine("Continuer avec une nouvelle opération ? (O/N)");
}
Logger.Write("Fermeture du service.");
}
private static void ExecuteCalculationCycle()
{
double operandOne = PromptForNumericValue("Saisissez la première valeur :");
double operandTwo = PromptForNumericValue("Saisissez la seconde valeur :");
string mathOperator = PromptForMathOperator();
double? computationResult = Calculate(operandOne, operandTwo, mathOperator);
if (computationResult.HasValue)
{
string formattedResult = $"Calcul : {operandOne} {mathOperator} {operandTwo} = {computationResult.Value}";
Console.WriteLine(formattedResult);
Logger.Write(formattedResult);
}
else
{
Console.WriteLine("Exception interceptée : Division par zéro impossible.");
Logger.Write("Erreur : Tentative de division par zéro.");
}
}
private static double PromptForNumericValue(string promptMessage)
{
Console.WriteLine(promptMessage);
while (!double.TryParse(Console.ReadLine(), out double parsedNumber))
{
Console.WriteLine("Format incorrect. Veuillez entrer une valeur numérique valide :");
Logger.Write("Avertissement : Saisie utilisateur non numérique.");
}
return parsedNumber;
}
private static string PromptForMathOperator()
{
var allowedOperators = new List<string> { "+", "-", "*", "/" };
Console.WriteLine("Sélectionnez une opération (+, -, *, /) :");
string selectedOperator = Console.ReadLine()?.Trim();
while (!allowedOperators.Contains(selectedOperator))
{
Console.WriteLine("Symbole non reconnu. Réessayez (+, -, *, /) :");
Logger.Write($"Avertissement : Opérateur invalide détecté ({selectedOperator}).");
selectedOperator = Console.ReadLine()?.Trim();
}
return selectedOperator;
}
private static double? Calculate(double x, double y, string op)
{
return op switch
{
"+" => x + y,
"-" => x - y,
"*" => x * y,
"/" => y != 0 ? x / y : null,
_ => null
};
}
private static void EnsureLogDirectoryExists()
{
if (!Directory.Exists(LogDirectory))
{
Directory.CreateDirectory(LogDirectory);
}
}
}
public static class Logger
{
public static void Write(string logMessage)
{
string currentLogPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs", $"{DateTime.Now:yyyy-MM-dd}.log");
string formattedEntry = $"[{DateTime.Now:HH:mm:ss}] {logMessage}";
using (StreamWriter fileWriter = new StreamWriter(currentLogPath, append: true))
{
fileWriter.WriteLine(formattedEntry);
}
}
}
}
Configuration du Dockerfile
Pour conteneuriser cette application, l'apprroche multi-époques (multi-stage build) est privilégiée. Elle permet de compiler le projet avec le SDK .NET complet tout en générant une image d'exécution finale extrêmement légère, dépourvue des outils de compilation inutiles en production.
# Phase de compilation
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-stage
WORKDIR /source
COPY . ./
RUN dotnet publish -c Release -o /publish
# Phase d'exécution
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS runtime-stage
WORKDIR /application
COPY --from=build-stage /publish .
ENTRYPOINT ["dotnet", "DockerConsoleLogger.dll"]
Construction et transfert de l'image
Une fois les fichiers source transférés sur l'environnement hôte Linux (via des protocoles sécurisés tels que SFTP avec MobaXterm ou SCP), l'image est assemblée par le démon Docker. La commande suivante lit le Dockerfile et exécute les étapes de build :
docker build -t dotnet-calc-engine .
Le moteur Docker télécharge les images de base nécessaires, compile le binaire C#, et génère une image locale étiquetée dotnet-calc-engine.
Déploiement et interaction avec le conteneur
L'exécution d'une application interactive impose l'allocation d'un pseudo-terminal. Les paramètres -t et -i sont donc indispensables pour maintenir les flux standards ouverts :
docker run --name calculator-node-01 -it dotnet-calc-engine
Si le processus principal verrouille le terminal hôte et qu'un détachement est nécessaire sans interrompre l'exécution, la séquence de raccourcis clavier Ctrl + P suivie de Ctrl + Q renvoie le conteneur en arrière-plan.
Consultation des journaux d'activité
Les images minimales (slim) de .NET n'intègrent pas d'éditeurs de texte complexes tels que Vim ou Nano. L'outil natif cat (concatenate) est la méthode standard pour afficher rapidement le contenu des fichiers journaux depuis le shell du conteneur :
docker exec -it calculator-node-01 /bin/bash
cd Logs
cat 2023-10-27.log
Exécution de processus supplémentaires
Le démon Docker permet d'injecter de nouvelles commandes dans un conteneur actif. Il est ainsi possible de lancer une instance parallèle de l'application console pour tester la concurrence d'écriture dans les fichiers journaux :
docker exec -it calculator-node-01 dotnet DockerConsoleLogger.dll