---
id: 7ecf5f1e3335910ea27e6beb68f04ae7
author: snowmerak
title: Exécuter un serveur Go de manière évolutive avec dotnet Aspire
description: 'Comment exécuter des serveurs Go de manière évolutive avec .NET Aspire : exemple de mise à l''échelle horizontale et de configuration automatisée à l''aide du reverse proxy YARP.'
language: fr
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 est un outil créé pour aider les développeurs dans le développement et la configuration d'applications cloud natives, à mesure que les environnements cloud natifs se multiplient. Cet outil permet aux développeurs .NET de déployer facilement leurs projets .NET, diverses infrastructures cloud natives, ainsi que des services ou des conteneurs écrits dans d'autres langages.

Naturellement, la chaîne va de Docker jusqu'à Kubernetes (k8s) pour la mise en production et l'exploitation, et de nombreux secteurs, industries et développeurs ont migré, ou sont déjà migrés, des environnements on-premise existants vers des environnements cloud natifs. C'est désormais un domaine mature. Il n'est donc, selon moi, pas nécessaire d'expliquer les difficultés antérieures liées aux noms d'hôte, à la configuration des ports, aux pare-feux, à la gestion des métriques, etc.

Ainsi, à la seule lecture de ces explications, il est probable que vous ne compreniez absolument pas ce qu'est dotnet aspire. En effet, Microsoft ne donne pas non plus de définition précise de cet outil. Je ne proposerai donc pas non plus de définition particulière. Cependant, cet article utilisera les fonctionnalités de base de dotnet aspire telles que je les ai comprises ; vous pourrez vous y référer pour définir votre propre positionnement.

## Configuration du projet

### Création d'un projet dotnet aspire

Si le modèle dotnet aspire n'est pas disponible, il faut d'abord l'installer. Installez le modèle à l'aide de la commande suivante. Si .NET n'est pas installé, veuillez l'installer vous-même.

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

Créez ensuite une nouvelle solution dans un dossier approprié.

```bash
dotnet new sln
```

Puis, depuis le dossier de la solution, exécutez la commande suivante pour créer le projet à partir du modèle aspire-apphost.

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

Un projet aspire-apphost contenant uniquement un code minimal de configuration est alors créé.

### Ajout de Valkey

Ajoutons maintenant brièvement Valkey.

Avant de l'ajouter, notez que dotnet aspire fournit diverses solutions tierces via le community hosting.  
Valkey peut naturellement bénéficier de ce support de community hosting, et il est facilement utilisable grâce au package NuGet suivant.

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

D'autres hébergements intégrés sont également disponibles et peuvent être consultés [ici](https://learn.microsoft.com/ko-kr/dotnet/aspire/fundamentals/integrations-overview).  
Revenons maintenant à Valkey : ouvrez le fichier Program.cs du projet AppHost et modifiez-le comme suit.

```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` est une implémentation de l'interface `IResourceBuilder` contenant les informations permettant de construire le service Valkey.  
`WithDataVolume` crée un volume destiné à stocker les données du cache, et `WithPersistence` permet de sauvegarder les données du cache de manière continue.  
À ce stade, cela semble jouer un rôle similaire à `volumes` dans `docker-compose`.  
Bien entendu, vous pouvez facilement le mettre en place vous-mêmes.  
Toutefois, cela dépasse le cadre de cet article, je n'en parlerai donc pas maintenant.

### Création d'un serveur d'écho en Go

Ajoutons maintenant un serveur Go simple.  
Commencez par initialiser un workspace avec `go work init` dans le dossier de la solution.  
Pour un développeur .NET, le workspace Go peut être considéré comme similaire à une solution.

Ensuite, créez un dossier nommé EchoServer, déplacez-vous à l'intérieur et exécutez `go mod init EchoServer`.  
Cette commande crée un module Go. Pour un développeur .NET, un module peut être considéré comme similaire à un projet.  
Créez ensuite un fichier `main.go` et écrivez le code suivant.

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

Ce serveur lit le port sur lequel il doit écouter à partir de la variable d'environnement `PORT`, que l'AppHost Aspire injecte lors de son exécution, puis démarre le serveur sur ce port.  
Il s'agit d'un serveur simple qui reçoit le paramètre de requête `name` et renvoie `Hello, {name}`.

Ajoutons maintenant ce serveur à dotnet aspire.

### Ajout du serveur d'écho à Aspire

Revenez au projet Aspire AppHost dans lequel vous avez ajouté Valkey, puis ajoutez le community hosting pour Go.

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

Ouvrez ensuite le fichier Program.cs et ajoutez l'instruction suivante.

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

Ici, `echoServer` est une implémentation de l'interface `IResourceBuilder` contenant les informations permettant de construire le serveur Go.  
La méthode `AddGolangApp` que nous venons d'ajouter est une méthode d'extension de l'hôte personnalisé permettant d'ajouter un serveur Go.  
Le serveur utilise le port 3000 de manière fixe, et l'on constate qu'il injecte la variable d'environnement `PORT`.  
Enfin, `WithExternalHttpEndpoints` permet d'y accéder depuis l'extérieur.

Pour tester, accédez à `http://localhost:3000/?name=world` : vous devriez voir s'afficher `Hello, world`.

Cependant, dotnet aspire impose actuellement une lourde pénalité aux projets non-dotnet.  
Il s'agit de...

## Extension du projet

### Comment réaliser la mise à l'échelle horizontale ?

Actuellement, dotnet aspire ne fournit l'option `WithReplica` que pour le builder des projets .NET ajoutés via la méthode `AddProject`.  
En revanche, cette option n'est pas disponible pour les hôtes Go ni pour les projets externes tels que ceux ajoutés via `AddContainer`.

Il faut donc implémenter soi-même la solution, en utilisant un load balancer ou un reverse proxy distinct.  
Toutefois, ce reverse proxy pourrait alors devenir un SPOF (point de défaillance unique) ; il est donc préférable qu'il bénéficie également de l'option `WithReplica`.  
Dans ce cas, le reverse proxy doit nécessairement être un projet .NET.

Jusqu'ici, j'avais tenté de résoudre ce problème avec nginx, Traefik, ou une implémentation personnalisée ; mais dès lors que la contrainte du projet .NET s'imposait, je n'avais pas de solution immédiate.  
J'ai donc recherché un reverse proxy implémenté en .NET et, heureusement, [YARP](https://microsoft.github.io/reverse-proxy/) s'est présenté comme une option.  
YARP est un reverse proxy implémenté en .NET, qui peut également jouer le rôle de load balancer et offre de nombreuses fonctionnalités ; je l'ai donc jugé comme un bon choix.

Ajoutons maintenant YARP.

### Configuration d'un reverse proxy avec YARP

Commencez par créer un projet pour utiliser YARP.

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

Puis déplacez-vous dans le projet et installez YARP.

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

Une fois l'installation terminée, ouvrez le fichier Program.cs et écrivez le code suivant.

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

Ce code constitue la configuration de base permettant d'utiliser YARP.  
`routes` contient les informations de routes que le reverse proxy utilisera, et `clusters` contient les informations de clusters que le reverse proxy utilisera.  
Ces informations sont chargées dans le reverse proxy via la méthode `LoadFromMemory`.  
Enfin, la méthode `MapReverseProxy` mappe et exécute le reverse proxy.

Ensuite, pour une utilisation réelle, ajoutez le projet reverse proxy comme référence dans le projet AppHost d'Aspire, puis ajoutez et modifiez l'instruction suivante dans le fichier 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();
```

Le reverse proxy peut désormais référencer le serveur d'écho.  
La structure évolue vers une configuration dans laquelle les requêtes externes sont reçues par le reverse proxy, puis transmises au serveur d'écho.

### Modification du reverse proxy

Tout d'abord, il faut modifier l'adresse d'écoute du projet attribué au reverse proxy.  
Supprimez la valeur `applicationUrl` dans le fichier `Properties/launchSettings.json`.  
Ensuite, ouvrez le fichier Program.cs et modifiez-le en profondeur comme indiqué ci-dessous.

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

Tout d'abord, modifiez les informations de `routes` et `clusters`.  
Ajoutez respectivement `echo-route` et `echo-cluster` afin de configurer l'envoi des requêtes vers le serveur d'écho.  
Ensuite, modifiez le code pour lire l'adresse du serveur d'écho à partir des variables d'environnement.

La règle de cette adresse est `services__{service-name}__http__{index}`.  
Dans le cas du serveur d'écho, le nom du service est `echo-server` et il s'agit d'une instance unique ; on utilise donc l'index `0`.  
Si vous ajoutez un serveur ASP.NET Core, plusieurs instances peuvent être créées via `WithReplica` ; il suffit alors d'incrémenter l'index.  
L'adresse `http://localhost:8080`, définie comme valeur de repli dans la gestion des exceptions, est une valeur sans signification.

Lancez maintenant le projet et accédez à `http://localhost:3000/?name=world` : vous devriez toujours voir s'afficher `Hello, world`.

### Idées d'extension

Nous avons maintenant ajouté un serveur Go à dotnet aspire et vérifié le transfert des requêtes via le reverse proxy.  
Il devient alors possible d'étendre ce processus afin de l'implémenter de manière programmatique.  
Par exemple, en ajoutant une numérotation après le nom du service pour le serveur d'écho, on peut créer plusieurs instances et ajouter automatiquement la configuration correspondante au reverse proxy.

Modifiez le fichier Program.cs du projet AppHost d'Aspire, qui utilise le reverse proxy et le serveur d'écho, comme suit.

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

Modifiez ensuite le fichier Program.cs du projet reverse proxy comme suit.

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

Nous ajoutons la configuration des destinations pour les 8 instances du serveur d'écho.  
Le reverse proxy dispose désormais des informations de destination de chacun des serveurs d'écho et peut transmettre les requêtes en conséquence.  
Accédez à nouveau à `http://localhost:3000/?name=world` : vous devriez toujours voir s'afficher `Hello, world`.

## Conclusion

Dans cet article, nous avons décrit le processus consistant à ajouter un serveur Go à dotnet aspire puis à transmettre les requêtes via un reverse proxy.  
Toutefois, l'extension n'a pas encore été entièrement rédigée ; un exemple plus programmatique, reposant sur des variables d'environnement, a été rédigé dans un dépôt séparé.  
Pour la configuration détaillée du projet et le code, veuillez consulter [snowmerak/AspireStartPack](https://github.com/snowmerak/AspireStarterPack).

Personnellement, je pense que dotnet aspire peut jouer son propre rôle, à la fois comme alternative à docker compose et comme outil de déploiement cloud.  
Des [générateurs](https://prom3theu5.github.io/aspirational-manifests/generate-command.html) produisant déjà des manifestes docker compose ou Kubernetes existent, et je pense que l'accès aux outils d'infrastructure est ainsi devenu plus aisé pour les développeurs ordinaires.
