提示
即使熟悉 Docker 或 Orleans,還是建議閱讀本文至結尾,以避免已知因應措施的潛在問題。
本文及其範例是進行中的工作。 歡迎使用意見反應、PR 或建議。
將 Orleans 解決方案部署至 Docker
由於 Docker 協調器和叢集堆疊的設計,部署 Orleans 至 Docker 可能會很棘手。 最複雜的部分是理解 Docker Swarm 中的 疊加網路 概念,以及 Kubernetes 的網路模型。
Docker 容器和網路模型主要是針對執行無狀態和固定容器而設計。 啟動運行 Node.js 或 Nginx 應用程式的叢集是相當容易的。 不過,使用更複雜的方案,例如真正的叢集或分散式應用程式(如基於Orleans的應用程式),可能會面臨設定上的困難。 這是可能的,但不像部署 Web 型應用程式那麼簡單。
Docker 叢集牽涉到將多個主機分組,以作為單一資源集區運作,並使用 容器 Orchestrator 進行管理。 Docker Inc. 提供 Swarm 作為其選項,而 Google 則提供 Kubernetes (也稱為 K8s)。 DC/OS 和 Mesos 等其他協調器存在,但本檔著重於 Swarm 和 K8,因為它們使用得更廣。
支援在任何地方 Orleans 執行的相同粒紋介面和實作也會在 Docker 容器上執行。 在 Docker 容器中執行 Orleans 應用程式不需要任何特殊考慮。
這裏討論的概念同時適用於的 .NET Core 和 .NET Framework 4.6.1 版本 Orleans。 不過,為了說明 Docker 和 .NET Core 的跨平台本質,使用 .NET Core 的範例著重於 。 可能需要視需要提供平臺特定詳細數據(Windows/Linux/macOS)。
必要條件
本文假設已安裝下列必要條件:
- Docker:D ocker4X 具有適用於主要支援平臺的易於使用安裝程式。 其中包含 Docker 引擎和 Docker Swarm。
- Kubernetes (K8s):Google 的容器協調流程供應專案。 其中包含安裝 Minikube(本地 K8s 部署)和 kubectl 的指引,並涵蓋相依性。
- .NET:.NET 的跨平台類別。
- Visual Studio Code (VSCode):可以使用任何慣用的 IDE。 VSCode 在這裡使用,因為它是跨平臺,確保範例在所有平臺上都能運作。 安裝 VSCode 之後,請安裝 C# 擴充功能。
重要
如果未使用它,則不需要 Kubernetes 安裝。 Docker4X 安裝程式已經包含 Swarm,因此 Swarm 不需要額外的安裝。
注意
在 Windows 上,Docker 安裝程式會在安裝期間啟用 Hyper-V。 由於本文及其範例使用 .NET Core,因此所使用的容器映像是以 Windows Server NanoServer 為基礎。 如果計劃改用 .NET Framework 4.6.1,請使用以 Windows Server Core 和 Orleans 1.4+ 版為基礎的映射(僅支援 .NET Framework)。
建立 Orleans 解決方案
下列指示示範如何使用 Orleans 工具建立標準 dotnet
解決方案。
視需要針對平台調整命令。 目錄結構只是建議;視需要進行調整。
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 中開啟目錄,並載入解決方案。
這是先前建立的解決方案結構。
Program.cs、 OrleansHostWrapper.cs、 IGreetingGrain.cs和 GreetingGrain.cs 檔案也分別新增至介面和粒紋專案。 以下是這些檔案的程式代碼:
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 (定址接收器):
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);
}
}
}
}
此處未涵蓋粒紋實作詳細數據,因為它不在本文的討論範圍內。 如需詳細資訊,請參閱其他相關文件。 這些檔案代表最少 Orleans 的應用程式,可作為本文其餘部分的起點。
本文使用 OrleansAzureUtils
會員提供者,但可以使用任何其他 Orleans 支援的提供者。
Dockerfile
Docker 會使用映像來建立容器。 如需建立自定義映像的詳細資訊,請參閱 Docker 檔。 本文使用官方 Microsoft影像。 根據目標和開發平臺,挑選適當的映像。
microsoft/dotnet:1.1.2-sdk
是一個基於 Linux 的映像,用於此處。 例如, microsoft/dotnet:1.1.2-sdk-nanoserver
對於 Windows,可以使用。 選擇符合需求的。
Windows 使用者的注意事項:如先前所述,為了維護跨平臺相容性,本文會使用 .NET Core 和 Orleans Technical Preview 2.0。 若要在 Windows 上使用完整發行的Orleans 1.4+ Docker,請使用以 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
中找到關於的更多詳細資訊。
Orleans針對部署,常見的使用案例牽涉到包含兩個服務的docker-compose.yml
檔案:一個用於OrleansSilo,另一個用於Orleans用戶端。 客戶端服務依賴Silo服務,只有在Silo服務運行後才會啟動。 另一種情境可能涉及新增儲存或資料庫服務/容器(例如 SQL Server),這應該在客戶端和儲存容器之前啟動。 在此情況下,客戶端和儲存穀倉服務會相依於資料庫服務。
注意
在繼續閱讀之前,請注意縮排在檔案中是很重要的。 如果發生問題,請留意。
本文中對這些服務的描述方式如下:
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 (production):
version: '3.1'
services:
orleans-client:
image: orleans-client
depends_on:
- orleans-silo
orleans-silo:
image: orleans-silo
在生產環境中,本機目錄不會被映射,build:
動作也不會被包含。 原因是在生產環境中,映像檔應該建置並推送至私人 Docker 儲存庫。
將所有項目放在一起
既然所有必要的元件都已就緒,讓我們將它們放在一起,在 Docker 內執行 Orleans 解決方案。
重要
您應該從解決方案目錄執行下列命令。
首先,請確保解決方案的所有 NuGet 套件已恢復。 這通常只需要執行一次,除非套件相依性變更。
dotnet restore
現在,如往常使用 dotnet
CLI 建置解決方案,並將它發佈至輸出目錄:
dotnet publish -o ./bin/PublishOutput
提示
publish
在這裡被使用以替代 build
,以避免在 Orleans 中動態載入的元件出現問題。 仍在尋求更好的解決方案。
建置和發佈應用程式後,使用 Dockerfiles 建置 Docker 映像。 此步驟通常需要為每個項目執行一次。 只有在 Dockerfile 或 docker-compose 檔案變更,或基於任何原因清除本地映像檔註冊表時,才需要再次進行此操作。
docker-compose build
Dockerfile
和 docker-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
專案中運行的兩個容器。
# 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 系統上的 PowerShell 對等命令 tail
,讓容器以類似的方式執行。
既然容器正在執行,每次應用程式需要啟動時 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
所述的應用程式,不需要額外的工作。 在docker-compose up -d
節點上執行Swarm
會根據設定的規則排程容器。 這同樣適用於其他 Swarm 型服務,例如 Azure Kubernetes Service (AKS) (在 Swarm 模式中)和 AWS 彈性容器服務 (ECS)。 只要在部署 Swarm
應用程式之前先部署Orleans叢集。
注意
如果使用 Docker 引擎搭配支援 stack
、deploy
、和 compose
v3 的 Swarm 模式,則部署解決方案的一個更好方法是 docker stack deploy -c docker-compose.yml <name>
。 請記住,這需要與 Docker 引擎相容的 v3 撰寫檔案。 許多託管服務,例如 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 和 Client 組態。 VSCode 會將 docker exec
命令傳送至執行 docker-compose
中的服務實例/容器,以啟動附加至應用程式的調試程式。 就是這樣! 偵錯工具會附加至容器,因此可以像偵錯本機執行的 Orleans 應用程式一樣使用。 主要差異在於應用程式會在容器內執行。 開發完成後,請將容器映射發佈至登錄,並將其提取至生產環境的 Docker 主機。