dotnet Aspire'da Go Sunucusunu Genişletilebilir Şekilde Çalıştırma
dotnet aspire?
dotnet aspire, bulut tabanlı ortamların artmasıyla birlikte, geliştiricilerin bulut tabanlı geliştirme ve yapılandırma süreçlerine yardımcı olmak amacıyla oluşturulmuş bir araçtır. Bu araç, .NET geliştiricilerinin .NET projelerini ve çeşitli bulut tabanlı altyapıları, ayrıca diğer dillerdeki servisleri veya container'ları kolayca dağıtabilmelerini sağlamaktadır.
Doğal olarak Docker'dan Kubernetes'e kadar piyasaya sürülüp işletilmekte ve mevcut on-premise ortamlardan oldukça fazla sayıda sektör, endüstri ve geliştirici bulut tabanlı ortamlara geçiş yapmış veya geçiş halindedir. Artık olgunlaşmış bir alandır. Bu nedenle, hostname, port yapılandırması, güvenlik duvarı, metric yönetimi gibi konularda mevcut zorlukları açıklamaya gerek olmadığını düşünüyorum.
Bu nedenle, yukarıdaki açıklamalardan yola çıkarak bile dotnet aspire'ın ne olduğu konusunda bir fikir edinmekte zorlanacaksınız. Çünkü Microsoft da buna kesin bir tanım getirmiyor. Bu yüzden ben de özel bir tanım yapmayacağım. Ancak, bu yazıda benim anladığım dotnet aspire'ın temel özelliklerini kullanacağım, bu nedenle kendi konumunuzu belirlemek için bunu referans alabilirsiniz.
Proje Yapılandırması
dotnet aspire projesi oluşturma
Eğer dotnet aspire şablonunuz yoksa, öncelikle şablonu yüklemeniz gerekmektedir. Şablonu aşağıdaki komutla yükleyin. Eğer .NET yüklü değilse, lütfen kendiniz yükleyin.
1dotnet new install Aspire.ProjectTemplates
Ardından, uygun bir klasörde yeni bir çözüm oluşturun.
1dotnet new sln
Daha sonra, çözüm klasöründe aşağıdaki komutu çalıştırarak aspire-apphost şablonundan bir proje oluşturun.
1dotnet new aspire-apphost -o AppHost
Böylece, sadece ayarlar için basit kodlar içeren bir aspire-apphost projesi oluşturulacaktır.
Valkey Ekleme
Şimdi kısaca Valkey'i ekleyelim.
Eklemeye başlamadan önce, dotnet aspire, community hosting aracılığıyla çeşitli üçüncü taraf çözümler sunar. Elbette Valkey de bu community hosting desteğinden faydalanabilir ve aşağıdaki nuget paketi aracılığıyla kolayca kullanılabilir.
1dotnet add package Aspire.Hosting.Valkey
Diğer çeşitli entegre hosting seçeneklerini buradan inceleyebilirsiniz. Tekrar Valkey'e dönecek olursak, AppHost projesinde Program.cs dosyasını açıp aşağıdaki gibi düzenleyin.
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, Valkey servisini inşa edebilecek bilgilere sahip bir IResourceBuilder arayüzünün bir implementasyonudur.WithDataVolume, cache verilerini depolamak için bir volume oluştururken, WithPersistence cache verilerinin sürekli olarak depolanmasını sağlar.
Buraya kadar bakıldığında, docker-compose'un volumes'una benzer bir rol üstlendiği görülmektedir.
Elbette, bunları siz de zorlanmadan oluşturabilirsiniz.
Ancak bu, bu yazının kapsamını aştığı için şimdi bu konuyu ele almayacağım.
Go Dili Echo Sunucusu Oluşturma
Şimdi basit bir Go dili sunucusu ekleyelim.
Öncelikle çözüm klasöründe go work init komutu ile bir workspace oluşturalım.
.NET geliştiricileri için Go workspace'i, solution'a benzer bir yapıdır.
Ardından EchoServer adında bir klasör oluşturun, içine girin ve go mod init EchoServer komutunu çalıştırın.
Bu komutla bir Go modülü oluşturulur. Modül, .NET geliştiricileri için proje ile benzer bir yapıdır.
Sonra main.go dosyasını oluşturun ve aşağıdaki gibi yazın.
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}
Bu sunucu, Aspire AppHost çalıştırıldığında, dinlemesi gereken PORT ortam değişkeni enjekte edildiğinde, bu portu okuyarak sunucuyu başlatır.
Basitçe name sorgusunu alıp Hello, {name} döndüren bir sunucudur.
Şimdi bu sunucuyu dotnet aspire'a ekleyelim.
Echo sunucusunu aspire'a ekleme
Valkey'i eklediğimiz Aspire AppHost projesine geri dönerek Go dili için community hosting'i ekleyelim.
1dotnet add package CommunityToolkit.Aspire.Hosting.Golang
Ardından Program.cs dosyasını açıp aşağıdaki kodu ekleyin.
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();
Burada echoServer, Go dili sunucusunu derleyebilecek bilgilere sahip bir IResourceBuilder arayüzünün bir implementasyonudur.
Az önce eklediğimiz AddGolangApp metodu, Go dili sunucusunu eklemek için özel bir hostun genişletme metodudur.
Sabit olarak 3000 portunu kullandığını ve PORT ortam değişkeninin enjekte edildiğini görebilirsiniz.
Son olarak WithExternalHttpEndpoints ise dışarıdan erişilebilir olmasını sağlar.
Test etmek için http://localhost:3000/?name=world adresine eriştiğinizde, Hello, world çıktısını göreceksiniz.
Ancak, şu anda dotnet aspire'da non-dotnet projelere uygulanan ağır bir ceza bulunmaktadır. Bu ceza ise...
Proje Genişletme
Peki ya yatay ölçekleme nasıl yapılır?
Şu anda dotnet aspire, yalnızca AddProject metoduyla eklenen .NET projeleri için WithReplica seçeneğini sunmaktadır.
Ancak Go dili hostları veya AddContainer gibi harici projelere bu seçeneği sunmamaktadır.
Bu nedenle, ayrı bir load balancer veya reverse proxy kullanarak doğrudan implementasyon yapılması gerekmektedir.
Ancak bu durumda, söz konusu reverse proxy bir SPOF (Single Point of Failure) haline gelebileceğinden, reverse proxy'nin WithReplica seçeneğini sunması daha iyi olacaktır.
Bu da kaçınılmaz olarak reverse proxy'nin bir .NET projesi olması gerektiği anlamına gelir.
Şimdiye kadar bu tür sorunlar için Nginx, Traefik veya doğrudan implementasyon gibi yöntemler kullandık, ancak .NET projesi kısıtlaması olduğunda elimde hemen bir çözüm yoktu. Bu nedenle, .NET ile implemente edilmiş bir reverse proxy aradım ve neyse ki YARP adında bir seçenek buldum. YARP, .NET ile implemente edilmiş bir reverse proxy olup, load balancer rolünü de üstlenebilir ve çeşitli özellikler sunması nedeniyle iyi bir seçenek olduğuna karar verdim.
Şimdi YARP'ı ekleyelim.
YARP ile reverse proxy yapılandırma
Öncelikle YARP kullanmak için bir proje oluşturun.
1dotnet new web -n ReverseProxy
Ardından projeye gidin ve YARP'ı yükleyin.
1dotnet add package Yarp.ReverseProxy --version 2.2.0
Kurulum tamamlandığında, Program.cs dosyasını açın ve aşağıdaki gibi yazın.
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"}");
Bu kod, YARP'ı kullanmak için temel bir koddur.routes, reverse proxy'nin kullanacağı route bilgilerini, clusters ise reverse proxy'nin kullanacağı cluster bilgilerini içerir.
Bu bilgiler LoadFromMemory metodu ile reverse proxy'ye yüklenir.
Son olarak, MapReverseProxy metodu kullanılarak reverse proxy eşleştirilir ve çalıştırılır.
Ve gerçek kullanım için aspire apphost projesinde reverse proxy projesini referans olarak ekleyin ve Program.cs dosyasına aşağıdaki kodu ekleyip düzenleyin.
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();
Artık reverse proxy, echo sunucusuna başvurabilir. Dışarıdan gelen istekler reverse proxy tarafından alınıp echo sunucusuna iletilen bir yapıya dönüşmektedir.
Reverse proxy düzenleme
Öncelikle reverse proxy'ye atanan projenin listening adresini değiştirmemiz gerekiyor.Properties/launchSettings.json dosyası içindeki applicationUrl'u kaldırın.
Ardından Program.cs dosyasını açın ve aşağıdaki gibi kapsamlı bir şekilde düzenleyin.
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"}");
Öncelikle routes ve clusters hakkındaki bilgileri düzenleyelim.
Her ikisine de echo-route ve echo-cluster ekleyerek isteklerin echo sunucusuna gönderilmesini sağlıyoruz.
Ve echo sunucusunun adresini ortam değişkeninden okuyarak kullanacak şekilde düzenliyoruz.
Bu adresin kuralı services__{service-name}__http__{index} şeklindedir.
Echo sunucusu için, servis adı echo-server ve tek bir örnek olduğu için indeks olarak 0 kullanılmıştır.
Eğer bir ASP.NET Core sunucusu eklenirse, WithReplica aracılığıyla birden fazla örnek oluşturulabileceğinden indeks artırılarak kullanılabilir.
Özel durum olarak işlenen http://localhost:8080 hiçbir anlamı olmayan bir çöp değerdir.
Şimdi projeyi çalıştırın ve http://localhost:3000/?name=world adresine eriştiğinizde, hala Hello, world çıktısını göreceksiniz.
Genişletme Fikirleri
Artık dotnet aspire'a Go sunucusu ekleyip, reverse proxy aracılığıyla istekleri iletmeyi gördük. Şimdi bu süreci programatik olarak implemente edebilecek şekilde genişletebiliriz. Örneğin, echo sunucusu için servis adının sonuna numaralandırma ekleyerek birden fazla örnek oluşturabilir ve reverse proxy için ayarları otomatik olarak ekleyebiliriz.
Aspire apphost projesindeki Program.cs dosyasında reverse proxy ve echo sunucusunu kullanan kodu aşağıdaki gibi değiştirin.
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}
Ve reverse proxy projesinin Program.cs dosyasını aşağıdaki gibi değiştirin.
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};
8'e çıkarılan echo sunucusu örnekleri için hedef ayarları eklenmiştir.
Artık reverse proxy, artan echo sunucuları hakkındaki hedef bilgilerine sahip olup, istekleri iletebilecektir.
Mevcut http://localhost:3000/?name=world adresine eriştiğinizde, hala Hello, world çıktısını göreceksiniz.
Sonuç
Bu yazıda, dotnet aspire'a Go sunucusu ekleme ve istekleri reverse proxy aracılığıyla iletme sürecini açıkladım. Ancak genişletme konusunda henüz her şeyi yazmadım ve ortam değişkenleri aracılığıyla daha programatik bir şekilde implemente edilebilecek örnekleri ayrı bir depoda hazırladım. Ayrıntılı proje yapılandırması ve kod için lütfen snowmerak/AspireStartPack adresine bakın.
Şahsen ben dotnet aspire'ın docker compose'a bir alternatif olarak ve bulut dağıtım aracı olarak kendi rolünü üstlenebileceğini umuyorum. Zaten docker compose veya k8s manifestleri oluşturan bir jeneratör mevcut olduğundan, genel bir geliştiricinin altyapı araçlarına erişilebilirliğinin daha iyi hale geldiğini düşünüyorum.