GoSuda

Изпълнение на Go сървър с възможност за разширяване в .NET Aspire

By snowmerak
views ...

.NET Aspire?

.NET Aspire е инструмент, създаден да подпомага разработчиците при разработването и конфигурирането на cloud-native приложения, тъй като cloud-native средите стават все по-разпространени. Този инструмент позволява на .NET разработчиците лесно да разгръщат .NET проекти, разнообразна cloud-native инфраструктура и услуги или контейнери, написани на други езици.

Разбира се, с пускането и функционирането на Docker до Kubernetes, значителен брой индустрии, сектори и разработчици преминаха и продължават да преминават от традиционни on-premise среди към cloud-native среди. Сега това е зряла област. Затова смятам, че няма нужда да обяснявам съществуващите неудобства, свързани с имена на хостове, конфигурации на портове, защитни стени и управление на метрики.

Затова, дори и с горните обяснения, може да ви е трудно да разберете какво точно е .NET Aspire. Причината е, че дори Microsoft не е дал точна дефиниция. Затова и аз няма да давам конкретна дефиниция. Въпреки това, тъй като в тази статия ще използвам основните функции на .NET Aspire, както аз ги разбирам, можете да ги използвате като отправна точка, за да определите собствената си позиция.

Конфигурация на проект

Създаване на .NET Aspire проект

Ако нямате шаблон за .NET Aspire, трябва първо да го инсталирате. Инсталирайте шаблона със следната команда. Ако нямате .NET, моля, инсталирайте го сами.

1dotnet new install Aspire.ProjectTemplates

След това създайте ново решение в подходяща папка.

1dotnet new sln

След това изпълнете следната команда в папката на решението, за да създадете проект от шаблона aspire-apphost.

1dotnet new aspire-apphost -o AppHost

Това ще създаде aspire-apphost проект, съдържащ само прост код за настройка.

Добавяне на Valkey

Сега нека накратко добавим Valkey.

Преди да го добавим, .NET Aspire предлага различни решения от трети страни чрез community hosting. Разбира се, Valkey също може да получи поддръжка от това community hosting и може лесно да бъде използван чрез следния NuGet пакет.

1dotnet add package Aspire.Hosting.Valkey

Предлагат се и други интегрирани хостинги, които можете да намерите тук. Връщайки се към Valkey, отворете файла Program.cs в проекта AppHost и го променете, както следва:

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 е имплементация на интерфейса IResourceBuilder, който съдържа информация, необходима за изграждане на Valkey услугата.WithDataVolume създава том за съхранение на кеш данни, а WithPersistence позволява постоянно съхранение на кеш данни. Оттук изглежда, че изпълнява подобна роля на volumes в docker-compose. Разбира се, можете лесно да създадете това сами. Въпреки това, това надхвърля обхвата на тази статия, така че няма да го обсъждам сега.

Създаване на Echo сървър на език Go

Сега нека добавим прост Go сървър. Първо, създайте работно пространство с go work init в папката на решението. За .NET разработчиците, Go работното пространство е подобно на решение.

След това създайте папка на име EchoServer, преместете се в нея и изпълнете go mod init EchoServer. Тази команда създава Go модул. Можете да разглеждате модула като подобен на проект за .NET разработчиците. След това създайте файл main.go и напишете следното:

 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}

Когато Aspire AppHost бъде стартиран, този сървър инжектира променливата на средата PORT, на която трябва да слуша, и след това чете този порт, за да стартира сървъра. Това е прост сървър, който приема заявка name и връща Hello, {name}.

Сега нека добавим този сървър към .NET Aspire.

Добавяне на Echo сървър към Aspire

Върнете се към проекта Aspire AppHost, където добавихте Valkey, и добавете community hosting за Go език.

1dotnet add package CommunityToolkit.Aspire.Hosting.Golang

След това отворете файла Program.cs и добавете следния код:

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

Тук echoServer е имплементация на интерфейса IResourceBuilder, който съдържа информация, необходима за изграждане на Go сървъра. Току-що добавеният метод AddGolangApp е разширен метод на персонализирания хост за добавяне на Go сървър. Можете да видите, че той използва фиксиран порт 3000 и инжектира променливата на средата PORT. И накрая, WithExternalHttpEndpoints позволява достъп отвън.

За да тествате, ако посетите http://localhost:3000/?name=world, ще видите "Hello, world" да се показва.

Въпреки това, понастоящем .NET Aspire налага сериозно наказание за non-dotnet проекти. Това е...

Разширение на проекта

Тогава как се извършва хоризонтално мащабиране?

В момента .NET Aspire предоставя опцията WithReplica само за builderi на .NET проекти, добавени чрез метода AddProject. Въпреки това, тази опция не се предлага за Go хостове или външни проекти като AddContainer.

Следователно, трябва да се имплементира директно, като се използва отделен load balancer или reverse proxy. Но това може да направи този reverse proxy SPOF (Single Point of Failure), така че е по-добре reverse proxy да предоставя опцията WithReplica. Тогава неизбежно reverse proxy трябва да бъде .NET проект.

Досега съм използвал методи като Nginx, Traefik или собствена имплементация за справяне с този проблем, но с ограничението за .NET проект нямах незабавно решение. Затова потърсих reverse proxy, имплементиран в .NET, и за щастие имаше опция, наречена YARP. YARP е reverse proxy, имплементиран в .NET, който може да действа като load balancer и предлага различни функции, което го прави добър избор.

Сега нека добавим YARP.

Конфигуриране на reverse proxy с YARP

Първо, създайте проект за използване на YARP.

1dotnet new web -n ReverseProxy

След това отидете до проекта и инсталирайте YARP.

1dotnet add package Yarp.ReverseProxy --version 2.2.0

След като инсталацията приключи, отворете файла Program.cs и напишете следното:

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

Този код е основният код за използване на YARP.routes съдържа информация за маршрутите, които ще използва reverse proxy, а clusters съдържа информация за клъстерите, които ще използва reverse proxy. Тази информация се зарежда в reverse proxy чрез метода LoadFromMemory. И накрая, reverse proxy се мапира и изпълнява с метода MapReverseProxy.

За практическа употреба, добавете референция към проекта reverse proxy в aspire apphost проекта и добавете и променете следния код във файла 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();

Сега reverse proxy може да реферира echo server. Входящите заявки отвън вече се приемат от reverse proxy и се препращат към echo server.

Модификация на Reverse Proxy

Първо, трябва да промените адреса за слушане на проекта, присвоен на reverse proxy. Премахнете applicationUrl от файла Properties/launchSettings.json. След това отворете файла Program.cs и го променете значително, както следва:

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

Първо, променяме информацията за routes и clusters. Добавяме съответно echo-route и echo-cluster, за да конфигурираме изпращането на заявки към echo server. Също така, модифицираме го, за да четем адреса на echo server от променливи на средата.

Правилото за този адрес е services__{service-name}__http__{index}. В случая на echo server, името на услугата е echo-server, и тъй като е единичен инстанс, използваме индекс 0. Ако добавите ASP.NET Core сървър, можете да използвате WithReplica, за да създадете множество инстанции и да увеличите индекса. Изключението http://localhost:8080 е безсмислена стойност.

Сега стартирайте проекта и ако отидете на http://localhost:3000/?name=world, все още ще видите "Hello, world".

Идея за разширение

Вече видяхме как да добавим Go сървър към .NET Aspire и да предаваме заявки през reverse proxy. Сега можем да разширим този процес, за да го имплементираме програмно. Например, можем да създадем множество инстанции на echo server, като добавим номерация след името на услугата, и автоматично да добавим конфигурация за reverse proxy.

Променете кода, който използва reverse proxy и echo server във файла Program.cs на aspire apphost проекта, както следва:

 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}

И променете файла Program.cs на проекта reverse proxy, както следва:

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

Добавяме настройки за дестинация за 8-те увеличени инстанции на echo server. Сега reverse proxy има информация за дестинациите на увеличените echo сървъри и може да препраща заявки. Ако посетите оригиналния http://localhost:3000/?name=world, все още ще видите "Hello, world".

Заключение

В тази статия обясних процеса на добавяне на Go сървър към .NET Aspire и предаване на заявки чрез reverse proxy. Въпреки това, разширението все още не е напълно написано и съм създал пример в отделно хранилище, който може да бъде имплементиран по-програмно чрез променливи на средата. За подробна конфигурация на проекта и код, моля, вижте snowmerak/AspireStartPack.

Лично аз очаквам .NET Aspire да изпълни своята роля като алтернатива на Docker Compose и като инструмент за cloud разгръщане. Вече съществува генератор, който създава Docker Compose или Kubernetes манифести, което според мен е подобрило достъпността на инфраструктурните инструменти за обикновените разработчици.