GoSuda

Pokus o škálovatelné spuštění serveru Go v rámci dotnet aspire

By snowmerak
views ...

dotnet aspire?

dotnet aspire je nástroj vytvořený s cílem pomoci vývojářům s vývojem a konfigurací cloud-native aplikací, s ohledem na rostoucí prostředí cloud-native. Tento nástroj umožňuje .NET vývojářům snadno nasazovat .NET projekty, různou cloud-native infrastrukturu a také služby či kontejnery napsané v jiných jazycích.

Samozřejmě, že se nasazuje a provozuje od Dockeru po K8s, a značná část oborů, průmyslu a vývojářů přechází a přešla z tradičních on-premise prostředí do cloud-native prostředí. Nyní je to již zralá oblast. Proto si myslím, že není nutné popisovat stávající nepříjemnosti ohledně názvů hostitelů, konfigurace portů, firewallů, správy metrik atd.

Takže, soudě podle výše uvedených popisů, pravděpodobně vůbec nechápete, co je dotnet aspire. Je to proto, že ani Microsoft sám nenabízí přesnou definici. Proto ani já nebudu definovat nic zvláštního. Pouze v tomto článku použiji základní funkce dotnet aspire, jak je chápu, takže si na základě toho můžete určit svou vlastní pozici.

Konfigurace projektu

Vytvoření dotnet aspire projektu

Pokud nemáte šablonu dotnet aspire, musíte ji nejprve nainstalovat. Nainstalujte šablonu pomocí následujícího příkazu. Pokud nemáte .net, nainstalujte si ho sami.

1dotnet new install Aspire.ProjectTemplates

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

1dotnet new sln

Následně vytvořte v složce řešení projekt šablony aspire-apphost spuštěním následujícího příkazu.

1dotnet new aspire-apphost -o AppHost

Tím se vytvoří projekt aspire-apphost s jednoduchým kódem pro nastavení.

Přidání Valkey

Zkusme jednoduše přidat Valkey.

Než jej přímo přidáme, dotnet aspire nabízí různá řešení třetích stran prostřednictvím community hosting. Samozřejmě, valkey může využívat podporu tohoto community hostingu a lze jej snadno používat prostřednictvím následujícího balíčku nuget.

1dotnet add package Aspire.Hosting.Valkey

Kromě toho nabízí různé integrované hostingové služby, které si můžete prohlédnout zde. Vrátíme-li se k valkey, otevřete soubor Program.cs v projektu AppHost 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 pro vytvoření služby valkey.WithDataVolume vytvoří svazek pro uložení dat mezipaměti a WithPersistence umožňuje trvalé ukládání dat mezipaměti. Zatím to vypadá, že hraje podobnou roli jako volumes v docker-compose. Samozřejmě, že si to můžete snadno vytvořit i sami. Ale to přesahuje rámec tohoto článku, takže to zde nebudeme probírat.

Vytvoření Go jazykového echo serveru

Přidejme jednoduchý Go jazykový server. Nejprve vytvořte pracovní prostor pomocí příkazu go work init v složce řešení. Pro .NET vývojáře je Go pracovní prostor podobný řešení.

Poté vytvořte složku s názvem EchoServer, přejděte do ní a spusťte go mod init EchoServer. Tímto příkazem se vytvoří Go modul. Modul je pro .NET vývojáře vnímán jako projekt. Poté vytvořte soubor main.go a zapište do něj následující.

 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 proměnnou prostředí PORT, kterou mu předá Aspire AppHost při spuštění, a spustí server na tomto portu. Jednoduše přijme name dotaz a vrátí Hello, {name}.

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

Přidání echo serveru do aspire

Vraťte se do projektu Aspire AppHost, kam jste přidali Valkey, a přidejte community hosting pro Go jazyk.

1dotnet add package CommunityToolkit.Aspire.Hosting.Golang

Poté otevřete soubor Program.cs a přidejte následující příkaz.

 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 implementace rozhraní IResourceBuilder, které obsahuje informace o vytvoření Go jazykového serveru. Právě přidaná metoda AddGolangApp je rozšiřující metoda pro vlastní hostování pro přidání Go jazykového serveru. Používá pevně daný port 3000 a můžete vidět, že vkládá proměnnou prostředí PORT. Nakonec WithExternalHttpEndpoints umožňuje externí přístup.

Pokud provedete test s přístupem na http://localhost:3000/?name=world, měli byste vidět výstup Hello, world.

Nicméně, v současné době má dotnet aspire velkou penalizaci pro non-dotnet projekty. A to je...

Rozšíření projektu

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

V současné době dotnet aspire nabízí možnost WithReplica pouze pro buildery .NET projektů přidaných metodou AddProject. Neposkytuje však tuto možnost pro Go jazykové hosty nebo externí projekty jako AddContainer.

Proto je nutné implementovat horizontální škálování samostatně pomocí load balanceru nebo reverse proxy. Pokud by se však reverse proxy stala SPOF, bylo by dobré, aby reverse proxy nabízela možnost WithReplica. V tomto případě by reverse proxy nutně musela být .NET projekt.

Až dosud jsme pro tyto problémy používali metody jako nginx, trafik nebo vlastní implementaci, ale pokud je omezením .NET projekt, nemám v tuto chvíli žádné řešení. Hledal jsem tedy reverse proxy implementovanou v .NET a naštěstí jsem našel možnost YARP. YARP je reverse proxy implementované v .NET, které může hrát roli load balanceru a poskytovat různé funkce, a proto jsem ho považoval za dobrou volbu.

Nyní 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 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í kód pro použití YARP.routes obsahuje informace o směrování, které bude reverse proxy používat, a clusters obsahuje informace o klastrech, které bude reverse proxy používat. Tyto informace se načtou do reverse proxy pomocí metody LoadFromMemory. Nakonec se reverse proxy namapuje a spustí pomocí metody MapReverseProxy.

Pro reálné použití přidejte odkaz na projekt reverse proxy v projektu aspire apphost a přidejte a upravte následující příkaz v souboru 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();

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

Úprava reverse proxy

Nejprve je třeba změnit adresu naslouchání pro projekt přiřazený reverse proxy. Odstraňte applicationUrl v souboru Properties/launchSettings.json. Poté otevřete soubor Program.cs a rozsáhle jej upravte takto.

 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 upravte informace o routes a clusters. Přidejte echo-route a echo-cluster a nastavte, aby se požadavky odesílaly na echo server. Poté upravte tak, aby se adresa echo serveru četla z proměnné prostředí.

Pravidlo pro tuto adresu je services__{service-name}__http__{index}. V případě echo serveru je název služby echo-server a protože jde o jedinou instanci, použijte index 0. Pokud přidáte asp .net core server, lze vytvořit více instancí pomocí WithReplica, takže můžete použít zvýšením indexu. Výjimkou je hodnota http://localhost:8080, což je bezvýznamná odpadní hodnota.

Nyní, když spustíte projekt a přistoupíte na 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 do dotnet aspire lze přidat Go server a že požadavky jsou předávány prostřednictvím reverse proxy. Nyní můžeme rozšířit tento proces tak, aby byl implementován programově. Můžeme například přidat číslování za názvem služby pro echo server, vytvořit více instancí a automaticky přidat konfiguraci pro reverse proxy.

Upravte kód v souboru Program.cs projektu aspire apphost tak, aby používal 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řidejte nastavení cíle pro 8 instancí echo serveru. Nyní má reverse proxy informace o cíli pro zvýšené echo servery a může předávat požadavky. Pokud se připojíte na stávající http://localhost:3000/?name=world, měli byste stále vidět výstup Hello, world.

Závěrem

V tomto článku jsme popsali proces přidávání Go serveru do dotnet aspire a předávání požadavků pomocí reverse proxy. Rozšíření však ještě není kompletně napsané a příklad, jak jej programově implementovat pomocí proměnných prostředí, je napsán v samostatném repozitáři. Podrobnosti o konfiguraci projektu a kódu naleznete v snowmerak/AspireStartPack.

Osobně očekávám, že dotnet aspire může hrát svou vlastní roli jako alternativa k docker compose a jako nástroj pro nasazování do cloudu. Již existuje generátor, který generuje docker compose nebo k8s manifesty, takže si myslím, že dostupnost infrastrukturních nástrojů pro běžné vývojáře se zlepšila.