GoSuda

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

By snowmerak
views ...

dotnet aspire?

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

Естествено, той се предлага и оперира от docker до k8s, а значителен брой области, индустрии и разработчици вече преминават или са преминали към cloud native среди от традиционните on-premise среди. Това е една зряла област вече. Затова не смятам, че е необходимо да обяснявам съществуващите неудобства, свързани с имена на хостове, конфигурации на портове, защитни стени, управление на метрики и т.н.

Ето защо, само въз основа на горните обяснения, вероятно няма да имате представа какво е dotnet aspire. Това е така, защото дори Microsoft не дава точна дефиниция. Затова и аз няма да давам конкретна дефиниция. Въпреки това, тъй като ще използвам основните функции на dotnet aspire, както ги разбирам в тази статия, бихте могли да се възползвате от това и да определите собствената си позиция.

Проектна структура

Създаване на dotnet aspire проект

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

1dotnet new install Aspire.ProjectTemplates

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

1dotnet new sln

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

1dotnet new aspire-apphost -o AppHost

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

Добавяне на Valkey

Нека просто добавим Valkey.

Преди да добавите, dotnet 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.
Разбира се, можете лесно да го създадете сами.
Но няма да говорим за това сега, тъй като е извън обхвата на тази статия.

Създаване на 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}

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

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

Добавяне на ехо сървъра към 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.

Но в момента dotnet aspire има тежка санкция, дадена на non-dotnet проекти.
Това е...

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

И така, как да направим хоризонтално разширение?

В момента dotnet aspire предоставя опцията WithReplica само за конструктори за 닷넷 проекти, добавени с метода AddProject.
Но не предоставя тази опция за Go езикови хостове или външни проекти като AddContainer.

Ето защо трябва да го реализирате сами, като използвате отделен load balancer или reverse proxy.
Но ако reverse proxy може да стане SPOF, е добра идея reverse proxy да предостави опцията WithReplica.
В такъв случай reverse proxy задължително трябва да е 닷넷 проект.

Досега сме използвали методи като nginx, trafik и директна реализация за този проблем, но нямах начин да го направя веднага, когато е наложено ограничението на 닷넷 проект.
Затова потърсих reverse proxy, реализиран с 닷넷, и за щастие имах опцията YARP.
YARP е reverse proxy, реализиран с 닷넷, може да играе ролята на 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 може да препраща към ехо сървъра.
Структурата се променя от получаване на входящи заявки в reverse proxy и предаването им към ехо сървъра.

Промяна на Reverse proxy

Първо, трябва да промените listening адреса на проекта, присвоен на 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, за да изпращате заявки към ехо сървъра.
И променете адреса на ехо сървъра, за да се чете от променливата на средата и да се използва.

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

Сега, ако стартирате проекта и се свържете с http://localhost:3000/?name=world, трябва да видите Hello, world.

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

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

Променете кода, използващ reverse proxy и ехо сървъра във файла 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 увеличени ехо сървърни инстанции.
Сега reverse proxy има информация за дестинацията за увеличените ехо сървъри и може да препраща заявки.
Ако се свържете със съществуващия http://localhost:3000/?name=world, трябва да видите Hello, world.

В заключение

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

Лично аз очаквам dotnet aspire да играе собствена роля като алтернатива на docker compose и като инструмент за cloud deployment.
Вече съществува генератор, който генерира docker compose или k8s manifest, така че смятам, че достъпът до инструменти за инфраструктура е по-добър за обикновените разработчици.