GoSuda

Hur man kör en skalbar Go-server i dotnet aspire

By snowmerak
views ...

dotnet aspire?

dotnet aspire är ett verktyg som skapats för att hjälpa utvecklare med molnbaserad utveckling och konfiguration, i takt med att molnbaserade miljöer blir allt vanligare. Detta verktyg gör det möjligt för .NET-utvecklare att enkelt driftsätta .NET-projekt, olika molnbaserade infrastrukturer samt tjänster eller containrar i andra språk.

Självklart lanseras och drivs det från Docker till k8s, och många områden, branscher och utvecklare migrerar eller har migrerat från den traditionella on-premise-miljön till molnbaserade miljöer. Det är nu ett moget område. Därför anser jag att det inte finns något behov av att förklara de tidigare olägenheterna med värdnamn, portkonfigurationer, brandväggar, hantering av mått etc.

Därför kommer du förmodligen inte att ha någon aning om vad dotnet aspire är, bara genom att läsa ovanstående förklaringar. Detta beror på att Microsoft inte har gett en exakt definition. Därför kommer inte heller jag att ge någon specifik definition. Jag kommer dock att använda de grundläggande funktionerna i dotnet aspire som jag förstår dem i den här artikeln, så jag hoppas att du tar det i beaktning och hittar din egen ståndpunkt.

Projektkonfiguration

Skapa ett dotnet aspire-projekt

Om du inte har en dotnet aspire-mall måste du först installera mallen. Installera mallen med följande kommando. Om du inte har .net installerat, installera det själv.

1dotnet new install Aspire.ProjectTemplates

Skapa sedan en ny lösning i en lämplig mapp.

1dotnet new sln

Kör sedan följande kommando i lösningsmappen för att skapa ett projekt med aspire-apphost-mallen.

1dotnet new aspire-apphost -o AppHost

Då skapas ett aspire-apphost-projekt som endast innehåller enkel kod för inställningarna.

Lägg till Valkey

Låt oss nu lägga till Valkey på ett enkelt sätt.

Innan vi lägger till det, tillhandahåller dotnet aspire olika tredjepartslösningar genom community hosting. Självklart stöds även Valkey av denna community hosting, och det kan enkelt användas via följande NuGet-paket.

1dotnet add package Aspire.Hosting.Valkey

Det finns andra diverse integrerade värdtjänster tillgängliga, som du kan se här. Låt oss återgå till Valkey och öppna filen Program.cs i AppHost-projektet och ändra den som följer:

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 är en implementation av IResourceBuilder-gränssnittet med information för att bygga en valkey-tjänst.WithDataVolume skapar en volym för att lagra cache-data, och WithPersistence möjliggör kontinuerlig lagring av cache-data. Hittills verkar det ha en liknande roll som volumes i docker-compose. Självklart kan du också skapa detta utan större svårighet. Men det går utanför ramen för den här artikeln, så jag kommer inte att prata om det nu.

Skapa en Go-språk eko-server

Låt oss nu lägga till en enkel Go-språkserver. Skapa först en arbetsyta med go work init i lösningsmappen. För .NET-utvecklare kan Go-arbetsytan ses som liknande en lösning.

Skapa sedan en mapp som heter EchoServer och flytta till den, kör sedan go mod init EchoServer. Detta kommando skapar en Go-modul. Modulen kan betraktas som liknande ett projekt för .NET-utvecklare. Skapa sedan en main.go-fil och skriv följande i den.

 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}

När denna server körs av Aspire AppHost, och en PORT-miljövariabel som servern ska lyssna på injiceras, läser den porten och kör servern. Det är en enkel server som tar emot en name-fråga och returnerar Hello, {name}.

Låt oss nu lägga till denna server till dotnet aspire.

Lägg till eko-servern till Aspire

Låt oss gå tillbaka till Aspire AppHost-projektet där vi lade till Valkey, och lägga till community hosting för Go-språket.

1dotnet add package CommunityToolkit.Aspire.Hosting.Golang

Öppna sedan filen Program.cs och lägg till följande uttryck.

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

Här är echoServer en implementation av IResourceBuilder-gränssnittet med information för att bygga en Go-språkserver. Metoden AddGolangApp som vi just lade till är en utökningsmetod för anpassad värdtjänst för att lägga till en Go-språkserver. Den använder fast port 3000 och man kan se att den injicerar PORT-miljövariabeln. Slutligen är WithExternalHttpEndpoints för att göra den tillgänglig externt.

Om du ansluter till http://localhost:3000/?name=world för att testa bör du se Hello, world skrivas ut.

Men för närvarande har dotnet aspire en tung nackdel för icke-dotnet-projekt. Det är...

Projektutökning

Hur är det med horisontell skalning?

För närvarande tillhandahåller dotnet aspire endast WithReplica-alternativet för byggare av .NET-projekt som lagts till med metoden AddProject. Men detta alternativ tillhandahålls inte för externa projekt som Go-språkvärdar eller AddContainer.

Därför måste det implementeras direkt med hjälp av en separat lastbalanserare eller omvänd proxy. Men i det här fallet kan den omvända proxyn bli en SPOF, så det är bra om den omvända proxyn har WithReplica-alternativet. Då måste den omvända proxyn nödvändigtvis vara ett .NET-projekt.

Hittills har jag använt metoder som nginx, trafik och direkt implementering för det här problemet, men med begränsningen av att det måste vara ett .NET-projekt fanns det ingen omedelbar lösning från min sida. Så jag letade efter en omvänd proxy som var implementerad i .NET, och lyckligtvis fanns det ett alternativ som heter YARP. YARP är en omvänd proxy implementerad i .NET, den kan även fungera som en lastbalanserare och tillhandahåller olika funktioner, så jag bedömde att det var ett bra val.

Låt oss nu lägga till YARP.

Konfigurera omvänd proxy med YARP

Skapa först ett projekt för att använda YARP.

1dotnet new web -n ReverseProxy

Gå sedan till projektet och installera YARP.

1dotnet add package Yarp.ReverseProxy --version 2.2.0

När installationen är klar, öppna filen Program.cs och skriv följande:

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

Denna kod är grundläggande kod för att använda YARP.routes innehåller routningsinformationen som den omvända proxyn ska använda, och clusters innehåller klusterinformationen som den omvända proxyn ska använda. Denna information laddas in i den omvända proxyn med metoden LoadFromMemory. Slutligen mappas och körs den omvända proxyn med metoden MapReverseProxy.

För praktisk användning, lägg till en referens till det omvända proxyprojektet i aspire apphost-projektet, och lägg till och ändra följande uttryck i filen 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 den omvända proxyn referera till eko-servern. Strukturen ändras så att förfrågningar som kommer in externt tas emot av den omvända proxyn och överförs till eko-servern.

Ändra omvänd proxy

Först måste lyssningsadressen för projektet som är tilldelat den omvända proxyn ändras. Ta bort applicationUrl inuti Properties/launchSettings.json-filen. Öppna sedan filen Program.cs och gör en stor ändring enligt nedan.

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

Ändra först informationen om routes och clusters. Lägg till echo-route och echo-cluster för att skicka förfrågningar till eko-servern. Ändra sedan så att eko-serverns adress läses från en miljövariabel och används.

Regeln för denna adress är services__{service-name}__http__{index}. För eko-servern är tjänstens namn echo-server och det är en enda instans, så vi använder 0 som index. Om du lägger till en asp .net core-server kan flera instanser skapas med WithReplica, så indexet kan ökas för att användas. Undantagshanterade http://localhost:8080 är ett värdelöst skräpvärde.

Om du nu kör projektet och ansluter till http://localhost:3000/?name=world, bör du fortfarande se Hello, world skrivas ut.

Utökningsidé

Nu har vi bekräftat hur man lägger till en Go-server till dotnet aspire och skickar förfrågningar via en omvänd proxy. Sedan kan du utöka detta för att implementera processen programmatiskt. Till exempel kan flera instanser skapas genom att lägga till en numrering efter tjänstens namn för eko-servern, och inställningarna för den omvända proxyn kan läggas till automatiskt.

Ändra koden i filen Program.cs för aspire apphost-projektet för att använda den omvända proxyn och eko-servern som följer.

 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}

Ändra sedan filen Program.cs i det omvända proxyprojektet som följer.

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

Lägg till destinationsinställningar för de 8 utökade eko-serverinstanserna. Nu har den omvända proxyn destinationsinformationen för de utökade eko-servrarna och kan vidarebefordra förfrågningar. Om du ansluter till den tidigare http://localhost:3000/?name=world, bör du fortfarande se Hello, world skrivas ut.

Avslutningsvis

I den här artikeln har jag förklarat processen att lägga till en Go-server till dotnet aspire och vidarebefordra förfrågningar via en omvänd proxy. Jag har dock inte skrivit allt om utökningar ännu, och jag har skrivit ett exempel som kan implementeras mer programmatiskt via miljövariabler i ett separat repo. Se snowmerak/AspireStartPack för detaljerad projektkonfiguration och kod.

Personligen förväntar jag mig att dotnet aspire kan spela sin egen roll som ett alternativ till docker compose och som ett molndistributionsverktyg. Det finns redan en generator som genererar docker compose- eller k8s-manifest, vilket jag tror har förbättrat tillgängligheten av infrastrukturverktyg för vanliga utvecklare.