Explorarea execuției scalabile a unui server Go în dotnet Aspire
Ce este dotnet aspire?
dotnet aspire este un instrument conceput pentru a sprijini dezvoltatorii în dezvoltarea și configurarea aplicațiilor cloud-native, pe măsură ce mediile cloud-native devin tot mai răspândite. Acest instrument permite dezvoltatorilor .NET să implementeze cu ușurință proiecte .NET, diverse infrastructuri cloud-native, precum și servicii sau containere scrise în alte limbaje.
În mod firesc, odată cu lansarea și operarea de la docker la k8s, un număr considerabil de domenii, industrii și dezvoltatori au migrat sau migrează de la mediile on-premise tradiționale către mediile cloud-native. Acesta este acum un domeniu matur. Prin urmare, consider că nu este necesar să explic inconvenientele anterioare legate de numele de gazdă, configurația porturilor, firewall-uri și gestionarea metricilor.
Prin urmare, chiar și din explicațiile de mai sus, este posibil să nu înțelegeți deloc ce este dotnet aspire. Acest lucru se datorează faptului că nici Microsoft nu a oferit o definiție exactă. Prin urmare, nici eu nu voi oferi o definiție specifică. Cu toate acestea, deoarece voi utiliza funcționalitățile de bază ale dotnet aspire, așa cum le înțeleg eu în acest articol, puteți să le folosiți ca referință pentru a vă stabili propria poziție.
Structura proiectului
Crearea unui proiect dotnet aspire
Dacă nu aveți șablonul dotnet aspire, trebuie să-l instalați mai întâi. Instalați șablonul folosind următoarea comandă. Dacă nu aveți .NET, vă rugăm să-l instalați singuri.
1dotnet new install Aspire.ProjectTemplates
Apoi, creați o nouă soluție într-un folder adecvat.
1dotnet new sln
După aceea, executați următoarea comandă în folderul soluției pentru a crea un proiect cu șablonul aspire-apphost.
1dotnet new aspire-apphost -o AppHost
Acest lucru va crea un proiect aspire-apphost care conține doar un cod simplu pentru configurare.
Adăugarea Valkey
Acum, să adăugăm Valkey.
Înainte de a adăuga direct, dotnet aspire oferă diverse soluții terțe prin intermediul comunității de hosting. Desigur, Valkey poate beneficia și de acest suport de hosting comunitar și poate fi utilizat cu ușurință prin intermediul următorului pachet nuget.
1dotnet add package Aspire.Hosting.Valkey
Pe lângă aceasta, sunt oferite diverse hosting-uri integrate, care pot fi găsite aici. Revenind la Valkey, deschideți fișierul Program.cs din proiectul AppHost și modificați-l astfel:
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 este o implementare a interfeței IResourceBuilder care conține informații pentru construirea serviciului Valkey.WithDataVolume creează un volum pentru stocarea datelor cache, iar WithPersistence permite stocarea persistentă a datelor cache.
Până aici, pare să îndeplinească un rol similar cu volumes din docker-compose.
Desigur, și voi puteți crea cu ușurință acest lucru.
Totuși, acest lucru depășește scopul acestui articol, așa că nu voi discuta despre el acum.
Crearea unui server Echo în Go
Acum, să adăugăm un server simplu în Go.
Mai întâi, creați un spațiu de lucru în folderul soluției folosind go work init.
Pentru dezvoltatorii .NET, un spațiu de lucru Go este similar cu o soluție.
Apoi, creați un folder numit EchoServer, navigați în el și rulați go mod init EchoServer.
Această comandă creează un modul Go. Pentru dezvoltatorii .NET, un modul poate fi perceput ca fiind similar cu un proiect.
Apoi, creați fișierul main.go și scrieți următorul cod:
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}
Acest server va citi variabila de mediu PORT, care trebuie injectată atunci când Aspire AppHost rulează, și va porni serverul pe portul respectiv.
Este un server simplu care primește o interogare name și returnează Hello, {name}.
Acum, să adăugăm acest server la dotnet aspire.
Adăugarea serverului Echo la Aspire
Reveniți la proiectul Aspire AppHost unde ați adăugat Valkey și adăugați hosting comunitar pentru limbajul Go.
1dotnet add package CommunityToolkit.Aspire.Hosting.Golang
Apoi, deschideți fișierul Program.cs și adăugați următoarele instrucțiuni:
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();
Aici, echoServer este o implementare a interfeței IResourceBuilder care conține informații pentru construirea serverului Go.
Metoda AddGolangApp pe care tocmai ați adăugat-o este o metodă de extensie a hostului personalizat pentru adăugarea unui server Go.
Puteți observa că utilizează un port fix de 3000 și injectează variabila de mediu PORT.
În cele din urmă, WithExternalHttpEndpoints permite accesul extern.
Pentru a testa, accesați http://localhost:3000/?name=world și veți vedea că se afișează Hello, world.
Cu toate acestea, în prezent, dotnet aspire impune o penalitate semnificativă proiectelor non-.NET. Aceasta este...
Extinderea proiectului
Atunci, cum se realizează scalarea orizontală?
În prezent, dotnet aspire oferă opțiunea WithReplica doar pentru builderii de proiecte .NET adăugați cu metoda AddProject.
Cu toate acestea, nu oferă această opțiune pentru host-urile Go sau pentru proiectele externe precum AddContainer.
Prin urmare, trebuie să implementați acest lucru direct utilizând un load balancer sau un reverse proxy separat.
Cu toate acestea, acest lucru ar putea face ca reverse proxy-ul să devină un SPOF (Single Point of Failure), de aceea este de preferat ca reverse proxy-ul să ofere opțiunea WithReplica.
Prin urmare, reverse proxy-ul ar trebui să fie în mod inevitabil un proiect .NET.
Până acum, am folosit metode precum nginx, trafik sau implementări personalizate pentru a rezolva această problemă, dar odată cu restricția unui proiect .NET, nu am avut nicio metodă imediată la dispoziție. Așa că am căutat un reverse proxy implementat în .NET și, din fericire, am găsit opțiunea YARP. YARP este un reverse proxy implementat în .NET, care poate acționa și ca load balancer și oferă diverse funcționalități, motiv pentru care a fost considerat o alegere bună.
Acum, să adăugăm YARP.
Configurarea unui reverse proxy cu YARP
Mai întâi, creați un proiect pentru a utiliza YARP.
1dotnet new web -n ReverseProxy
Apoi, navigați la proiect și instalați YARP.
1dotnet add package Yarp.ReverseProxy --version 2.2.0
După finalizarea instalării, deschideți fișierul Program.cs și scrieți următorul cod:
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"}");
Acest cod este codul de bază pentru utilizarea YARP.routes conține informațiile despre rutele pe care le va utiliza reverse proxy-ul, iar clusters conține informațiile despre clusterul pe care îl va utiliza reverse proxy-ul.
Aceste informații sunt încărcate în reverse proxy cu metoda LoadFromMemory.
În cele din urmă, metoda MapReverseProxy este utilizată pentru a mapa și a rula reverse proxy-ul.
Apoi, pentru utilizare practică, adăugați o referință la proiectul reverse proxy în proiectul aspire apphost și adăugați și modificați următoarele instrucțiuni în fișierul 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();
Acum reverse proxy-ul poate referi serverul echo. Structura se modifică, astfel încât cererile externe sunt primite de reverse proxy și transmise serverului echo.
Modificarea reverse proxy-ului
În primul rând, trebuie să modificăm adresa de ascultare a proiectului alocat reverse proxy-ului.
Eliminați applicationUrl din fișierul Properties/launchSettings.json.
Apoi, deschideți fișierul Program.cs și modificați-l substanțial, după cum urmează:
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"}");
În primul rând, modificăm informațiile despre routes și clusters.
Adăugăm echo-route și echo-cluster pentru a configura reverse proxy-ul să trimită cereri către serverul echo.
De asemenea, modificăm adresa serverului echo pentru a fi citită dintr-o variabilă de mediu.
Regula pentru această adresă este services__{service-name}__http__{index}.
Pentru serverul echo, numele serviciului este echo-server, și deoarece este o singură instanță, folosim 0 ca index.
Dacă adăugați un server asp .net core, mai multe instanțe pot fi create prin WithReplica, astfel încât indexul poate fi incrementat.
Valoarea excepțională http://localhost:8080 este o valoare irelevantă, fără nicio semnificație.
Acum, rulați proiectul și accesați http://localhost:3000/?name=world și veți vedea că se afișează în continuare Hello, world.
Idei de extindere
Acum am adăugat un server Go la dotnet aspire și am confirmat că cererile sunt transmise prin intermediul unui reverse proxy. Acum, putem extinde acest proces pentru a-l implementa programatic. De exemplu, pentru serverul echo, putem adăuga numerotare după numele serviciului pentru a crea mai multe instanțe și putem adăuga automat setările pentru reverse proxy.
Modificați codul care utilizează reverse proxy-ul și serverul echo în fișierul Program.cs al proiectului aspire apphost, după cum urmează:
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}
Apoi, modificați fișierul Program.cs al proiectului reverse proxy, după cum urmează:
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};
Adăugăm setări de destinație pentru cele 8 instanțe de server echo.
Acum, reverse proxy-ul are informațiile despre destinațiile serverelor echo extinse și poate transmite cererile.
Dacă accesați http://localhost:3000/?name=world ca înainte, veți vedea că se afișează în continuare Hello, world.
Concluzie
În acest articol, am explicat procesul de adăugare a unui server Go la dotnet aspire și de transmitere a cererilor prin intermediul unui reverse proxy. Cu toate acestea, în ceea ce privește extinderea, nu am scris încă totul și am creat un exemplu separat într-un depozit pentru a demonstra cum poate fi implementat programatic prin variabile de mediu. Pentru o configurație detaliată a proiectului și cod, vă rugăm să consultați snowmerak/AspireStartPack.
Personal, mă aștept ca dotnet aspire să-și îndeplinească propriul rol ca alternativă la docker compose și ca instrument de implementare în cloud. Există deja generatoare care creează manifesturi docker compose sau k8s, ceea ce, cred eu, a îmbunătățit accesibilitatea instrumentelor de infrastructură pentru dezvoltatorii obișnuiți.