Schaalbare Go-servers uitvoeren in dotnet aspire
Wat is dotnet aspire?
dotnet aspire is een hulpmiddel dat is ontwikkeld om ontwikkelaars te helpen bij de ontwikkeling en configuratie van cloud-native toepassingen, gezien de toenemende prevalentie van cloud-native omgevingen. Dit hulpmiddel stelt .NET-ontwikkelaars in staat om .NET-projecten, diverse cloud-native infrastructuren en services of containers in andere talen eenvoudig te implementeren.
Vanzelfsprekend zijn Docker en Kubernetes uitgebracht en operationeel, en een aanzienlijk aantal sectoren, industrieën en ontwikkelaars is gemigreerd of migreert momenteel van on-premises omgevingen naar cloud-native omgevingen. Dit is inmiddels een volwassen vakgebied. Daarom acht ik het overbodig om de eerdere ongemakken met betrekking tot hostnamen, poortconfiguraties, firewalls en metrisch beheer toe te lichten.
Op basis van de bovenstaande uitleg zult u waarschijnlijk nog steeds geen duidelijk beeld hebben van wat dotnet aspire precies is. Dit komt doordat zelfs Microsoft er geen precieze definitie van geeft. Daarom zal ik er ook geen specifieke definitie aan geven. Echter, in dit artikel zal ik de basisfunctionaliteiten van dotnet aspire gebruiken zoals ik die heb begrepen, en u kunt deze als referentie gebruiken om uw eigen standpunt te bepalen.
Projectconfiguratie
dotnet aspire project aanmaken
Indien de dotnet aspire-sjabloon niet beschikbaar is, dient deze eerst geïnstalleerd te worden. Installeer de sjabloon met het volgende commando. Indien .NET niet geïnstalleerd is, dient u dit zelf te doen.
1dotnet new install Aspire.ProjectTemplates
Maak vervolgens een nieuwe oplossing aan in een geschikte map.
1dotnet new sln
Voer daarna, in de oplossingsmap, het volgende commando uit om een project van de aspire-apphost-sjabloon te genereren.
1dotnet new aspire-apphost -o AppHost
Hierdoor wordt een aspire-apphost-project gecreëerd dat enkel een minimale code bevat voor de configuratie.
Valkey toevoegen
Laten we nu Valkey toevoegen.
Voordat we direct toevoegen, biedt dotnet aspire diverse oplossingen van derden via community hosting. Vanzelfsprekend kan Valkey ook ondersteuning krijgen via deze community hosting, en kan het eenvoudig worden gebruikt via het volgende NuGet-pakket.
1dotnet add package Aspire.Hosting.Valkey
Diverse andere geïntegreerde hostingopties zijn beschikbaar op deze locatie. Terugkerend naar Valkey, open het bestand Program.cs in het AppHost-project en wijzig het als volgt:
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 is een implementatie van de IResourceBuilder-interface die informatie bevat om de Valkey-service te bouwen.WithDataVolume creëert een volume om cachegegevens op te slaan, en WithPersistence zorgt ervoor dat cachegegevens persistent worden opgeslagen.
Tot zover lijkt dit een vergelijkbare rol te spelen als de volumes in docker-compose.
Vanzelfsprekend kunt u dit ook zonder veel moeite zelf creëren.
Dit valt echter buiten de reikwijdte van dit artikel, dus ik zal het nu niet bespreken.
Go-taal Echo-server aanmaken
Laten we nu een eenvoudige Go-taal server toevoegen.
Maak eerst een workspace aan via go work init in de oplossingsmap.
Voor .NET-ontwikkelaars is een Go workspace vergelijkbaar met een oplossing.
Maak vervolgens een map genaamd EchoServer, navigeer ernaartoe en voer go mod init EchoServer uit.
Dit commando creëert een Go-module. Voor .NET-ontwikkelaars is een module vergelijkbaar met een project.
Maak vervolgens het bestand main.go aan en schrijf de volgende code:
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}
Deze server start wanneer Aspire AppHost wordt uitgevoerd, injecteert de PORT om te luisteren, en leest die poort om de server te starten.
Het is een eenvoudige server die een name query ontvangt en Hello, {name} retourneert.
Laten we deze server nu toevoegen aan dotnet aspire.
Echo-server toevoegen aan Aspire
Ga terug naar het Aspire AppHost-project waar Valkey eerder werd toegevoegd en voeg community hosting toe voor de Go-taal.
1dotnet add package CommunityToolkit.Aspire.Hosting.Golang
Open vervolgens het bestand Program.cs en voeg de volgende code toe:
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 is echoServer een implementatie van de IResourceBuilder-interface die informatie bevat voor het bouwen van de Go-taal server.
De zojuist toegevoegde AddGolangApp-methode is een extensiemethode van de aangepaste host voor het toevoegen van een Go-taal server.
We kunnen zien dat deze een vaste poort 3000 gebruikt en de PORT-omgevingsvariabele injecteert.
Tot slot zorgt WithExternalHttpEndpoints ervoor dat de server van buitenaf toegankelijk is.
Om te testen, navigeer naar http://localhost:3000/?name=world en u zult Hello, world zien verschijnen.
Echter, momenteel legt dotnet aspire een zware straf op aan non-dotnet projecten. Namelijk...
Projectuitbreiding
Hoe werkt horizontale schaalvergroting dan?
Momenteel biedt dotnet aspire de WithReplica-optie alleen aan builders voor .NET-projecten die zijn toegevoegd met de AddProject-methode.
Deze optie is echter niet beschikbaar voor Go-taal hosts of externe projecten zoals die worden toegevoegd met AddContainer.
Daarom moet een afzonderlijke load balancer of reverse proxy handmatig worden geïmplementeerd.
Dit kan echter de reverse proxy tot een SPOF (Single Point of Failure) maken; het is daarom wenselijk dat de reverse proxy zelf de WithReplica-optie biedt.
Dit impliceert onvermijdelijk dat de reverse proxy een .NET-project moet zijn.
Tot op heden zijn er verschillende methoden gebruikt om dit probleem aan te pakken, zoals nginx, trafik of eigen implementaties, maar met de beperking van een .NET-project had ik op dat moment geen directe oplossing. Daarom zocht ik naar een reverse proxy geïmplementeerd in .NET en gelukkig was daar de optie YARP. YARP is een reverse proxy geïmplementeerd in .NET, die ook als load balancer kan fungeren en diverse functionaliteiten biedt, waardoor het een goede keuze bleek te zijn.
Laten we nu YARP toevoegen.
Reverse proxy configuratie met YARP
Maak eerst een project aan voor het gebruik van YARP.
1dotnet new web -n ReverseProxy
Ga vervolgens naar de projectmap en installeer YARP.
1dotnet add package Yarp.ReverseProxy --version 2.2.0
Nadat de installatie is voltooid, opent u het bestand Program.cs en schrijft u het volgende:
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"}");
Deze code is de basiscode voor het gebruik van YARP.routes bevat informatie over de routes die de reverse proxy zal gebruiken, en clusters bevat informatie over de clusters die de reverse proxy zal gebruiken.
Deze informatie wordt geladen in de reverse proxy via de LoadFromMemory-methode.
Tot slot worden de reverse proxy gemapt en uitgevoerd met de MapReverseProxy-methode.
Voor praktisch gebruik voegt u een referentie naar het reverse proxy-project toe in het aspire apphost-project en voegt u de volgende instructies toe aan en wijzigt u het bestand Program.cs:
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();
Nu kan de reverse proxy verwijzen naar de echo server. Externe verzoeken worden nu ontvangen door de reverse proxy en doorgestuurd naar de echo server.
Reverse proxy aanpassen
Eerst moet het luisteradres van het aan de reverse proxy toegewezen project worden gewijzigd.
Verwijder de applicationUrl in het bestand Properties/launchSettings.json.
Open vervolgens het bestand Program.cs en wijzig het ingrijpend als volgt:
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"}");
Eerst wordt de informatie over routes en clusters gewijzigd.
Respectievelijk worden echo-route en echo-cluster toegevoegd om verzoeken naar de echo-server te sturen.
Vervolgens wordt het adres van de echo-server aangepast om te worden gelezen uit een omgevingsvariabele.
De regel voor dit adres is services__{service-name}__http__{index}.
Voor de echo-server is de servicenaam echo-server en, aangezien het een enkele instantie is, wordt 0 als index gebruikt.
Indien een ASP.NET Core-server wordt toegevoegd, kunnen meerdere instanties worden gecreëerd via WithReplica, en kan de index dienovereenkomstig worden verhoogd.
De uitzonderlijk verwerkte waarde http://localhost:8080 is een willekeurige, betekenisloze waarde.
Voer nu het project uit en navigeer naar http://localhost:3000/?name=world. U zult nog steeds Hello, world zien verschijnen.
Uitbreidingsideeën
Nu hebben we een Go-server toegevoegd aan dotnet aspire en hebben we gezien hoe verzoeken via een reverse proxy worden doorgestuurd. We kunnen dit proces nu uitbreiden om het programmatisch te implementeren. Bijvoorbeeld, voor de echo-server kunnen we meerdere instanties creëren door nummering toe te voegen aan de servicenaam en de configuratie voor de reverse proxy automatisch toevoegen.
Wijzig de code die de reverse proxy en de echo-server gebruikt in het bestand Program.cs van het aspire apphost-project als volgt:
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}
En wijzig het bestand Program.cs van het reverse proxy-project als volgt:
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};
Er wordt een doelconfiguratie toegevoegd voor de 8 uitgebreide echo-serverinstanties.
De reverse proxy kan nu verzoeken doorsturen met de doelgegevens van de uitgebreide echo-servers.
Als u naar de bestaande http://localhost:3000/?name=world navigeert, zult u nog steeds Hello, world zien verschijnen.
Conclusie
In dit artikel heb ik het proces beschreven van het toevoegen van een Go-server aan dotnet aspire en het doorsturen van verzoeken via een reverse proxy. Wat betreft de uitbreidingen heb ik echter nog niet alles beschreven; een voorbeeld van een meer programmatische implementatie via omgevingsvariabelen is apart opgeslagen in een repository. Voor gedetailleerde projectconfiguratie en code, raadpleeg snowmerak/AspireStartPack.
Persoonlijk verwacht ik dat dotnet aspire een eigen rol kan spelen als alternatief voor Docker Compose en als tool voor cloudimplementatie. Er bestaan al generatoren voor het genereren van Docker Compose- en Kubernetes-manifesten, waardoor de toegankelijkheid van infrastructuurtools voor de gemiddelde ontwikkelaar naar mijn mening is verbeterd.