Docker 部署

提示

即使你非常熟悉 Docker 和/或 Orleans,也建议阅读到最后,就像阅读任何其他 Orleans 文档一样,以免遇到我们已解决的问题。

本文及其示例正在不断完善中。 欢迎提供任何反馈、PR 或建议。

将 Orleans 解决方案部署到 Docker

由于 Docker 业务流程协调程序和群集堆栈的设计方式,将 Orleans 部署到 Docker 可能有点棘手。 最复杂的问题是理解 Docker Swarm 和 Kubernetes 网络模型中叠加网络的概念。

Docker 容器和网络模型旨在运行大多数无状态和不可变容器。 因此,启动运行 node.js 或 Nginx 应用程序的群集非常容易。 但是,如果你尝试使用更复杂的东西,例如真正的群集应用程序或分布式应用程序(如基于 Orleans 的应用程序),最终在设置该应用程序时会遇到麻烦。 它可能不如基于 Web 的应用程序那么容易设置。

Docker 群集组建涉及到将多个主机组合成单个资源池并使用容器业务流程协调程序对其进行管理。 Docker Inc. 提供 Swarm 作为容器业务流程选项,而 Google 提供 Kubernetes(也称为 K8s)。 还有其他业务流程协调程序,例如 DC/OS 和 Mesos,但本文档讨论的是 Swarm 和 K8s,因为它们的使用更广泛。

在已经能够支持 Orleans 的任何位置运行的相同 grain 接口和实现也可以在 Docker 容器上运行。 无需经过特殊考虑即可在 Docker 容器中运行应用程序。

此处讨论的概念可用于 .NET Core 和 .NET 4.6.1 风格的 Orleans,但为了演示 Docker 和 .NET Core 的跨平台性,我们将重点介绍使用 .NET Core 的示例。 本文可能会提供特定于平台 (Windows/Linux/OSX) 的详细信息。

先决条件

本文假设已安装以下必备组件:

  • Docker - Docker4X 提供了适用于主流受支持平台的易用安装程序。 其中包含 Docker 引擎和 Docker Swarm。
  • Kubernetes (K8s) - Google 的容器业务流程产品。 其中包含有关安装 Minikube(K8s 的本地部署)和 kubectl 及其所有依赖项的指导。
  • .NET - .NET 的跨平台风格
  • Visual Studio Code (VSCode) - 可以使用任何所需的 IDE。 VSCode 是跨平台工具,因此可以确保它能在所有平台上正常运行。 安装 VSCode 后,请安装 C# 扩展

重要

如果你不打算使用 Kubernetes,则不需要安装它。 Docker4X 安装程序已包含 Swarm,因此无需进行额外的安装即可使用它。

注意

在 Windows 上,Docker 安装程序将在安装过程中启用 Hyper-V。 由于本文及其示例使用 .NET Core,因此使用的容器映像基于 Windows Server NanoServer。 如果你不打算使用 .NET Core,而是将 .NET 4.6.1 完整框架用作目标,则使用的映像应是 Windows Server Core 和 1.4+ 版 Orleans(仅支持 .NET 完整框架)

创建 Orleans 解决方案

以下说明介绍如何使用新的 dotnet 工具创建普通 Orleans 解决方案。

请调整命令,使其适用于你的平台。 此外,目录结构只是建议。 请根据需要进行调整。

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

到目前为止,我们只是编写了样板代码来创建解决方案的结构和项目,并在项目之间添加了引用。 这与普通 Orleans 项目没有什么不同。

在撰写本文时,Orleans 2.0(这是唯一支持 .NET Core 和跨平台工具的版本)作为技术预览版提供,因此其 NuGet 包托管在 MyGet 源中,而未发布到 Nuget.org 官方源。 为了安装预览版 NuGet 包,我们将使用 dotnet CLI,并强制使用 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

你现已获得所有基本依赖项,接下来可以运行一个简单的 Orleans 应用程序。 请注意,到目前为止,与普通 Orleans 应用程序没有任何差别。 现在,让我们添加一些代码,以便可以用它来做一些事情。

实现 Orleans 应用程序

假设使用的是 VSCode。从解决方案目录运行 code .。 这会在 VSCode 中打开该目录并加载解决方案。

这是之前刚刚创建的解决方案结构。

Visual Studio Code: Explorer with Program.cs selected.

我们还将 Program.cs、OrleansHostWrapper.cs、IGreetingGrain.cs 和 GreetingGrain.cs 文件分别添加到了接口和 grain 项目,下面是这些文件的代码:

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(客户端):

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

本文不会详细介绍有关 grain 实现的详细信息,因为这超出了本文的范围。 请查看其他相关文件。 这些文件本质上是一个极简的 Orleans 应用程序,我们将从它着手探讨本文的其余内容。

本文将使用 OrleansAzureUtils 成员资格提供程序,但你可以使用 Orleans 支持的任何其他提供程序。

Dockerfile

Docker 使用映像来创建容器。 有关如何创建你自己的容器更多详细信息,可以查看 Docker 文档。 本文将使用官方 Microsoft 映像。 需要根据目标和开发平台选择适当的映像。 本文将使用基于 Linux 的映像 microsoft/dotnet:1.1.2-sdk。 例如,你可以使用适用于 Windows 的 microsoft/dotnet:1.1.2-sdk-nanoserver。 请选择符合需求的映像。

Windows 用户说明:如前所述,本文将使用 .NET Core 和 Orleans 技术预览版 2.0 来实现跨平台操作。 如果你要将 Windows 上的 Docker 与完整发布的 Orleans 1.4+ 配合使用,需要使用基于 Windows Server Core 的映像,因为 NanoServer 和基于 Linux 的映像仅支持 .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"]

此 Dockerfile 实质上用于下载和安装 VSdbg 调试器,以及启动一个空容器并使其永远保持活动状态,因此我们在调试时无需拆装。

目前,映像对于生产环境而言较小,因为它只包含 .NET Core 运行时而不包含整个 SDK;另外,Dockerfile 有点简单:

Dockerfile

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

docker-compose 文件

docker-compose.yml 文件实质上用于(在项目中)在服务级别定义一组服务及其依赖项。 每个服务包含给定容器的一个或多个实例,该容器基于你在 Dockerfile 中选择的映像。 可以在 docker-compose 文档中找到有关 docker-compose 的更多详细信息。

对于 Orleans 部署,一种常见用例是创建包含两个服务的 docker-compose.yml。 一个服务用于 Orleans 接收器,另一个服务用于 Orleans 客户端。 客户端依赖于 Silo,这意味着,它只会在 Silo 服务启动后才启动。 另一种用例是添加一个存储/数据库服务/容器(例如针对 SQL Server),应该首先启动这个添加的资源,然后再启动客户端和 Silo,因此上述两个服务都应该依赖于该资源。

注意

在继续阅读之前,请注意 docker-compose 文件中的缩进非常重要。 如果有任何问题,请留意这些内容。

下面是我们描述本文中的服务的方式:

docker-compose.override.yml(调试):

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(生产):

version: '3.1'

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

在生产环境中,我们不会映射本地目录,也不执行 build: 操作。 原因是在生产环境中,应该生成映像并将其推送到你自己的 Docker 注册表。

将所有内容放在一起

现在我们有了运行 Orleans 应用程序所需的所有部件,接下来我们将它们组合到一起,以便可以在 Docker 中运行 Orleans 解决方案(终于到了这一步!)。

重要

应从解决方案目录执行以下命令。

首先,确保从解决方案中还原所有 NuGet 包。 此操作只需执行一次。 仅当更改了项目中的任何包依赖项时,才需要再次执行此操作。

dotnet restore

现在,让我们照常使用 dotnet CLI 生成解决方案并将其发布到输出目录:

dotnet publish -o ./bin/PublishOutput

提示

此处我们将使用 publish 而不是 build,以避免在 Orleans 中动态加载的程序集出现问题。 我们仍在寻找更好的解决方案。

生成并发布应用程序后,需要生成 Dockerfile 映像。 此步骤只需在每个项目中执行一次,仅当更改了 Dockerfile、docker-compose 或出于任何原因清理了本地映像注册表时才需要再次执行。

docker-compose build

Dockerfiledocker-compose.yml 中使用的所有映像都是从注册表中拉取的,它们缓存在开发计算机上。 现已生成映像,并完成了运行前的所有准备工作。

现在让我们运行解决方案!

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

如果运行了 docker-compose ps,则会看到为 orleansdocker 项目运行的 2 个容器:

# 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

注意

如果在 Windows 上操作,并且容器使用 Windows 映像作为基础,则“命令”列将显示与 *NIX 系统上的 tail 相关的 PowerShell 命令,因此容器将保持相同的行为。

容器现已启动,你不需要在每次启动 Orleans 应用程序时都停止容器。 只需集成 IDE,以便在先前在 docker-compose.yml 中映射的容器内部调试应用程序。

扩展

运行 compose 项目后,可以使用 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

几秒钟后,你将看到服务缩放到请求的特定数量的实例。

# 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

重要

这些示例中的 Command 列显示了 tail 命令,这只是因为我们使用的是调试器容器。 例如,如果我们在生产环境中操作,则该列会显示 dotnet OrleansSilo.dll

Docker Swarm

Docker 群集堆栈称为 Swarm。有关详细信息,请参阅 Docker Swarm

若要在 Swarm 群集中运行本文所述的命令,可以直接运行。 在 Swarm 节点中运行 docker-compose up -d 时,该命令将根据配置的规则计划容器。 这同样适用于其他基于 Swarm 的服务,例如 Docker DatacenterAzure ACS(Swarm 模式)和 AWS ECS Container Service。 只需在部署 Docker 容器化 Orleans 应用程序之前部署 Swarm 群集即可。

注意

如果你使用的 Docker 引擎采用 Swarm 模式且已经能够支持 stackdeploycompose v3,则部署解决方案的更好方法是运行 docker stack deploy -c docker-compose.yml <name>。 请记住,它需要通过 v3 compose 文件来支持 Docker 引擎,而大多数托管服务(例如 Azure 和 AWS)仍使用 v2 和更低版本的引擎。

Google Kubernetes (K8s)

如果你打算使用 Kubernetes 来托管 Orleans,可以借助 OrleansContrib\Orleans.Clustering.Kubernetes 中由社区维护的群集提供程序,并可以在此库中找到有关如何使用提供程序在 Kubernetes 中无缝托管 Orleans 的文档和示例。

在容器内部调试 Orleans

现在你已了解如何从头开始在容器中运行 Orleans,接下来可以运用 Docker 中的最重要原则之一了。 容器是不可变的。 在开发中,它们应该包含与生产环境(几乎)相同的映像、依赖项和运行时。 这样,“我的计算机也可以运行它!”这句老话就确实不值一提。 为此,需要通过一种方式在容器内部进行开发,这包括将一个调试器附加到容器内部的应用程序。

可以使用多种工具通过多种方法实现此目的。 在评估多种方法之后,当我撰写本文时,我最终选择了一种看起来更简单且对应用程序的干扰更少的方法。

如本文前面所述,我们将使用 VSCode 来开发示例,因此,下面介绍的是如何将调试器附加到容器内部的 Orleans 应用程序。

首先,更改解决方案的 .vscode 目录中的两个文件:

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

此文件实质上告知 VSCode,每当你生成项目时,它都要如前所述手动执行 publish 命令。

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

现在可以从 VSCode(发布)生成解决方案并启动 silo 和客户端。 它向正在运行的 docker-compose 服务实例/容器发送 docker exec 命令以在应用程序中启动调试器,仅此而已。 将调试器附加到容器,并像使用本地运行的 Orleans 应用程序一样使用它。 不同之处在于该调试器位于容器内部,完成操作后,可将容器发布到注册表并将其拉取到生产环境中的 Docker 主机上。