Exécuter un serveur Go de manière extensible avec dotnet aspire
dotnet aspire?
dotnet aspire est un outil conçu pour aider les développeurs dans le développement et la configuration d'applications cloud-native, en réponse à l'augmentation des environnements cloud-native. Cet outil permet aux développeurs .NET de déployer facilement des projets .NET, diverses infrastructures cloud-native, ainsi que des services ou des conteneurs écrits dans d'autres langages.
Il est évident que, depuis le lancement et l'exploitation de Docker jusqu'à Kubernetes, un nombre considérable de secteurs, d'industries et de développeurs sont passés ou sont en train de passer des environnements on-premise traditionnels aux environnements cloud-native. C'est désormais un domaine mature. Par conséquent, je pense qu'il n'est pas nécessaire d'expliquer les inconvénients antérieurs liés à la gestion des noms d'hôte, des configurations de ports, des pare-feu et des métriques.
Ainsi, même avec les explications ci-dessus, il est fort probable que vous n'ayez aucune idée de ce qu'est dotnet aspire. En effet, même Microsoft ne lui a pas donné de définition précise. Je ne donnerai 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, ce qui vous permettra de vous situer en conséquence.
Configuration du projet
Création d'un projet dotnet aspire
Si vous ne disposez pas du modèle dotnet aspire, vous devez d'abord l'installer. Installez le modèle avec la commande suivante. Si vous n'avez pas .NET, veuillez l'installer vous-même.
1dotnet new install Aspire.ProjectTemplates
Ensuite, créez une nouvelle solution dans un dossier approprié.
1dotnet new sln
Après cela, exécutez la commande suivante dans le dossier de la solution pour créer un projet à partir du modèle aspire-apphost.
1dotnet new aspire-apphost -o AppHost
Cela créera un projet aspire-apphost contenant uniquement un code simple pour la configuration.
Ajout de Valkey
Nous allons maintenant ajouter Valkey simplement.
Avant de l'ajouter directement, dotnet aspire propose diverses solutions tierces via le community hosting. Bien entendu, Valkey peut également bénéficier de ce support de community hosting, et peut être facilement utilisé via le package NuGet suivant.
1dotnet add package Aspire.Hosting.Valkey
Divers autres hébergements intégrés sont également disponibles, comme vous pouvez le vérifier ici. Pour revenir à Valkey, ouvrez le fichier Program.cs dans le projet AppHost et modifiez-le comme suit.
1var builder = DistributedApplication.CreateBuilder(args);
2
3var cache = builder.AddValkey("cache")
4 .WithDataVolume(isReadOnly: false)
5 .WithPersistence(interval: TimeSpan.FromMinutes(5),
6 keysChangedThreshold: 100);
7
8builder.Build().Run();
cache est une implémentation de l'interface IResourceBuilder qui contient les informations nécessaires à la construction du service Valkey.WithDataVolume crée un volume pour stocker les données du cache, et WithPersistence permet de stocker les données du cache de manière persistante.
Jusqu'ici, cela semble jouer un rôle similaire à celui des volumes de docker-compose.
Naturellement, vous pouvez facilement les créer vous-même.
Cependant, cela dépasse le cadre de cet article et je n'en parlerai donc pas maintenant.
Création d'un serveur Echo en Go
Nous allons maintenant ajouter un simple serveur Go.
Tout d'abord, créez un workspace dans le dossier de la solution via go work init.
Pour les développeurs .NET, un workspace Go est 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 les développeurs .NET, un module est similaire à un projet.
Ensuite, créez un fichier main.go et écrivez-le comme suit.
1package main
2
3import (
4 "log"
5 "net/http"
6 "os"
7)
8
9func main() {
10 addr := os.Getenv("PORT")
11 log.Printf("Server started on %s", addr)
12
13 http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
14 name := request.URL.Query().Get("name")
15 writer.Write([]byte("Hello, " + name))
16 })
17
18 http.ListenAndServe(":"+addr, nil)
19}
Lorsque Aspire AppHost est exécuté, ce serveur reçoit la variable d'environnement PORT à écouter, lit ce port et démarre le serveur.
C'est un serveur simple qui prend une requête name et renvoie Hello, {name}.
Nous allons maintenant ajouter ce serveur à dotnet aspire.
Ajout du serveur Echo à Aspire
Retournez au projet Aspire AppHost où vous avez ajouté Valkey et ajoutez le community hosting pour le langage Go.
1dotnet add package CommunityToolkit.Aspire.Hosting.Golang
Ensuite, ouvrez le fichier Program.cs et ajoutez les lignes suivantes.
1var builder = DistributedApplication.CreateBuilder(args);
2
3var cache = builder.AddValkey("cache")
4 .WithDataVolume(isReadOnly: false)
5 .WithPersistence(interval: TimeSpan.FromMinutes(5),
6 keysChangedThreshold: 100);
7
8var echoServer = builder.AddGolangApp("echo-server", "../EchoServer")
9 .WithHttpEndpoint(port: 3000, env: "PORT")
10 .WithExternalHttpEndpoints();
11
12builder.Build().Run();
Ici, echoServer est une implémentation de l'interface IResourceBuilder qui contient les informations nécessaires à la construction du serveur Go.
La méthode AddGolangApp que nous venons d'ajouter est une méthode d'extension de l'hôte personnalisé pour ajouter un serveur Go.
On peut voir qu'elle utilise un port fixe de 3000 et injecte la variable d'environnement PORT.
Enfin, WithExternalHttpEndpoints permet un accès externe.
Pour tester, si vous accédez à http://localhost:3000/?name=world, vous devriez voir Hello, world s'afficher.
Cependant, actuellement, dotnet aspire impose une pénalité importante aux projets non-.NET. C'est...
Extension de projet
Comment gérer la mise à l'échelle horizontale ?
Actuellement, dotnet aspire ne propose l'option WithReplica que pour les builders de projets .NET ajoutés via la méthode AddProject.
Cependant, cette option n'est pas disponible pour les hôtes Go ou les projets externes comme AddContainer.
Par conséquent, il est nécessaire de l'implémenter soi-même en utilisant un équilibreur de charge ou un reverse proxy distinct.
Cependant, cela peut faire du reverse proxy un SPOF (Single Point of Failure), il est donc préférable que le reverse proxy offre l'option WithReplica.
Dans ce cas, le reverse proxy doit nécessairement être un projet .NET.
Jusqu'à présent, j'ai utilisé des méthodes telles que nginx, trafik ou des implémentations directes pour résoudre ce problème, mais avec la contrainte d'un projet .NET, je n'avais pas de solution immédiate. J'ai donc cherché un reverse proxy implémenté en .NET et, heureusement, j'ai trouvé l'option YARP. YARP est un reverse proxy implémenté en .NET qui peut également servir d'équilibreur de charge et offre diverses fonctionnalités, ce qui en fait un excellent choix.
Nous allons maintenant ajouter YARP.
Configuration du reverse proxy avec YARP
Tout d'abord, créez un projet pour utiliser YARP.
1dotnet new web -n ReverseProxy
Ensuite, accédez au projet et installez YARP.
1dotnet add package Yarp.ReverseProxy --version 2.2.0
Une fois l'installation terminée, ouvrez le fichier Program.cs et écrivez-le comme suit.
1using Yarp.ReverseProxy.Configuration;
2
3var builder = WebApplication.CreateBuilder(args);
4
5var routes = new List<RouteConfig>();
6var clusters = new List<ClusterConfig>();
7
8builder.Services.AddReverseProxy()
9 .LoadFromMemory(routes, clusters);
10
11var app = builder.Build();
12
13app.MapReverseProxy();
14app.Run(url: $"http://0.0.0.0:{Environment.GetEnvironmentVariable("PORT") ?? "5000"}");
Ce code est le code de base pour utiliser YARP.routes contient les informations de routage que le reverse proxy utilisera, et clusters contient les informations de cluster que le reverse proxy utilisera.
Ces informations sont chargées dans le reverse proxy via la méthode LoadFromMemory.
Enfin, la méthode MapReverseProxy est utilisée pour mapper et exécuter le reverse proxy.
Pour une utilisation réelle, ajoutez une référence au projet reverse proxy dans le projet aspire apphost, puis ajoutez et modifiez les lignes suivantes dans le fichier Program.cs.
1dotnet add reference ../ReverseProxy
1var echoServer = builder.AddGolangApp("echo-server", "../EchoServer")
2 .WithHttpEndpoint(env: "PORT");
3
4var reverseProxy = builder.AddProject<Projects.ReverseProxy>("gateway")
5 .WithReference(echoServer)
6 .WithHttpEndpoint(port: 3000, env: "PORT", isProxied: true)
7 .WithExternalHttpEndpoints();
Maintenant, le reverse proxy peut référencer le serveur echo. La structure est en train de changer : les requêtes entrantes de l'extérieur sont reçues par le reverse proxy et transmises au serveur echo.
Modification du Reverse Proxy
Il faut d'abord modifier l'adresse d'écoute du projet alloué au reverse proxy.
Supprimez applicationUrl à l'intérieur du fichier Properties/launchSettings.json.
Ensuite, ouvrez le fichier Program.cs et modifiez-le considérablement comme suit.
1using Yarp.ReverseProxy.Configuration;
2
3var builder = WebApplication.CreateBuilder(args);
4
5var routes = new List<RouteConfig>
6{
7 new RouteConfig
8 {
9 ClusterId = "cluster-echo",
10 RouteId = "route-echo",
11 Match = new RouteMatch
12 {
13 Path = "/"
14 }
15 }
16};
17
18var echoServerAddr = Environment.GetEnvironmentVariable("services__echo-server__http__0") ?? "http://localhost:8080";
19
20var clusters = new List<ClusterConfig>
21{
22 new ClusterConfig
23 {
24 ClusterId = "cluster-echo",
25 Destinations = new Dictionary<string, DestinationConfig>
26 {
27 { "destination-echo", new DestinationConfig { Address = echoServerAddr } }
28 }
29 }
30};
31
32builder.Services.AddReverseProxy()
33 .LoadFromMemory(routes, clusters);
34
35var app = builder.Build();
36
37app.MapReverseProxy();
38app.Run(url: $"http://0.0.0.0:{Environment.GetEnvironmentVariable("PORT") ?? "5000"}");
Tout d'abord, les informations relatives aux routes et aux clusters sont modifiées.echo-route et echo-cluster sont ajoutés respectivement pour acheminer les requêtes vers le serveur echo.
L'adresse du serveur echo est également modifiée pour être lue à partir d'une variable d'environnement.
La règle pour cette adresse est services__{service-name}__http__{index}.
Dans le cas du serveur echo, le nom du service est echo-server, et comme il s'agit d'une instance unique, l'index 0 est utilisé.
Si un serveur ASP.NET Core est ajouté, plusieurs instances peuvent être créées via WithReplica, de sorte que l'index peut être incrémenté.
La valeur http://localhost:8080 traitée comme une exception est une valeur inutile sans signification.
Maintenant, exécutez le projet et accédez à http://localhost:3000/?name=world. Vous devriez toujours voir Hello, world affiché.
Idée d'extension
Nous avons maintenant ajouté un serveur Go à dotnet aspire et confirmé que les requêtes sont transmises via un reverse proxy. Nous pourrions maintenant étendre ce processus pour l'implémenter de manière programmatique. Par exemple, pour le serveur echo, nous pourrions ajouter une numérotation après le nom du service pour créer plusieurs instances et ajouter automatiquement la configuration pour le reverse proxy.
Modifiez le code utilisant le reverse proxy et le serveur echo dans le fichier Program.cs du projet aspire apphost comme suit.
1var reverseProxy = builder.AddProject<Projects.ReverseProxy>("gateway")
2 .WithHttpEndpoint(port: 3000, env: "PORT", isProxied: true)
3 .WithExternalHttpEndpoints();
4
5for (var i = 0; i < 8; i++)
6{
7 var echoServer = builder.AddGolangApp($"echo-server-{i}", "../EchoServer")
8 .WithHttpEndpoint(env: "PORT");
9 reverseProxy.WithReference(echoServer);
10}
Et modifiez le fichier Program.cs du projet reverse proxy comme suit.
1var echoServerDestinations = new Dictionary<string, DestinationConfig>();
2for (var i = 0; i < 8; i++)
3{
4 echoServerDestinations[$"destination-{i}"] = new DestinationConfig
5 {
6 Address = Environment.GetEnvironmentVariable($"services__echo-server-{i}__http__0") ?? "http://localhost:8080"
7 };
8}
9
10var clusters = new List<ClusterConfig>
11{
12 new ClusterConfig
13 {
14 ClusterId = "cluster-echo",
15 Destinations = echoServerDestinations
16 }
17};
Ajoute les paramètres de destination pour les 8 instances de serveur echo qui ont été augmentées.
Maintenant, le reverse proxy dispose des informations de destination pour les serveurs echo étendus et peut transmettre les requêtes.
Si vous accédez à l'adresse http://localhost:3000/?name=world comme auparavant, vous devriez toujours voir Hello, world affiché.
Conclusion
Cet article a décrit le processus d'ajout d'un serveur Go à dotnet aspire et de transmission des requêtes via un reverse proxy. Cependant, l'extension n'a pas encore été entièrement rédigée, et un exemple d'implémentation plus programmatique via des variables d'environnement a été préparé dans un référentiel séparé. Pour plus de détails sur la configuration du projet et le code, veuillez consulter snowmerak/AspireStartPack.
Personnellement, j'attends de dotnet aspire qu'il joue son propre rôle en tant qu'alternative à docker compose et en tant qu'outil de déploiement cloud. L'existence de générateurs qui créent déjà des manifestes docker compose ou k8s, a, je pense, amélioré l'accessibilité des outils d'infrastructure pour les développeurs ordinaires.