Exécuter un serveur Go évolutif avec dotnet aspire
dotnet aspire ?
dotnet aspire est un outil créé pour aider les développeurs dans le développement et la configuration d'applications natives du cloud, en réponse à la croissance des environnements natifs du cloud. Cet outil permet aux développeurs .NET de déployer facilement des projets .NET, diverses infrastructures natives du cloud, et des services ou conteneurs écrits dans d'autres langages.
Naturellement, il est déployé et exploité de docker à k8s. De nombreux secteurs, industries et développeurs migrent et ont migré vers des environnements natifs du cloud depuis des environnements sur site traditionnels. Il s'agit désormais d'un domaine mature. Par conséquent, je pense qu'il est inutile d'expliquer les inconvénients traditionnels liés au nom d'hôte, à la configuration des ports, au pare-feu et à la gestion des métriques.
De ce fait, même avec les explications ci-dessus, vous n'aurez probablement aucune idée de ce qu'est dotnet aspire. En effet, Microsoft lui-même n'en donne pas une définition précise. Je n'en donnerai donc pas non plus. Cependant, dans cet article, j'utiliserai les fonctionnalités de base de dotnet aspire telles que je les comprends, vous permettant ainsi de vous situer par rapport à cet outil.
Structure du projet
Création d'un projet dotnet aspire
Si vous n'avez pas le modèle dotnet aspire, vous devez d'abord l'installer. Utilisez la commande suivante pour installer le modèle. Si vous n'avez pas .NET, installez-le vous-même.
1dotnet new install Aspire.ProjectTemplates
Puis, créez une nouvelle solution dans un dossier approprié.
1dotnet new sln
Ensuite, dans le dossier de la solution, exécutez la commande suivante pour créer le projet à partir du modèle aspire-apphost.
1dotnet new aspire-apphost -o AppHost
Un projet aspire-apphost contenant uniquement un code simple pour la configuration sera alors créé.
Ajout de Valkey
Ajoutons simplement Valkey.
Avant de l'ajouter, notez que dotnet aspire fournit diverses solutions tierces via l'hébergement communautaire. Naturellement, Valkey peut bénéficier de ce support d'hébergement communautaire et peut être facilement utilisé via le package NuGet suivant.
1dotnet add package Aspire.Hosting.Valkey
D'autres intégrations d'hébergement sont disponibles. Vous pouvez les consulter ici : ici. Revenons à Valkey. Ouvrez le fichier Program.cs du 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
contenant les informations nécessaires pour construire le service Valkey.WithDataVolume
crée un volume pour stocker les données du cache, et WithPersistence
permet de sauvegarder les données du cache de manière persistante.
Cela semble similaire à la fonction volumes
de docker-compose
.
Naturellement, vous pouvez également le créer facilement.
Cependant, cela dépasse le cadre de cet article, je n'en parlerai donc pas pour le moment.
Création d'un serveur Echo en Go
Ajoutons un serveur simple en Go.
Tout d'abord, créez un espace de travail dans le dossier de la solution à l'aide de go work init
.
Pour les développeurs .NET, l'espace de travail Go est similaire à une solution.
Créez ensuite un dossier nommé EchoServer, accédez-y 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.
Créez ensuite le fichier main.go
et écrivez-y ce qui 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}
Ce serveur, lorsqu'Aspire AppHost est exécuté, lit la variable d'environnement PORT
qui doit être injectée pour indiquer le port d'écoute et exécute le serveur sur ce port.
Il s'agit d'un serveur simple qui reçoit une requête name
et renvoie Hello, {name}
.
Ajoutons maintenant ce serveur à dotnet aspire.
Ajout du serveur Echo à aspire
Retournez au projet Aspire AppHost où vous avez ajouté Valkey et ajoutez l'hébergement communautaire pour Go.
1dotnet add package CommunityToolkit.Aspire.Hosting.Golang
Ouvrez ensuite le fichier Program.cs et ajoutez le code suivant :
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
contenant les informations nécessaires pour construire le serveur Go.
La méthode AddGolangApp
que vous venez d'ajouter est une méthode d'extension de l'hôte personnalisé pour ajouter un serveur Go.
Elle utilise le port 3000 de manière fixe et injecte la variable d'environnement PORT
.
Enfin, WithExternalHttpEndpoints
permet l'accès externe.
Si vous accédez à http://localhost:3000/?name=world
pour tester, vous verrez Hello, world
s'afficher.
Cependant, dotnet aspire impose actuellement une pénalité importante aux projets non .NET. À savoir :
Extension du projet
Alors, comment faire pour l'extensibilité horizontale ?
Actuellement, dotnet aspire ne fournit l'option WithReplica
pour le constructeur que pour les projets .NET ajoutés avec la méthode AddProject
.
Cependant, cette option n'est pas fournie pour les projets externes tels que les hôtes Go ou AddContainer
.
Par conséquent, vous devez l'implémenter vous-même à l'aide d'un équilibreur de charge ou d'un proxy inverse.
Cependant, cela peut faire du proxy inverse un point de défaillance unique (SPOF), il est donc préférable que le proxy inverse fournisse l'option WithReplica
.
Par conséquent, le proxy inverse doit nécessairement être un projet .NET.
Jusqu'à présent, j'ai utilisé des méthodes telles que nginx, Traefik et une implémentation personnalisée pour résoudre ce problème, mais la restriction selon laquelle il doit s'agir d'un projet .NET m'a empêché de trouver une solution immédiate. J'ai donc cherché un proxy inverse implémenté en .NET et j'ai heureusement trouvé YARP. YARP est un proxy inverse implémenté en .NET, il peut également servir d'équilibreur de charge et offre de nombreuses fonctionnalités, ce qui en fait un bon choix.
Ajoutons maintenant YARP.
Configuration du proxy inverse avec YARP
Commencez par créer un projet pour utiliser YARP.
1dotnet new web -n ReverseProxy
Puis, 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 ce qui 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 un code de base pour utiliser YARP.routes
contient les informations de routage utilisées par le proxy inverse, et clusters
contient les informations de cluster utilisées par le proxy inverse.
Ces informations sont chargées dans le proxy inverse via la méthode LoadFromMemory
.
Enfin, le proxy inverse est mappé et exécuté à l'aide de la méthode MapReverseProxy
.
Pour une utilisation réelle, ajoutez le projet de proxy inverse comme référence dans le projet aspire apphost et ajoutez et modifiez le code suivant 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();
Le proxy inverse peut désormais référencer le serveur echo. La structure est modifiée pour que les requêtes entrantes soient reçues par le proxy inverse et transmises au serveur echo.
Modification du proxy inverse
Tout d'abord, vous devez modifier l'adresse d'écoute du projet attribué au proxy inverse.
Supprimez applicationUrl
dans le fichier Properties/launchSettings.json
.
Ouvrez ensuite 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"}");
Modifiez d'abord les informations sur routes
et clusters
.
Ajoutez echo-route
et echo-cluster
respectivement pour envoyer les requêtes au serveur echo.
Modifiez ensuite le code pour lire l'adresse du serveur echo à partir de la 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 il s'agit d'une seule instance, donc 0
est utilisé comme index.
Si vous ajoutez un serveur asp .net core, plusieurs instances peuvent être créées via WithReplica
, vous pouvez donc augmenter l'index pour l'utiliser.
La valeur http://localhost:8080
gérée par l'exception est une valeur inutile.
Exécutez maintenant le projet et accédez à http://localhost:3000/?name=world
. Vous verrez toujours Hello, world
s'afficher.
Idée d'extension
Nous avons maintenant ajouté un serveur Go à dotnet aspire et transmis les requêtes via un proxy inverse. Nous pouvons maintenant étendre cela pour implémenter le processus de manière programmatique. Par exemple, vous pouvez ajouter une numérotation après le nom du service du serveur echo pour créer plusieurs instances et ajouter automatiquement la configuration du proxy inverse.
Modifiez le code qui utilise le proxy inverse 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}
Modifiez ensuite le fichier Program.cs du projet de proxy inverse 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};
Ajoutez les paramètres de destination pour les 8 instances du serveur echo.
Le proxy inverse dispose désormais des informations de destination pour les serveurs echo supplémentaires et peut transmettre les requêtes.
Si vous accédez à http://localhost:3000/?name=world
, vous verrez toujours Hello, world
s'afficher.
Conclusion
Dans cet article, nous avons expliqué comment ajouter un serveur Go à dotnet aspire et transmettre les requêtes via un proxy inverse. Cependant, l'extension n'est pas encore entièrement écrite, et un exemple d'implémentation plus programmatique via des variables d'environnement est disponible dans un dépôt séparé. Pour plus d'informations sur la configuration du projet et le code, veuillez consulter snowmerak/AspireStartPack.
Personnellement, je pense que dotnet aspire peut jouer son propre rôle en tant qu'alternative à docker compose et outil de déploiement cloud. L'existence d'un générateur qui génère déjà des fichiers docker compose ou des manifestes k8s améliore l'accessibilité des outils d'infrastructure pour les développeurs ordinaires.