Microservice Mimarilerinde Consul ile Service Discovery

Merhaba arkadaşlar.

Tekrardan microservice mimarilerine dayanan bir konu ile karşınızdayım. Bu konu kapsamında ise, “Service Discovery nedir?”, “Ne zaman ihtiyaç duyarız?” gibi kavramlardan bahsedip, bu işlemleri Consul ile nasıl gerçekleştirebileceğimize değinmeye çalışacağım.

Service Discovery Nedir?

Microservice’ler ile alakalı diğer makalelerimde de bahsettiğim gibi, bizlere kattığı artıların yanında bazı challenge’ları da beraberlerinde getirdiklerinden bahsetmiştik. Bu challenge’lardan belki de en önemlileri ise, management ve monitoring konularıdır.

Monolithic yapılara baktığımızda ise her şey tek bir çatı altındadır. Biz yazılımcılar için proje git gide büyümeye başladığında pek de hoş olmasa da, operasyonel anlamda sistemciler için oldukça rahat bir şey aslında. 🙂  Fakat microservice yapılarına baktığımızda ise, işler operasyonel anlamda biraz değişiyor. Çünkü hepimizin de bildiği gibi -n tane farklı sorumlulukları yerine getiren distributed  microservice’ler ortaya çıkmaya başlıyor ve işte bu noktada microservice’lerin beraberlerinde getirdiği management ve monitoring gibi ortak challenge’lar ile, “Service Discovery” kavramı ortaya çıkıyor.

Örneğin:

Yukarıdaki yapıya baktığımızda, auto scale olan ve dynamic olarak değişen instance’lara sahip bir yapıyı görüyoruz. Burada dikkat edersek IP adresleri de dynamic olarak assign edilmektedir. Bu gibi dynamic case’ler karşısında, client hangi IP adresine istek atacağını bilemeyecektir. Service discovery ise bu gibi durumları nasıl otomatik olarak handle edebilirize odaklanmaktadır.

Service discovery  temel olarak üç kavram üzerinde durmaktadır:

  1. Discovery: Service’lerin dynamic bir ortamda cluster içerisindeki diğer service’ler ile iletişim kurabilmeleri için, birbirlerinin IP ve port bilgilerini bulmaya ihtiyaçları vardır. Discovery ise bunu sağlamaktadır.
  2. Health check: Health check işlemi ile sadece up olan service’lerin sistemde kalmaları, down olan’ların ise dynamic bir şekilde sistem dışı kalmaları sağlanmaktadır.
  3. Load balancing: Hepimizin de bildiği gibi bir hizmete gelmiş olan request’in, bu hizmeti sağlayan diğer instance’lara da dynamic olarak dağıtılmasını sağlamaktır.

Service discovery’i uygulayabilmek için “Client-side service discovery” ve “Server-side service discovery” olmak üzere iki farklı pattern bulunmaktadır. Dilerseniz ilk pattern olan client-side kısmına bir bakalım.

1) Client-side Service Discovery Pattern

Bu yaklaşımda service instance’ları, kendi network location’larını Service Registery üzerine kayıt ederler. Service registery ise service discovery’nin bir parçasıdır. Bu sayede buradaki service’lere, hangi IP ve port üzerinden erişilebileceği bilgisi service registery üzerinde bulunur. Client ise herhangi bir request’i göndermeden önce service registery’e gelerek, request göndermek istediği service’in location bilgilerini elde eder ve o bilgiler doğrultusunda request işlemini gerçekleştirir. Kompleks olarak gözükebilir ama genel anlamda bakılacak olursa gayet kolay bir işlemdir.

Bir diğer yandan bu pattern load balancer hatalarından sistemi korur, fakat balancing işlemini ise client’a bırakır. Çünkü client, registery üzerinden istediği service’in IP ve port bilgilerini alır ve birden çok IP adresine sahip ise kendi belirleyeceği bir IP adresine request işlemini gerçekleştirir. Bu noktada dezavantajına baktığımızda ise artık client’lar, service registery ile konuşması gerektiğini bilmek zorundadırlar ve balancing işlemlerini kendileri handle etmelidirler.

2) Server-side Service Discovery Pattern

Bu yaklaşımda ise dikkatinizi çekmek istediğim ilk nokta: client’ın artık ilk olarak service registery ile konuşmak yerine direkt olarak load balancer üzerine geliyor olmasıdır. Load balancer ise service registery üzerinden ilgili servis’in location bilgilerini alarak, route işlemini kendisi gerçekleştirmektedir.

Bu pattern’ın avantajı ise, client ile service registery’nin decoupled bir şekilde olmaları ve client’ın hangi service’in hangi node’da olduğundan bir haberi olmamasıdır. Dezavantajı ise buradaki load balancer’ın, single point of failure durumda olmasıdır.

Consul Nedir?

Consul, kapsamlı bir service discovery aracıdır. Öncelikle consul’ün mimarisine biraz değinecek olursak consistency için server node’larında Raft consensus‘u kullanmaktadır.

Raft consensus ise:

Paxos temelli bir consensus algoritmasıdır.

Konu konuyu açıyor fakat nedir bu Paxos da dersek eğer: distributed computing’de, consensus’u sağlayabilmek için kullanılan bir protokol olarak tanımlayabiliriz. Paxos hakkındaki detaylı bilgiye ise, buradan ulaşabilirsiniz. Raft’ a geri dönecek olursak, Paxos’a göre daha basit ve anlaşılabilir bir algoritma olarak tasarlanmıştır. Consul ise Raft’ı, node’lar arasındaki consistency durumunu veya leader election’ı sağlayabilmek için kullanmaktadır.

Örnek vermek gerekirse: 3 adet server node’u olduğunu düşünelim. Bu node’lar aralarındaki consistency’i sağlayabilmek için leader node, aşağıdaki gibi diğer node’lara bir heartbeat göndermektedir.

Eğer bu heartbeat timeout’a uğrar ise, x node’undan birtanesi yeni bir election(oylama) başlatacaktır. Bu election ise node’lar arasından hangisinin yeni leader olacağına karar verilebilmesi için yapılmaktadır. Bu işlemin gerçekleşebilmesi için ise election’ı başlatan node, aşağıdaki gibi diğer node’lardan leader olabilmek için oy istemektedir.

Diğer node’lardan gerekli oyu alabilirse eğer, leader olarak seçilmektedir. Bu oy isteme işlemine ise “Quorum” denmektedir ve bu işlem için (n/2)+1 kadar üye gerekmektedir. Raft hakkında daha fazla bilgi edinebilmek için, burayı ziyaret edebilirsiniz.

Consul’ün diğer bazı architectural detaylarına baktığımızda ise:

  • Etkileşim için bir REST endpoint’i sunmaktadır
  • Dynamic load balancing işlemini gerçekleştirebilmektedir
  • Multiple datacenter desteği vardır
  • In-built olarak kapsamlı bir service health checking sağlamaktadır
  • Service database’i için, distributed key-value store’a sahiptir

Bunlara ek olarak consul, highly fault tolerant‘a sahiptir. Tüm Consul service cluster’ı down olduğunda dahi, bu durum service discovery işlemini durdurmayacaktır. Bu işlemi ise Consul, Serf ile sağlamaktadır. Serf, tamamen bir Gossip protokol’ü olup bir nevi node orchestration tool’udur. Serf membershipment’ı yönetmek, failure detection ve event broadcasting yapabilme işlemlerini sağlayabilmektedir. Ayrıca server node’ları için ise clustering sağlamaktadır. Consul’ü tanımlamaya çalışırken bir çok farklı konuya değindik ve Consul nedir’i artık toparlamak gerekirse eğer, gördüğümüz gibi bir çok bileşeni mevcuttur Consul’ün.

Fakat bir bütün olarak baktığımızda ise altyapımızdaki service’leri discovery edebilmemiz ve configuring işlemlerini yapabilmemiz için geliştirilmiş bir tool’dur diyebiliriz. Consul’ün mimarisi ile ilgili son olarak da health checking konusuna da değinmek gerekirse, health checking işlemini ise client agent’ları aracılığı ile yapabilmektedir. Consul mimarisi hakkında daha detaylı bilgi edinebilmek isterseniz, burayı inceleyebilirsiniz.

Basic Implementasyon

Gerçekleştirecek olduğumuz implementasyon da, local ortamda Consul’ü development modunda çalıştıracağız. Ardından örnek olarak oluşturacak olduğumuz bir Web API projesini, Consul’ün .NET client library’sini kullanarak iki farklı port üzerinden IIS altında host edip, Consul üzerine registration işlemini gerçekleştireceğiz. Dilerseniz öncelikle buradan, Consul’ün windows için olan versiyonunu indirelim ve istediğimiz bir dizin altına koyalım.

Komut istemcisini açalım ve aşağıdaki komutu, Consul’ü koyduğumuz dizinde çalıştıralım.

consul agent -dev

Komutu çalıştırdıktan sonra Consul, agent’ını in-memory olarak development modunda yukarıdaki gibi çalıştırmaktadır. Log’lara baktığımızda ise bir tek “GOKGOKALP” node’u olduğu için, leader election’da “1” oya ihtiyacı olduğunu ve oylamayı yaparak kendisini leader olarak seçtiğini görebiliriz.

UI’ına ise localhost üzerinden “8300” port’u ile erişebiliriz. “http://localhost:8500/ui

Default olarak Consul, kendi servisi ile gelmektedir. “Serf Health Status” ü ile ise “GOKGOKALP” node’u üzerine bulunan consul servis’inin, “alive” ve “reachable” olup olmadığına bakmaktadır.

Dilerseniz şimdi basit bir service oluşturalım ve Consul içerisine register edelim. Bunun için “ConsulSampleAspNetCore” isminde bir Asp.NET Core Application Web API projesi oluşturuyorum ve içerisine aşağıdaki gibi bir “HelpController” ekliyorum.

namespace ConsulSampleAspNetCore.Controllers
{
    [Route("[controller]")]
    public class HelpController : Controller
    {
        [HttpGet("")]
        public string Ping()
        {
            return "OK";
        }
    }
}

Bu endpoint’i Consul tarafında health check işlemi için kullanıyor olacağız. Controller’ı tamamladıktan sonra, Nuget Package Manager üzerinden aşağıdaki gibi “Consul” paketini projeye dahil edelim.

Client library’sini ekledikten sonra ise, API projesi ile birlikte gelen “Startup.cs” i açalım ve içerisine aşağıdaki gibi “RegisterServiceToConsul” isminde yeni bir method oluşturalım.

private void RegisterServiceToConsul()
{
    using (var client = new ConsulClient())
    {
        var registration = new AgentServiceRegistration()
        {
            ID = "consul-sample-api-9090",
            Name = "consul-sample-api",
            Address = "localhost",
            Port = 9090,
            Check = new AgentCheckRegistration()
            {
                HTTP = "http://localhost:9090/health",
                Interval = TimeSpan.FromSeconds(10)
            }
        };

        client.Agent.ServiceRegister(registration).Wait();
    }
}

Burada en basit hali ile bir agent registration işlemi oluşturuyoruz. Service’in “localhost” üzerinde olduğunu ve “9090” port’u ile erişilebileceğini belirtiyoruz. Health checking işlemi için ise, öncesinde oluşturmuş olduğumuz URL adresini veriyoruz ve bu işlemi “10” saniyelik aralıklar ile gerçekleştirmesi gerektiğini bildiriyoruz. Sonrasında ise bu method’u, “Startup” method’u içerisinde call ediyoruz.

public Startup(IHostingEnvironment env)
{
    ...

    RegisterServiceToConsul();
}

Artık bu service ilk ayağa kalktığında, kendisini Consul üzerine register edecektir.

Test işlemlerine hazır durumdayız. Web API projesini publish ederek, IIS üzerinde “9090” port’undan host edelim. Ben bu noktada host işleminin, nasıl olduğunun konusuna değinmeyeceğim. Host işlemini başarıyla gerçekleştirdi isek, “http://localhost:9090/health” URL’ine gidelim ve “OK” result’ını görelim.

Bu result’ı görmemiz ile beraber service, Consul üzerine kendisinin registration işlemini gerçekleştirecektir. Öncelikle Consul’ün console üzerindeki log’larına bir bakalım.

Log’lara baktığımızda kendisine bir HTTP request’i geldiğini ve ardından “consul-sample-api-9090” ismi ile sync ettiğini görebiliyoruz. Bunun yanında ise “Check ‘service:consul-sample-api-9090’ is passing” satırı ile eklemiş olduğumuz service’in health check endpoint’ine giderek, up olup olmadığını kontrol ettiği log’unu da görebiliyoruz.

Şimdi Consul’ün UI ekranına girelim ve bir de oradan bakalım. UI ekranına girdikten sonra “SERVICES” tab’ına girelim ve buraya yeni eklenmiş olan “consul-sample-api” service’ine tıklayalım.

Gördüğümüz gibi sağ kısımda bulunan nodes tab’ının altındaki “Service ‘consul-sample-api’ check” bilgisini, passing olarak göstermektedir.

Detayına tıkladığımızda ise aşağıdaki gibi bir ekran bizi karşılıyor olacaktır.

Burada sağ altta bulunan output kısmına baktığımızda ise, service’i register ederken kullandığımız health check endpoint’ine bir GET request’i attığını ve ardından “OK” response’unu aldığı bilgisini görebilmekteyiz.

Dilerseniz birde olumsuz durumlarda ne yaptığına bir bakalım. Bunun için IIS üzerinden oluşturduğumuz host’u stop edelim ve mevcut UI ekranını tekrardan geri dönelim.

Artık “consul-sample-api” için durumun critical olduğunu ve output kısmında ise ilgili health check endpoint’ine gitmeye çalışılırken aldığı hata message’ını da görebilmekteyiz. Consul, endpoint’e tekrar erişebildiğinde ise, service’in status’ünü passing olarak gösterip, aktif service’ler arasına dahil edecektir.

Dilerseniz şimdi aynı service’i bu sefer “8080” port’u üzerinden IIS’de tekrar host edelim ve ardından “http://localhost:8080/health” health check endpoint’ini call edelim. Mevcut service’de olduğu gibi “OK” response’unu aldıktan sonra UI ekranına tekrar dönelim.

Sağ tarafa baktığımızda ise artık iki adet “consul-sample-api” olduğunu ve “8080” ile “9090” port’ları üzerinden sağlıklı bir şekilde hizmet verdiklerini görebilmekteyiz.

Bunlara ek olarak birde, Consul’ün API’ından bahsetmek istiyorum. Client-side tarafı için yararlı olabilecek olan health endpoint’i ile, query’leme yaparak sadece passing durumda bulunan service host bilgilerini elde edebilmek mümkündür.

Bunun için “http://127.0.0.1:8500/v1/health/service/consul-sample-api?passing” URL’ine bir GET request’inde bulunalım.

[
    {
        "Node": {
            "ID": "e93cdbc2-0a30-42f5-8f12-e32e690b24cd",
            "Node": "GOKGOKALP",
            "Address": "127.0.0.1",
            "TaggedAddresses": {
                "lan": "127.0.0.1",
                "wan": "127.0.0.1"
            },
            "Meta": {},
            "CreateIndex": 4,
            "ModifyIndex": 5
        },
        "Service": {
            "ID": "consul-sample-api-8080",
            "Service": "consul-sample-api",
            "Tags": null,
            "Address": "localhost",
            "Port": 8080,
            "EnableTagOverride": false,
            "CreateIndex": 585,
            "ModifyIndex": 585
        },
        "Checks": [
            {
                "Node": "GOKGOKALP",
                "CheckID": "serfHealth",
                "Name": "Serf Health Status",
                "Status": "passing",
                "Notes": "",
                "Output": "Agent alive and reachable",
                "ServiceID": "",
                "ServiceName": "",
                "CreateIndex": 4,
                "ModifyIndex": 4
            },
            {
                "Node": "GOKGOKALP",
                "CheckID": "service:consul-sample-api-8080",
                "Name": "Service 'consul-sample-api' check",
                "Status": "passing",
                "Notes": "",
                "Output": "HTTP GET http://localhost:8080/health: 200 OK Output: OK",
                "ServiceID": "consul-sample-api-8080",
                "ServiceName": "consul-sample-api",
                "CreateIndex": 585,
                "ModifyIndex": 586
            }
        ]
    },
    {
        "Node": {
            "ID": "e93cdbc2-0a30-42f5-8f12-e32e690b24cd",
            "Node": "GOKGOKALP",
            "Address": "127.0.0.1",
            "TaggedAddresses": {
                "lan": "127.0.0.1",
                "wan": "127.0.0.1"
            },
            "Meta": {},
            "CreateIndex": 4,
            "ModifyIndex": 5
        },
        "Service": {
            "ID": "consul-sample-api-9090",
            "Service": "consul-sample-api",
            "Tags": null,
            "Address": "localhost",
            "Port": 9090,
            "EnableTagOverride": false,
            "CreateIndex": 538,
            "ModifyIndex": 538
        },
        "Checks": [
            {
                "Node": "GOKGOKALP",
                "CheckID": "serfHealth",
                "Name": "Serf Health Status",
                "Status": "passing",
                "Notes": "",
                "Output": "Agent alive and reachable",
                "ServiceID": "",
                "ServiceName": "",
                "CreateIndex": 4,
                "ModifyIndex": 4
            },
            {
                "Node": "GOKGOKALP",
                "CheckID": "service:consul-sample-api-9090",
                "Name": "Service 'consul-sample-api' check",
                "Status": "passing",
                "Notes": "",
                "Output": "HTTP GET http://localhost:9090/health: 200 OK Output: OK",
                "ServiceID": "consul-sample-api-9090",
                "ServiceName": "consul-sample-api",
                "CreateIndex": 538,
                "ModifyIndex": 539
            }
        ]
    }
]

Gelen response içerisinde ise “GOKGOKALP” node’unda, “consul-sample-api” ın “9090” ve “8080” port’larında healthy durumda yer aldıklarını görebiliyoruz.

Daha önce de gerçekleştirdiğimiz gibi service’lerden herhangi birisini durdurduğumuz durumda ise, durdurulan service bu response içerisine dahil olmayacaktır. Bu sayede client istediği bir service’in, healthy olanlarının hangi adres bilgileri üzerinden erişilebilir olduğunu bilerek, istediği bir adres üzerinden consume işlemlerini gerçekleştirebilmektedir. Bunun dışında kullanışlı olan bir diğer endpoint ise, “Key/Value Store” endpoint’idir. Bu endpoint aracılığı ise, config transform için service configuration bilgilerini tutup, elde edebilmek mümkündür.

Bu makale kapsamında aslında Service Discovery ve Consul kavramlarına değinip, IIS üzerinde Consul ile discovery işlemini nasıl gerçekleştirebiliriz konusuna bir giriş yapmak istedim. Bundan sonraki makalede ise, Consul ile dynamic load balancing nasıl yapabiliriz konusuna değinmeyi planlıyorum. Örneğimizdeki Consul projesine ise, aşağıdaki link üzerinden erişebilirsiniz.

Umarım faydalı bir yazı olmuştur.

https://github.com/GokGokalp/consul-aspnetcore-sample

Kaynaklar

https://www.consul.io/intro/index.html
http://www.ryantomlinson.com/consul-service-discovery-in-a-microservice-world/
https://technologyconversations.com/2015/09/08/service-discovery-zookeeper-vs-etcd-vs-consul/

Gökhan Gökalp

View Comments

    • Merhaba öncelikle teşekkür ederim. Daha önce kullandığım bir paket olmadığı için yorumda bulunamayacağım fakat client-side olarak kullanılabilir bir paket gibi duruyor ama commit'lere bir bakmak lazım şuan pek aktif gibi durmuyor. Saygılar.

  • Merhaba,

    Bu yapıyı ngnix loadbalancer ile ilişkilendirdim ve sadece ayakta olanlara yönlendirme yapmasını sağlıyorum diyelim. HealtCheck işlemi 10 saniyede bir gerçekleşiyor. Elbette kısaltılabilir ama her zaman bir gecikme olacaktır. Production'da ayakta olmayan bir servise 3-4 saniye bile olsa yönlendirme yapmaya devam etmenin sonuçları sizce nasıl olur? Ya da bir çözüm var mı?
    Elbette bu kötü senaryolar hangi teknoloji olursa olsun gerçekleşebilir. Sadece fikrinizi merak ediyorum.

    Ek olarak, sizin örnekte 10 saniye olarak belirlediğiniz sürenin best practice'i nedir? Çok fazla ping atmasının ağ trafiği ve performansa yan etkisi olur mu?

    Teşekkürler.

  • Gerçekten Türkçe kaynak oluşturduğunuzu mu düşünüyorsunuz? Keşke dibine kadar plaza dili kullanmasaydınız

    • Yorumunuz için teşekkürler. Özellikle dil imla kurallarına uyarak "Türkçe" kaynak oluşturacağım diye bir gayretim yok. Plaza dilinden ziyade çevirmediğim terimler, çevrildiğinde bana göre anlamını kaybeden ve her bir developer'ın bilmesi gereken terimler olduğunu düşünüyorum. Yaklaşımım da her zaman bu yönde olacaktır.

  • Efektif ve güncel teknolojiler, mimariler hakkındaki yazılarınız için teşekkürler. Merakım ise bu yazıda bahsettiğiniz yapının ApiGatway'lerden tam farkı nedir ? Ek olarak performans artışına yönelik aynı işi yapan birden fazla microservis'in apigatway arkasında oluşturulmasına bakış açınız nedir ? Teşekkürler...

    • Merhaba öncelikle yorumunuz için teşekkür ederim. Evet teknoloji aldı başını gitti. Eskiden ne kubernetes ortamları vardı, nede çok fazla API gateway'ler.
      Benim cevabım ise, API Gateway'e bakış açınız ne olurdu? Bir kaç tane API'ı birleştirip, tek bir noktadan bir response dönmek ve resiliency/security etc. gibi konuları ortak bir noktadan yönetebilmek mi?
      Şuanda consul'ün kullanım oranı nedir bilemiyorum ama, zamanında basit olarak bize dağıtık ortamlarda "dns" tabalı service discovery imkanı sunuyordu. Örneğin bir API Gateway içerisinden şöyle bir API'a erişmeye çalıştığında, "xyz.com/orders" API Gateway nereye gideceğini, hangi API instance'ını çağıracağını bilmez. Amacımız da bu zaten. Soyutlamak ve dinamik olarak istediğimiz kadar instance ekleyebilmek. Bir nevi loadbalancer. Bu işlemi ise Consul ile gerçekleştirebilmek oldukça basit bir işlemdi. Tabi bunun yanında makale içerisinden de okuyabileceğin gibi, sağladığı başka avantajlarda bulunmaktadır.

  • Selamlar,
    Yazdığınız makaleler kadar, her bir soruya da ayrı ayrı değerli cevaplar vermeniz gerçekten saygı duyulası.
    Çok teşekkürler

    • Merhaba güzel yorumunuz için ben teşekkür ederim. Elimden geldiğince, vakit ayırabildikçe yanıtlamaya çalışıyorum.

Recent Posts

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Policy Enforcement-Automated Governance with OPA Gatekeeper and Ratify) – Part 2

{:tr} Makalenin ilk bölümünde, Software Supply Chain güvenliğinin öneminden ve containerized uygulamaların güvenlik risklerini azaltabilmek…

6 months ago

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Security Scanning, SBOMs, Signing&Verifying Artifacts) – Part 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

8 months ago

Delegating Identity & Access Management to Azure AD B2C and Integrating with .NET

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

1 year ago

How to Order Events in Microservices by Using Azure Service Bus (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

2 years ago

Providing Atomicity for Eventual Consistency with Outbox Pattern in .NET Microservices

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

2 years ago

Building Microservices by Using Dapr and .NET with Minimum Effort – 02 (Azure Container Apps)

{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…

2 years ago