GoSuda

Încercarea de a rula un server Go extensibil în dotnet aspire

By snowmerak
views ...

dotnet aspire?

dotnet aspire este un instrument creat pentru a asista dezvoltatorii în dezvoltarea și configurarea cloud-native, având în vedere creșterea mediilor cloud-native. Acest instrument permite dezvoltatorilor .NET să implementeze cu ușurință proiecte .NET, diverse infrastructuri cloud-native, servicii în alte limbi și containere.

Desigur, se lansează și operează de la docker la k8s, iar un număr considerabil de domenii, industrii și dezvoltatori au trecut la mediul cloud-native de la mediul on-premise existent și se află deja în această stare. Acum este un domeniu matur. Prin urmare, cred că nu este necesar să explicăm inconvenientele existente privind numele de gazdă, configurarea porturilor, firewall-ul, gestionarea metricilor etc.

Prin urmare, chiar și din explicațiile de mai sus, este posibil să nu înțelegeți ce este dotnet aspire. Asta pentru că nici Microsoft nu a oferit o definiție exactă. Prin urmare, nici eu nu voi oferi o definiție specifică. Totuși, în acest articol voi utiliza funcțiile de bază ale dotnet aspire așa cum le-am înțeles eu, așa că vă rog să le folosiți ca referință pentru a vă stabili propria poziție.

Structura proiectului

Crearea unui proiect dotnet aspire

Dacă nu aveți un șablon dotnet aspire, trebuie să instalați mai întâi șablonul. Instalați șablonul cu următoarea comandă. Dacă nu aveți .net, vă rugăm să îl instalați dumneavoastră.

1dotnet new install Aspire.ProjectTemplates

Apoi, creați o soluție nouă într-un folder adecvat.

1dotnet new sln

Apoi, rulați următoarea comandă în folderul soluției pentru a crea un proiect cu șablonul aspire-apphost.

1dotnet new aspire-apphost -o AppHost

Apoi, se creează un proiect aspire-apphost care conține doar codul de bază pentru configurare.

Adăugarea Valkey

Acum, vom adăuga simplu Valkey.

Înainte de a adăuga, dotnet aspire oferă diverse soluții terțe prin intermediul community hosting. Desigur, valkey poate primi suport din partea acestei comunități de hosting și poate fi utilizat cu ușurință prin următorul pachet nuget.

1dotnet add package Aspire.Hosting.Valkey

De asemenea, oferă o varietate de hosting integrat, pe care îl puteți verifica aici. Revenind la valkey, deschideți fișierul Program.cs din proiectul AppHost și modificați-l după cum urmează.

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 continuă a datelor cache. Până aici, pare să aibă un rol similar cu volumes din docker-compose. Bineînțeles, puteți face acest lucru cu ușurință. Dar, deoarece depășește sfera acestui articol, nu voi discuta despre el acum.

Crearea unui server echo în Go

Acum, vom adăuga un simplu server în limbajul Go. Mai întâi, creați un spațiu de lucru prin go work init în folderul soluției. Pentru dezvoltatorii .NET, spațiul de lucru Go este similar cu o soluție.

Apoi, creați un folder numit EchoServer, deplasați-vă în interiorul său și rulați go mod init EchoServer. Această comandă creează un modul Go. Un modul este similar cu un proiect pentru dezvoltatorii .NET. Apoi, creați un fișier main.go și scrieți după cum urmează.

 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 citește variabila de mediu PORT, pe care Aspire AppHost o va injecta la rulare, și pornește serverul pe portul respectiv. Este un server simplu care primește o interogare name și returnează Hello, {name}.

Acum, vom adăuga 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ătoarea instrucțiune.

 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 am adăugat-o este o metodă de extensie a gazdei personalizate pentru adăugarea unui server în limbajul Go. Se poate vedea că utilizează fix portul 3000 și injectează variabila de mediu PORT. În final, WithExternalHttpEndpoints permite accesul din exterior.

Pentru testare, dacă accesați http://localhost:3000/?name=world, veți vedea Hello, world afișat.

Dar, în prezent, dotnet aspire are o penalitate mare pentru proiectele non-dotnet. Și anume...

Extinderea proiectului

Cum facem scalarea orizontală?

În prezent, dotnet aspire oferă opțiunea WithReplica numai pentru constructoarele de proiecte dotnet adăugate cu metoda AddProject. Cu toate acestea, nu oferă această opțiune pentru gazdele Go sau pentru proiectele externe precum AddContainer.

Prin urmare, trebuie să o implementați singur utilizând un load balancer sau un reverse proxy separat. Cu toate acestea, deoarece acest reverse proxy poate deveni un SPOF, este preferabil ca reverse proxy-ul să ofere opțiunea WithReplica. Atunci, în mod necesar, reverse proxy-ul trebuie să fie un proiect .NET.

Până acum, am folosit metode precum nginx, trafik sau implementare directă pentru această problemă, dar nu am avut nicio metodă la îndemână odată ce am fost restricționat la un proiect .NET. Așa că am căutat un reverse proxy implementat în .NET și, din fericire, am avut o opțiune numită YARP. YARP este un reverse proxy implementat în .NET, care poate acționa și ca load balancer, și oferă diverse funcții, așa că am considerat că este o alegere bună.

Acum, vom adăuga YARP.

Configurare reverse proxy cu YARP

Mai întâi, creați un proiect pentru a utiliza YARP.

1dotnet new web -n ReverseProxy

Apoi, deplasați-vă 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 după cum urmează.

 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"}");

Acesta este codul de bază pentru utilizarea YARP.routes conține informațiile de rutare pe care reverse proxy le va utiliza, iar clusters conține informațiile de cluster pe care reverse proxy le va utiliza. Aceste informații sunt încărcate în reverse proxy cu metoda LoadFromMemory. În final, reverse proxy este mapat și rulat cu metoda MapReverseProxy.

Apoi, pentru a fi utilizat în practică, adăugați proiectul reverse proxy ca referință în proiectul aspire apphost și adăugați și modificați următoarea instrucțiune î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 poate face referire la serverul echo. Structura se schimbă astfel încât cererile din exterior sunt primite de reverse proxy și transmise la serverul echo.

Modificarea reverse proxy

Mai întâi, trebuie să modificăm adresa de listening 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 radical 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"}");

Mai întâi, modificăm informațiile pentru routes și clusters. Adăugăm echo-route și echo-cluster pentru a trimite solicitări către serverul echo. Apoi, modificăm astfel încât să utilizăm adresa serverului echo citită din variabila de mediu.

Regula pentru această adresă este services__{nume-serviciu}__http__{index}. În cazul serverului 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, se pot crea mai multe instanțe prin WithReplica, deci puteți crește indexul și să îl utilizați. Valoarea http://localhost:8080, care este gestionată ca excepție, este o valoare nedorită fără sens.

Acum, dacă rulați proiectul și accesați http://localhost:3000/?name=world, veți vedea că Hello, world este încă afișat.

Idee de extindere

Acum am verificat adăugarea unui server Go la dotnet aspire și transmiterea cererilor prin reverse proxy. Atunci, acum putem extinde acest proces astfel încât să poată fi implementat 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 ș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 ale serverului echo extinse. Acum, reverse proxy are informații despre destinația pentru serverele echo extinse și poate transmite cereri. Dacă accesați http://localhost:3000/?name=world existent, veți vedea că Hello, world este încă afișat.

În concluzie

În acest articol, am explicat procesul de adăugare a unui server Go la dotnet aspire și de transmitere a solicitărilor prin reverse proxy. Cu toate acestea, nu am scris încă totul despre extindere și am creat un exemplu care poate fi implementat mai programatic prin variabile de mediu într-un depozit separat. Pentru o structură detaliată a proiectului și cod, vă rugăm să consultați snowmerak/AspireStartPack.

Eu personal mă aștept ca dotnet aspire să își joace propriul rol ca alternativă la docker compose și ca instrument de implementare cloud. Deoarece există deja un generator care creează docker compose sau manifest k8s, cred că accesibilitatea instrumentelor de infrastructură pentru dezvoltatorii obișnuiți s-a îmbunătățit.