dotnet aspire içinde ölçeklenebilir bir şekilde Go sunucusu çalıştırmayı deneme
dotnet aspire?
dotnet aspire, bulut tabanlı ortamların artmasıyla birlikte, geliştiricilerin bulut tabanlı geliştirme ve yapılandırmalarına 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 hizmetleri veya container'ları kolayca dağıtmasını sağlar.
Elbette docker'dan k8s'ye kadar yayınlanır ve işletilir; mevcut şirket içi (on-premise) ortamlardan oldukça fazla sayıda sektör, endüstri ve geliştirici bulut tabanlı ortamlara geçiş yapmakta veya geçiş yapmış durumdadır. Artık olgunlaşmış bir alandır. Bu nedenle, host adları, port yapılandırması, güvenlik duvarı, metrik yönetimi gibi konularda mevcut rahatsızlıkları açıklamanın gereksiz olduğunu düşünüyorum.
Bu nedenle, yukarıdaki açıklamalardan bile dotnet aspire'ın ne olduğu konusunda bir fikir sahibi olmanız zor olabilir. Çünkü Microsoft da net bir tanım yapmamaktadır. Bu yüzden ben de ayrı bir tanım yapmayacağım. Ancak, bu yazıda anladığım kadarıyla dotnet aspire'ın temel işlevlerini kullanacağım, bu nedenle referans alarak kendi konumunuzu belirlemeniz iyi olacaktır.
Proje Yapılandırması
dotnet aspire Projesi Oluşturma
Eğer dotnet aspire şablonu yoksa, öncelikle şablonu yüklemeniz gerekir. Aşağıdaki komut ile şablonu yükleyin. Eğer .net yoksa, onu kendiniz kurmanız gerekir.
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 şablonunun projesini oluşturun.
1dotnet new aspire-apphost -o AppHost
Bu, ayarlama için sadece basit kodların bulunduğu bir aspire-apphost projesi oluşturacaktır.
Valkey Ekleme
Şimdi basitçe Valkey ekleyelim.
Hemen eklemeden önce belirtmek gerekir ki, dotnet aspire, community hosting aracılığıyla çeşitli üçüncü taraf çözümler sunmaktadır. Elbette valkey de bu community hosting desteğinden yararlanabilir ve aşağıdaki nuget paketi aracılığıyla kolayca kullanılabilir.
1dotnet add package Aspire.Hosting.Valkey
Bunun dışında çeşitli entegre hosting seçenekleri de sunmaktadır; buradan kontrol edebilirsiniz. Tekrar valkey'e dönersek, AppHost projesindeki 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 hizmetini inşa edebilecek bilgilere sahip IResourceBuilder
arayüzünün bir uygulamasıdır.WithDataVolume
, önbellek verilerini saklayacak bir birim oluşturur ve WithPersistence
, önbellek verilerinin sürekli olarak saklanmasını sağlar.
Buraya kadar bakıldığında, docker-compose
'daki volumes
ile benzer bir rol üstlendiği görülmektedir.
Elbette, bunu sizin de zorlanmadan yapabileceğiniz görülmektedir.
Ancak bu yazının kapsamını aştığı için şimdilik bahsetmeyeceğim.
Go Dilinde Echo Sunucusu Oluşturma
Şimdi basit bir Go dili sunucusu ekleyelim.
Öncelikle çözüm klasöründe go work init
komutu ile bir çalışma alanı oluşturalım.
.NET geliştiricileri için Go çalışma alanı, çözüme benzer bir şey olarak düşünülebilir.
Ardından EchoServer adında bir klasör oluşturup içine girdikten sonra go mod init EchoServer
komutunu çalıştırın.
Bu komut ile bir Go modülü oluşturulur. Modül, .NET geliştiricileri için projeye benzer bir şey olarak kabul edilebilir.
Sonra main.go
dosyasını oluşturup 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şkenini enjekte eder ve bu portu okuyarak sunucuyu çalıştırı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
Tekrar Valkey'i eklediğimiz Aspire AppHost projesine dönerek Go dili için community hosting'i ekleyelim.
1dotnet add package CommunityToolkit.Aspire.Hosting.Golang
Ardından Program.cs dosyasını açarak aşağıdaki ifadeleri 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 inşa edebilecek bilgilere sahip IResourceBuilder
arayüzünün bir uygulamasıdır.
Az önce eklediğimiz AddGolangApp
metodu, Go dili sunucusu eklemek için özel bir host'un genişletme metodudur.
Sabit olarak 3000 portunu kullanır ve PORT
ortam değişkenini enjekte ettiğini görebilirsiniz.
Son olarak, WithExternalHttpEndpoints
, dışarıdan erişilebilir olmasını sağlar.
Test için http://localhost:3000/?name=world
adresine eriştiğinizde, Hello, world
çıktısını göreceksiniz.
Ancak şu anda dotnet aspire'da, dotnet harici projelere uygulanan ağır bir ceza bulunmaktadır. O da şudur...
Proje Genişletme
Peki ya Yatay Ölçeklendirme Nasıl Yapılır?
Şu anda dotnet aspire, AddProject
metoduyla eklenen .NET projelerinin builder'ları için yalnızca WithReplica
seçeneğini sunmaktadır.
Ancak Go dili host veya AddContainer
gibi harici projeler için bu seçeneği sunmamaktadır.
Bu nedenle, ayrı bir load balancer veya reverse proxy kullanarak doğrudan uygulamanız gerekir.
Ancak bu durumda, söz konusu reverse proxy bir SPOF (Single Point of Failure) olabileceği için, reverse proxy'nin WithReplica
seçeneği sunması daha iyi olacaktır.
Bu durumda, reverse proxy'nin zorunlu olarak bir .NET projesi olması gerekmektedir.
Şimdiye kadar bu sorun için nginx, trafik, doğrudan uygulama gibi yöntemler kullanılmış olsa da, .NET projesi gibi bir sınırlama getirildiğinde, şu anda benim elimde bir çözüm yoktu. Bu nedenle, .NET ile uygulanmış bir reverse proxy aradım ve şans eseri YARP adında bir seçenek buldum. YARP, .NET ile uygulanmış bir reverse proxy olup, load balancer görevi de görebiliyor ve çeşitli özellikler sunduğu için iyi bir seçenek olduğuna karar verdim.
O zaman şimdi YARP'ı ekleyelim.
YARP ile Reverse Proxy Yapılandırması
Öncelikle YARP'ı kullanmak için bir proje oluşturalım.
1dotnet new web -n ReverseProxy
Ardından projeye girerek YARP'ı yükleyin.
1dotnet add package Yarp.ReverseProxy --version 2.2.0
Kurulum bittikten sonra, Program.cs dosyasını açarak 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ğı rota bilgilerini, clusters
ise reverse proxy'nin kullanacağı cluster bilgilerini içerir.
Bu bilgiler, LoadFromMemory
metoduyla 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 ekleyip Program.cs dosyasına aşağıdaki ifadeleri 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();
Şimdi reverse proxy, echo sunucusuna referans verebilir. Dışarıdan gelen istekler reverse proxy'den alınacak ve echo sunucusuna yönlendirilecek şekilde yapı değiştirilmektedir.
Reverse Proxy'yi Düzenleme
Öncelikle reverse proxy'ye atanan projenin dinleme adresini değiştirmemiz gerekiyor.Properties/launchSettings.json
dosyası içindeki applicationUrl
'yi kaldırın.
Ardından Program.cs dosyasını açarak 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
ile ilgili bilgileri düzenleyin.
Her birine echo-route
ve echo-cluster
ekleyerek, isteği echo sunucusuna gönderecek şekilde ayarlayın.
Ayrıca echo sunucusunun adresini ortam değişkeninden okuyarak kullanacak şekilde düzenleyin.
Bu adresin kuralı services__{service-name}__http__{index}
şeklindedir.
echo sunucusu durumunda, servis adı echo-server
olup, tek örnek olduğu için indeks olarak 0
kullanılır.
Eğer asp .net core sunucusu eklerseniz, WithReplica
aracılığıyla birden fazla örnek oluşturulabileceği için indeksi artırarak kullanabilirsiniz.
İstisna olarak ele alınan http://localhost:8080
anlamsız bir çöp değeridir.
Şimdi projeyi çalıştırıp, http://localhost:3000/?name=world
adresine eriştiğinizde, yine Hello, world
çıktısını göreceksiniz.
Genişleme Fikri
Artık dotnet aspire'a Go sunucusu ekleyip, reverse proxy aracılığıyla istekleri yönlendirdiğimizi doğruladık. O zaman şimdi bu süreci programatik olarak uygulayabilecek ş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 ayarlarını otomatik olarak ekleyebilirsiniz.
aspire apphost projesinin Program.cs dosyasında reverse proxy ve echo sunucusunu kullanan kodu aşağıdaki gibi düzenleyin.
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 düzenleyin.
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 sunucu örneği için hedef ayarlarını ekliyoruz.
Artık reverse proxy, artan echo sunucularına ilişkin hedef bilgilerine sahip ve istekleri iletebilecek duruma geldi.
Mevcut http://localhost:3000/?name=world
adresine eriştiğinizde, yine Hello, world
çıktısını göreceksiniz.
Sonuç
Bu yazıda, dotnet aspire'a Go sunucusu ekleme ve reverse proxy aracılığıyla istekleri iletme sürecini açıkladık. Ancak genişleme konusunda henüz tamamını yazmadım ve ortam değişkenleri aracılığıyla daha programatik bir şekilde uygulanabilecek bir örneği ayrı bir repoda hazırladım. Ayrıntılı proje yapılandırması ve kod için snowmerak/AspireStartPack adresine bakabilirsiniz.
Ben şahsen dotnet aspire'ın docker compose'a bir alternatif olarak ve bir bulut dağıtım aracı olarak kendine özgü bir rol oynayabileceğini umuyorum. Zaten docker compose veya k8s manifest'i oluşturan bir jeneratör bulunduğu için, genel geliştiricinin altyapı araçlarına erişiminin daha iyi hale geldiğini düşünüyorum.