---
id: 7ecf5f1e3335910ea27e6beb68f04ae7
author: snowmerak
title: Dotnet Aspiressa skaalautuvan Go-palvelimen suorittaminen
description: 'Go-palvelimen skaalautuva suorittaminen .NET Aspirellä: esimerkki vaakasuuntaisesta skaalaamisesta ja automaattisesta konfiguroinnista YARP reverse proxy -palvelun avulla.'
language: fi
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 on työkalu, joka on luotu auttamaan kehittäjiä pilvinatiivin kehityksessä ja konfiguroinnissa pilvinatiivien ympäristöjen yleistyessä. Tämä työkalu mahdollistaa sen, että .NET-kehittäjät voivat helposti ottaa käyttöön .NET-projekteja, erilaista pilvinatiivia infrastruktuuria sekä muilla kielillä kirjoitettuja palveluita tai kontteja.

Luonnollisesti se toimii ja on käytössä Dockerista Kubernetesiin, ja monet alat, toimialat ja kehittäjät ovat siirtymässä perinteisistä on-premises-ympäristöistä pilvinatiiviin ympäristöön tai ovat jo siirtyneet. Tämä alue on jo kypsä. Näin ollen oletan, ettei ole tarvetta selittää niitä vanhoja haasteita, joita on ollut isäntänimien, porttikonfiguraation, palomuurien ja metriikoiden hallinnassa.

Siksi pelkästään edellä olevien selitysten perusteella dotnet aspiren merkitys ei todennäköisesti vielä aukea. Syy on se, ettei Microsoftkaan ole antanut sille täsmällistä määritelmää. Siksi en itsekään aio antaa sille erityistä määritelmää. Tässä kirjoituksessa käytän kuitenkin siitä ymmärtämiani perustoimintoja, joten voit viitata niihin ja muodostaa oman käsityksesi.

## Projektin rakenne

### dotnet aspire -projektin luominen

Jos dotnet aspire -mallipohjaa ei ole, se on asennettava ensin. Asenna mallipohja seuraavalla komennolla. Jos .NET ei ole asennettuna, asenna se itse.

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

Luo sen jälkeen sopivaan kansioon uusi ratkaisu.

```bash
dotnet new sln
```

Suorita tämän jälkeen ratkaisukansiossa seuraava komento, jolla luodaan aspire-apphost-mallipohjan mukainen projekti.

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

Tuloksena syntyy aspire-apphost-projekti, joka sisältää ainoastaan asetuksiin tarvittavan yksinkertaisen koodin.

### Valkeyn lisääminen

Lisätään seuraavaksi yksinkertaisesti Valkey.

Ennen varsinaista lisäämistä on huomattava, että dotnet aspire tarjoaa community hosting -toiminnon kautta useita kolmannen osapuolen ratkaisuja. Valkey saa luonnollisesti tämän yhteisöllisen hostingin tuen, ja sitä voi käyttää helposti seuraavan NuGet-paketin avulla.

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

Muita monipuolisia integroituja hosting-ratkaisuja on tarjolla, ja niitä voi tarkastella [täällä](https://learn.microsoft.com/ko-kr/dotnet/aspire/fundamentals/integrations-overview).  
Palataan nyt Valkeyyn. Avaa AppHost-projektin Program.cs-tiedosto ja muokkaa sitä seuraavasti.

```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` on `IResourceBuilder`-rajapinnan toteutus, joka sisältää tiedot Valkey-palvelun rakentamiseen.  
`WithDataVolume` luo tallennusvolyymin välimuistidatalle, ja `WithPersistence` mahdollistaa välimuistidatan jatkuvan tallentamisen.  
Tämän perusteella se näyttää toimivan samankaltaisesti kuin `docker-compose`-tiedoston `volumes`.  
Tämän voi toki luonnollisesti toteuttaa itsekin melko helposti.  
Se ylittää kuitenkin tämän kirjoituksen laajuuden, joten en käsittele sitä nyt.

### Go-kielisen kaikupalvelimen luominen

Luodaan seuraavaksi yksinkertainen Go-kielinen palvelin.  
Luo ensin ratkaisukansiossa työtila komennolla `go work init`.  
.NET-kehittäjälle Go-työtila vastaa suunnilleen ratkaisua.

Luo sen jälkeen kansio nimeltä EchoServer, siirry sen sisään ja suorita `go mod init EchoServer`.  
Tällä komennolla luodaan Go-moduuli. Moduuli vastaa .NET-kehittäjälle projektia.  
Luo sitten tiedosto `main.go` ja kirjoita siihen seuraava koodi.

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

Tämä palvelin lukee portin, jolla sen tulee kuunnella, `PORT`-ympäristömuuttujasta, jonka Aspire AppHost injektoi suorituksen aikana, ja käynnistyy kyseisessä portissa.  
Se on yksinkertainen palvelin, joka vastaanottaa `name`-kyselyparametrin ja palauttaa `Hello, {name}`.

Lisätään seuraavaksi tämä palvelin dotnet aspireen.

### Kaikupalvelimen lisääminen aspireen

Siirry takaisin Valkeyn lisänneeseen Aspire AppHost -projektiin ja lisää Go-kielelle tarkoitettu community hosting.

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

Avaa sitten Program.cs-tiedosto ja lisää seuraava lauseke.

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

Tässä `echoServer` on `IResourceBuilder`-rajapinnan toteutus, joka sisältää tiedot Go-kielisen palvelimen rakentamiseen.  
Äsken lisätty `AddGolangApp`-metodi on mukautetun hostin laajennusmetodi, jolla Go-kielinen palvelin lisätään.  
Palvelin käyttää kiinteästi porttia 3000, ja on nähtävissä, että se injektoi `PORT`-ympäristömuuttujan.  
Lopuksi `WithExternalHttpEndpoints` mahdollistaa pääsyn palvelimeen ulkopuolelta.

Testaa palvelinta avaamalla osoite `http://localhost:3000/?name=world`. Näet tulosteen `Hello, world`.

Nykyisessä dotnet aspiressa on kuitenkin raskas rangaistus projekteille, jotka eivät ole .NET-projekteja.  
Se on nimittäin...

## Projektin skaalaaminen

### Miten horisontaalinen skaalaus tehdään?

Tällä hetkellä dotnet aspire tarjoaa `WithReplica`-vaihtoehdon vain `AddProject`-metodilla lisättyjen .NET-projektien builderille.  
Go-kielisille isännille tai `AddContainer`-metodin kaltaisille ulkoisille projekteille tätä vaihtoehtoa ei kuitenkaan tarjota.

Siksi on rakennettava erillinen load balancer tai reverse proxy itse.  
Tämä luo kuitenkin yhden kohdan, joka voi muodostua SPOF:iksi, joten reverse proxylle kannattaa tarjota `WithReplica`-vaihtoehto.  
Tällöin reverse proxyn on väistämättä oltava .NET-projekti.

Olen tähän asti ratkaissut tämän ongelman käyttämällä nginxiä, traefikia ja omia toteutuksia, mutta kun .NET-projektirajoite tuli vastaan, minulla ei ollut tuolloin käytettävissä ratkaisua.  
Etsin siksi .NET-toteutuksia reverse proxyille, ja onneksi löysin vaihtoehdon [YARP](https://microsoft.github.io/reverse-proxy/).  
YARP on .NET-toteutettu reverse proxy, joka voi toimia myös load balancerina ja tarjoaa monipuolisia ominaisuuksia, joten pidin sitä hyvänä valintana.

Lisätään nyt YARP.

### Reverse proxyn rakentaminen YARP:lla

Luo ensin projekti YARP:n käyttöön.

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

Siirry sitten projektiin ja asenna YARP.

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

Kun asennus on valmis, avaa Program.cs-tiedosto ja kirjoita seuraava koodi.

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

Tämä koodi on YARP:n käytön perusta.  
`routes` sisältää reverse proxyn käyttämät reittitiedot ja `clusters` reverse proxyn käyttämät klusteritiedot.  
Nämä tiedot ladataan reverse proxyyn `LoadFromMemory`-metodilla.  
Lopuksi reverse proxy kytketään ja käynnistetään `MapReverseProxy`-metodilla.

Lisäksi käyttöä varten aspire apphost -projektiin on lisättävä viittaus reverse proxy -projektiin, ja Program.cs-tiedostoon on lisättävä ja muokattava seuraava koodi.

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

Nyt reverse proxy voi viitata echo serveriin.  
Ulkopuolelta tulevat pyynnöt vastaanotetaan reverse proxyssa ja välitetään echo serverille, ja rakenne muuttuu tämän mukaisesti.

### Reverse proxyn muokkaaminen

Ensin on muutettava reverse proxy -projektille määritettyä kuunteluosoitetta.  
Poista `Properties/launchSettings.json`-tiedoston `applicationUrl`. Avaa sitten Program.cs-tiedosto ja muokkaa sitä laajasti seuraavasti.

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

Ensin muokataan tietoja `routes`- ja `clusters`-muuttujille.  
Lisätään vastaavasti `echo-route` ja `echo-cluster`, ja määritetään ne lähettämään pyynnöt echo serverille.  
Lisäksi echo serverin osoite luetaan ympäristömuuttujasta.

Tämän osoitteen sääntö on `services__{service-name}__http__{index}`.  
Echo serverin tapauksessa palvelun nimi on `echo-server`, ja koska instansseja on vain yksi, indeksiksi käytetään `0`.  
Jos lisätään ASP.NET Core -palvelin, useita instansseja voidaan luoda `WithReplica`-vaihtoehdolla, ja silloin indeksiä kasvatetaan.  
Varakäytössä oleva `http://localhost:8080` on merkityksetön arvo.

Nyt voit käynnistää projektin ja avata osoitteen `http://localhost:3000/?name=world`. Näet edelleen tulosteen `Hello, world`.

### Laajennusideoita

Olemme nyt lisänneet Go-palvelimen dotnet aspireen ja varmistaneet, että pyynnöt välittyvät reverse proxyn kautta.  
Tätä prosessia voi nyt laajentaa niin, että se toteutetaan ohjelmallisesti.  
Esimerkiksi echo serverille voidaan lisätä palvelunimen perään numerointi, jolloin luodaan useita instansseja, ja reverse proxyn asetukset voidaan lisätä automaattisesti.

Muokkaa aspire apphost -projektin Program.cs-tiedostoa siten, että se käyttää reverse proxyä ja echo serveriä seuraavasti.

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

Muokkaa lisäksi reverse proxy -projektin Program.cs-tiedostoa seuraavasti.

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

Lisätään kohdeasetukset kahdeksaan echo server -instanssiin.  
Reverse proxy tuntee nyt kaikkien echo server -instanssien kohdetiedot ja voi välittää pyynnöt niille.  
Kun avaat aiemman osoitteen `http://localhost:3000/?name=world`, näet edelleen tulosteen `Hello, world`.

## Lopuksi

Tässä kirjoituksessa kuvasin, miten Go-palvelin lisätään dotnet aspireen ja miten pyynnöt välitetään reverse proxyn kautta.  
Laajennuksia en ole vielä kirjoittanut kokonaan, mutta olen laatinut erilliseen repositorioon esimerkin, jossa toteutus tehdään ohjelmallisemmin ympäristömuuttujien avulla.  
Tarkemmat projektirakenteet ja koodi löytyvät osoitteesta [snowmerak/AspireStartPack](https://github.com/snowmerak/AspireStarterPack).

Itse uskon, että dotnet aspire voi toimia Docker Composen vaihtoehtona sekä pilvijulkaisutyökaluna omalla tavallaan.  
Koska jo on olemassa [generaattoreita](https://prom3theu5.github.io/aspirational-manifests/generate-command.html), jotka luovat docker compose -tiedostoja ja k8s-manifesteja, uskon, että tavallisten kehittäjien pääsy infrastruktuurityökaluihin on helpottunut.
