---
id: 7ecf5f1e3335910ea27e6beb68f04ae7
author: snowmerak
title: Vyzkoušení škálovatelného spuštění Go serveru v dotnet Aspire
description: 'Jak škálovatelně provozovat server v Go pomocí .NET Aspire: příklad konfigurace horizontálního škálování a automatizace s reverzní proxy YARP.'
language: cs
date: 2025-01-05T05:04:22.744778105Z
path: /blog/posts/exploring-the-scalable-execution-of-go-servers-in-net-aspire-z42f3b6ee
category: network
---

## dotnet aspire?

dotnet aspire je nástroj vytvořený k tomu, aby vývojářům pomohl s vývojem a konfigurací cloud-native aplikací v souvislosti s rostoucím rozšířením cloud-native prostředí. Tento nástroj umožňuje vývojářům v .NET snadno nasazovat projekty v .NET, různou cloud-native infrastrukturu a také služby nebo kontejnery napsané v jiných jazycích.

Samozřejmě se od Dockeru až po K8s vyvíjí a provozuje, a mnoho oblastí, odvětví a vývojářů v současnosti přechází z tradičních on-premise prostředí do cloud-native prostředí, a již přešlo. Nyní je to již vyspělá oblast. Proto se domnívám, že není třeba vysvětlovat dřívější nepohodlí spojené s názvy hostitelů, konfigurací portů, firewally nebo správou metrik.

Proto vás už z výše uvedených vysvětlení pravděpodobně nenapadne, co dotnet aspire vlastně je. Je to proto, že ani Microsoft zatím nepodal přesnou definici. Proto ani já nebudu uvádět žádnou vlastní definici. Místo toho v tomto článku použiji základní funkce dotnet aspire, tak jak jsem je pochopil, a vy si na jejich základě můžete sami určit jeho místo.

## Konfigurace projektu

### Vytvoření projektu dotnet aspire

Pokud nemáte šablonu dotnet aspire, je třeba ji nejprve nainstalovat. Šablonu nainstalujete následujícím příkazem. Pokud nemáte .NET, nainstalujte si ho prosím sami.

```bash
dotnet new install Aspire.ProjectTemplates
```

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

```bash
dotnet new sln
```

Následně ve složce řešení spusťte následující příkaz, kterým vytvoříte projekt šablony aspire-apphost s názvem AppHost.

```bash
dotnet new aspire-apphost -o AppHost
```

Tím se vytvoří projekt aspire-apphost obsahující pouze jednoduchý kód pro nastavení.

### Přidání Valkey

Nyní přidáme Valkey jednoduchým způsobem.

Než ji přidáme, je třeba říci, že dotnet aspire poskytuje různá řešení třetích stran prostřednictvím community hosting.  
Valkey samozřejmě může tuto podporu komunitního hostingu využívat a snadno ji lze použít prostřednictvím následujícího balíčku NuGet.

```bash
dotnet add package Aspire.Hosting.Valkey
```

Kromě toho existuje mnoho dalších integrovaných hostingů, které si můžete prohlédnout [zde](https://learn.microsoft.com/ko-kr/dotnet/aspire/fundamentals/integrations-overview).  
Vraťme se nyní k Valkey: v projektu AppHost otevřete soubor Program.cs a upravte jej následujícím způsobem.

```csharp
var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddValkey("cache")
    .WithDataVolume(isReadOnly: false)
    .WithPersistence(interval: TimeSpan.FromMinutes(5),
        keysChangedThreshold: 100);

builder.Build().Run();
```

`cache` je implementace rozhraní `IResourceBuilder`, která obsahuje informace potřebné k sestavení služby valkey.  
`WithDataVolume` vytváří svazek pro ukládání dat cache a `WithPersistence` zajišťuje, že se data cache průběžně ukládají.  
Z uvedeného se zdá, že tyto metody plní podobnou roli jako `volumes` v `docker-compose`.  
Samozřejmě i vy to můžete snadno vytvořit.  
Toto však přesahuje rozsah tohoto článku, proto o tom teď nebudeme mluvit.

### Vytvoření echo serveru v Go

Nyní přidáme jednoduchý server v jazyce Go.  
Nejprve ve složce řešení vytvořte workspace pomocí `go work init`.  
Pro vývojáře v .NET lze Go workspace považovat za něco podobného řešení (solution).

Poté vytvořte složku EchoServer, přejděte do ní a spusťte `go mod init EchoServer`.  
Tímto příkazem vytvoříte Go modul. Modul lze pro vývojáře v .NET chápat jako něco podobného projektu.  
Poté vytvořte soubor `main.go` a napište do něj následující kód.

```go
package main

import (
	"log"
	"net/http"
	"os"
)

func main() {
	addr := os.Getenv("PORT")
	log.Printf("Server started on %s", addr)

	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		name := request.URL.Query().Get("name")
		writer.Write([]byte("Hello, " + name))
	})

	http.ListenAndServe(":"+addr, nil)
}
```

Tento server načte port z proměnné prostředí `PORT`, kterou mu při spuštění Aspire AppHost předá, a na tomto portu spustí server.  
Jedná se o jednoduchý server, který přijme parametr dotazu `name` a vrátí `Hello, {name}`.

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

### Přidání echo serveru do aspire

Vraťte se do projektu Aspire AppHost, kde jsme přidali Valkey, a přidejte komunitní hosting pro jazyk Go.

```bash
dotnet add package CommunityToolkit.Aspire.Hosting.Golang
```

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

```csharp
var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddValkey("cache")
    .WithDataVolume(isReadOnly: false)
    .WithPersistence(interval: TimeSpan.FromMinutes(5),
        keysChangedThreshold: 100);

var echoServer = builder.AddGolangApp("echo-server", "../EchoServer")
    .WithHttpEndpoint(port: 3000, env: "PORT")
    .WithExternalHttpEndpoints();

builder.Build().Run();
```

Zde je `echoServer` implementací rozhraní `IResourceBuilder`, která obsahuje informace potřebné k sestavení serveru v Go.  
Metoda `AddGolangApp`, kterou jsme právě přidali, je metoda rozšíření pro vlastní host, určená k přidání serveru v Go.  
Používá pevně nastavený port 3000 a je vidět, že předává proměnnou prostředí `PORT`.  
Nakonec `WithExternalHttpEndpoints` zajišťuje, že je server dostupný zvenčí.

Pro otestování otevřete `http://localhost:3000/?name=world`, měli byste vidět výstup `Hello, world`.

Současná verze dotnet aspire však má pro projekty, které nejsou v .NET, jednu výraznou nevýhodu.  
Tou je přesně...

## Rozšiřování projektu

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

Aktuálně dotnet aspire poskytuje možnost `WithReplica` pouze pro builder projektů .NET přidaných metodou `AddProject`.  
Pro hostitele v Go nebo pro externí projekty, například přidané metodou `AddContainer`, tuto možnost neposkytuje.

Proto je nutné použít samostatný load balancer nebo reverse proxy a implementovat to ručně.  
Pokud by však reverse proxy byla SPOF, mělo by být možné také pro ni použít `WithReplica`.  
V takovém případě by reverse proxy nutně musela být projektem .NET.

Dosud jsem se s tímto problémem potýkal pomocí nginx, Traefiku nebo vlastní implementace, ale jakmile se objevilo omezení na projekty .NET, neměl jsem okamžitě žádné řešení.  
Proto jsem vyhledal reverse proxy implementovanou v .NET a naštěstí existovala možnost [YARP](https://microsoft.github.io/reverse-proxy/).  
YARP je reverse proxy implementovaná v .NET, která může plnit i roli load balanceru a nabízí mnoho funkcí, proto jsem ji považoval za dobrou volbu.

Nyní tedy přidáme YARP.

### Konfigurace reverse proxy pomocí YARP

Nejprve vytvořte projekt pro použití YARP.

```bash
dotnet new web -n ReverseProxy
```

Poté přejděte do projektu a nainstalujte YARP.

```bash
dotnet add package Yarp.ReverseProxy --version 2.2.0
```

Po dokončení instalace otevřete soubor Program.cs a napište do něj následující kód.

```csharp
using Yarp.ReverseProxy.Configuration;

var builder = WebApplication.CreateBuilder(args);

var routes = new List<RouteConfig>();
var clusters = new List<ClusterConfig>();

builder.Services.AddReverseProxy()
    .LoadFromMemory(routes, clusters);

var app = builder.Build();

app.MapReverseProxy();
app.Run(url: $"http://0.0.0.0:{Environment.GetEnvironmentVariable("PORT") ?? "5000"}");
```

Tento kód je základním kódem pro použití YARP.  
`routes` obsahuje informace o trasách, které bude reverse proxy používat, a `clusters` obsahuje informace o clusterech, které bude reverse proxy používat.  
Tyto informace se do reverse proxy načtou metodou `LoadFromMemory`.  
Nakonec se metodou `MapReverseProxy` reverse proxy namapuje a spustí.

Pro skutečné použití pak v projektu aspire apphost přidejte odkaz na projekt reverse proxy a v souboru Program.cs přidejte nebo upravte následující výraz.

```bash
dotnet add reference ../ReverseProxy
```

```csharp
var echoServer = builder.AddGolangApp("echo-server", "../EchoServer")
    .WithHttpEndpoint(env: "PORT");

var reverseProxy = builder.AddProject<Projects.ReverseProxy>("gateway")
    .WithReference(echoServer)
    .WithHttpEndpoint(port: 3000, env: "PORT", isProxied: true)
    .WithExternalHttpEndpoints();
```

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

### Úprava reverse proxy

Nejprve je třeba změnit naslouchací adresu projektu, který je přiřazen k reverse proxy.  
Odstraňte `applicationUrl` ze souboru `Properties/launchSettings.json`.  
Poté otevřete soubor Program.cs a výrazně jej upravte následujícím způsobem.

```csharp
using Yarp.ReverseProxy.Configuration;

var builder = WebApplication.CreateBuilder(args);

var routes = new List<RouteConfig>
{
    new RouteConfig
    {
        ClusterId = "cluster-echo",
        RouteId = "route-echo",
        Match = new RouteMatch
        {
            Path = "/"
        }
    }
};

var echoServerAddr = Environment.GetEnvironmentVariable("services__echo-server__http__0") ?? "http://localhost:8080";

var clusters = new List<ClusterConfig>
{
    new ClusterConfig
    {
        ClusterId = "cluster-echo",
        Destinations = new Dictionary<string, DestinationConfig>
        {
            { "destination-echo", new DestinationConfig { Address = echoServerAddr } }
        }
    }
};

builder.Services.AddReverseProxy()
    .LoadFromMemory(routes, clusters);

var app = builder.Build();

app.MapReverseProxy();
app.Run(url: $"http://0.0.0.0:{Environment.GetEnvironmentVariable("PORT") ?? "5000"}");
```

Nejprve upravíme informace o `routes` a `clusters`.  
Přidáním `echo-route` a `echo-cluster` nastavujeme, aby se požadavky posílaly na echo server.  
Také upravujeme kód tak, aby se adresa echo serveru načítala z proměnné prostředí.

Pravidlo pro tuto adresu je `services__{service-name}__http__{index}`.  
U echo serveru je název služby `echo-server` a protože jde o jednu instanci, používáme jako index `0`.  
Pokud bychom přidali server ASP.NET Core, může být pomocí `WithReplica` vytvořeno více instancí, a proto stačí index zvýšit.  
Hodnota `http://localhost:8080` použitá v případě výjimky je bezcenná hodnota bez významu.

Nyní spusťte projekt a otevřete `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 lze do dotnet aspire přidat server v Go a předávat požadavky prostřednictvím reverse proxy.  
Tento postup nyní můžeme rozšířit tak, aby byl implementován programově.  
Například u echo serveru lze za název služby přidat číslování, vytvořit více instancí a automaticky přidat konfiguraci do reverse proxy.

Do souboru Program.cs projektu aspire apphost upravte kód používající reverse proxy a echo server následujícím způsobem.

```csharp
var reverseProxy = builder.AddProject<Projects.ReverseProxy>("gateway")
    .WithHttpEndpoint(port: 3000, env: "PORT", isProxied: true)
    .WithExternalHttpEndpoints();

for (var i = 0; i < 8; i++)
{
    var echoServer = builder.AddGolangApp($"echo-server-{i}", "../EchoServer")
        .WithHttpEndpoint(env: "PORT");
    reverseProxy.WithReference(echoServer);
}
```

Poté upravte soubor Program.cs projektu reverse proxy následujícím způsobem.

```csharp
var echoServerDestinations = new Dictionary<string, DestinationConfig>();
for (var i = 0; i < 8; i++)
{
    echoServerDestinations[$"destination-{i}"] = new DestinationConfig
    {
        Address = Environment.GetEnvironmentVariable($"services__echo-server-{i}__http__0") ?? "http://localhost:8080"
    };
}

var clusters = new List<ClusterConfig>
{
    new ClusterConfig
    {
        ClusterId = "cluster-echo",
        Destinations = echoServerDestinations
    }
};
```

Přidáváme cílové nastavení pro 8 instancí echo serveru.  
Nyní reverse proxy disponuje informacemi o cílích všech echo serverů a může požadavky předávat mezi nimi.  
Pokud otevřete původní `http://localhost:3000/?name=world`, měli byste stále vidět výstup `Hello, world`.

## Závěr

V tomto článku jsme popsali přidání serveru v Go do dotnet aspire a předávání požadavků prostřednictvím reverse proxy.  
Rozšíření jsme však zatím nepopsali celé, a proto jsme v samostatném repozitáři připravili příklad, který umožňuje implementovat to programově pomocí proměnných prostředí.  
Podrobnou konfiguraci projektu a kód najdete v repozitáři [snowmerak/AspireStartPack](https://github.com/snowmerak/AspireStarterPack).

Osobně očekávám, že dotnet aspire si může najít vlastní roli jako alternativa k docker compose a také jako nástroj pro nasazení do cloudu.  
Již existují [generátory](https://prom3theu5.github.io/aspirational-manifests/generate-command.html), které vytvářejí manifesty docker compose nebo k8s, takže se myslím, že přístup k infrastrukturním nástrojům pro běžné vývojáře se zlepšil.
