Erkundung der skalierbaren Ausführung eines Go-Servers in dotnet aspire
dotnet aspire?
dotnet aspire ist ein Werkzeug, das mit der Zunahme von Cloud-nativen Umgebungen entwickelt wurde, um Entwicklern bei der Cloud-nativen Entwicklung und Konfiguration zu helfen. Dieses Tool ermöglicht es .NET-Entwicklern, .NET-Projekte und verschiedene Cloud-native Infrastrukturen sowie Dienste oder Container in anderen Sprachen einfach bereitzustellen.
Selbstverständlich wird es von Docker bis K8s veröffentlicht und betrieben, und eine beträchtliche Anzahl von Bereichen, Branchen und Entwicklern aus bestehenden On-Premise-Umgebungen gehen in Cloud-native Umgebungen über oder sind bereits migriert. Es ist nun ein ausgereifter Bereich. Daher denke ich, dass es nicht notwendig ist, die bestehenden Unannehmlichkeiten in Bezug auf Hostnamen, Portkonfigurationen, Firewalls, Metrikverwaltung usw. zu erläutern.
Daher wird es Ihnen angesichts der obigen Erklärungen nicht leicht fallen zu verstehen, was dotnet aspire ist. Dies liegt daran, dass selbst Microsoft keine genaue Definition dafür gegeben hat. Daher werde auch ich keine besondere Definition geben. Da ich jedoch die grundlegenden Funktionen von dotnet aspire, wie ich sie verstehe, in diesem Artikel verwenden werde, sollten Sie dies berücksichtigen und Ihre eigene Position bestimmen.
Projektkonfiguration
Erstellen eines dotnet aspire Projekts
Wenn Sie keine dotnet aspire Vorlage haben, müssen Sie zuerst die Vorlage installieren. Installieren Sie die Vorlage mit dem folgenden Befehl. Wenn Sie .net nicht haben, installieren Sie es bitte selbst.
1dotnet new install Aspire.ProjectTemplates
Erstellen Sie dann eine neue Lösung in einem geeigneten Ordner.
1dotnet new sln
Führen Sie anschließend den folgenden Befehl im Lösungsordner aus, um ein Projekt der aspire-apphost Vorlage zu erstellen.
1dotnet new aspire-apphost -o AppHost
Dann wird ein aspire-apphost Projekt mit nur einfachem Code für die Einrichtung erstellt.
Hinzufügen von Valkey
Fügen wir nun einfach Valkey hinzu.
Bevor wir es hinzufügen, bietet dotnet aspire verschiedene Drittanbieterlösungen über Community-Hosting an. Natürlich kann valkey auch von diesem Community-Hosting unterstützt werden und kann einfach über das folgende NuGet-Paket verwendet werden.
1dotnet add package Aspire.Hosting.Valkey
Zusätzlich bietet es verschiedene integrierte Hosting-Optionen, die Sie hier finden können. Kehren wir zu Valkey zurück und öffnen die Datei Program.cs im AppHost Projekt, um sie wie folgt zu ändern.
1var builder = DistributedApplication.CreateBuilder(args);
2
3var cache = builder.AddValkey("cache")
4 .WithDataVolume(isReadOnly: false)
5 .WithPersistence(interval: TimeSpan.FromMinutes(5),
6 keysChangedThreshold: 100);
7
8builder.Build().Run();
cache
ist eine Implementierung der IResourceBuilder
Schnittstelle, die Informationen zum Erstellen eines Valkey Dienstes enthält.WithDataVolume
erstellt ein Volume zum Speichern von Cache-Daten, und WithPersistence
ermöglicht das kontinuierliche Speichern von Cache-Daten.
Bisher scheint es eine ähnliche Rolle wie volumes
in docker-compose
zu spielen.
Natürlich können Sie dies auch problemlos erstellen.
Da dies jedoch den Rahmen dieses Artikels sprengt, werde ich es jetzt nicht besprechen.
Erstellen eines Go-Sprach-Echo-Servers
Fügen wir nun einen einfachen Go-Sprach-Server hinzu.
Erstellen Sie zunächst einen Arbeitsbereich mit go work init
im Lösungsordner.
Für .NET-Entwickler ist ein Go-Arbeitsbereich ähnlich einer Lösung.
Erstellen Sie dann einen Ordner namens EchoServer, wechseln Sie in diesen Ordner und führen Sie go mod init EchoServer
aus.
Dieser Befehl erstellt ein Go-Modul. Ein Modul ist für .NET-Entwickler ähnlich einem Projekt.
Erstellen Sie dann eine Datei main.go
und schreiben Sie Folgendes hinein.
1package main
2
3import (
4 "log"
5 "net/http"
6 "os"
7)
8
9func main() {
10 addr := os.Getenv("PORT")
11 log.Printf("Server started on %s", addr)
12
13 http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
14 name := request.URL.Query().Get("name")
15 writer.Write([]byte("Hello, " + name))
16 })
17
18 http.ListenAndServe(":"+addr, nil)
19}
Wenn dieser Server die Umgebungsvariable PORT
erhält, die Aspire AppHost beim Start auslesen soll, liest er den entsprechenden Port und startet den Server.
Es ist ein einfacher Server, der eine name
-Abfrage empfängt und Hello, {name}
zurückgibt.
Fügen wir diesen Server nun zu dotnet aspire hinzu.
Hinzufügen des Echo-Servers zu Aspire
Wechseln Sie zurück zum Aspire AppHost Projekt, wo Sie Valkey hinzugefügt haben, und fügen Sie Community-Hosting für die Go-Sprache hinzu.
1dotnet add package CommunityToolkit.Aspire.Hosting.Golang
Öffnen Sie dann die Datei Program.cs und fügen Sie den folgenden Code hinzu.
1var builder = DistributedApplication.CreateBuilder(args);
2
3var cache = builder.AddValkey("cache")
4 .WithDataVolume(isReadOnly: false)
5 .WithPersistence(interval: TimeSpan.FromMinutes(5),
6 keysChangedThreshold: 100);
7
8var echoServer = builder.AddGolangApp("echo-server", "../EchoServer")
9 .WithHttpEndpoint(port: 3000, env: "PORT")
10 .WithExternalHttpEndpoints();
11
12builder.Build().Run();
Hier ist echoServer
eine Implementierung der IResourceBuilder
Schnittstelle, die Informationen zum Erstellen eines Go-Sprachservers enthält.
Die gerade hinzugefügte Methode AddGolangApp
ist eine Erweiterungsmethode für einen benutzerdefinierten Host zum Hinzufügen eines Go-Sprachservers.
Es wird standardmäßig Port 3000 verwendet, und es ist möglich zu bestätigen, dass die Umgebungsvariable PORT
injiziert wird.
Schließlich ermöglicht WithExternalHttpEndpoints
den externen Zugriff.
Wenn Sie zum Testen auf http://localhost:3000/?name=world
zugreifen, können Sie sehen, dass Hello, world
ausgegeben wird.
Derzeit gibt es jedoch einen schweren Nachteil für Nicht-.NET-Projekte in dotnet aspire. Das ist...
Projekterweiterung
Wie funktioniert die horizontale Skalierung?
Derzeit bietet dotnet aspire die Option WithReplica
nur für Builder von .NET-Projekten, die mit der Methode AddProject
hinzugefügt werden.
Diese Option wird jedoch nicht für Go-Sprachhosts oder externe Projekte wie AddContainer
angeboten.
Daher ist es notwendig, dies mit einem separaten Load Balancer oder Reverse Proxy zu implementieren.
Wenn dies jedoch der Fall ist, kann dieser Reverse Proxy zu einem SPOF werden, daher ist es ratsam, dass der Reverse Proxy die Option WithReplica
anbietet.
Daher muss der Reverse Proxy zwangsläufig ein .NET-Projekt sein.
Bisher haben wir Methoden wie nginx, trafik oder die direkte Implementierung für dieses Problem verwendet, aber wenn es eine Einschränkung für .NET-Projekte gibt, hatte ich keine sofortige Lösung. Also habe ich nach einem mit .NET implementierten Reverse Proxy gesucht und glücklicherweise gab es die Option YARP. YARP ist ein mit .NET implementierter Reverse Proxy, der auch als Load Balancer fungieren kann und verschiedene Funktionen bietet, daher hielt ich ihn für eine gute Wahl.
Fügen wir nun YARP hinzu.
Konfigurieren eines Reverse Proxy mit YARP
Erstellen Sie zunächst ein Projekt zur Verwendung von YARP.
1dotnet new web -n ReverseProxy
Wechseln Sie dann zum Projekt und installieren Sie YARP.
1dotnet add package Yarp.ReverseProxy --version 2.2.0
Wenn die Installation abgeschlossen ist, öffnen Sie die Datei Program.cs und schreiben Sie Folgendes hinein.
1using Yarp.ReverseProxy.Configuration;
2
3var builder = WebApplication.CreateBuilder(args);
4
5var routes = new List<RouteConfig>();
6var clusters = new List<ClusterConfig>();
7
8builder.Services.AddReverseProxy()
9 .LoadFromMemory(routes, clusters);
10
11var app = builder.Build();
12
13app.MapReverseProxy();
14app.Run(url: $"http://0.0.0.0:{Environment.GetEnvironmentVariable("PORT") ?? "5000"}");
Dieser Code ist der grundlegende Code zur Verwendung von YARP.routes
enthält die Routing-Informationen, die der Reverse Proxy verwenden soll, und clusters
enthält die Cluster-Informationen, die der Reverse Proxy verwenden soll.
Diese Informationen werden mit der Methode LoadFromMemory
in den Reverse Proxy geladen.
Schließlich wird der Reverse Proxy mit der Methode MapReverseProxy
gemappt und ausgeführt.
Fügen Sie für die tatsächliche Verwendung einen Verweis auf das Reverse Proxy Projekt im Aspire AppHost Projekt hinzu und fügen Sie den folgenden Code in die Datei Program.cs ein und ändern Sie ihn.
1dotnet add reference ../ReverseProxy
1var echoServer = builder.AddGolangApp("echo-server", "../EchoServer")
2 .WithHttpEndpoint(env: "PORT");
3
4var reverseProxy = builder.AddProject<Projects.ReverseProxy>("gateway")
5 .WithReference(echoServer)
6 .WithHttpEndpoint(port: 3000, env: "PORT", isProxied: true)
7 .WithExternalHttpEndpoints();
Jetzt kann der Reverse Proxy auf den Echo-Server verweisen. Die Struktur ändert sich so, dass eingehende Anfragen vom Reverse Proxy empfangen und an den Echo-Server weitergeleitet werden.
Ändern des Reverse Proxy
Zunächst muss die Listening-Adresse des dem Reverse Proxy zugewiesenen Projekts geändert werden.
Entfernen Sie applicationUrl
in der Datei Properties/launchSettings.json
.
Öffnen Sie dann die Datei Program.cs und ändern Sie sie umfassend wie folgt.
1using Yarp.ReverseProxy.Configuration;
2
3var builder = WebApplication.CreateBuilder(args);
4
5var routes = new List<RouteConfig>
6{
7 new RouteConfig
8 {
9 ClusterId = "cluster-echo",
10 RouteId = "route-echo",
11 Match = new RouteMatch
12 {
13 Path = "/"
14 }
15 }
16};
17
18var echoServerAddr = Environment.GetEnvironmentVariable("services__echo-server__http__0") ?? "http://localhost:8080";
19
20var clusters = new List<ClusterConfig>
21{
22 new ClusterConfig
23 {
24 ClusterId = "cluster-echo",
25 Destinations = new Dictionary<string, DestinationConfig>
26 {
27 { "destination-echo", new DestinationConfig { Address = echoServerAddr } }
28 }
29 }
30};
31
32builder.Services.AddReverseProxy()
33 .LoadFromMemory(routes, clusters);
34
35var app = builder.Build();
36
37app.MapReverseProxy();
38app.Run(url: $"http://0.0.0.0:{Environment.GetEnvironmentVariable("PORT") ?? "5000"}");
Zunächst werden die Informationen zu routes
und clusters
geändert.
Fügen Sie echo-route
und echo-cluster
hinzu, um Anfragen an den Echo-Server zu senden.
Ändern Sie es dann so, dass die Adresse des Echo-Servers aus der Umgebungsvariable gelesen und verwendet wird.
Die Regel für diese Adresse ist services__{service-name}__http__{index}
.
Im Fall des Echo-Servers ist der Dienstname echo-server
, und da es sich um eine einzelne Instanz handelt, wird 0
als Index verwendet.
Wenn Sie einen ASP.NET Core Server hinzufügen, können mehrere Instanzen durch WithReplica
erstellt werden, sodass Sie den Index inkrementell verwenden können.
Der Ausnahmebehandlungswert http://localhost:8080
ist ein bedeutungsloser Dummy-Wert.
Wenn Sie das Projekt jetzt ausführen und auf http://localhost:3000/?name=world
zugreifen, können Sie sehen, dass Hello, world
immer noch ausgegeben wird.
Erweiterungsidee
Wir haben nun bestätigt, dass ein Go-Server zu dotnet aspire hinzugefügt und Anfragen über einen Reverse Proxy weitergeleitet werden. Dann ist es nun möglich, diesen Prozess programmatisch zu erweitern. Beispielsweise können Sie mehrere Instanzen des Echo-Servers erstellen, indem Sie eine Nummerierung hinter dem Dienstnamen hinzufügen, und die Konfiguration für den Reverse Proxy automatisch hinzufügen.
Ändern Sie den Code in der Datei Program.cs des Aspire AppHost Projekts wie folgt, um den Reverse Proxy und den Echo-Server zu verwenden.
1var reverseProxy = builder.AddProject<Projects.ReverseProxy>("gateway")
2 .WithHttpEndpoint(port: 3000, env: "PORT", isProxied: true)
3 .WithExternalHttpEndpoints();
4
5for (var i = 0; i < 8; i++)
6{
7 var echoServer = builder.AddGolangApp($"echo-server-{i}", "../EchoServer")
8 .WithHttpEndpoint(env: "PORT");
9 reverseProxy.WithReference(echoServer);
10}
Ändern Sie dann die Datei Program.cs des Reverse Proxy Projekts wie folgt.
1var echoServerDestinations = new Dictionary<string, DestinationConfig>();
2for (var i = 0; i < 8; i++)
3{
4 echoServerDestinations[$"destination-{i}"] = new DestinationConfig
5 {
6 Address = Environment.GetEnvironmentVariable($"services__echo-server-{i}__http__0") ?? "http://localhost:8080"
7 };
8}
9
10var clusters = new List<ClusterConfig>
11{
12 new ClusterConfig
13 {
14 ClusterId = "cluster-echo",
15 Destinations = echoServerDestinations
16 }
17};
Fügen Sie Zielkonfigurationen für die 8 erweiterten Echo-Serverinstanzen hinzu.
Der Reverse Proxy verfügt nun über Zielinformationen für die erweiterten Echo-Server und kann Anfragen weiterleiten.
Wenn Sie auf die bestehende http://localhost:3000/?name=world
zugreifen, können Sie sehen, dass Hello, world
immer noch ausgegeben wird.
Abschließend
In diesem Artikel wurde der Prozess des Hinzufügens eines Go-Servers zu dotnet aspire und des Weiterleitens von Anfragen über einen Reverse Proxy erläutert. Die Erweiterung ist jedoch noch nicht vollständig geschrieben, und ein Beispiel, in dem es über Umgebungsvariablen programmatischer implementiert werden kann, wurde in einem separaten Repository geschrieben. Weitere Informationen zur Projektkonfiguration und zum Code finden Sie unter snowmerak/AspireStartPack.
Ich persönlich erwarte, dass dotnet aspire eine eigene Rolle als Alternative zu Docker Compose und als Cloud-Bereitstellungstool spielen kann. Da es bereits einen Generator gibt, der Docker Compose oder K8s Manifeste erstellt, denke ich, dass sich der Zugang für normale Entwickler zu Infrastrukturtools verbessert hat.