Condividi tramite


Distribuzione di Docker

Suggerimento

Anche se si ha familiarità con Docker o Orleans, è consigliabile leggere questo articolo fino alla fine per evitare eventuali problemi per cui esistono soluzioni alternative.

Questo articolo e il relativo esempio sono ancora un work in progress. Feedback, richieste pull o suggerimenti sono graditi.

Distribuire soluzioni Orleans in Docker

La distribuzione Orleans in Docker potrebbe essere complessa, considerando il modo in cui gli genti di orchestrazione Docker e gli stack di clustering sono progettati. L’aspetto più complicato consiste nel comprendere il concetto di Rete sovrapposta da Docker Swarm e dal modello di networking di Kubernetes.

I contenitori Docker e i modelli di rete sono progettati principalmente per l'esecuzione di contenitori senza stato e non modificabili. Quindi, la rotazione di un cluster che esegue node.js o applicazioni Nginx è un’operazione piuttosto semplice. Tuttavia, se si tenta di usare qualcosa di più elaborato, ad esempio un'applicazione reale in cluster o distribuita (ad esempio, quelle basate su Orleans), si riscontreranno problemi nella configurazione. Questo è possibile, ma non semplice quanto le applicazioni basate sul Web.

Il clustering Docker consiste nel raggruppare più host perché funzionino come un singolo pool di risorse, gestito usando un agente di orchestrazione contenitori. Docker Inc. fornisce Swarm come opzione per l'orchestrazione dei contenitori, mentre Google ha Kubernetes (noto anche come K8s). Esistono altri agenti di orchestrazione, come DC/OS e Mesos, ma questo documento tratterà di Swarm e K8s, poiché sono di uso più comune.

Le stesse interfacce di granularità e implementazione eseguite ovunque Orleans sia già supportato verranno eseguite anche nei contenitori Docker. Non sono necessarie considerazioni particolari per poter eseguire l'applicazione nei contenitori Docker.

I concetti qui illustrati possono essere usati sia nelle versioni .NET Core che .NET 4.6.1 di Orleans, ma al fine di illustrare la natura multipiattaforma di Docker e di .NET Core, questo documento si focalizzerà sull’esempio di uso di .NET Core. Questo articolo potrebbe fornire dettagli specifici alla piattaforma (Windows/Linux/OSX).

Prerequisiti

Questo articolo presuppone che siano installati i seguenti prerequisiti:

  • Docker: Docker4X include un programma di installazione di semplice uso per le principali piattaforme supportate. Contiene il motore Docker e Docker Swarm.
  • Kubernetes (K8s): la soluzione Google per l'orchestrazione dei contenitori. Contiene indicazioni per installare Minikube (una distribuzione locale di K8s) e kubectl insieme a tutte le relative dipendenze.
  • .NET: versione multipiattaforma di .NET
  • Visual Studio Code (VSCode): è possibile usare qualsiasi IDE desiderato. VSCode è multipiattaforma, e viene quindi usato per accertarsi che funzioni correttamente su tutte le piattaforme. Dopo aver installato VSCode, installare l’estensione C#.

Importante

Non è necessario che Kubernetes sia installato se non si intende farne uso. Il programma di installazione Docker4X include già Swarm, quindi non è necessaria alcuna installazione aggiuntiva per farne uso.

Nota

In Windows, il programma di installazione di Docker abiliterà Hyper-V durante il processo di installazione. Poiché questo articolo e i relativi esempi usano .NET Core, le immagini del contenitore usate sono basate su Windows Server NanoServer. Se non si prevede di usare .NET Core ma il framework completo .NET 4.6.1, l'immagine usata deve essere Windows Server Core e la versione 1.4+ di Orleans (che supporta solo il framework completo di .NET).

Crea la soluzione Orleans

Le seguenti istruzioni illustrano come creare una normale soluzione Orleans usando i nuovi strumenti dotnet.

Adattare i comandi in base alla piattaforma in uso. Inoltre, la struttura di directory è semplicemente un suggerimento. Si prega di adattarla alle proprie esigenze.

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

Finora si è usato solo codice boilerplate per creare la struttura e i progetti della soluzione e aggiungere riferimenti tra progetti. Ciò non differisce da un ordinario progetto Orleans.

Al momento della scrittura di questo articolo, Orleans 2.0 (l'unica versione che supporta .NET Core e multipiattaforma) è in Technology Preview, in modo che i pacchetti NuGet siano ospitati in un feed MyGet e non pubblicati sul feed ufficiale di Nuget.org. Per installare i pacchetti NuGet di anteprima, si userà l'interfaccia dotnet della riga di comando, forzando il feed di origine e la versione da 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

Ora si hanno a disposizione tutte le dipendenze di base per eseguire una semplice applicazione Orleans. Notare che finora non ci sono differenze rispetto all’ordinaria applicazione Orleans. A questo punto, aggiungere del codice in modo da poter eseguire un'operazione.

Implementare l'applicazione Orleans

Supponendo che si stia usando VSCode, eseguire code . dalla directory della soluzione. Questa operazione aprirà la directory in VSCode e caricherà la soluzione.

Si tratta della struttura della soluzione appena creata.

Visual Studio Code: strumento di esplorazione con Program.cs selezionato.

Abbiamo anche aggiunto i file Program.cs, OrleansHostWrapper.cs, IGreetingGrain.cs e GreetingGrain.cs rispettivamente ai progetti di interfaccia e di intervallo; ecco il codice per questi file:

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

Non verranno fornite informazioni dettagliate sull'implementazione della granularità poiché ciò non rientra nell'ambito di questo articolo. Consultare altri documenti correlati. Questi file sono essenzialmente un'applicazione minimale Orleans, ed essa sarà il punto di partenza per il resto questo articolo.

In questo articolo, viene usato il provider di appartenenze OrleansAzureUtils, ma è possibile usare qualsiasi altro file già supportato da Orleans.

Dockerfile

Per creare il contenitore, Docker usa immagini. Per ulteriori informazioni su come creare il proprio, consultare la documentazione di Docker. In questo articolo verranno usate immagini di Microsoft ufficiali. In base alle piattaforme di destinazione e sviluppo, è necessario selezionare l'immagine appropriata. In questo articolo viene usata microsoft/dotnet:1.1.2-sdk, un'immagine basata su Linux. Ad esempio, è possibile usare microsoft/dotnet:1.1.2-sdk-nanoserver per Windows. Scegliere una soluzione adatta alle proprie esigenze.

Nota per gli utenti di Windows: come accennato in precedenza, questo articolo usa .NET Core e Orleans Technical preview 2.0 al fine di essere multipiattaforma. Se si desidera usare Docker in Windows con la versione interamente rilasciata Orleans 1.4+, è necessario usare le immagini basate su Windows Server Core, poiché NanoServer e le immagini basate su Linux supportano solo .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"]

Essenzialmente, questo Dockerfile scarica e installa il debugger VSdbg e avvia un contenitore vuoto, mantenendolo attivo a tempo indeterminato in modo che non siano necessari tear up o tear down durante il debug.

Per produzione, l'immagine è più piccola perché contiene solo il runtime di .NET Core e non l'intero SDK, quindi il dockerfile risulta più semplice:

Dockerfile:

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

File docker-compose

Essenzialmente, il file docker-compose.yml definisce (all'interno di un progetto) un set di servizi e le relative dipendenze a livello di servizio. Ogni servizio contiene una o più istanze di un determinato contenitore, basandosi sulle immagini selezionate nel Dockerfile. Ulteriori informazioni su docker-compose sono disponibili nella documentazione di docker-compose.

Per una distribuzione Orleans, un caso comune consiste nell'avere un docker-compose.yml contenente due servizi. Uno per Silo Orleans e l'altro per Client Orleans. Il client avrà una dipendenza dal silo, e quindi verrà avviato solo dopo che il servizio Silo è attivo. Un altro caso consiste nell'aggiungere un contenitore o un servizio di archiviazione o database (ad esempio SQL Server), che dovrà essere inizializzato prima del client e del silo; in questo modo, entrambi avrebbero una dipendenza da esso.

Nota

Prima di continuare la lettura, tenere presente che il rientro è importante nei file docker-compose. Quindi, prestare attenzione ad esso se si dovessero riscontrare problemi.

Ecco come i nostri servizi saranno descritti in questo articolo:

docker-compose.override.yml (Debug):

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 (produzione):

version: '3.1'

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

In produzione, non viene eseguito il mapping della directory locale e non è disponibile alcuna azione build:. Il motivo è che in produzione, le immagini devono essere compilate e ne deve essere eseguito il push nel proprio Registro Docker.

Mettere insieme tutti gli elementi

Ora che si hanno a disposizione tutti gli elementi mobili necessari per l’esecuzione dell'applicazione Orleans, è il momento di unire i pezzi così da poter (finalmente!) eseguire la soluzione Orleans all'interno di Docker.

Importante

I seguenti comandi devono essere eseguiti dalla directory della soluzione.

Prima di tutto, accertarsi di ripristinare tutti i pacchetti NuGet dalla soluzione. È necessario svolgere quest’operazione una volta sola. Sarà necessario eseguire nuovamente l’operazione solo se si modifica una dipendenza del pacchetto nel progetto.

dotnet restore

A questo punto, compilare la soluzione usando l'interfaccia della riga di comando dotnet come di consueto e pubblicarla in una directory di output:

dotnet publish -o ./bin/PublishOutput

Suggerimento

In questo caso si sta usando publish anziché compilare per evitare problemi relativi gli assembly caricati dinamicamente in Orleans. Stiamo ancora lavorando a una soluzione migliore per questo problema.

Una volta che l'applicazione è compilata e pubblicata, è necessario compilare le immagini Dockerfile. Questo passaggio deve essere eseguito una sola volta per ogni progetto e deve essere eseguito nuovamente solo se si dovesse modificare il Dockerfile, docker-compose o se per qualsiasi motivo il registro delle immagini locali sia stato pulito.

docker-compose build

Tutte le immagini usate in Dockerfile e docker-compose.yml vengono estratte dal Registro di sistema e memorizzate nella cache nel computer di sviluppo. Le immagini vengono compilate e sono pronte per l'esecuzione.

Ora si può procedere con l’esecuzione.

# 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
#

Se si esegue un docker-compose ps, verranno visualizzati 2 contenitori in esecuzione per il progetto 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

Nota

Se si usa Windows e il contenitore usa un'immagine di Windows come base, la colonna Comando mostrerà il comando di PowerShell relativo a un tail in sistemi *NIX, in modo che il contenitore prosegua allo stesso modo.

Ora che i contenitori sono attivi, non è necessario arrestare il processo ogni volta che si vuole avviare l'applicazione Orleans. È sufficiente integrare l'IDE per eseguire il debug dell'applicazione all'interno del contenitore di cui è stato precedentemente eseguito il mapping in docker-compose.yml.

Scalabilità

Dopo aver eseguito il progetto compose, è possibile aumentare o ridurre facilmente l'applicazione tramite il comando 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

Dopo alcuni secondi, verranno mostrati i servizi ridimensionati in base al numero specifico di istanze richieste.

# 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

Importante

In questi esempi, la colonna Command mostra il comando tail solo perché si sta usando il contenitore del debugger. Se si fosse in produzione, la colonna mostrerebbe, ad esempio, dotnet OrleansSilo.dll.

Docker Swarm

Lo stack di clustering Docker è denominato Swarm. Per ulteriori informazioni, consultare Docker Swarm.

Per eseguire questo articolo in un cluster Swarm, non sarà necessario lavoro extra. Quando si esegue docker-compose up -d in un nodo Swarm, questo pianificherà i contenitori in base alle regole configurate. Lo stesso vale per altri servizi basati su Swarm, come ACS di Azure (in modalità Swarm) e il servizio ECS di AWS. Basterà distribuire il cluster Swarm prima di distribuire l'applicazione dockerizedOrleans.

Nota

Se si usa un motore Docker con la modalità Swarm che dispone già del supporto per stack, deploy, e compose v3, docker stack deploy -c docker-compose.yml <name> è un approccio migliore per la distribuzione della soluzione. Tenere presente che questo richiede il file di composizione v3 per supportare il motore Docker, e la maggior parte dei servizi ospitati, come Azure e AWS, usano ancora v2 e motori precedenti.

Google Kubernetes (K8s)

Se si prevede di utilizzare Kubernetes per l'hosting di Orleans, è disponibile un provider di clustering gestito dalla community all'indirizzo OrleansContrib\Orleans.Clustering.Kubernetes. Lì sono disponibili documentazione ed esempi su come ospitare Orleans in Kubernetes senza problemi usando il provider.

Eseguire il debug Orleans all'interno di contenitori

Ora che si sa come eseguire Orleans in un contenitore partendo da zero, è utile sfruttare uno dei principi più importanti di Docker. I contenitori non sono modificabili. E dovrebbero avere (quasi) la stessa immagine, dipendenze e runtime nello sviluppo che in produzione. In questo modo, si garantisce che il vecchio detto "Funziona sul mio computer!" non sia pronunciato mai più. Perché ciò sia possibile, è necessario un metodo per sviluppare all'interno del contenitore e che includa un debugger collegato all'applicazione all'interno del contenitore.

Esistono vari modi per ottenere questo risultato usando diversi strumenti. Dopo aver valutato diversi metodi, al momento della stesura di questo articolo, si è scelto quello che sembra più semplice ed è meno invadente nell'applicazione.

Come accennato in precedenza in questo articolo, si sta usando VSCode per sviluppare il campione; quindi, ecco come collegare il debugger all'applicazione Orleans all'interno del contenitore.

Prima di tutto, modificare due file all'interno della directory .vscode nella soluzione:

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

Essenzialmente, questo file comunica a VSCode che ogni volta che si compila il progetto, questo eseguirà il comando publish, come è stato fatto manualmente in precedenza.

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

A questo punto è possibile compilare la soluzione da VSCode (che pubblicherà) e avviare sia il silo che il client. Invierà un comando docker exec all'istanza o al contenitore del servizio docker-compose in esecuzione per avviare il debugger all'applicazione, e questo è tutto. Il debugger è collegato al contenitore e lo si usa come se fosse un'applicazione Orleans in esecuzione in locale. La differenza è che questa si trova all'interno del contenitore e, una volta completata l’operazione, è sufficiente pubblicare il contenitore nel registro ed eseguirne il pull negli host Docker in produzione.