GoSuda

Vyzkoušení škálovatelného spuštění Go serveru v dotnet Aspire

By snowmerak views ...

dotnet aspire?

dotnet aspire je nástroj vytvořený k tomu, aby vývojářům pomohl s vývojem a konfigurací cloud-native aplikací v souvislosti s rostoucím rozšířením cloud-native prostředí. Tento nástroj umožňuje vývojářům v .NET snadno nasazovat projekty v .NET, různou cloud-native infrastrukturu a také služby nebo kontejnery napsané v jiných jazycích.

Samozřejmě se od Dockeru až po K8s vyvíjí a provozuje, a mnoho oblastí, odvětví a vývojářů v současnosti přechází z tradičních on-premise prostředí do cloud-native prostředí, a již přešlo. Nyní je to již vyspělá oblast. Proto se domnívám, že není třeba vysvětlovat dřívější nepohodlí spojené s názvy hostitelů, konfigurací portů, firewally nebo správou metrik.

Proto vás už z výše uvedených vysvětlení pravděpodobně nenapadne, co dotnet aspire vlastně je. Je to proto, že ani Microsoft zatím nepodal přesnou definici. Proto ani já nebudu uvádět žádnou vlastní definici. Místo toho v tomto článku použiji základní funkce dotnet aspire, tak jak jsem je pochopil, a vy si na jejich základě můžete sami určit jeho místo.

Konfigurace projektu

Vytvoření projektu dotnet aspire

Pokud nemáte šablonu dotnet aspire, je třeba ji nejprve nainstalovat. Šablonu nainstalujete následujícím příkazem. Pokud nemáte .NET, nainstalujte si ho prosím sami.

1dotnet new install Aspire.ProjectTemplates

Poté vytvořte na vhodné složce nové řešení.

1dotnet new sln

Následně ve složce řešení spusťte následující příkaz, kterým vytvoříte projekt šablony aspire-apphost s názvem AppHost.

1dotnet new aspire-apphost -o AppHost

Tím se vytvoří projekt aspire-apphost obsahující pouze jednoduchý kód pro nastavení.

Přidání Valkey

Nyní přidáme Valkey jednoduchým způsobem.

Než ji přidáme, je třeba říci, že dotnet aspire poskytuje různá řešení třetích stran prostřednictvím community hosting.
Valkey samozřejmě může tuto podporu komunitního hostingu využívat a snadno ji lze použít prostřednictvím následujícího balíčku NuGet.

1dotnet add package Aspire.Hosting.Valkey

Kromě toho existuje mnoho dalších integrovaných hostingů, které si můžete prohlédnout zde.
Vraťme se nyní k Valkey: v projektu AppHost otevřete soubor Program.cs a upravte jej následujícím způsobem.

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 implementace rozhraní IResourceBuilder, která obsahuje informace potřebné k sestavení služby valkey.
WithDataVolume vytváří svazek pro ukládání dat cache a WithPersistence zajišťuje, že se data cache průběžně ukládají.
Z uvedeného se zdá, že tyto metody plní podobnou roli jako volumes v docker-compose.
Samozřejmě i vy to můžete snadno vytvořit.
Toto však přesahuje rozsah tohoto článku, proto o tom teď nebudeme mluvit.

Vytvoření echo serveru v Go

Nyní přidáme jednoduchý server v jazyce Go.
Nejprve ve složce řešení vytvořte workspace pomocí go work init.
Pro vývojáře v .NET lze Go workspace považovat za něco podobného řešení (solution).

Poté vytvořte složku EchoServer, přejděte do ní a spusťte go mod init EchoServer.
Tímto příkazem vytvoříte Go modul. Modul lze pro vývojáře v .NET chápat jako něco podobného projektu.
Poté vytvořte soubor main.go a napište do něj následující 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 načte port z proměnné prostředí PORT, kterou mu při spuštění Aspire AppHost předá, a na tomto portu spustí server.
Jedná se o jednoduchý server, který přijme parametr dotazu name a vrátí Hello, {name}.

Nyní tento server přidáme do dotnet aspire.

Přidání echo serveru do aspire

Vraťte se do projektu Aspire AppHost, kde jsme přidali Valkey, a přidejte komunitní hosting pro jazyk Go.

1dotnet add package CommunityToolkit.Aspire.Hosting.Golang

Poté otevřete soubor Program.cs a přidejte následující 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();

Zde je echoServer implementací rozhraní IResourceBuilder, která obsahuje informace potřebné k sestavení serveru v Go.
Metoda AddGolangApp, kterou jsme právě přidali, je metoda rozšíření pro vlastní host, určená k přidání serveru v Go.
Používá pevně nastavený port 3000 a je vidět, že předává proměnnou prostředí PORT.
Nakonec WithExternalHttpEndpoints zajišťuje, že je server dostupný zvenčí.

Pro otestování otevřete http://localhost:3000/?name=world, měli byste vidět výstup Hello, world.

Současná verze dotnet aspire však má pro projekty, které nejsou v .NET, jednu výraznou nevýhodu.
Tou je přesně...

Rozšiřování projektu

Jak tedy provést horizontální škálování?

Aktuálně dotnet aspire poskytuje možnost WithReplica pouze pro builder projektů .NET přidaných metodou AddProject.
Pro hostitele v Go nebo pro externí projekty, například přidané metodou AddContainer, tuto možnost neposkytuje.

Proto je nutné použít samostatný load balancer nebo reverse proxy a implementovat to ručně.
Pokud by však reverse proxy byla SPOF, mělo by být možné také pro ni použít WithReplica.
V takovém případě by reverse proxy nutně musela být projektem .NET.

Dosud jsem se s tímto problémem potýkal pomocí nginx, Traefiku nebo vlastní implementace, ale jakmile se objevilo omezení na projekty .NET, neměl jsem okamžitě žádné řešení.
Proto jsem vyhledal reverse proxy implementovanou v .NET a naštěstí existovala možnost YARP.
YARP je reverse proxy implementovaná v .NET, která může plnit i roli load balanceru a nabízí mnoho funkcí, proto jsem ji považoval za dobrou volbu.

Nyní tedy přidáme YARP.

Konfigurace reverse proxy pomocí YARP

Nejprve vytvořte projekt pro použití YARP.

1dotnet new web -n ReverseProxy

Poté přejděte do projektu a nainstalujte YARP.

1dotnet add package Yarp.ReverseProxy --version 2.2.0

Po dokončení instalace otevřete soubor Program.cs a napište do něj následující 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ím kódem pro použití YARP.
routes obsahuje informace o trasách, které bude reverse proxy používat, a clusters obsahuje informace o clusterech, které bude reverse proxy používat.
Tyto informace se do reverse proxy načtou metodou LoadFromMemory.
Nakonec se metodou MapReverseProxy reverse proxy namapuje a spustí.

Pro skutečné použití pak v projektu aspire apphost přidejte odkaz na projekt reverse proxy a v souboru Program.cs přidejte nebo upravte následující výraz.

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

Nyní může reverse proxy odkazovat na echo server.
Struktura se mění tak, že externí požadavky přijímá reverse proxy a předává je echo serveru.

Úprava reverse proxy

Nejprve je třeba změnit naslouchací adresu projektu, který je přiřazen k reverse proxy.
Odstraňte applicationUrl ze souboru Properties/launchSettings.json.
Poté otevřete soubor Program.cs a výrazně jej upravte následujícím způsobem.

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

Nejprve upravíme informace o routes a clusters.
Přidáním echo-route a echo-cluster nastavujeme, aby se požadavky posílaly na echo server.
Také upravujeme kód tak, aby se adresa echo serveru načítala z proměnné prostředí.

Pravidlo pro tuto adresu je services__{service-name}__http__{index}.
U echo serveru je název služby echo-server a protože jde o jednu instanci, používáme jako index 0.
Pokud bychom přidali server ASP.NET Core, může být pomocí WithReplica vytvořeno více instancí, a proto stačí index zvýšit.
Hodnota http://localhost:8080 použitá v případě výjimky je bezcenná hodnota bez významu.

Nyní spusťte projekt a otevřete http://localhost:3000/?name=world, měli byste stále vidět výstup Hello, world.

Nápady na rozšíření

Nyní jsme ověřili, že lze do dotnet aspire přidat server v Go a předávat požadavky prostřednictvím reverse proxy.
Tento postup nyní můžeme rozšířit tak, aby byl implementován programově.
Například u echo serveru lze za název služby přidat číslování, vytvořit více instancí a automaticky přidat konfiguraci do reverse proxy.

Do souboru Program.cs projektu aspire apphost upravte kód používající reverse proxy a echo server následujícím způsobem.

 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}

Poté upravte soubor Program.cs projektu reverse proxy následujícím způsobem.

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

Přidáváme cílové nastavení pro 8 instancí echo serveru.
Nyní reverse proxy disponuje informacemi o cílích všech echo serverů a může požadavky předávat mezi nimi.
Pokud otevřete původní http://localhost:3000/?name=world, měli byste stále vidět výstup Hello, world.

Závěr

V tomto článku jsme popsali přidání serveru v Go do dotnet aspire a předávání požadavků prostřednictvím reverse proxy.
Rozšíření jsme však zatím nepopsali celé, a proto jsme v samostatném repozitáři připravili příklad, který umožňuje implementovat to programově pomocí proměnných prostředí.
Podrobnou konfiguraci projektu a kód najdete v repozitáři snowmerak/AspireStartPack.

Osobně očekávám, že dotnet aspire si může najít vlastní roli jako alternativa k docker compose a také jako nástroj pro nasazení do cloudu.
Již existují generátory, které vytvářejí manifesty docker compose nebo k8s, takže se myslím, že přístup k infrastrukturním nástrojům pro běžné vývojáře se zlepšil.