GoSuda

Dotnet Aspiressa skaalautuvan Go-palvelimen suorittaminen

By snowmerak views ...

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.

1dotnet new install Aspire.ProjectTemplates

Luo sen jälkeen sopivaan kansioon uusi ratkaisu.

1dotnet new sln

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

1dotnet 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.

1dotnet add package Aspire.Hosting.Valkey

Muita monipuolisia integroituja hosting-ratkaisuja on tarjolla, ja niitä voi tarkastella täällä.
Palataan nyt Valkeyyn. Avaa AppHost-projektin Program.cs-tiedosto ja muokkaa sitä seuraavasti.

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 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.

 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}

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.

1dotnet add package CommunityToolkit.Aspire.Hosting.Golang

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

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

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.
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.

1dotnet new web -n ReverseProxy

Siirry sitten projektiin ja asenna YARP.

1dotnet add package Yarp.ReverseProxy --version 2.2.0

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

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

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.

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

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.

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

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.

 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}

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

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

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.

Itse uskon, että dotnet aspire voi toimia Docker Composen vaihtoehtona sekä pilvijulkaisutyökaluna omalla tavallaan.
Koska jo on olemassa generaattoreita, jotka luovat docker compose -tiedostoja ja k8s-manifesteja, uskon, että tavallisten kehittäjien pääsy infrastruktuurityökaluihin on helpottunut.