Spustenie škálovateľného Go servera v .NET Aspire

dotnet aspire?
dotnet aspire je nástroj, ktorý vznikol na pomoc vývojárom pri vývoji a konfigurácii cloud-native aplikácií v súvislosti s rastúcim rozšírením cloud-native prostredia. Tento nástroj umožňuje vývojárom .NET jednoducho nasadzovať projekty v .NET, rôznu cloud-native infraštruktúru, a tiež služby napísané v iných jazykoch alebo kontajnery.
Samozrejme, od Dockera až po K8s sa to už využíva a prevádzkuje, a mnohé oblasti, odvetvia a vývojári v rámci tradičného on-premise prostredia už migrovali alebo sú v procese migrácie do cloud-native prostredia. Dnes je to už vyspelá oblasť. Preto predpokladám, že nie je potrebné vysvetľovať existujúce nepohodlie spojené s hostname, konfiguráciou portov, firewallom či správou metrík.
Práve preto vás na základe vyššie uvedených vysvetlení určite stále nenapadne, čo dotnet aspire vlastne je. Dôvod je ten, že ani Microsoft zatiaľ neposkytuje presnú definíciu. Ani ja preto nebudem uvádzať žiadnu vlastnú definíciu. Namiesto toho v tomto článku použijem základné funkcie dotnet aspire, ako ich chápem ja, a môžete ich použiť ako referenciu na vytvorenie vlastného pohľadu.
Konfigurácia projektu
Vytvorenie projektu dotnet aspire
Ak šablóna dotnet aspire nie je k dispozícii, najprv ju musíte nainštalovať. Šablónu nainštalujete nasledujúcim príkazom. Ak nemáte nainštalovaný .NET, nainštalujte si ho prosím sami.
1dotnet new install Aspire.ProjectTemplates
Potom na vhodnom priečinku vytvorte nové riešenie.
1dotnet new sln
Následne v priečinku riešenia spustite nasledujúci príkaz, ktorým vytvoríte projekt zo šablóny aspire-apphost.
1dotnet new aspire-apphost -o AppHost
Vytvorí sa projekt aspire-apphost, ktorý obsahuje iba jednoduchý kód na nastavenie.
Pridanie Valkey
Teraz jednoducho pridáme Valkey.
Skôr než ho pridáme, dotnet aspire poskytuje rôzne riešenia tretích strán prostredníctvom community hostingu. Valkey samozrejme môže využívať túto podporu komunitného hostingu a jednoducho ho možno použiť prostredníctvom nasledujúceho balíka NuGet.
1dotnet add package Aspire.Hosting.Valkey
Ďalšie rôzne integrované hostingy sú k dispozícii, a môžete si ich pozrieť tu. Vráťme sa k Valkey: v projekte AppHost otvorte súbor Program.cs a upravte ho nasledovne.
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 je implementácia rozhrania IResourceBuilder, ktorá obsahuje informácie potrebné na zostavenie služby Valkey.WithDataVolume vytvára zväzok na uloženie dát cache, a WithPersistence zabezpečuje, že dáta cache sa budú trvalo ukladať.
Na základe toho sa zdá, že plní podobnú úlohu ako volumes v docker-compose.
Samozrejme, aj vy si to môžete jednoducho vytvoriť.
Keďže však presahuje rámec tohto článku, nebudem sa tomu venovať teraz.
Vytvorenie echo servera v Go
Teraz pridáme jednoduchý server v jazyku Go.
Najprv v priečinku riešenia vytvorte workspace príkazom go work init.
Pre vývojárov v .NET možno Go workspace považovať za niečo podobné ako riešenie (solution).
Potom vytvorte priečinok EchoServer, prejdite do neho a spustite go mod init EchoServer.
Týmto príkazom vytvoríte Go modul. Modul môžete chápať ako niečo podobné projektu pre vývojárov v .NET.
Následne vytvorte súbor main.go a napíšte do neho nasledujúci kód.
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}
Tento server pri spustení AppHost v Aspire dostane premennú prostredia PORT, na ktorej má počúvať, načíta tento port a spustí server.
Ide o jednoduchý server, ktorý prijme query parameter name a vráti Hello, {name}.
Teraz pridajme tento server do dotnet aspire.
Pridanie echo servera do aspire
Vráťte sa do projektu Aspire AppHost, v ktorom ste pridali Valkey, a pridajte community hosting pre jazyk Go.
1dotnet add package CommunityToolkit.Aspire.Hosting.Golang
Potom otvorte súbor Program.cs a pridajte nasledujúci výraz.
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();
Tu echoServer je implementácia rozhrania IResourceBuilder, ktorá obsahuje informácie potrebné na zostavenie servera v Go.
Práve pridaná metóda AddGolangApp je rozširujúca metóda vlastného hosta na pridanie servera v Go.
Server používa pevne nastavený port 3000 a je vidieť, že sa mu vkladá premenná prostredia PORT.
Nakoniec WithExternalHttpEndpoints umožňuje prístup zvonku.
Na otestovanie otvorte http://localhost:3000/?name=world, a mali by ste vidieť výstup Hello, world.
Avšak v súčasnosti dotnet aspire prináša pre projekty, ktoré nie sú v .NET, značnú penalizáciu. Tou je práve...
Rozširovanie projektu
Ako je to potom s horizontálnym škálovaním?
Dotnet aspire momentálne poskytuje možnosť WithReplica iba pre builder .NET projektov pridaných metódou AddProject.
Pre hosty Go ani pre externé projekty, ako napríklad AddContainer, však túto možnosť neposkytuje.
Preto je potrebné použiť samostatný load balancer alebo reverse proxy a implementovať to ručne.
Keďže však reverse proxy by sa mohol stať SPOF, je vhodné, aby aj reverse proxy poskytoval možnosť WithReplica.
Potom nevyhnutne musí byť reverse proxy projekt v .NET.
Doteraz sme na tento problém používali nginx, traefik, vlastnú implementáciu a podobné riešenia, no keď sa objavilo obmedzenie na projekt v .NET, nemal som v tej chvíli žiadnu možnosť. Preto som hľadal reverse proxy implementované v .NET a našťastie existovala možnosť YARP. YARP je reverse proxy implementované v .NET, ktoré dokáže fungovať aj ako load balancer a ponúka rôzne funkcie, preto som ho považoval za dobrú voľbu.
Teraz pridáme YARP.
Konfigurácia reverse proxy s YARP
Najprv vytvorte projekt na použitie YARP.
1dotnet new web -n ReverseProxy
Potom prejdite do projektu a nainštalujte YARP.
1dotnet add package Yarp.ReverseProxy --version 2.2.0
Po dokončení inštalácie otvorte súbor Program.cs a napíšte nasledujúci kód.
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"}");
Tento kód je základný kód na použitie YARP.routes obsahuje informácie o route, ktoré bude reverse proxy používať, a clusters obsahuje informácie o clusteroch, ktoré bude reverse proxy používať.
Tieto informácie sa načítavajú do reverse proxy metódou LoadFromMemory.
Nakoniec pomocou metódy MapReverseProxy namapujeme reverse proxy a spustíme ho.
Na reálne použitie potom v projekte aspire apphost pridajte referenciu na projekt reverse proxy a v súbore Program.cs pridajte alebo upravte nasledujúci kód.
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();
Teraz reverse proxy môže odkazovať na echo server. Štruktúra sa mení tak, že externé požiadavky prijíma reverse proxy a preposiela ich na echo server.
Úprava reverse proxy
Najprv je potrebné zmeniť adresu, na ktorej projekt reverse proxy počúva.
V súbore Properties/launchSettings.json odstráňte applicationUrl.
Potom otvorte súbor Program.cs a výrazne ho upravte nasledovne.
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"}");
Najprv upravíme informácie o routes a clusters.
Pridali sme echo-route a echo-cluster, aby sa požiadavky posielali na echo server.
Ďalej upravíme kód tak, aby sa adresa echo servera načítavala z premenných prostredia.
Pravidlo pre túto adresu je services__{service-name}__http__{index}.
Pre echo server je názov služby echo-server a keďže ide o jednu inštanciu, ako index používame 0.
Ak by sme pridali server asp .net core, pomocou WithReplica by sa mohlo vytvoriť viacero inštancií, takže stačí zvýšiť index.
Hodnota http://localhost:8080 použitá pri spracovaní výnimky je bezvýznamná hodnota bez akéhokoľvek významu.
Teraz spustite projekt a otvorte http://localhost:3000/?name=world, a aj tak by ste mali vidieť výstup Hello, world.
Nápady na rozšírenie
Teraz sme overili, že sme do dotnet aspire pridali server v Go a že reverse proxy preposiela požiadavky. Tento proces môžeme ďalej rozšíriť tak, aby sa dal implementovať programatickým spôsobom. Napríklad pre echo server môžeme za názov služby pridať číslovanie, vytvoriť viacero inštancií a automaticky pridať konfiguráciu do reverse proxy.
Do súboru Program.cs v projekte aspire apphost upravte kód používajúci reverse proxy a echo server nasledovne.
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}
Potom upravte súbor Program.cs v projekte reverse proxy nasledovne.
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};
Pridali sme konfiguráciu cieľov pre osem inštancií echo servera.
Reverse proxy teraz má informácie o cieľoch pre všetky echo servery a dokáže požiadavky preposielať.
Ak otvoríte pôvodnú adresu http://localhost:3000/?name=world, aj tak by ste mali vidieť výstup Hello, world.
Záver
V tomto článku sme popísali, ako pridať server v Go do dotnet aspire a ako preposielať požiadavky cez reverse proxy. Rozšírenie sme však zatiaľ úplne nepopísali; v samostatnom repozitári sme pripravili príklad, ktorý umožňuje implementovať to programatickejšie pomocou premenných prostredia. Podrobnú konfiguráciu projektu a kód nájdete v snowmerak/AspireStartPack.
Osobne očakávam, že dotnet aspire môže zohrávať vlastnú úlohu ako alternatíva k docker compose a zároveň ako nástroj na nasadzovanie do cloudu. Už existujú generátory, ktoré vytvárajú docker compose alebo k8s manifesty, takže si myslím, že dostupnosť infraštruktúrnych nástrojov pre bežných vývojárov sa zlepšila.