Implantação do Docker

Dica

Mesmo que você esteja muito familiarizado com o Docker e/ou Orleans, como qualquer outra documentação de Orleans, é recomendável lê-lo até o fim para evitar problemas que você possa enfrentar que já trabalhamos.

Este artigo e seu exemplo são um trabalho em andamento. Qualquer comentário, PR ou sugestão é muito bem-vindo.

Implantar soluções do Orleans no Docker

A implantação do Orleans no Docker pode ser complicada, devido à maneira como os orquestradores do Docker e as pilhas de clustering foram projetados. O mais complicado é entender o conceito de Rede de Sobreposição do modelo de rede de Docker Swarm e Kubernetes.

Os contêineres do Docker e os modelos de rede foram projetados para executar principalmente contêineres sem estado e imutáveis. Portanto, girar um cluster executando aplicativos node.js ou Nginx é muito fácil. No entanto, se você tentar usar algo mais elaborado, como um aplicativo real clusterizado ou distribuído (como os baseados no Orleans), você eventualmente terá problemas para configurá-lo. É possível, mas não tão simples quanto aplicativos baseados na Web.

O clustering do Docker consiste em reunir vários hosts para funcionar como um único pool de recursos, gerenciado usando um Orquestrador de Contêineres. A Docker Inc. fornece o Swarm como opção para Orquestração de contêiner, enquanto o Google tem o Kubernetes (também conhecido como K8s). Há outros Orchestrators como DC/OS, Mesos, mas neste documento, falaremos sobre Swarm e K8s à medida que forem mais amplamente utilizados.

As mesmas interfaces e implementação de grãos que executam em qualquer lugar em que o Orleans já tem suporte também serão executadas em contêineres do Docker. Não são necessárias considerações especiais para poder executar seu aplicativo em contêineres do Docker.

Os conceitos discutidos aqui podem ser usados nos sabores .NET Core e .NET 4.6.1 do Orleans, mas para ilustrar a natureza multiplataforma do Docker e do .NET Core, vamos nos concentrar no exemplo considerando que você está usando o .NET Core. Detalhes específicos da plataforma (Windows/Linux/OSX) podem ser fornecidos neste artigo.

Pré-requisitos

Este artigo pressupõe que você tenha os seguintes pré-requisitos instalados:

  • O Docker – Docker4X tem um instalador fácil de usar para as principais plataformas com suporte. Ele contém o mecanismo do Docker e também o Docker Swarm.
  • Kubernetes (K8s) – oferta do Google para Orquestração de Contêineres. Ele contém diretrizes para instalar o Minikube (uma implantação local de K8s) e kubectl junto com todas as suas dependências.
  • .NET: versão multiplataforma do .NET
  • Visual Studio Code (VSCode) – você pode usar o IDE desejado. O VSCode é multiplataforma, portanto, estamos usando-o para garantir que ele funcione em todas as plataformas. Depois de instalar o VSCode, instale a extensão C#.

Importante

Você não precisará ter o Kubernetes instalado se não for usá-lo. O instalador do Docker4X já inclui o Swarm, portanto, nenhuma instalação extra é necessária para usá-lo.

Observação

No Windows, o instalador do Docker habilitará o Hyper-V no processo de instalação. Como este artigo e seus exemplos estão usando o .NET Core, as imagens de contêiner usadas são baseadas no NanoServer do Windows Server. Se você não planeja usar o .NET Core e terá como destino a estrutura completa do .NET 4.6.1, a imagem usada deverá ser o Windows Server Core e a versão 1.4+ do Orleans (que dá suporte apenas à estrutura completa do .NET).

Criar solução do Orleans

As instruções a seguir mostram como criar uma solução regular do Orleans usando as novas ferramentas dotnet.

Adapte os comandos ao que for apropriado em sua plataforma. Além disso, a estrutura de diretório é apenas uma sugestão. Adapte-o às suas necessidades.

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

O que fizemos até agora foi apenas um código clichê para criar a estrutura e projetos da solução e adicionar referências entre projetos. Nada diferente de um projeto regular do Orleans.

No momento em que este artigo foi escrito, o Orleans 2.0 (que é a única versão que dá suporte ao .NET Core e multiplataforma) está no Technology Preview para que seus pacotes NuGet sejam hospedados em um feed do MyGet e não sejam publicados em Nuget.org feed oficial. Para instalar os pacotes do NuGet de visualização, usaremos a CLI dotnet forçando o feed de origem e a versão do 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

Ok, agora você tem todas as dependências básicas para executar um aplicativo Orleans simples. Observe que até agora, nada mudou do seu aplicativo regular do Orleans. Agora, vamos adicionar algum código para que possamos fazer algo com ele.

Implementar seu aplicativo Orleans

Supondo que você esteja usando o VSCode, no diretório da solução, execute code .. Isso abrirá o diretório no VSCode e carregará a solução.

Essa é a estrutura de solução que acabamos de criar anteriormente.

Visual Studio Code: Explorer with Program.cs selected.

Também adicionamos arquivos Program.cs, OrleansHostWrapper.cs, IGreetingGrain.cs e GreetingGrain.cs aos projetos de interfaces e granularidade, respectivamente, e aqui está o código para esses arquivos:

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

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

Não entraremos em detalhes sobre a implementação de granularidade aqui, pois ela está fora do escopo deste artigo. Verifique outros documentos relacionados a ele. Esses arquivos são essencialmente um aplicativo mínimo do Orleans e começaremos a partir dele para avançar com o restante deste artigo.

Neste artigo, estamos usando o provedor de associação OrleansAzureUtils, mas você pode usar qualquer outro já compatível com o Orleans.

O Dockerfile

Para criar seu contêiner, o Docker usa imagens. Para obter mais detalhes sobre como criar sua própria, você pode verificar a documentação do Docker. Neste artigo, usaremos imagens oficiais da Microsoft. Com base nas plataformas de destino e desenvolvimento, você precisa escolher a imagem apropriada. Neste artigo, estamos usando microsoft/dotnet:1.1.2-sdk o que é uma imagem baseada em Linux. Você pode usar microsoft/dotnet:1.1.2-sdk-nanoserver para o Windows, por exemplo. Escolha uma que atenda às suas necessidades.

Observação para usuários do Windows: como mencionado anteriormente, para ser multiplataforma, estamos usando .NET Core e oOrleans Technical preview 2.0 neste artigo. Se você quiser usar o Docker no Windows com o Orleans 1.4+ totalmente lançado, precisará usar as imagens baseadas no Windows Server Core, já que as imagens baseadas em NanoServer e Linux só dão suporte ao .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"]

Esse Dockerfile essencialmente baixa e instala o depurador VSdbg e inicia um contêiner vazio, mantendo-o vivo para sempre para que não precisemos derrubar/depurar durante a depuração.

Agora, para produção, a imagem é menor, pois contém apenas o runtime do .NET Core e não todo o SDK, e o dockerfile é um pouco mais simples:

Dockerfile:

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

O arquivo docker-compose

O arquivo docker-compose.yml, essencialmente, define (dentro de um projeto) um conjunto de serviços e suas dependências no nível do serviço. Cada serviço contém uma ou mais instâncias de um determinado contêiner, que se baseia nas imagens selecionadas no Dockerfile. Mais detalhes sobre o recurso docker-compose podem ser encontrados na documentação do docker-compose.

Para uma implantação de Orleans, um caso de uso comum é ter docker-compose.yml que contenha dois serviços. Um para o Silo Orleans e outro para o Cliente Orleans. O Cliente teria uma dependência no Silo e isso significa que ele só será iniciado depois que o serviço Silo estiver ativado. Outro caso é adicionar um serviço/contêiner de armazenamento/banco de dados, como por exemplo SQL Server, que deve começar primeiro antes do cliente e do silo, portanto, ambos os serviços devem ter uma dependência dele.

Observação

Antes de ler mais, observe que recuoimporta em docker-compose. Portanto, preste atenção a ele se você tiver algum problema.

Veja como descreveremos nossos serviços para este artigo:

docker-compose.override.yml (Depuração):

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 (produção):

version: '3.1'

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

Na produção, não mapeamos o diretório local e nem temos a ação build:. O motivo é que, em produção, as imagens devem ser criadas e enviadas por push para seu próprio Registro do Docker.

Colocar tudo em conjunto

Agora que temos todas as partes móveis necessárias para executar seu aplicativo Orleans, vamos colocá-lo juntos para que possamos executar nossa solução do Orleans dentro do Docker (finalmente!).

Importante

Os comandos a seguir devem ser executados no diretório da solução.

Primeiro, vamos garantir que restauremos todos os pacotes NuGet de nossa solução. Você só precisa fazer isso uma vez. Você só precisará fazer isso novamente se alterar qualquer dependência de pacote em seu projeto.

dotnet restore

Agora, vamos criar nossa solução usando a CLI dotnet como de costume e publicá-la em um diretório de saída:

dotnet publish -o ./bin/PublishOutput

Dica

Estamos usando publish aqui em vez de compilar, para evitar problemas com nossos assemblies carregados dinamicamente no Orleans. Ainda estamos procurando uma solução melhor para ele.

Com o aplicativo criado e publicado, você precisa criar suas imagens do Dockerfile. Esta etapa só precisa ser executada uma vez por projeto e só deverá ser executada novamente se você alterar o Dockerfile, o docker-compose ou se, por qualquer motivo, tenha limpado o registro de imagem local.

docker-compose build

Todas as imagens usadas em Dockerfile e docker-compose.yml são extraídas do registro e armazenadas em cache no computador de desenvolvimento. Suas imagens são criadas esta tudo pronto para a execução.

Agora, vamos executá-lo!

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

Agora, se você executar um docker-compose ps, verá dois contêineres em execução para o projeto 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

Observação

Se você estiver no Windows e seu contêiner estiver usando uma imagem do Windows como base, a coluna Comando mostrará o comando relativo do PowerShell para tail em sistemas *NIX para que o contêiner continue da mesma maneira.

Agora que você tem seus contêineres para cima, você não precisa pará-lo toda vez que quiser iniciar seu aplicativo Orleans. Tudo o que você precisa é integrar seu IDE para depurar o aplicativo dentro do contêiner que foi mapeado anteriormente em seu docker-compose.yml.

Scaling

Depois de executar seu projeto de composição, você poderá escalar ou reduzir facilmente o aplicativo usando o 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

Após alguns segundos, você verá os serviços dimensionados para o número específico de instâncias solicitadas.

# 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

A coluna Command nesses exemplos está mostrando o comando tail apenas porque estamos usando o contêiner do depurador. Se estivéssemos em produção, seria exibido dotnet OrleansSilo.dll, por exemplo.

Docker Swarm

A pilha de clustering do Docker é chamada swarm, para obter mais informações, consulte Docker Swarm.

Para executar este artigo em um cluster Swarm, você não tem nenhum trabalho extra. Quando você executar docker-compose up -d em um nó Swarm, ele agendará contêineres com base nas regras configuradas. O mesmo se aplica a outros serviços baseados em Swarm, como Docker Datacenter, ACS do Azure (no modo Swarm) e Serviço de Contêiner do ECS do AWS. Tudo o que você precisa fazer é implantar seu cluster Swarm antes de implantar seu aplicativo convertido para dockerOrleans.

Observação

Se você estiver usando um mecanismo do Docker com o modo Swarm que já tem suporte para stack, deploye compose v3, uma abordagem melhor para implantar sua solução seria docker stack deploy -c docker-compose.yml <name>. Basta ter em mente que ele requer o arquivo de composição v3 para dar suporte ao mecanismo do Docker e a maioria dos serviços hospedados, como o Azure e a AWS, ainda usam mecanismos v2 e mais antigos.

Google Kubernetes (K8s)

Se você planeja usar o Kubernetes para hospedar o Orleans, há um provedor de clustering mantido pela comunidade disponível em OrleansContrib\Orleans.Clustering.Kubernetes e lá você pode encontrar documentação e exemplos sobre como hospedar o Orleans no Kubernetes diretamente usando o provedor.

Depurar o Orleans dentro de contêineres

Bem, agora que você sabe como executar desde o início o Orleans em um contêiner, seria bom aproveitar um dos princípios mais importantes no Docker. Os contêineres são imutáveis. E eles devem ter (quase) a mesma imagem, dependências e runtime em desenvolvimento como em produção. Isso garante que a boa e velha declaração "Funciona na minha máquina!" nunca mais aconteça. Para tornar isso possível, você precisa ter uma maneira de desenvolver dentro do contêiner e isso inclui ter um depurador anexado ao seu aplicativo dentro do contêiner.

Há várias maneiras de conseguir isso usando várias ferramentas. Depois de avaliar vários, quando escrevi este artigo, acabei escolhendo um que parece mais simples e é menos intrusivo no aplicativo.

Conforme mencionado anteriormente neste artigo, estamos usando VSCode para desenvolver o exemplo, portanto, aqui está como anexar o depurador ao aplicativo Orleans dentro do contêiner.

Primeiro, altere dois arquivos dentro do diretório .vscode em sua solução:

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

Esse arquivo essencialmente informa VSCode que sempre que você compilar o projeto, ele executará o comando publish como fizemos manualmente anteriormente.

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

Agora você pode apenas criar a solução (que VSCode será publicada) e iniciar o Silo e o Cliente. Ele enviará um comando docker exec para a instância/contêiner de serviço em execução docker-compose para iniciar o depurador para o aplicativo e é isso. Você tem o depurador anexado ao contêiner e o usa como se fosse um aplicativo Orleans em execução local. A diferença agora é que ele está dentro do contêiner e, depois de terminar, basta publicar o contêiner no registro e puxá-lo nos hosts do Docker em produção.