Déploiement de Docker

Conseil

Même si vous connaissez bien Docker et/ou Orleans ainsi que la documentation d’Orleans, il est recommandé de la lire intégralement pour éviter des problèmes potentiels auxquels nous avons déjà apporté des solutions.

Cet article et son exemple sont un travail en cours. Les commentaires, RP ou suggestions sont les bienvenus.

Déployer des solutions Orleans sur Docker

Le déploiement d’Orleans sur Docker peut s’avérer délicat en raison de la façon dont les orchestrateurs Docker et les piles de clustering ont été conçus. Il n’est pas facile de comprendre le concept de réseau de superposition de Docker Swarm et du modèle réseau Kubernetes.

Les conteneurs Docker et les modèles réseau ont été conçus pour exécuter principalement des conteneurs sans état et immuables. Ainsi, il est assez facile de lancer un cluster exécutant des applications node.js ou Nginx. Cependant, si vous essayez d’utiliser quelque chose de plus élaboré, comme une véritable application en cluster ou distribuée (comme celles basées sur Orleans), vous rencontrerez des problèmes pour la configurer. Cela n’a rien d’impossible, mais ce n’est pas aussi simple que pour les applications web.

Le clustering Docker consiste à regrouper plusieurs hôtes pour qu’ils fonctionnent sous la forme d’un seul pool de ressources, managé à l’aide d’un orchestrateur de conteneurs. Docker Inc. fournit Swarm pour permettre l’orchestration de conteneurs, alors que Google propose Kubernetes (alias K8s). Il existe d’autres orchestrateurs tels que DC/OS, Mesos, mais dans ce document, nous allons parler de Swarm et de K8s, car ils sont plus largement utilisés.

Les mêmes interfaces et la même implémentation des grains qui s’exécutent partout où Orleans est déjà pris en charge s’exécutent également sur des conteneurs Docker. Vous n’avez pas à prendre en compte de considérations particulières pour pouvoir exécuter votre application dans des conteneurs Docker.

Les concepts présentés ici peuvent être utilisés à la fois sur les variantes .NET Core et .NET 4.6.1 d’Orleans, mais pour illustrer la nature multiplateforme de Docker et de .NET Core, nous allons nous concentrer sur l’exemple en partant du principe que vous utilisez .NET Core. Des détails spécifiques à la plateforme (Windows/Linux/OSX) peuvent être fournis dans cet article.

Prérequis

Cet article suppose que les prérequis suivants sont installés :

  • Docker - Docker4X dispose d’un programme d’installation facile à utiliser pour les principales plateformes prises en charge. Il contient le moteur Docker ainsi que Docker Swarm.
  • Kubernetes (K8s) - Offre de Google pour l’orchestration de conteneurs. Elle contient des conseils d’aide pour l’installation de Minikube (un déploiement local de K8s) et de kubectl ainsi que de toutes ses dépendances.
  • .NET – Saveur multiplateforme de .NET
  • Visual Studio Code (VS Code) - Vous pouvez utiliser l’IDE de votre choix. Dans la mesure où VS Code est multiplateforme, nous l’utilisons pour garantir un fonctionnement sur toutes les plateformes. Une fois que vous avez installé VS Code, installez l’extension C#.

Important

Vous n’êtes pas obligé d’installer Kubernetes si vous ne comptez pas l’utiliser. Le programme d’installation de Docker4X inclut déjà Swarm, aucune installation supplémentaire n’est donc nécessaire pour l’utiliser.

Notes

Sur Windows, le programme d’installation de Docker active Hyper-V durant le processus d’installation. Dans la mesure où cet article et ses exemples utilisent .NET Core, les images conteneur utilisées sont basées sur Windows Server NanoServer. Si vous ne prévoyez pas d’utiliser .NET Core et si vous allez cibler .NET Full Framework 4.6.1, l’image utilisée doit être Windows Server Core et la version 1.4+ de Orleans (qui prend en charge uniquement .NET Full Framework).

Créer une solution Orleans

Les instructions suivantes montrent comment créer une solution Orleans classique en utilisant les nouveaux outils dotnet.

Adaptez les commandes en fonction de ce qui est approprié sur votre plateforme. De même, la structure des répertoires n’est qu’une suggestion. Adaptez-la à vos besoins.

mkdir Orleans-Docker
cd Orleans-Docker
dotnet new sln
mkdir -p src/OrleansSilo
mkdir -p src/OrleansClient
mkdir -p src/OrleansGrains
mkdir -p src/OrleansGrainInterfaces
dotnet new console -o src/OrleansSilo --framework netcoreapp1.1
dotnet new console -o src/OrleansClient --framework netcoreapp1.1
dotnet new classlib -o src/OrleansGrains --framework netstandard1.5
dotnet new classlib -o src/OrleansGrainInterfaces --framework netstandard1.5
dotnet sln add src/OrleansSilo/OrleansSilo.csproj
dotnet sln add src/OrleansClient/OrleansClient.csproj
dotnet sln add src/OrleansGrains/OrleansGrains.csproj
dotnet sln add src/OrleansGrainInterfaces/OrleansGrainInterfaces.csproj
dotnet add src/OrleansClient/OrleansClient.csproj reference src/OrleansGrainInterfaces/OrleansGrainInterfaces.csproj
dotnet add src/OrleansSilo/OrleansSilo.csproj reference src/OrleansGrainInterfaces/OrleansGrainInterfaces.csproj
dotnet add src/OrleansGrains/OrleansGrains.csproj reference src/OrleansGrainInterfaces/OrleansGrainInterfaces.csproj
dotnet add src/OrleansSilo/OrleansSilo.csproj reference src/OrleansGrains/OrleansGrains.csproj

Jusqu’à présent, nous avons simplement écrit du code réutilisable pour créer la structure de la solution et les projets, et ajouter des références entre les projets. Rien de différent par rapport à un projet Orleans classique.

Au moment où cet article a été rédigé, Orleans 2.0 (qui est seule version prenant en charge .NET Core et le multiplateforme) était en Technology Preview : ses packages NuGet sont donc hébergés dans un flux MyGet et ne sont pas publiés sur le flux officiel Nuget.org. Pour installer les packages NuGet en préversion, nous allons utiliser l’interface CLI dotnet en forçant le flux source et la version à partir de MyGet :

dotnet add src/OrleansClient/OrleansClient.csproj package Microsoft.Orleans.Core -s https://dotnet.myget.org/F/orleans-prerelease/api/v3/index.json -v 2.0.0-preview2-201705020000
dotnet add src/OrleansGrainInterfaces/OrleansGrainInterfaces.csproj package Microsoft.Orleans.Core -s https://dotnet.myget.org/F/orleans-prerelease/api/v3/index.json -v 2.0.0-preview2-201705020000
dotnet add src/OrleansGrains/OrleansGrains.csproj package Microsoft.Orleans.Core -s https://dotnet.myget.org/F/orleans-prerelease/api/v3/index.json -v 2.0.0-preview2-201705020000
dotnet add src/OrleansSilo/OrleansSilo.csproj package Microsoft.Orleans.Core -s https://dotnet.myget.org/F/orleans-prerelease/api/v3/index.json -v 2.0.0-preview2-201705020000
dotnet add src/OrleansSilo/OrleansSilo.csproj package Microsoft.Orleans.OrleansRuntime -s https://dotnet.myget.org/F/orleans-prerelease/api/v3/index.json -v 2.0.0-preview2-201705020000
dotnet restore

Vous disposez à présent de toutes les dépendances de base pour exécuter une application Orleans simple. Notez que jusqu’à présent, rien n’a changé par rapport à votre application Orleans classique. À présent, ajoutons du code pour pouvoir en faire quelque chose.

Implémenter votre application Orleans

En supposant que vous utilisiez VS Code, dans le répertoire de la solution, exécutez code .. Cela entraîne l’ouverture du répertoire dans VS Code et le chargement de la solution.

Il s’agit de la structure de solution que nous venons de créer.

Visual Studio Code: Explorer with Program.cs selected.

Nous avons également ajouté les fichiers Program.cs, OrleansHostWrapper.cs, IGreetingGrain.cs et GreetingGrain.cs respectivement aux interfaces et aux projets de grains. Voici le code de ces fichiers :

IGreetingGrain.cs :

using System;
using System.Threading.Tasks;
using Orleans;

namespace OrleansGrainInterfaces
{
    public interface IGreetingGrain : IGrainWithGuidKey
    {
        Task<string> SayHello(string name);
    }
}

GreetingGrain.cs :

using System;
using System.Threading.Tasks;
using OrleansGrainInterfaces;

namespace OrleansGrains
{
    public class GreetingGrain : Grain, IGreetingGrain
    {
        public Task<string> SayHello(string name)
        {
            return Task.FromResult($"Hello from Orleans, {name}");
        }
    }
}

OrleansHostWrapper.cs :

using System;
using System.NET;
using Orleans.Runtime;
using Orleans.Runtime.Configuration;
using Orleans.Runtime.Host;

namespace OrleansSilo;

public class OrleansHostWrapper
{
    private readonly SiloHost _siloHost;

    public OrleansHostWrapper(ClusterConfiguration config)
    {
        _siloHost = new SiloHost(Dns.GetHostName(), config);
        _siloHost.LoadOrleansConfig();
    }

    public int Run()
    {
        if (_siloHost is null)
        {
            return 1;
        }

        try
        {
            _siloHost.InitializeOrleansSilo();

            if (_siloHost.StartOrleansSilo())
            {
                Console.WriteLine(
                    $"Successfully started Orleans silo '{_siloHost.Name}' as a {_siloHost.Type} node.");
                return 0;
            }
            else
            {
                throw new OrleansException(
                    $"Failed to start Orleans silo '{_siloHost.Name}' as a {_siloHost.Type} node.");
            }
        }
        catch (Exception exc)
        {
            _siloHost.ReportStartupError(exc);
            Console.Error.WriteLine(exc);

            return 1;
        }
    }

    public int Stop()
    {
        if (_siloHost is not null)
        {
            try
            {
                _siloHost.StopOrleansSilo();
                _siloHost.Dispose();
                Console.WriteLine($"Orleans silo '{_siloHost.Name}' shutdown.");
            }
            catch (Exception exc)
            {
                siloHost.ReportStartupError(exc);
                Console.Error.WriteLine(exc);

                return 1;
            }
        }
        return 0;
    }
}

Program.cs (Silo) :

using System;
using System.Collections.Generic;
using System.Linq;
using System.NET;
using System.Threading.Tasks;
using Orleans.Runtime.Configuration;

namespace OrleansSilo
{
    public class Program
    {
        private static OrleansHostWrapper s_hostWrapper;

        static async Task<int> Main(string[] args)
        {
            int exitCode = await InitializeOrleansAsync();

            Console.WriteLine("Press Enter to terminate...");
            Console.ReadLine();

            exitCode += ShutdownSilo();

            return exitCode;
        }

        private static int InitializeOrleansAsync()
        {
            var config = new ClusterConfiguration();
            config.Globals.DataConnectionString =
                "[AZURE STORAGE CONNECTION STRING HERE]";
            config.Globals.DeploymentId = "Orleans-Docker";
            config.Globals.LivenessType =
                GlobalConfiguration.LivenessProviderType.AzureTable;
            config.Globals.ReminderServiceType =
                GlobalConfiguration.ReminderServiceProviderType.AzureTable;
            config.Defaults.PropagateActivityId = true;
            config.Defaults.ProxyGatewayEndpoint =
                new IPEndPoint(IPAddress.Any, 10400);
            config.Defaults.Port = 10300;
            var ips = await Dns.GetHostAddressesAsync(Dns.GetHostName());
            config.Defaults.HostNameOrIPAddress =
                ips.FirstOrDefault()?.ToString();

            s_hostWrapper = new OrleansHostWrapper(config);
            return hostWrapper.Run();
        }

        static int ShutdownSilo() =>
            s_hostWrapper?.Stop() ?? 0;
    }
}

Program.cs (client) :

using System;
using System.NET;
using System.Threading;
using System.Threading.Tasks;
using Orleans;
using Orleans.Runtime.Configuration;
using OrleansGrainInterfaces;

namespace OrleansClient
{
    class Program
    {
        private static IClusterClient s_client;
        private static bool s_running;

        static async Task Main(string[] args)
        {
            await InitializeOrleansAsync();

            Console.ReadLine();

            s_running = false;
        }

        static async Task InitializeOrleansAsync()
        {
            var config = new ClientConfiguration
            {
                DeploymentId = "Orleans-Docker";
                PropagateActivityId = true;
            };
            var hostEntry =
                await Dns.GetHostEntryAsync("orleans-silo");
            var ip = hostEntry.AddressList[0];
            config.Gateways.Add(new IPEndPoint(ip, 10400));

            Console.WriteLine("Initializing...");

            using client = new ClientBuilder().UseConfiguration(config).Build();
            await client.Connect();
            s_running = true;
            Console.WriteLine("Initialized!");

            var grain = client.GetGrain<IGreetingGrain>(Guid.Empty);

            while (s_running)
            {
                var response = await grain.SayHello("Gutemberg");
                Console.WriteLine($"[{DateTime.UtcNow}] - {response}");

                await Task.Delay(1000);
            }
        }
    }
}

Nous n’entrons pas dans les détails de l’implémentation du grain ici, car cela dépasse le cadre de cet article. Consultez les autres documents qui s’y rapportent. Ces fichiers sont essentiellement une application Orleans minimale. Nous allons nous en servir comme point de départ avant de passer au reste de cet article.

Dans cet article, nous utilisons le fournisseur d’appartenances OrleansAzureUtils, mais vous pouvez utiliser n’importe quel autre fournisseur déjà pris en charge par Orleans.

Le fichier Dockerfile

Pour créer votre conteneur, Docker utilise des images. Pour plus d’informations sur une création personnalisée, vous pouvez consulter la documentation Docker. Dans cet article, nous allons utiliser des images Microsoft officielles. En fonction de la cible et des plateformes de développement, vous devez choisir l’image appropriée. Dans cet article, nous utilisons microsoft/dotnet:1.1.2-sdk, qui est une image Linux. Vous pouvez utiliser microsoft/dotnet:1.1.2-sdk-nanoserver pour Windows par exemple. Choisissez-en une qui correspond à vos besoins.

Remarque pour les utilisateurs Windows : Comme indiqué précédemment, pour obtenir un résultat multiplateforme, nous utilisons dans cet article .NET Core et Orleans Technical Preview 2.0. Si vous voulez utiliser Docker sur Windows avec la version complète d’Orleans 1.4+, vous devez utiliser les images Windows Server Core, car les images NanoServer et Linux prennent en charge seulement .NET Core.

Dockerfile.debug :

FROM microsoft/dotnet:1.1.2-sdk
ENV NUGET_XMLDOC_MODE skip
WORKDIR /vsdbg
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        unzip \
    && rm -rf /var/lib/apt/lists/* \
    && curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg
WORKDIR /app
ENTRYPOINT ["tail", "-f", "/dev/null"]

Ce fichier Dockerfile télécharge et installe essentiellement le débogueur VSdbg, puis démarre un conteneur vide, en le maintenant toujours actif pour que nous n’ayons pas besoin de l’activer/le désactiver durant le débogage.

Désormais, pour la production, l’image est plus petite, car elle contient uniquement le runtime .NET Core et non l’ensemble du kit SDK. De plus, le fichier Dockerfile est un peu plus simple :

Dockerfile :

FROM microsoft/dotnet:1.1.2-runtime
WORKDIR /app
ENTRYPOINT ["dotnet", "OrleansSilo.dll"]
COPY . /app

Le fichier docker-compose

Le fichier docker-compose.yml définit essentiellement (dans un projet) un ensemble de services et ses dépendances au niveau du service. Chaque service contient une ou plusieurs instances d’un conteneur donné, en fonction des images que vous avez sélectionnées dans votre fichier Dockerfile. Vous trouverez plus d’informations sur le fichier docker-compose dans la documentation relative à docker-compose.

Pour un déploiement Orleans, il est courant d’avoir un fichier docker-compose.yml qui contient deux services. Un pour le silo Orleans et l’autre pour le client Orleans. Le client a une dépendance par rapport au silo, ce qui signifie qu’il ne démarre qu’une fois le service de silo activé. Vous pouvez également ajouter un stockage/service de base de données/conteneur, par exemple SQL Server, qui doit démarrer avant le client et le silo. Ainsi, les deux services doivent avoir une dépendance.

Notes

Avant de poursuivre votre lecture, notez que la mise en retraitest importante dans les fichiers docker-compose. Pensez à vérifier ce point en cas de problème.

Voici comment nous allons décrire nos services pour cet article :

docker-compose.override.yml (débogage) :

version: '3.1'

services:
  orleans-client:
    image: orleans-client:debug
    build:
      context: ./src/OrleansClient/bin/PublishOutput/
      dockerfile: Dockerfile.Debug
    volumes:
      - ./src/OrleansClient/bin/PublishOutput/:/app
      - ~/.nuget/packages:/root/.nuget/packages:ro
    depends_on:
      - orleans-silo
  orleans-silo:
    image: orleans-silo:debug
    build:
      context: ./src/OrleansSilo/bin/PublishOutput/
      dockerfile: Dockerfile.Debug
    volumes:
      - ./src/OrleansSilo/bin/PublishOutput/:/app
      - ~/.nuget/packages:/root/.nuget/packages:ro

docker-compose.yml (production) :

version: '3.1'

services:
  orleans-client:
    image: orleans-client
    depends_on:
      - orleans-silo
  orleans-silo:
    image: orleans-silo

En production, nous ne mappons pas le répertoire local, et nous n’avons pas non plus l’action build:. En effet, en production, les images doivent être générées et envoyées (push) vers votre propre registre Docker.

Regrouper tous les éléments

Nous avons maintenant tous les éléments nécessaires à l’exécution de votre application Orleans. Nous allons les assembler pour pouvoir exécuter notre solution Orleans dans Docker (enfin !).

Important

Vous devez exécuter les commandes suivantes à partir du répertoire de la solution.

Tout d’abord, vérifions que nous restaurons tous les packages NuGet à partir de notre solution. Vous ne devez effectuer cette opération qu’une seule fois. Vous devez uniquement la répéter si vous changez une dépendance de package dans votre projet.

dotnet restore

À présent, générons notre solution à l’aide de l’interface CLI dotnet comme d’habitude, et publions-la sur un répertoire de sortie :

dotnet publish -o ./bin/PublishOutput

Conseil

Nous utilisons ici publish à la place de build, pour éviter tout problème avec nos assemblys chargés dynamiquement dans Orleans. Nous sommes toujours à la recherche d’une meilleure solution.

Une fois l’application générée et publiée, vous devez générer vos images de fichiers Dockerfile. Cette étape ne doit être effectuée qu’une seule fois par projet et ne doit être répétée que si vous changez le fichier Dockerfile, docker-compose, ou si vous avez nettoyé votre registre d’images local pour une raison quelconque.

docker-compose build

Toutes les images utilisées dans Dockerfile et docker-compose.yml sont tirées (pull) du registre et mises en cache sur votre machine de développement. Vos images sont générées, et vous êtes prêt pour l’exécution.

À présent, lançons l’exécution !

# docker-compose up -d
Creating network "orleansdocker_default" with the default driver
Creating orleansdocker_orleans-silo_1 ...
Creating orleansdocker_orleans-silo_1 ... done
Creating orleansdocker_orleans-client_1 ...
Creating orleansdocker_orleans-client_1 ... done
#

Si vous exécutez docker-compose ps, vous voyez 2 conteneurs en cours d’exécution pour le projet orleansdocker :

# docker-compose ps
             Name                     Command        State   Ports
------------------------------------------------------------------
orleansdocker_orleans-client_1   tail -f /dev/null   Up
orleansdocker_orleans-silo_1     tail -f /dev/null   Up

Notes

Si vous êtes sur Windows et si votre conteneur utilise une image Windows, la colonne Command vous montre la commande PowerShell relative à tail sur les systèmes *NIX pour que le conteneur suive le même processus.

Maintenant que vos conteneurs sont opérationnels, vous n’avez pas besoin de les arrêter chaque fois que vous voulez démarrer votre application Orleans. Il vous suffit d’intégrer votre IDE pour déboguer l’application au sein du conteneur qui a été mappé dans votre fichier docker-compose.yml.

Mise à l'échelle

Une fois votre projet compose en cours d’exécution, vous pouvez facilement effectuer un scale-up ou un scale-down de l’application à l’aide de la commande docker-compose scale :

# docker-compose scale orleans-silo=15
Starting orleansdocker_orleans-silo_1 ... done
Creating orleansdocker_orleans-silo_2 ...
Creating orleansdocker_orleans-silo_3 ...
Creating orleansdocker_orleans-silo_4 ...
Creating orleansdocker_orleans-silo_5 ...
Creating orleansdocker_orleans-silo_6 ...
Creating orleansdocker_orleans-silo_7 ...
Creating orleansdocker_orleans-silo_8 ...
Creating orleansdocker_orleans-silo_9 ...
Creating orleansdocker_orleans-silo_10 ...
Creating orleansdocker_orleans-silo_11 ...
Creating orleansdocker_orleans-silo_12 ...
Creating orleansdocker_orleans-silo_13 ...
Creating orleansdocker_orleans-silo_14 ...
Creating orleansdocker_orleans-silo_15 ...
Creating orleansdocker_orleans-silo_6
Creating orleansdocker_orleans-silo_5
Creating orleansdocker_orleans-silo_3
Creating orleansdocker_orleans-silo_2
Creating orleansdocker_orleans-silo_4
Creating orleansdocker_orleans-silo_9
Creating orleansdocker_orleans-silo_7
Creating orleansdocker_orleans-silo_8
Creating orleansdocker_orleans-silo_10
Creating orleansdocker_orleans-silo_11
Creating orleansdocker_orleans-silo_15
Creating orleansdocker_orleans-silo_12
Creating orleansdocker_orleans-silo_14
Creating orleansdocker_orleans-silo_13

Après quelques secondes, vous voyez que les services sont mis à l’échelle en fonction du nombre spécifique d’instances que vous avez demandé.

# docker-compose ps
             Name                     Command        State   Ports
------------------------------------------------------------------
orleansdocker_orleans-client_1   tail -f /dev/null   Up
orleansdocker_orleans-silo_1     tail -f /dev/null   Up
orleansdocker_orleans-silo_10    tail -f /dev/null   Up
orleansdocker_orleans-silo_11    tail -f /dev/null   Up
orleansdocker_orleans-silo_12    tail -f /dev/null   Up
orleansdocker_orleans-silo_13    tail -f /dev/null   Up
orleansdocker_orleans-silo_14    tail -f /dev/null   Up
orleansdocker_orleans-silo_15    tail -f /dev/null   Up
orleansdocker_orleans-silo_2     tail -f /dev/null   Up
orleansdocker_orleans-silo_3     tail -f /dev/null   Up
orleansdocker_orleans-silo_4     tail -f /dev/null   Up
orleansdocker_orleans-silo_5     tail -f /dev/null   Up
orleansdocker_orleans-silo_6     tail -f /dev/null   Up
orleansdocker_orleans-silo_7     tail -f /dev/null   Up
orleansdocker_orleans-silo_8     tail -f /dev/null   Up
orleansdocker_orleans-silo_9     tail -f /dev/null   Up

Important

La colonne Command de ces exemples affiche la commande tail simplement parce que nous utilisons le conteneur du débogueur. Si nous étions en production, dotnet OrleansSilo.dll s’afficherait par exemple.

Docker swarm

La pile de clustering Docker s’appelle Swarm. Pour plus d’informations, consultez Docker Swarm.

Pour exécuter le contenu de cet article dans un cluster Swarm, vous n’avez aucun travail supplémentaire à effectuer. Quand vous exécutez docker-compose up -d dans un nœud Swarm, il planifie les conteneurs en fonction des règles configurées. Il en va de même pour les autres services basés sur Swarm, par exemple Docker Datacenter, Azure ACS (en mode Swarm) et AWS ECS Container Service. Il vous suffit de déployer votre cluster Swarm avant de déployer votre application Orleansdockerisée.

Notes

Si vous utilisez un moteur Docker en mode Swarm prenant déjà en charge stack, deploy et compose v3, docker stack deploy -c docker-compose.yml <name> est une meilleure approche pour déployer votre solution. Gardez simplement à l’esprit qu’un fichier compose v3 est nécessaire pour la prise en charge de votre moteur Docker. De plus, la majorité des services hébergés tels qu’Azure et AWS utilisent toujours les moteurs v2 ou plus anciens.

Google Kubernetes (K8s)

Si vous prévoyez d’utiliser Kubernetes pour héberger Orleans, un fournisseur de clustering géré par la communauté est disponible sur OrleansContrib\Orleans.Clustering.Kubernetes. Vous y trouverez de la documentation et des exemples sur la façon d’héberger Orleans dans Kubernetes de façon transparente en utilisant le fournisseur.

Déboguer Orleans dans des conteneurs

Maintenant que vous savez comment exécuter Orleans dans un conteneur à partir de zéro, il est temps de tirer parti d’un des principes les plus importants de Docker. Les conteneurs sont immuables. De plus, ils doivent avoir (quasiment) la même image, les mêmes dépendances et le même runtime en développement qu’en production. Ainsi, le bon vieux « Ça fonctionne sur ma machine ! » disparaîtra définitivement. Pour ce faire, vous devez disposer d’un moyen de développer à l’intérieur du conteneur, ce qui implique l’attachement d’un débogueur à votre application au sein du conteneur.

Il existe plusieurs façons d’y parvenir à l’aide de plusieurs outils. Après en avoir évalué plusieurs, au moment où j’ai écrit cet article, j’ai fini par en choisir un qui a l’air plus simple et moins intrusif dans l’application.

Comme indiqué plus haut dans cet article, nous utilisons VSCode pour développer l’exemple. Voici donc comment attacher le débogueur à votre application Orleans au sein du conteneur.

Tout d’abord, changez deux fichiers dans le répertoire .vscode de votre solution :

tasks.json :

{
    "version": "0.1.0",
    "command": "dotnet",
    "isShellCommand": true,
    "args": [],
    "tasks": [
        {
            "taskName": "publish",
            "args": [
                "${workspaceRoot}/Orleans-Docker.sln", "-c", "Debug", "-o", "./bin/PublishOutput"
            ],
            "isBuildCommand": true,
            "problemMatcher": "$msCompile"
        }
    ]
}

Ce fichier indique essentiellement à VSCode que chaque fois que vous générez le projet, il va exécuter la commande publish, comme nous l’avons déjà fait manuellement.

launch.json :

{
   "version": "0.2.0",
   "configurations": [
        {
            "name": "Silo",
            "type": "coreclr",
            "request": "launch",
            "cwd": "/app",
            "program": "/app/OrleansSilo.dll",
            "sourceFileMap": {
                "/app": "${workspaceRoot}/src/OrleansSilo"
            },
            "pipeTransport": {
                "debuggerPath": "/vsdbg/vsdbg",
                "pipeProgram": "/bin/bash",
                "pipeCwd": "${workspaceRoot}",
                "pipeArgs": [
                    "-c",
                    "docker exec -i orleansdocker_orleans-silo_1 /vsdbg/vsdbg --interpreter=vscode"
                ]
            }
        },
        {
            "name": "Client",
            "type": "coreclr",
            "request": "launch",
            "cwd": "/app",
            "program": "/app/OrleansClient.dll",
            "sourceFileMap": {
                "/app": "${workspaceRoot}/src/OrleansClient"
            },
            "pipeTransport": {
                "debuggerPath": "/vsdbg/vsdbg",
                "pipeProgram": "/bin/bash",
                "pipeCwd": "${workspaceRoot}",
                "pipeArgs": [
                    "-c",
                    "docker exec -i orleansdocker_orleans-client_1 /vsdbg/vsdbg --interpreter=vscode"
                ]
            }
        }
    ]
}

Vous pouvez désormais générer la solution à partir de VSCode (qui va la publier), et démarrer à la fois le silo et le client. Il envoie une commande docker exec à l’instance/au conteneur de service docker-compose en cours d’exécution pour démarrer le débogueur de l’application, et c’est tout. Le débogueur est attaché au conteneur, et vous l’utilisez comme s’il s’agissait d’une application Orleans s’exécutant localement. La différence vient du fait qu’il se trouve à l’intérieur du conteneur. Une fois que vous avez fini, il vous suffit de publier le conteneur sur votre registre et de le tirer (pull) sur vos hôtes Docker en production.