GoSuda

Eksekvering av Go-servere skalerbart i .NET Aspire

By snowmerak
views ...

dotnet aspire?

dotnet aspire er et verktøy som er utviklet for å bistå utviklere med utvikling og konfigurasjon av skynative applikasjoner, som følge av den økende utbredelsen av skynative miljøer. Dette verktøyet gjør det mulig for .NET-utviklere å enkelt distribuere .NET-prosjekter, diverse skynative infrastruktur, samt tjenester eller containere skrevet i andre språk.

Det er naturlig at fra Docker til Kubernetes har blitt lansert og er i drift, og et betydelig antall sektorer, industrier og utviklere har migrert, eller er i ferd med å migrere, fra tradisjonelle on-premise miljøer til skynative miljøer. Dette er nå et modent felt. Derfor anser jeg det som unødvendig å forklare de tidligere ulempene knyttet til vertsnavn, portkonfigurasjon, brannmur og metrikkstyring.

Basert på forklaringene ovenfor er det derfor sannsynligvis vanskelig å forstå hva dotnet aspire egentlig er. Dette skyldes at selv Microsoft ikke har gitt en presis definisjon. Jeg vil derfor heller ikke gi en spesifikk definisjon. Imidlertid vil jeg i denne artikkelen benytte meg av de grunnleggende funksjonene i dotnet aspire slik jeg har forstått dem, og du kan gjerne bruke dette som referanse for å finne din egen tilnærming.

Prosjektkonfigurasjon

Opprettelse av dotnet aspire-prosjekt

Hvis du ikke har dotnet aspire-malen, må du først installere malen. Installer malen med følgende kommando. Hvis du ikke har .NET, må du installere det selv.

1dotnet new install Aspire.ProjectTemplates

Deretter oppretter du en ny løsning i en passende mappe.

1dotnet new sln

Deretter kjører du følgende kommando i løsningsmappen for å opprette et prosjekt fra aspire-apphost-malen.

1dotnet new aspire-apphost -o AppHost

Dette vil opprette et aspire-apphost-prosjekt som kun inneholder enkel kode for oppsett.

Legge til Valkey

La oss nå enkelt legge til Valkey.

Før vi legger det til, tilbyr dotnet aspire ulike tredjepartsløsninger via community hosting. Valkey kan naturligvis også dra nytte av denne fellesskapshostingen, og kan enkelt benyttes via følgende NuGet-pakke.

1dotnet add package Aspire.Hosting.Valkey

I tillegg finnes det en rekke integrerte hosting-løsninger, som du kan se her. Tilbake til Valkey, åpne filen Program.cs i AppHost-prosjektet og endre den som følger.

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 er en implementasjon av IResourceBuilder-grensesnittet som inneholder informasjon for å bygge Valkey-tjenesten.WithDataVolume oppretter et volum for å lagre cache-data, og WithPersistence gjør det mulig å lagre cache-data permanent. Så langt ser det ut til å ha en lignende funksjon som volumes i docker-compose. Selvfølgelig kan dere enkelt lage dette selv. Men dette faller utenfor omfanget av denne artikkelen, så jeg vil ikke diskutere det nå.

Opprettelse av Go-språk Echo-server

La oss nå legge til en enkel Go-språkserver. Først oppretter vi et arbeidsområde ved å kjøre go work init i løsningsmappen. For .NET-utviklere kan et Go-arbeidsområde betraktes som noe lignende en løsning.

Deretter oppretter du en mappe kalt EchoServer, navigerer inn i den, og kjører go mod init EchoServer. Denne kommandoen oppretter en Go-modul. For .NET-utviklere kan en modul oppfattes som noe lignende et prosjekt. Deretter oppretter du filen main.go og skriver følgende:

 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}

Denne serveren vil, når Aspire AppHost kjører, motta PORT miljøvariabelen den skal lytte på, lese denne porten og starte serveren. Det er en enkel server som mottar en name-spørring og returnerer Hello, {name}.

La oss nå legge til denne serveren i dotnet aspire.

Legge til Echo-serveren i Aspire

Gå tilbake til Aspire AppHost-prosjektet der Valkey ble lagt til, og legg til community hosting for Go-språket.

1dotnet add package CommunityToolkit.Aspire.Hosting.Golang

Deretter åpner du filen Program.cs og legger til følgende utsagn:

 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();

Her er echoServer en implementasjon av IResourceBuilder-grensesnittet som inneholder informasjon for å bygge Go-språkserveren. Den nylig tilføyde AddGolangApp-metoden er en utvidelsesmetode for den tilpassede verten for å legge til en Go-språkserver. Du kan se at den bruker port 3000 fast, og injiserer PORT miljøvariabelen. Til slutt er WithExternalHttpEndpoints for å gjøre den tilgjengelig eksternt.

For å teste det, kan du koble deg til http://localhost:3000/?name=world, og du vil se "Hello, world" bli skrevet ut.

Imidlertid har dotnet aspire for øyeblikket en betydelig ulempe for non-dotnet prosjekter. Det er...

Prosjektutvidelse

Hvordan skalerer man horisontalt da?

For øyeblikket tilbyr dotnet aspire kun WithReplica-alternativet til byggere for .NET-prosjekter som er lagt til med AddProject-metoden. Dette alternativet er imidlertid ikke tilgjengelig for Go-språkverter eller eksterne prosjekter som AddContainer.

Derfor må man implementere dette direkte ved å bruke en separat load balancer eller reverse proxy. Men dette kan føre til at reverse proxyen blir et SPOF (Single Point of Failure), så det er å foretrekke at reverse proxyen tilbyr WithReplica-alternativet. Dermed må reverse proxyen nødvendigvis være et .NET-prosjekt.

Jeg har tidligere brukt metoder som Nginx, Traefik eller egen implementasjon for slike problemer, men med begrensningen til .NET-prosjekter hadde jeg ingen umiddelbar løsning. Derfor søkte jeg etter en reverse proxy implementert i .NET, og heldigvis fantes det et alternativ: YARP. YARP er en reverse proxy implementert i .NET, som også kan fungere som en load balancer og tilbyr ulike funksjoner, så det ble ansett som et godt valg.

La oss nå legge til YARP.

Konfigurere reverse proxy med YARP

Først oppretter vi et prosjekt for å bruke YARP.

1dotnet new web -n ReverseProxy

Deretter navigerer du til prosjektet og installerer YARP.

1dotnet add package Yarp.ReverseProxy --version 2.2.0

Når installasjonen er fullført, åpner du filen Program.cs og skriver følgende:

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

Denne koden er grunnleggende for å bruke YARP.routes inneholder ruteinformasjonen som reverse proxyen skal bruke, og clusters inneholder klyngeinformasjonen som reverse proxyen skal bruke. Denne informasjonen lastes inn i reverse proxyen ved hjelp av LoadFromMemory-metoden. Til slutt, MapReverseProxy-metoden brukes til å mappe og kjøre reverse proxyen.

For praktisk bruk legger du til reverse proxy-prosjektet som en referanse i Aspire AppHost-prosjektet, og legger til og endrer følgende utsagn i Program.cs-filen.

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();

Nå kan reverse proxyen referere til echo serveren. Strukturen endres slik at eksterne forespørsler mottas av reverse proxyen og videresendes til echo serveren.

Endre Reverse Proxy

Først må lytteadressen til prosjektet som er tilordnet reverse proxyen endres. Fjern applicationUrl inne i Properties/launchSettings.json-filen. Deretter åpner du filen Program.cs og endrer den omfattende som vist nedenfor.

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

Først endrer vi informasjonen om routes og clusters. Vi legger til henholdsvis echo-route og echo-cluster for å konfigurere forespørsler til å sendes til echo-serveren. Deretter endrer vi for å bruke echo-serverens adresse, som leses fra miljøvariabelen.

Regelen for denne adressen er services__{service-name}__http__{index}. For echo-serveren er tjenestenavnet echo-server, og siden det er en enkelt instans, brukes indeksen 0. Hvis man legger til en ASP.NET Core-server, kan flere instanser genereres via WithReplica, så indeksen kan økes deretter. Den unntakshåndterte verdien http://localhost:8080 er en meningsløs søppelverdi.

Nå kan du kjøre prosjektet og koble deg til http://localhost:3000/?name=world, og du vil fortsatt se "Hello, world" bli skrevet ut.

Utvidelsesideer

Nå har vi lagt til en Go-server i dotnet aspire og bekreftet at forespørsler videresendes via en reverse proxy. Vi kan nå utvide denne prosessen for å implementere den programmatisk. For eksempel kan vi generere flere instanser av echo-serveren ved å legge til nummerering etter tjenestenavnet, og automatisk legge til innstillinger for reverse proxyen.

I Program.cs-filen til aspire apphost-prosjektet endrer du koden som bruker reverse proxy og echo server som følger:

 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}

Og endre Program.cs-filen i reverse proxy-prosjektet som følger:

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

Legger til destinasjonsinnstillinger for 8 utvidede echo server-instanser. Nå har reverse proxyen destinasjonsinformasjon for de utvidede echo serverne og kan videresende forespørsler. Hvis du kobler deg til den eksisterende http://localhost:3000/?name=world, vil du fortsatt se "Hello, world" bli skrevet ut.

Avslutning

I denne artikkelen har jeg beskrevet prosessen med å legge til en Go-server i dotnet aspire og videresende forespørsler via en reverse proxy. Jeg har imidlertid ikke fullført alt angående utvidelse, og har utarbeidet et eksempel i et eget repository som viser hvordan man kan implementere dette mer programmatisk via miljøvariabler. For detaljert prosjektkonfigurasjon og kode, se snowmerak/AspireStartPack.

Jeg personlig forventer at dotnet aspire vil kunne fylle sin egen rolle som et alternativ til Docker Compose og som et verktøy for skybasert distribusjon. Det finnes allerede generatorer for å generere Docker Compose- eller Kubernetes-manifestfiler, noe som jeg tror har gjort infrastrukturverktøy mer tilgjengelige for vanlige utviklere.