---
id: 7ecf5f1e3335910ea27e6beb68f04ae7
author: snowmerak
title: Spustenie škálovateľného Go servera v .NET Aspire
description: 'Ako spustiť Go server škálovateľne pomocou .NET Aspire: príklad horizontálneho škálovania a automatizovanej konfigurácie s reverzným proxy serverom YARP.'
language: sk
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, ktorý vznikol na pomoc vývojárom pri vývoji a konfigurácii cloud-native aplikácií v súvislosti s rastúcim rozšírením cloud-native prostredia. Tento nástroj umožňuje vývojárom .NET jednoducho nasadzovať projekty v .NET, rôznu cloud-native infraštruktúru, a tiež služby napísané v iných jazykoch alebo kontajnery.

Samozrejme, od Dockera až po K8s sa to už využíva a prevádzkuje, a mnohé oblasti, odvetvia a vývojári v rámci tradičného on-premise prostredia už migrovali alebo sú v procese migrácie do cloud-native prostredia. Dnes je to už vyspelá oblasť. Preto predpokladám, že nie je potrebné vysvetľovať existujúce nepohodlie spojené s hostname, konfiguráciou portov, firewallom či správou metrík.

Práve preto vás na základe vyššie uvedených vysvetlení určite stále nenapadne, čo dotnet aspire vlastne je. Dôvod je ten, že ani Microsoft zatiaľ neposkytuje presnú definíciu. Ani ja preto nebudem uvádzať žiadnu vlastnú definíciu. Namiesto toho v tomto článku použijem základné funkcie dotnet aspire, ako ich chápem ja, a môžete ich použiť ako referenciu na vytvorenie vlastného pohľadu.

## Konfigurácia projektu

### Vytvorenie projektu dotnet aspire

Ak šablóna dotnet aspire nie je k dispozícii, najprv ju musíte nainštalovať. Šablónu nainštalujete nasledujúcim príkazom. Ak nemáte nainštalovaný .NET, nainštalujte si ho prosím sami.

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

Potom na vhodnom priečinku vytvorte nové riešenie.

```bash
dotnet new sln
```

Následne v priečinku riešenia spustite nasledujúci príkaz, ktorým vytvoríte projekt zo šablóny aspire-apphost.

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

Vytvorí sa projekt aspire-apphost, ktorý obsahuje iba jednoduchý kód na nastavenie.

### Pridanie Valkey

Teraz jednoducho pridáme Valkey.

Skôr než ho pridáme, dotnet aspire poskytuje rôzne riešenia tretích strán prostredníctvom community hostingu.
Valkey samozrejme môže využívať túto podporu komunitného hostingu a jednoducho ho možno použiť prostredníctvom nasledujúceho balíka NuGet.

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

Ďalšie rôzne integrované hostingy sú k dispozícii, a môžete si ich pozrieť [tu](https://learn.microsoft.com/ko-kr/dotnet/aspire/fundamentals/integrations-overview).
Vráťme sa k Valkey: v projekte AppHost otvorte súbor Program.cs a upravte ho nasledovne.

```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 implementácia rozhrania `IResourceBuilder`, ktorá obsahuje informácie potrebné na zostavenie služby Valkey.
`WithDataVolume` vytvára zväzok na uloženie dát cache, a `WithPersistence` zabezpečuje, že dáta cache sa budú trvalo ukladať.
Na základe toho sa zdá, že plní podobnú úlohu ako `volumes` v `docker-compose`.
Samozrejme, aj vy si to môžete jednoducho vytvoriť.
Keďže však presahuje rámec tohto článku, nebudem sa tomu venovať teraz.

### Vytvorenie echo servera v Go

Teraz pridáme jednoduchý server v jazyku Go.
Najprv v priečinku riešenia vytvorte workspace príkazom `go work init`.
Pre vývojárov v .NET možno Go workspace považovať za niečo podobné ako riešenie (solution).

Potom vytvorte priečinok EchoServer, prejdite do neho a spustite `go mod init EchoServer`.
Týmto príkazom vytvoríte Go modul. Modul môžete chápať ako niečo podobné projektu pre vývojárov v .NET.
Následne vytvorte súbor `main.go` a napíšte do neho nasledujúci 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 pri spustení AppHost v Aspire dostane premennú prostredia `PORT`, na ktorej má počúvať, načíta tento port a spustí server.
Ide o jednoduchý server, ktorý prijme query parameter `name` a vráti `Hello, {name}`.

Teraz pridajme tento server do dotnet aspire.

### Pridanie echo servera do aspire

Vráťte sa do projektu Aspire AppHost, v ktorom ste pridali Valkey, a pridajte community hosting pre jazyk Go.

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

Potom otvorte súbor Program.cs a pridajte nasledujúci 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();
```

Tu `echoServer` je implementácia rozhrania `IResourceBuilder`, ktorá obsahuje informácie potrebné na zostavenie servera v Go.
Práve pridaná metóda `AddGolangApp` je rozširujúca metóda vlastného hosta na pridanie servera v Go.
Server používa pevne nastavený port 3000 a je vidieť, že sa mu vkladá premenná prostredia `PORT`.
Nakoniec `WithExternalHttpEndpoints` umožňuje prístup zvonku.

Na otestovanie otvorte `http://localhost:3000/?name=world`, a mali by ste vidieť výstup `Hello, world`.

Avšak v súčasnosti dotnet aspire prináša pre projekty, ktoré nie sú v .NET, značnú penalizáciu.
Tou je práve...

## Rozširovanie projektu

### Ako je to potom s horizontálnym škálovaním?

Dotnet aspire momentálne poskytuje možnosť `WithReplica` iba pre builder .NET projektov pridaných metódou `AddProject`.
Pre hosty Go ani pre externé projekty, ako napríklad `AddContainer`, však túto možnosť neposkytuje.

Preto je potrebné použiť samostatný load balancer alebo reverse proxy a implementovať to ručne.
Keďže však reverse proxy by sa mohol stať SPOF, je vhodné, aby aj reverse proxy poskytoval možnosť `WithReplica`.
Potom nevyhnutne musí byť reverse proxy projekt v .NET.

Doteraz sme na tento problém používali nginx, traefik, vlastnú implementáciu a podobné riešenia, no keď sa objavilo obmedzenie na projekt v .NET, nemal som v tej chvíli žiadnu možnosť.
Preto som hľadal reverse proxy implementované v .NET a našťastie existovala možnosť [YARP](https://microsoft.github.io/reverse-proxy/).
YARP je reverse proxy implementované v .NET, ktoré dokáže fungovať aj ako load balancer a ponúka rôzne funkcie, preto som ho považoval za dobrú voľbu.

Teraz pridáme YARP.

### Konfigurácia reverse proxy s YARP

Najprv vytvorte projekt na použitie YARP.

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

Potom prejdite do projektu a nainštalujte YARP.

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

Po dokončení inštalácie otvorte súbor Program.cs a napíšte nasledujúci 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ý kód na použitie YARP.
`routes` obsahuje informácie o route, ktoré bude reverse proxy používať, a `clusters` obsahuje informácie o clusteroch, ktoré bude reverse proxy používať.
Tieto informácie sa načítavajú do reverse proxy metódou `LoadFromMemory`.
Nakoniec pomocou metódy `MapReverseProxy` namapujeme reverse proxy a spustíme ho.

Na reálne použitie potom v projekte aspire apphost pridajte referenciu na projekt reverse proxy a v súbore Program.cs pridajte alebo upravte nasledujúci kód.

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

Teraz reverse proxy môže odkazovať na echo server.
Štruktúra sa mení tak, že externé požiadavky prijíma reverse proxy a preposiela ich na echo server.

### Úprava reverse proxy

Najprv je potrebné zmeniť adresu, na ktorej projekt reverse proxy počúva.
V súbore `Properties/launchSettings.json` odstráňte `applicationUrl`.
Potom otvorte súbor Program.cs a výrazne ho upravte nasledovne.

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

Najprv upravíme informácie o `routes` a `clusters`.
Pridali sme `echo-route` a `echo-cluster`, aby sa požiadavky posielali na echo server.
Ďalej upravíme kód tak, aby sa adresa echo servera načítavala z premenných prostredia.

Pravidlo pre túto adresu je `services__{service-name}__http__{index}`.
Pre echo server je názov služby `echo-server` a keďže ide o jednu inštanciu, ako index používame `0`.
Ak by sme pridali server asp .net core, pomocou `WithReplica` by sa mohlo vytvoriť viacero inštancií, takže stačí zvýšiť index.
Hodnota `http://localhost:8080` použitá pri spracovaní výnimky je bezvýznamná hodnota bez akéhokoľvek významu.

Teraz spustite projekt a otvorte `http://localhost:3000/?name=world`, a aj tak by ste mali vidieť výstup `Hello, world`.

### Nápady na rozšírenie

Teraz sme overili, že sme do dotnet aspire pridali server v Go a že reverse proxy preposiela požiadavky.
Tento proces môžeme ďalej rozšíriť tak, aby sa dal implementovať programatickým spôsobom.
Napríklad pre echo server môžeme za názov služby pridať číslovanie, vytvoriť viacero inštancií a automaticky pridať konfiguráciu do reverse proxy.

Do súboru Program.cs v projekte aspire apphost upravte kód používajúci reverse proxy a echo server nasledovne.

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

Potom upravte súbor Program.cs v projekte reverse proxy nasledovne.

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

Pridali sme konfiguráciu cieľov pre osem inštancií echo servera.
Reverse proxy teraz má informácie o cieľoch pre všetky echo servery a dokáže požiadavky preposielať.
Ak otvoríte pôvodnú adresu `http://localhost:3000/?name=world`, aj tak by ste mali vidieť výstup `Hello, world`.

## Záver

V tomto článku sme popísali, ako pridať server v Go do dotnet aspire a ako preposielať požiadavky cez reverse proxy.
Rozšírenie sme však zatiaľ úplne nepopísali; v samostatnom repozitári sme pripravili príklad, ktorý umožňuje implementovať to programatickejšie pomocou premenných prostredia.
Podrobnú konfiguráciu projektu a kód nájdete v [snowmerak/AspireStartPack](https://github.com/snowmerak/AspireStarterPack).

Osobne očakávam, že dotnet aspire môže zohrávať vlastnú úlohu ako alternatíva k docker compose a zároveň ako nástroj na nasadzovanie do cloudu.
Už existujú [generátory](https://prom3theu5.github.io/aspirational-manifests/generate-command.html), ktoré vytvárajú docker compose alebo k8s manifesty, takže si myslím, že dostupnosť infraštruktúrnych nástrojov pre bežných vývojárov sa zlepšila.
