Microservice mimarileri, sistemleri bağımsız yönetilebilir parçalara ayırarak esneklik sağlasa da beraberinde ciddi bir operasyonel karmaşa getirir. Bir servisin diğerine ihtiyaç duyduğu anlarda, hedef servisin ağ üzerindeki konumunu (IP port bilgisi) bilmesi gerekir. Geleneksel monolitik yapılarda bu durum statik konfigürasyonlarla çözülebilirken, dinamik olarak ölçeklenen modern dünyada sabit IP adresleri kullanmak sürdürülebilir değildir.
Biraz daha açarsak, localhost, test, pre-prod ve prod gibi tüm ortamlarınız için appsettings.json dosyalarınız aynı değildir. Her birisinde adres bilgileri, değişkenler, ortama göre kullanacağınız veriler değişkenlik gösterebilir. Bu dosyaları düzgün şekilde yönetmeli ve dağıtmalısınız. Diğer taraftan yeni bir servis eklerken veya mevcut olanı farklı bir node üzerine taşırken bu yapılandırmalardan birini güncellemeyi unutmak runtime hatalarına yol açabilir.
Bu noktada Service Discovery yapısı, microservice uygulamalarının birbirlerini manuel olarak değil, dinamik ve akıllı bir rehber aracılığıyla bulmasını sağlayan kritik bir altyapı sunuyor. Service discovery, servislerin birbirlerinin ağ konumlarını bulmalarını sağlayan bir yapıdır. Teknik olarak iki ana model mevcuttur:
Client-Side Discovery: İstemci kayıt defterine gider, servis listesini alır ve load balancing işlemini kendi üzerinde yaparak hangi instance noktasına gideceğine karar verir.
Server-Side Discovery: İstemci bir load balancer noktasına gider, load balancer kayıt defterini sorgular ve isteği uygun yere yönlendirir.
Service Registry ve Client
Service Registry, halihazırda çalışan mikroservis örneklerinin (instance) listesini ve bunlara karşılık gelen ağ konumlarını tutan merkezi bir bileşendir. Service Registry Client ise microservice uygulamalarınızın iletişim kurmaları gereken diğer servisleri kayıt defterinden sorgulamak için tuttuğu bileşendir.
Service discovery için kullanılan çok tercih edilen iki uygulama bulunuyor:
Netflix Eureka: Netflix tarafından açık kaynak olarak sunulan REST tabanlı bir servis kayıt ve keşif sunucusudur. Microservice adres bilgilerini içeren bir defter görevi görür. Servisler arası yük dengeleme ve failover için temel veriyi sağlar.
HashiCorp Consul: Çok fonksiyonlu bir service mesh ve konfigürasyon yönetimi yapar. Sadece servis keşfi değil, aynı zamanda health checking, KV store (key-value deposu) ve güvenli servis iletişimi sağlar.
Bu makalede Eureka kullanımı üzerinden detaylandıracağım. İstemci kütüphanesi olarak Steeltoe kullanacağız. Steeltoe, .NET uygulamalarında cloud-native mimarilere, özellikle Spring Cloud ekosistemine uyum sağlamasını sağlayan açık kaynaklı bir kütüphanedir. .NET uygulamalarının Eureka, Consul gibi araçlarla konuşmasını sağlar. Mevcut Spring tabanlı bir altyapıya .NET mikroservisleri eklenirken veya yeni bir .NET cloud-native mimari geliştirilirken kullanılır.
Sistem şu 3 adımda çalışır:
Kayıt Olma (Registration): Geliştirdiğiniz bir OrderService ayağa kalktığında, Steeltoe kütüphanesi aracılığıyla Eureka’ya bir mesaj gönderir. Bu mesajda IP ve port bilgisini verir ve registry bu bilgiyi listesine ekler.
Sorgulama (Discovery): Başka bir servis, örneğin PaymentService, bir siparişi doğrulamak için OrderService’e ulaşmak istediğinde IP adresini bilmez. Registry tarafına “OrderService adresini” sorar.
Adres Alımı: Registry elindeki güncel listeden OrderService’in adresini (192.168.1.50:5001) döner. PaymentService artık nereye istek atacağını bilir.
Eğer sistem statik olsaydı PaymentService içine elle şunu yazacaktınız:
var orderServiceUrl = "http://192.168.1.50:5001";Ancak Service Registry Client kullandığında kodun şuna dönüşür:
var orderServiceUrl = "http://OrderService";Steeltoe arka planda bu ismi gerçek IP’ye çevirir. Bu sayede OrderService yarın başka bir sunucuya taşınsa ve IP adresi değişse dahi Registry Client güncel adresi otomatik olarak bulur ve senin kodunda hiçbir şeyi değiştirmen gerekmez.
Eureka Service Registry Kurulumu
Eureka’nın direkt çalıştırılabilir bir Windows uygulaması desteği yok. Eureka’yı Docker üzerinde kuracağız. Bununla container ayağa kaldırabilirsiniz:
docker run -d -p 8761:8761 --name eureka-server springcloud/eurekaEureka Server configuration dosyası application.yml. İçeriğini böyle düzenleyebilirsiniz:
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
server:
waitTimeInMsWhenSyncEmpty: 0Dilerseniz aynı configuration ile Docker üzerinde böyle çalıştırabilirsiniz:
docker run -d `
--name eureka-server `
-p 8761:8761 `
-e "EUREKA_CLIENT_REGISTERWITHEUREKA=false" `
-e "EUREKA_CLIENT_FETCHREGISTRY=false" `
springcloud/eureka-p 8761:8761: Container içindeki 8761 portunu localhost’a açar.
REGISTERWITHEUREKA=false: Eureka sunucusunun kendisini bir müşteri olarak listeye kaydetmesini engeller.
FETCHREGISTRY=false: Sunucunun başka bir sunucudan kayıt listesi çekmeye çalışmasını durdurur çünkü tekil çalışıyor.
Bununla Windows üzerinde arka planda bir Eureka sunucusu çalışmaya başlar ve localhost:8761 adresinden direkt erişebilirsiniz. Java veya Maven ile uğraşmadan böyle çalıştırabilirsiniz.
Alternatif olarak Consul uygulaması Windows için direkt .exe olarak indirilebiliyor. Kurulumu ise consul.exe dosyasını bir klasöre atın. Console’a consul agent -dev yazın. Consul direkt ayağa kalkıyor ve web üzerinden erişebilirsiniz. Steeltoe kütüphanesi hem Eureka hem de Consul ile çalıştığı için .NET tarafındaki kodunuz neredeyse aynı kalacaktır.
Bu araçlar genellikle sunucu taraflı bileşenler olduğu için kurulum sihirbazı yerine ya Docker üzerinden ya da Java/Go runtime üzerinden komutla çalıştırılacak şekilde tasarlanmıştır.

Registering Microservice
Burada bir microservice bölümünün service registry tarafına nasıl kayıt edileceğine odaklanacağız. Şimdi mevzuat servisimizin (LegalConsultantService) diğer servisler tarafından bulunabilmesi için Startup.cs sınıfına gerekli tanımlamaları yapıyoruz. Burada ConfigureServices ve Configure metodlarına birer satır ekleyeceğiz.
İlk olarak Steeltoe paketini kuralım:
dotnet add package Steeltoe.Discovery.ClientCoreŞimdi Startup.cs içerisine discovery client eklememiz gerekiyor.
using Steeltoe.Discovery.Client;
namespace LegalConsultantService
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDiscoveryClient(Configuration);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDiscoveryClient();
}
}
}Son ve en önemli adım, appsettings.json dosyasında gerekli ayarları yapmaktır. Bu dosya servisimizin Eureka tarafına hangi kimlikle kaydedileceğini belirler.
{
"spring": {
"application": {
"name": "LegalConsultantService"
}
},
"eureka": {
"client": {
"shouldRegisterWithEureka": true,
"serviceUrl": "http://localhost:8761/eureka",
"ValidateCertificates": false
},
"instance": {
"appName": "LegalConsultantService",
"hostName": "localhost",
"port": "5040"
}
}
}spring.application.name: Servisimizin sistem genelindeki resmi adıdır.
eureka.client.shouldRegisterWithEureka: Bu ayar servisimizin kendisini Eureka’ya kaydedip etmeyeceğini belirler. “true” olursa diğer servisler tarafından çağrılabilmesini sağlar.
serviceUrl: Eureka sunucusunun adresidir.
instance: Servisin Eureka’da nasıl görüneceğini belirler:
appName: Diğer servislerin bizi ararken kullanacağı isim.
hostName ve port: Servisin çalıştığı makine adı ve isteklerin dinlendiği numara.
Bu yapılandırmayla birlikte servisinizi ayağa kaldırdığınızda Eureka paneline girip (localhost:8761) kayıt olduğunu görebilirsiniz.
Neden manuel port ve host tanımlıyoruz?
Bu aslında Network Address Translation (NAT) ve Isolation kavramlarıyla açıklanabilir. Bir .NET uygulamasını Docker container içinde çalıştırdığınızda, uygulama kendini genellikle 172.17.0.x gibi dahili bir IP’de ve 80 portunda görür. Eğer uygulama Eureka’ya “Benim IP’m 172.17.0.x” derse, Docker ağının dışındaki diğer servisler bu IP’ye ulaşamaz.
Bu nedenle servislerin birbirini bulabilmesi için bu manuel gibi görünen ama aslında kontrolü geliştiriciye veren yapı, ağ hatalarını minimize etmek için tasarlanmıştır. Docker containers arasında bir bridge kurduğunuzda bu ayarlar sayesinde servislerin birbiriyle sorunsuz konuşmasını sağlarsınız.
Eureka client yapılandırması için gerekli dokümana buradan ulaşabilirsiniz:
https://steeltoe.io/docs/v4/discovery/index.html#1-2-2-eureka-client-settings

Servisler Arası İletişim
İlk adımımız, daha önce LegislationService için yaptığımız gibi, istemci tarafına Eureka client yapılandırmasını eklemektir. Artık bir servisimizi diğerinden çağırmaya hazırız. Kendi örneğimize odaklanırsak, bir dava dosyası oluşturma aşamasında LegislationService içerisinde bulunan hukuki verilere ihtiyaç duyduğumuz için, LegalCaseService üzerinden LegislationService tarafını çağıracağız.
using Microsoft.Extensions.Configuration;
using Polly;
using LegislationService.Api.Queries;
using RestEase;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Steeltoe.Common.Discovery;
namespace LegalCaseService.RestClients
{
public interface ILegislationClient
{
[Get("api/legislation/{id}")]
Task<LegislationDetail> GetLegislationDetailAsync([Path] int id);
}
public class LegislationClient : ILegislationClient
{
private readonly ILegislationClient client;
// Retry pattern
private static IAsyncPolicy _retryPolicy = Policy
.Handle<Exception>()
.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(3));
public LegislationClient(IConfiguration configuration, IDiscoveryClient discoveryClient)
{
var handler = new DiscoveryHttpClientHandler(discoveryClient);
var httpClient = new HttpClient(handler, false)
{
BaseAddress = new Uri(configuration.GetValue<string>("LegislationServiceUri"))
};
// RestEase HTTP client
client = RestClient.For<ILegislationClient>(httpClient);
}
public Task<LegislationDetail> GetLegislationDetailAsync(int id)
{
return retryPolicy.ExecuteAsync(async () => await client.GetLegislationDetail(id));
}
}
}LegislationService tarafından sunulan işlemleri temsil eden bir arayüz yazdık. Ardından Eureka’daki servis kayıt defterinden adresi çeken, Polly ve RestEase ile birleştirilmiş bir uygulama oluşturduk.
HTTP handler burada:
var handler = new DiscoveryHttpClientHandler(discoveryClient);
var httpClient = new HttpClient(handler, false) { ... };DiscoveryHttpClientHandler, URL’deki servis ismini Eureka’dan gerçek IP’ye çözer. appsettings.json içerisindeki adres artık gerçek bir ağ adresi değil servisin adıdır:
"LegislationServiceUri" : "http://LegislationService"URL kısmında yazan http://LegislationService ifadesini Eureka’ya gider, bu isme karşılık gelen 192.168.x.x:5040 adresini alır ve isteği oraya yönlendirir.
Şimdi bu istemciyi IoC tarafına eklemeliyiz. Bunun için bir RestClientsInstaller sınıfı kullanıyoruz:
public static class RestClientsInstaller
{
public static IServiceCollection AddLegislationRestClient(this IServiceCollection services)
{
services.AddSingleton<ILegislationClient, LegislationClient>();
return services;
}
}Bunu ise Startup.cs içerisinde şu şekilde çağırıyoruz:
public void ConfigureServices(IServiceCollection services)
{
services.AddDiscoveryClient(Configuration);
services.AddLegislationRestClient();
services.AddMvc();
}Artık projenin herhangi bir yerinde (örneğin bir Controller veya Service sınıfında) ILegislationClient arayüzünü enjekte edebilir ve LegislationService tarafı sanki projenin içindeki bir metodu çağırıyormuşçasına kullanabilirsiniz. Arka planda adresin bulunması, bağlantının kurulması ve hata durumunda tekrar denenmesi tamamen otomatik olarak halledilir.
Sonuç
Eureka bize bir dashboard sunar ama bu sadece bir fotoğraftır. Gerçek bir takip için health checks mutlaka kontrol edilmeli. Sadece servisin Eureka listesinde görünmesi yetmez, Steeltoe ile gelen Health Actuators sayesinde, servisin gerçekten iş yapıp yapamadığını (DB bağlantısı var mı gibi) takip etmelisiniz. Sunucu tarafında servislerin ayakta olduğunu takip etmelisiniz. Bazen bir servis çöker ama Eureka’dan kaydı düşmez. Dolayısıyla operasyon ekibi, Eureka dashboard bildirimlerinde kırmızı uyarıları izlemelidir. Elbette bu bir ekip işi.
Sonuç olarak microservice mimarilerinin en büyük çıkmazı olan servislerin birbirini bulma ve kararlı iletişim kurma sorunu, Eureka ve Steeltoe ikilisiyle birlikte kendi kendini yöneten bir ekosisteme dönüşüyor.



Yorum bırakın