---
id: 7ecf5f1e3335910ea27e6beb68f04ae7
author: snowmerak
title: Ejecutar un servidor Go de forma escalable con .NET Aspire
description: 'Cómo ejecutar servidores Go de forma escalable con .NET Aspire: ejemplo de escalado horizontal y configuración automatizada mediante el proxy inverso YARP.'
language: es
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 es una herramienta creada para ayudar a los desarrolladores en el desarrollo y la configuración nativos de la nube, a medida que crecen los entornos nativos de la nube. Esta herramienta permite que los desarrolladores de .NET desplieguen fácilmente proyectos de .NET, diversas infraestructuras nativas de la nube, así como servicios escritos en otros lenguajes o contenedores, entre otros.

Naturalmente, se lanza y opera desde Docker hasta K8s, y muchos campos, industrias y desarrolladores han migrado, o ya han migrado, de entornos locales tradicionales a entornos nativos de la nube. Hoy en día es un ámbito maduro. Por ello, supongo que no es necesario explicar las incomodidades existentes en torno a la gestión de nombres de host, la configuración de puertos, los cortafuegos o las métricas.

Así que, solo con las explicaciones anteriores, es probable que no tenga ni idea de qué es dotnet aspire. Esto se debe a que ni siquiera Microsoft ofrece una definición precisa. Por eso, yo tampoco daré una definición propia. Sin embargo, en este artículo utilizaré las funciones básicas de dotnet aspire que yo he comprendido, así que puede tomarlas como referencia y definir su propia posición.

## Configuración del proyecto

### Creación de un proyecto dotnet aspire

Si no tiene la plantilla de dotnet aspire, primero deberá instalarla. Instale la plantilla con el siguiente comando. Si no tiene .NET, instálelo por su cuenta.

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

Luego, cree una nueva solución en una carpeta adecuada.

```bash
dotnet new sln
```

A continuación, ejecute el siguiente comando en la carpeta de la solución para crear un proyecto de la plantilla aspire-apphost.

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

Así se creará un proyecto aspire-apphost que solo contiene un código sencillo para la configuración.

### Añadir Valkey

Ahora, añadiremos Valkey de forma sencilla.

Antes de añadirlo directamente, dotnet aspire ofrece diversas soluciones de terceros mediante community hosting.  
Por supuesto, Valkey también puede beneficiarse de este community hosting, y es fácil de usar mediante el siguiente paquete NuGet.

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

Además, se ofrecen diversas integraciones de hosting, que puede consultar [aquí](https://learn.microsoft.com/ko-kr/dotnet/aspire/fundamentals/integrations-overview).  
Volviendo a Valkey, abra el archivo Program.cs del proyecto AppHost y modifíquelo de la siguiente manera.

```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` es una implementación de la interfaz `IResourceBuilder` que contiene la información necesaria para construir el servicio de Valkey.  
`WithDataVolume` crea un volumen para almacenar los datos de la caché, y `WithPersistence` permite guardar los datos de la caché de forma continua.  
Hasta aquí, parece que cumple una función similar a `volumes` en `docker-compose`.  
Naturalmente, esto también puede crearlo sin dificultad.  
Sin embargo, esto excede el alcance de este artículo, por lo que no lo trataremos ahora.

### Creación de un servidor de eco en Go

Ahora, añadiremos un servidor sencillo en Go.  
Primero, en la carpeta de la solución, cree un workspace mediante `go work init`.  
Para los desarrolladores de .NET, puede considerar que un workspace de Go es algo similar a una solución.

Luego, cree una carpeta llamada EchoServer, entre en ella y ejecute `go mod init EchoServer`.  
Con este comando se crea un módulo de Go. Puede entender que un módulo es algo similar a un proyecto para los desarrolladores de .NET.  
A continuación, cree el archivo `main.go` y escriba lo siguiente.

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

Este servidor, cuando se ejecuta Aspire AppHost, lee el puerto de la variable de entorno `PORT` que se le inyecta, es decir, el puerto en el que debe escuchar, y arranca el servidor en dicho puerto.  
Es un servidor sencillo que recibe el parámetro de consulta `name` y devuelve `Hello, {name}`.

Ahora, añadamos este servidor a dotnet aspire.

### Añadir el servidor de eco a Aspire

Volvamos al proyecto Aspire AppHost, donde añadimos Valkey, y añadamos el community hosting para Go.

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

Luego, abra el archivo Program.cs y añada la siguiente sentencia.

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

Aquí, `echoServer` es una implementación de la interfaz `IResourceBuilder` que contiene la información necesaria para construir el servidor en Go.  
El método `AddGolangApp` que acabamos de añadir es un método de extensión del host personalizado para añadir un servidor en Go.  
Utiliza el puerto 3000 de forma fija, y se puede comprobar que inyecta la variable de entorno `PORT`.  
Por último, `WithExternalHttpEndpoints` permite el acceso desde el exterior.

Para probarlo, acceda a `http://localhost:3000/?name=world` y verá que se muestra `Hello, world`.

Sin embargo, actualmente dotnet aspire impone una penalización considerable a los proyectos que no son de .NET.  
Esa penalización es precisamente...

## Extensión del proyecto

### Entonces, ¿cómo se hace el escalado horizontal?

Actualmente, dotnet aspire solo ofrece la opción `WithReplica` para los builders de proyectos .NET añadidos mediante el método `AddProject`.  
Sin embargo, no ofrece esta opción para hosts en Go ni para proyectos externos como los añadidos con `AddContainer`.

Por ello, es necesario implementarlo manualmente mediante un balanceador de carga o un reverse proxy independiente.  
No obstante, si el reverse proxy se convierte en un SPOF, sería conveniente que dicho reverse proxy ofreciera la opción `WithReplica`.  
En ese caso, el reverse proxy debería ser necesariamente un proyecto de .NET.

Hasta ahora, para este problema hemos utilizado nginx, Traefik, implementaciones propias, entre otras opciones, pero con la restricción de que debía ser un proyecto de .NET, no tuve de inmediato ninguna alternativa.  
Así que busqué reverse proxies implementados en .NET y, afortunadamente, encontré [YARP](https://microsoft.github.io/reverse-proxy/).  
YARP es un reverse proxy implementado en .NET que también puede desempeñar la función de balanceador de carga y ofrece diversas funcionalidades, por lo que consideré que era una buena elección.

Ahora, añadamos YARP.

### Configuración de un reverse proxy con YARP

Primero, cree un proyecto para utilizar YARP.

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

Luego, acceda al proyecto e instale YARP.

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

Una vez finalizada la instalación, abra el archivo Program.cs y escriba lo siguiente.

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

Este código es la configuración básica para utilizar YARP.  
`routes` contiene la información de las rutas que utilizará el reverse proxy, y `clusters` contiene la información de los clústeres que utilizará el reverse proxy.  
Esta información se carga en el reverse proxy mediante el método `LoadFromMemory`.  
Por último, se utiliza el método `MapReverseProxy` para mapear y ejecutar el reverse proxy.

Después, para el uso real, añada una referencia al proyecto del reverse proxy en el proyecto apphost de Aspire y añada o modifique en Program.cs la siguiente sentencia.

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

Ahora el reverse proxy puede referenciar al servidor de eco.  
La estructura está cambiando para que las solicitudes externas se reciban en el reverse proxy y se transfieran al servidor de eco.

### Modificación del reverse proxy

Primero, es necesario cambiar la dirección de escucha del proyecto asignado al reverse proxy.  
Elimine el `applicationUrl` dentro del archivo `Properties/launchSettings.json`.  
Luego, abra el archivo Program.cs y modifíquelo considerablemente como se indica a continuación.

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

Primero, se modifica la información de `routes` y `clusters`.  
Se añaden `echo-route` y `echo-cluster` respectivamente, para configurar que las solicitudes se envíen al servidor de eco.  
Además, se modifica para leer la dirección del servidor de eco desde una variable de entorno y utilizarla.

La regla de esta dirección es `services__{service-name}__http__{index}`.  
En el caso del servidor de eco, el nombre del servicio es `echo-server` y, al ser una única instancia, se utiliza `0` como índice.  
Si añadimos un servidor ASP.NET Core, pueden crearse varias instancias mediante `WithReplica`, por lo que basta con incrementar el índice para utilizarlas.  
El valor `http://localhost:8080`, que aparece como excepción controlada, es un valor basura sin ningún significado.

Ahora, ejecute el proyecto y acceda a `http://localhost:3000/?name=world`, y verá que sigue apareciendo `Hello, world`.

### Ideas de extensión

Hasta aquí hemos comprobado que se puede añadir un servidor en Go a dotnet aspire y reenviar las solicitudes mediante un reverse proxy.  
A partir de ahí, se puede ampliar para implementar este proceso de forma programática.  
Por ejemplo, se pueden crear varias instancias del servidor de eco añadiendo una numeración tras el nombre del servicio, y añadir automáticamente la configuración correspondiente al reverse proxy.

Modifique el código que utiliza el reverse proxy y el servidor de eco en el archivo Program.cs del proyecto apphost de Aspire de la siguiente manera.

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

Y modifique el archivo Program.cs del proyecto del reverse proxy de la siguiente manera.

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

Se añade la configuración de destinos para las 8 instancias del servidor de eco.  
Ahora el reverse proxy dispone de la información de destino de las instancias del servidor de eco y puede reenviar las solicitudes.  
Si accede a `http://localhost:3000/?name=world` como antes, verá que sigue apareciendo `Hello, world`.

## Conclusión

En este artículo hemos explicado el proceso de añadir un servidor en Go a dotnet aspire y reenviar las solicitudes mediante un reverse proxy.  
Sin embargo, la parte de extensión aún no la hemos documentado completamente; en un repositorio aparte hemos preparado un ejemplo que permite implementarla de forma más programática mediante variables de entorno.  
Para la estructura detallada del proyecto y el código, consulte [snowmerak/AspireStartPack](https://github.com/snowmerak/AspireStarterPack).

Personalmente, espero que dotnet aspire pueda desempeñar su propio papel como alternativa a docker compose y como herramienta de despliegue en la nube.  
Ya existen [generadores](https://prom3theu5.github.io/aspirational-manifests/generate-command.html) que crean manifiestos de docker compose o de k8s, por lo que creo que el acceso a las herramientas de infraestructura para los desarrolladores comunes se ha vuelto más sencillo.
