---
id: 7ecf5f1e3335910ea27e6beb68f04ae7
author: snowmerak
title: Att köra en skalbar Go-server med .NET Aspire
description: 'Hur man kör en Go-server skalbart med .NET Aspire: Exempel på horisontell skalning och automatiserad konfiguration med en YARP-omvänd proxy.'
language: sv
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 är ett verktyg som skapades för att hjälpa utvecklare med utveckling och konfiguration av molnnativa lösningar, i takt med att molnnativa miljöer blir allt vanligare. Verktyget gör det möjligt för .NET-utvecklare att enkelt distribuera .NET-projekt, olika molnnativa infrastrukturer samt tjänster på andra språk, containrar och liknande.

Naturligtvis lanseras och drivs allt från Docker till K8s, och många områden, branscher och utvecklare flyttar från traditionella lokala (on-premises) miljöer till molnnativa miljöer, och har redan gjort det. Detta är numera ett moget område. Därför anser jag att det inte behövs någon förklaring av de tidigare besvärligheterna med värdnamn, portkonfiguration, brandväggar, mätvärdeshantering och liknande.

Utifrån ovanstående beskrivningar kommer du troligen inte alls att kunna föreställa dig vad dotnet aspire är. Detta beror på att inte ens Microsoft ger en exakt definition. Därför kommer inte heller jag att ge någon egen definition. Jag kommer dock att använda de grundläggande funktioner i dotnet aspire som jag själv har förstått i denna artikel, så du kan utgå från dem när du bildar din egen uppfattning.

## Projektkonfiguration

### Skapa ett dotnet aspire-projekt

Om mallen för dotnet aspire inte finns tillgänglig måste du först installera mallen. Installera mallen med följande kommando. Om .NET inte finns installerat får du installera det själv.

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

Skapa sedan en ny lösning i en lämplig mapp.

```bash
dotnet new sln
```

Kör därefter följande kommando i lösningsmappen för att skapa ett projekt med mallen aspire-apphost.

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

Då skapas ett aspire-apphost-projekt som endast innehåller en enkel kod för konfiguration.

### Lägga till Valkey

Nu ska vi snabbt lägga till Valkey.

Innan vi lägger till det, bör du veta att dotnet aspire via community hosting tillhandahåller många olika tredjepartslösningar.  
Naturligtvis kan Valkey också dra nytta av detta communitystöd, och det är enkelt att använda via följande NuGet-paket.

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

Det finns även många andra integrerade hostingalternativ, som du kan se [här](https://learn.microsoft.com/ko-kr/dotnet/aspire/fundamentals/integrations-overview).  
Låt oss återgå till Valkey: öppna filen Program.cs i AppHost-projektet och ändra den enligt följande.

```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` är en implementation av gränssnittet `IResourceBuilder` som innehåller information för att bygga en Valkey-tjänst.  
`WithDataVolume` skapar en volym för att lagra cachedata, och `WithPersistence` gör det möjligt att kontinuerligt spara cachedata.  
Utifrån detta verkar det utföra en uppgift liknande `volumes` i `docker-compose`.  
Naturligtvis kan ni också skapa detta utan större svårigheter.  
Men eftersom detta ligger utanför ramen för denna artikel tar vi inte upp det här.

### Skapa en Go-ekoserver

Låt oss nu lägga till en enkel Go-server.  
Skapa först ett Go-workspace i lösningsmappen med `go work init`.  
För .NET-utvecklare kan man se Go-workspace som något liknande en lösning.

Skapa sedan en mapp med namnet EchoServer, gå in i den och kör `go mod init EchoServer`.  
Med detta kommando skapas en Go-modul. För .NET-utvecklare kan man se en modul som något liknande ett projekt.  
Skapa sedan filen `main.go` och skriv följande.

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

Denna server läser porten från miljövariabeln `PORT`, som Aspire AppHost injicerar när den körs, och startar servern på den porten.  
Den är en enkel server som tar emot frågeparametern `name` och returnerar `Hello, {name}`.

Låt oss nu lägga till denna server i dotnet aspire.

### Lägga till ekoservern i Aspire

Gå tillbaka till Aspire AppHost-projektet där vi lade till Valkey och lägg till community hosting för Go.

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

Öppna sedan filen Program.cs och lägg till följande rader.

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

Här är `echoServer` en implementation av gränssnittet `IResourceBuilder` som innehåller information för att bygga en Go-server.  
Den nyss tillagda metoden `AddGolangApp` är en tilläggsmetod för en anpassad host som lägger till en Go-server.  
Du kan se att den använder port 3000 fast och injicerar miljövariabeln `PORT`.  
Slutligen gör `WithExternalHttpEndpoints` att servern kan nås utifrån.

För att testa, gå till `http://localhost:3000/?name=world`, så kommer du att se att `Hello, world` skrivs ut.

Men för närvarande finns det en tung nackdel för projekt som inte är .NET-projekt i dotnet aspire.  
Den nackdelen är just...

## Utöka projektet

### Hur gör man då horisontell skalning?

För närvarande tillhandahåller dotnet aspire alternativet `WithReplica` endast för builders för .NET-projekt som lagts till med metoden `AddProject`.  
För Go-värdar eller externa projekt som läggs till med `AddContainer` erbjuds dock inte detta alternativ.

Därför måste man implementera det själv med en separat load balancer eller reverse proxy.  
Men om reverse proxy:n i sig blir en SPOF kan det bli ett problem, så det är bra om reverse proxy:n i sin tur kan använda alternativet `WithReplica`.  
Då måste reverse proxy:n nödvändigtvis vara ett .NET-projekt.

Hittills har jag använt metoder som nginx, Traefik och egen implementation för att lösa detta, men när det finns begränsningen att det måste vara ett .NET-projekt fanns det inget direkt alternativ för mig.  
Därför sökte jag efter en reverse proxy implementerad i .NET, och lyckligtvis fanns [YARP](https://microsoft.github.io/reverse-proxy/) som ett alternativ.  
YARP är en reverse proxy implementerad i .NET, som även kan fungera som load balancer och erbjuder många funktioner, så jag bedömde att det var ett bra val.

Låt oss nu lägga till YARP.

### Konfigurera reverse proxy med YARP

Skapa först ett projekt för att använda YARP.

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

Gå sedan till projektet och installera YARP.

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

När installationen är klar, öppna filen Program.cs och skriv följande.

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

Denna kod är grundläggande kod för att använda YARP.  
`routes` innehåller ruttinformation som reverse proxy:n använder, och `clusters` innehåller klusterinformation som reverse proxy:n använder.  
Denna information laddas in i reverse proxy:n med metoden `LoadFromMemory`.  
Slutligen använder vi metoden `MapReverseProxy` för att mappa och köra reverse proxy:n.

För faktisk användning ska vi sedan lägga till reverse proxy-projektet som referens i Aspire AppHost-projektet och lägga till eller ändra följande rader i Program.cs.

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

Nu kan reverse proxy:n referera till ekoservern.  
Inkommande externa förfrågningar tas emot av reverse proxy:n och vidarebefordras till ekoservern, och strukturen har ändrats till detta.

### Ändra reverse proxy

Först måste vi ändra lyssnaradressen för projektet som används som reverse proxy.  
Ta bort `applicationUrl` i filen `Properties/launchSettings.json`.  
Öppna sedan filen Program.cs och ändra den omfattande enligt nedan.

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

Först ändrar vi informationen om `routes` och `clusters`.  
Vi lägger till `echo-route` respektive `echo-cluster` och konfigurerar dem så att förfrågningar skickas till ekoservern.  
Dessutom ändrar vi koden så att ekoserverns adress läses från miljövariabler och används.

Regeln för denna adress är `services__{service-name}__http__{index}`.  
För ekoservern är tjänstenamnet `echo-server` och eftersom det är en enda instans använder vi `0` som index.  
Om vi lägger till en ASP.NET Core-server kan flera instanser skapas via `WithReplica`, och då ökar vi bara indexet.  
Det undantagshanterade värdet `http://localhost:8080` är ett meningslöst skräpvärde.

Kör nu projektet och gå till `http://localhost:3000/?name=world`, så kommer du fortfarande att se att `Hello, world` skrivs ut.

### Idéer för utökning

Nu har vi bekräftat att en Go-server lagts till i dotnet aspire och att förfrågningar vidarebefordras via reverse proxy:n.  
Då kan vi utöka detta så att processen kan implementeras programmatiskt.  
Till exempel kan vi skapa flera instanser genom att lägga till nummer efter tjänstenamnet för ekoservern, och automatiskt lägga till konfigurationer för reverse proxy:n.

Ändra koden som använder reverse proxy och ekoservern i filen Program.cs i Aspire AppHost-projektet enligt följande.

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

Ändra sedan filen Program.cs i reverse proxy-projektet enligt följande.

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

Vi lägger till målkonfigurationer för de 8 ekoserverinstanserna.  
Nu kan reverse proxy:n använda målinformationen för de utökade ekoservrarna och vidarebefordra förfrågningar till dem.  
Om du går till det tidigare `http://localhost:3000/?name=world` kommer du fortfarande att se att `Hello, world` skrivs ut.

## Avslutning

I denna artikel har vi beskrivit hur man lägger till en Go-server i dotnet aspire och vidarebefordrar förfrågningar via en reverse proxy.  
Vi har dock ännu inte skrivit klart allt om utökningen, men vi har lagt en separat repository med ett exempel där man kan implementera det mer programmatiskt via miljövariabler.  
För detaljerad projektstruktur och kod, se [snowmerak/AspireStartPack](https://github.com/snowmerak/AspireStarterPack).

Personligen förväntar jag mig att dotnet aspire kan spela sin egen roll både som ett alternativ till docker compose och som ett verktyg för molndistribution.  
Eftersom det redan finns [generatorer](https://prom3theu5.github.io/aspirational-manifests/generate-command.html) som skapar docker compose- eller k8s-manifest, tror jag att tillgängligheten till infrastrukturverktyg har blivit bättre för vanliga utvecklare.
