GoSuda

Расширяемое выполнение Go-сервера в dotnet aspire

By snowmerak
views ...

dotnet aspire?

dotnet aspire — это инструмент, разработанный для помощи разработчикам в облачной нативной разработке и конфигурации в условиях растущего числа облачных нативных сред. Этот инструмент позволяет .NET-разработчикам легко развертывать проекты .NET, различные облачные нативные инфраструктуры, а также сервисы или контейнеры, написанные на других языках.

Очевидно, что с момента выпуска и эксплуатации Docker и k8s, многие области, отрасли и разработчики перешли или переходят из традиционных локальных сред в облачные нативные. Это уже зрелая область. Поэтому я считаю, что нет необходимости объяснять прежние неудобства, связанные с именами хостов, конфигурацией портов, брандмауэрами, управлением метриками и т. д.

Поэтому, даже исходя из вышеприведенных объяснений, вы, вероятно, не сможете понять, что такое 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}

Этот сервер запускается, когда Aspire AppHost вводит переменную окружения PORT, которую он должен прослушивать, и считывает этот порт для запуска сервера. Это простой сервер, который принимает запрос 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 только для билдеров .NET-проектов, добавленных с помощью метода AddProject. Однако для хостов на языке Go или внешних проектов, таких как AddContainer, эта опция не предоставляется.

Поэтому необходимо реализовать это самостоятельно, используя отдельный load balancer или reverse proxy. Однако в этом случае такой reverse proxy может стать единой точкой отказа (SPOF), поэтому желательно, чтобы reverse proxy предоставлял опцию WithReplica. Следовательно, reverse proxy обязательно должен быть .NET-проектом.

До сих пор для решения этой проблемы использовались такие методы, как nginx, Trafik или собственная реализация, но с ограничением на .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. Наконец, метод MapReverseProxy используется для сопоставления и запуска reverse proxy.

Для практического использования добавьте ссылку на проект 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-сервера в dotnet 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 server и может передавать запросы. Если вы перейдете по старому адресу http://localhost:3000/?name=world, вы сможете убедиться, что по-прежнему выводится Hello, world.

В заключение

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

Я лично ожидаю, что dotnet aspire сможет выполнять свою роль как альтернатива docker compose и как инструмент для облачного развертывания. Уже существуют генераторы, которые создают docker compose или k8s manifest, что, на мой взгляд, улучшило доступность инфраструктурных инструментов для обычных разработчиков.