GoSuda

Spróbuj uruchomić serwer Go w sposób skalowalny w .NET Aspire

By snowmerak
views ...

dotnet aspire?

dotnet aspire to narzędzie stworzone, aby pomóc programistom w rozwoju i konfiguracji aplikacji natywnych dla chmury, w obliczu rosnącej liczby środowisk natywnych dla chmury. To narzędzie umożliwia programistom .NET łatwe wdrażanie projektów .NET oraz różnorodnej infrastruktury natywnej dla chmury, a także usług i kontenerów napisanych w innych językach.

Oczywiście, od Dockera po K8s, wszystko jest wydawane i obsługiwane, a wiele dziedzin, gałęzi przemysłu i programistów migruje lub już zmigrowało ze środowisk on-premise do środowisk natywnych dla chmury. Jest to teraz dojrzała dziedzina. Dlatego nie sądzę, że istnieje potrzeba wyjaśniania istniejących niedogodności związanych z nazwami hostów, konfiguracją portów, zaporami ogniowymi i zarządzaniem metrykami.

Dlatego, sądząc po powyższych wyjaśnieniach, trudno będzie zrozumieć, czym jest dotnet aspire. Ponieważ nawet Microsoft nie podaje dokładnej definicji. Dlatego ja również nie podam żadnej konkretnej definicji. Jednak w tym artykule będę używał podstawowych funkcji dotnet aspire, które rozumiem, więc proszę wziąć to pod uwagę i określić swoją własną pozycję.

Konfiguracja Projektu

Tworzenie projektu dotnet aspire

Jeśli nie masz szablonu dotnet aspire, najpierw musisz zainstalować szablon. Zainstaluj szablon za pomocą następującego polecenia. Jeśli nie masz .NET, zainstaluj go samodzielnie.

1dotnet new install Aspire.ProjectTemplates

Następnie utwórz nowe rozwiązanie w odpowiednim folderze.

1dotnet new sln

Następnie uruchom następujące polecenie w folderze rozwiązania, aby utworzyć projekt z szablonu aspire-apphost.

1dotnet new aspire-apphost -o AppHost

Spowoduje to utworzenie projektu aspire-apphost zawierającego jedynie prosty kod konfiguracji.

Dodawanie Valkey

Dodajmy teraz w prosty sposób Valkey.

Zanim zaczniesz dodawać, dotnet aspire zapewnia różnorodne rozwiązania firm trzecich poprzez hosting społecznościowy. Oczywiście, Valkey również może korzystać z tego wsparcia hostingu społecznościowego i można go łatwo użyć za pomocą następującego pakietu NuGet.

1dotnet add package Aspire.Hosting.Valkey

Ponadto, można znaleźć różne zintegrowane hostingi tutaj. Wracając do Valkey, otwórz plik Program.cs w projekcie AppHost i zmodyfikuj go w następujący sposób:

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 to implementacja interfejsu IResourceBuilder, który zawiera informacje umożliwiające budowę usługi Valkey.WithDataVolume tworzy wolumin do przechowywania danych cache, a WithPersistence umożliwia trwałe przechowywanie danych cache. Patrząc na to, wydaje się, że odgrywa podobną rolę do volumes w docker-compose. Oczywiście, możesz to również łatwo stworzyć. Jednak wykracza to poza zakres tego artykułu, więc nie będę o tym teraz mówił.

Tworzenie serwera echo w języku Go

Dodajmy teraz prosty serwer w języku Go. Najpierw utwórz przestrzeń roboczą za pomocą polecenia go work init w folderze rozwiązania. Dla programistów .NET przestrzeń robocza Go jest podobna do rozwiązania.

Następnie utwórz folder o nazwie EchoServer, przejdź do niego i uruchom go mod init EchoServer. To polecenie tworzy moduł Go. Moduł można postrzegać jako odpowiednik projektu dla programistów .NET. Następnie utwórz plik main.go i napisz w nim następujący kod.

 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}

Ten serwer, po wstrzyknięciu zmiennej środowiskowej PORT, którą Aspire AppHost powinien nasłuchiwać podczas uruchamiania, odczytuje ten port i uruchamia serwer. Jest to prosty serwer, który pobiera zapytanie name i zwraca Hello, {name}.

Dodajmy teraz ten serwer do dotnet aspire.

Dodawanie serwera echo do aspire

Wróć do projektu Aspire AppHost, w którym dodałeś Valkey i dodaj hosting społecznościowy dla języka Go.

1dotnet add package CommunityToolkit.Aspire.Hosting.Golang

Następnie otwórz plik Program.cs i dodaj następującą instrukcję.

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

Tutaj echoServer to implementacja interfejsu IResourceBuilder, który zawiera informacje umożliwiające budowę serwera w języku Go. Właśnie dodana metoda AddGolangApp to metoda rozszerzenia niestandardowego hosta do dodawania serwerów w języku Go. Można zauważyć, że domyślnie używany jest port 3000 i wstrzykiwana jest zmienna środowiskowa PORT. Na koniec WithExternalHttpEndpoints służy do umożliwienia dostępu z zewnątrz.

Aby przetestować, spróbuj połączyć się z http://localhost:3000/?name=world, a powinieneś zobaczyć Hello, world.

Jednak obecnie dotnet aspire ma poważne ograniczenia w odniesieniu do projektów innych niż dotnet. To jest właśnie...

Rozszerzenie Projektu

Jak zatem wygląda skalowanie w poziomie?

Obecnie dotnet aspire zapewnia opcję WithReplica tylko dla buildera projektów .NET dodanych za pomocą metody AddProject. Jednak nie zapewnia tej opcji dla hostów języka Go ani projektów zewnętrznych, takich jak AddContainer.

Dlatego trzeba go wdrożyć bezpośrednio za pomocą oddzielnego load balancera lub reverse proxy. Jednak w takim przypadku dany reverse proxy może stać się SPOF, dlatego dobrym pomysłem jest, aby reverse proxy oferował opcję WithReplica. W takim przypadku reverse proxy musi być projektem .NET.

Do tej pory stosowałem metody takie jak nginx, trafik i bezpośrednia implementacja, ale kiedy pojawia się ograniczenie projektu .NET, nie mam natychmiastowego rozwiązania. Dlatego szukałem reverse proxy zaimplementowanego w .NET i na szczęście pojawiła się opcja YARP. YARP to reverse proxy zaimplementowane w .NET, które może również pełnić rolę load balancera i zapewnia różne funkcje, dlatego uznałem, że jest to dobry wybór.

Dodajmy teraz YARP.

Konfiguracja reverse proxy z YARP

Najpierw utwórz projekt do korzystania z YARP.

1dotnet new web -n ReverseProxy

Następnie przejdź do projektu i zainstaluj YARP.

1dotnet add package Yarp.ReverseProxy --version 2.2.0

Po zakończeniu instalacji otwórz plik Program.cs i napisz w nim następujący kod.

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

To jest podstawowy kod do korzystania z YARP.routes zawiera informacje o trasach, z których będzie korzystać reverse proxy, a clusters zawiera informacje o klastrach, z których będzie korzystać reverse proxy. Te informacje są ładowane do reverse proxy za pomocą metody LoadFromMemory. Na koniec użyj metody MapReverseProxy, aby zamapować i uruchomić reverse proxy.

W celu praktycznego użycia dodaj projekt reverse proxy jako referencję w projekcie aspire apphost i dodaj i zmodyfikuj następującą instrukcję w pliku 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();

Teraz reverse proxy może odwoływać się do serwera echo. Struktura jest zmieniana w taki sposób, że żądania przychodzące z zewnątrz są odbierane przez reverse proxy i przekazywane do serwera echo.

Modyfikacja reverse proxy

Przede wszystkim musimy zmienić adres nasłuchiwania przypisany do projektu reverse proxy. Usuń applicationUrl w pliku Properties/launchSettings.json. Następnie otwórz plik Program.cs i zmodyfikuj go całkowicie w następujący sposób.

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

Najpierw zmodyfikuj informacje o routes i clusters. Dodaj odpowiednio echo-route i echo-cluster, aby żądania były wysyłane do serwera echo. Zmodyfikuj kod tak, aby adres serwera echo był odczytywany ze zmiennej środowiskowej.

Reguła tego adresu to services__{service-name}__http__{index}. W przypadku serwera echo nazwa usługi to echo-server, a ponieważ jest to pojedyncza instancja, jako indeks używa się 0. Jeśli dodajesz serwer asp .net core, można utworzyć wiele instancji za pomocą WithReplica, więc możesz użyć indeksu, zwiększając go. Wyjątkowo obsłużona wartość http://localhost:8080 jest bez znaczenia i jest po prostu wartością śmieciową.

Teraz, po uruchomieniu projektu i wejściu na http://localhost:3000/?name=world, powinieneś nadal zobaczyć Hello, world.

Pomysł na rozbudowę

Teraz zweryfikowaliśmy dodanie serwera Go do dotnet aspire i przekazywanie żądań za pośrednictwem reverse proxy. Następnie możesz rozszerzyć ten proces, aby zaimplementować go programowo. Na przykład możesz utworzyć wiele instancji serwera echo, dodając numerację po nazwie usługi i automatycznie dodawać ustawienia dla reverse proxy.

Zmodyfikuj kod w pliku Program.cs projektu aspire apphost, który używa reverse proxy i serwera echo w następujący sposób.

 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}

Następnie zmodyfikuj plik Program.cs projektu reverse proxy w następujący sposób.

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

Dodaj ustawienia docelowe dla 8 instancji serwera echo. Teraz reverse proxy ma informacje o docelowych adresach powiększonych serwerów echo i może przekazywać żądania. Po wejściu na http://localhost:3000/?name=world, powinieneś nadal zobaczyć Hello, world.

Podsumowanie

W tym artykule opisano proces dodawania serwera Go do dotnet aspire i przekazywania żądań za pośrednictwem reverse proxy. Jednak w odniesieniu do rozszerzenia nie wszystko zostało jeszcze napisane, a przykłady, które można zaimplementować bardziej programowo za pomocą zmiennych środowiskowych, zostały napisane w oddzielnym repozytorium. Szczegółową konfigurację projektu i kod można znaleźć w snowmerak/AspireStartPack.

Osobiście mam nadzieję, że dotnet aspire może pełnić swoją rolę jako alternatywa dla docker compose i jako narzędzie do wdrażania w chmurze. Ponieważ istnieje już generator, który generuje manifesty docker compose lub k8s, uważam, że dostęp do narzędzi infrastrukturalnych dla ogólnych programistów został ułatwiony.