Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

Docker + Ocelot Kütüphanesi İle Load Balancing Operasyonu

Merhaba,

Bir önceki Load Balancing(Yük Dengeleme) ve Load Balancer(Yük Dengeleyici) Nedir? başlıklı makalemizde Load Balancing ve Load Balancer konularına açıklık getirmiştik. Bu içeriğimizde ise pratikte load balancing’i örneklendirebilmek için load balancer olarak Ocelot kütüphanesini ele alıyor olacağız.

Ocelot, bilindiği üzere genellikle API Gateway amaçlı kullandığımız oldukça elverişli bir kütüphanedir. İlgili kütüphaneye dair fazlasıyla bilgi edinebilmek için resmi github sayfasına göz atabilir ya da tarafımca klavyeye alınmış olan Ocelot API Gateway ve Ocelot İle Authentication İşlemleri başlıklı makalelerimi sırasıyla okuyabilirsiniz.

Şimdi bizler bu içeriğimizde Ocelot kütüphanesiyle load balancing işleminin nasıl gerçekleştirilebildiğini ele alacak ve bunun için Docker’da deploy edilmiş bir Asp.NET Core temelli API’dan faydalanacağız. Öncelikle geliştirilmiş API uygulamasından iki farklı instance(container) ayağa kaldıracağız. Ardından Ocelot üzerinden bu instance’lara load balancing işlemi sağlayacağız. Tabi tüm bunları yaparken önemli noktalar üzerinde kritik yapacak ve Ocelot’un getirdiği algoritmaların dışında custom bir algoritmanın nasıl geliştirilebildiğini incelemiş olacağız. O halde fazla vakit kaybetmeksizin buyrun gelin başlayalım…

Asp.NET Core API Uygulamasının Geliştirilmesi ve Docker’da Deploy Edilmesi

Yapacağımız örneklendirme için aşağıdaki gibi basit bir API’ın geliştirilmesi yeterli olacaktır.

    [ApiController]
    [Route("[controller]")]
    public class ExampleController : ControllerBase
    {
        readonly IConfiguration _configuration;

        public ExampleController(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        [HttpGet]
        public IActionResult Get()
        {
            var data = _configuration["data"];
            return Ok(data);
        }
    }

Burada tek dikkat etmenizi istediğim husus, ‘IConfiguration’dan ‘data’ key’ine karşılık beklenen değerin geriye gönderilmesidir. Bu key’e karşılık değeri ayağa kaldırılacak container’a environment olarak göndereceğiz. Böylece ‘Get’ action’ına yapılan request neticesinde, load balancing ile hangi instance’a yönlendirildiğimizi rahatlıkla görebilmiş olacağız.

API’ı geliştirdikten sonra yapılması gereken husus Docker işlemleridir. Bunun için öncelikle ilgili API projesine bir Dockerfile dosyası eklemek ve içeriğini aşağıdaki gibi doldurmak gerekmektedir.

FROM mcr.microsoft.com/dotnet/sdk:5.0
WORKDIR /app
COPY . .
RUN dotnet restore
RUN dotnet publish APIProject.csproj -c Release -o out
WORKDIR out
ENV ASPNETCORE_URLS="http://*:1453"
ENTRYPOINT ["dotnet", "APIProject.dll"]

Ardından powershell yahut cmd üzerinden docker build -t loadbalancerexample . talimatını vererek ‘loadbalancerexample’ isminde bir image oluşturulması gerekmektedir.
Docker + Ocelot Kütüphanesi İle Load Balancing OperasyonuBu işlemden sonra son olarak container oluşturulması gerekmektedir. Bunun içinde yine powershell ya da cmd üzerinden aşağıdaki iki talimatı vererek 1071 ve 1072 portlarında olmak üzere iki container’ı ayağa kaldıralım.
docker run -p 1071:1453 --env data='API 1' --name api1 loadbalancerexample
docker run -p 1072:1453 --env data='API 2' --name api2 loadbalancerexample
Docker + Ocelot Kütüphanesi İle Load Balancing OperasyonuDikkat edilirse eğer her iki container’a environment olarak uygun değerler gönderilmiştir.
Şimdi ilgili portlar üzerinden yapılan istekler neticesinde nasıl bir sonuçla karşılaşıyoruz, inceleyelim.

Container 1 Container 2
Docker + Ocelot Kütüphanesi İle Load Balancing Operasyonu Docker + Ocelot Kütüphanesi İle Load Balancing Operasyonu

İşte… Görüldüğü üzere environment olarak verilen dataları istek neticesinde başarıyla görebilmekteyiz. Bu noktadan itibaren yapacağımız load balancing operasyonunda istek neticesinde hangi container’a yönlendirildiğimizi rahatlıkla anlayabileceğiz.

Ocelot İle Load Balancıng Operasyonu

Ocelot ile load balancing operasyonunu gerçekleştirebilmek için bir API Gateway görevi görecek Asp.NET Core temelli uygulama oluşturulmalıdır. Ardından bu uygulamaya Nuget’ten Ocelot kütüphanesinin yüklenmesi gerekmektedir. Devamında ise uygulamanın ana dizinine ‘ocelot.json’ isminde bir dosya ekleyerek içeriğini aşağıdaki gibi doldurmak gerekmektedir.

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/example",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 1071
        },
        {
          "Host": "localhost",
          "Port": 1072
        }
      ],
      "UpstreamPathTemplate": "/example",
      "LoadBalancerOptions": {
        "Type": "LeastConnection"
      },
      "UpstreamHttpMethod": [ "Get" ]
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "https://localhost:5001"
  }
}

Yukarıdaki yapılanmayı parça parça incelersek eğer;

  • 4. satır; DownstreamPathTemplate API uygulamasının route bilgisi bildirilmektedir.
  • 5. satır; DownstreamScheme İsteklerin hangi protokol üzerinden gerçekleştirileceği bildirilmektedir.
  • 6. satır; DownstreamHostAndPorts Load balancing operasyonunun yapılacağı Host ve Port bilgileri verilmektedir.
  • 16. satır; UpstreamPathTemplate API Gateway görevi gören bu uygulamaya hangi route üzerinden istek atılacağını belirlemektedir.
  • 17. satır; LoadBalancerOptions Load balancer konfigürasyonları belirlenmektedir.
  • 18. satır; Type Hangi load balancing algoritmasının kullanılacağı bildirilmektedir.
    Ocelot; LeastConnection, RoundRobin, NoLoadBalancer ve CookieStickySessions olmak üzere dört farklı algoritma kullanmaktadır.

    • LeastConnection
      Gelen isteği en az maliyette çalışan ya da başka bir deyişle en az hizmet yoğunluğunda olan sunucuya yönlendirir.
    • RoundRobin
      Gelen isteği sunucular arasında döngüsel olarak sırasıyla paylaştırır.
    • NoLoadBalancer
      Gelen isteğin nereye yönlendirileceğini Service Discovery’den alır. Peki nedir bu Service Discovery? diye sorarsanız, servislerin birbirleriyle iletişim kurabilmesini, healt check ile sadece ayakta olan servislerin kullanılabilmesini ve load balancing ile servislerin dinamik olarak dağılmasını sağlayan bir özelliktir.
    • CookieStickySessions
      Bir kullanıcıdan gelen tüm istekleri sürekli aynı sunucuya yönlendirir.

            .
            .
            .
            "LoadBalancerOptions": {
              "Type": "CookieStickySessions",
              "Key": "ASP.NET_SessionId",
              "Expiry": 1800000
            }
            .
            .
            .
      
  • 20. satır; UpstreamHttpMethod Bu route’a hangi türdeki isteklerin yapılabileceği bildirilmektedir.
  • 24. satır; BaseUrl API Gateway uygulamasının base url’i bildirilmektedir.

Docker + Ocelot Kütüphanesi İle Load Balancing Operasyonu
Şimdi bu konfigürasyonlar neticesinde https://localhost:5001/example adresine istek gönderdiğimizde yanda görüldüğü üzere load balancing sağlanmakta ve uygun olan sunucu/instance ilgili isteği karşılamaktadır.
 

Custom Algoritma Geliştirme

Bazen, kullanılan load balancer’ın getirdiği algoritmalar pek ihtiyacımızı karşılamayabilmektedir. İşte öyle bir durumda eğer ki Ocelot kullanılıyorsa ihtiyacımıza uygun algoritmayı özel olarak geliştirebilmekteyiz. Bunun için aşağıdaki gibi ILoadBalancer interface’inden türetilmiş bir sınıf tasarlamak yeterli olacaktır.

    public class CustomLoadBalancer : ILoadBalancer
    {
        //Sunucular
        readonly Func<Task<List<Service>>> _services;
        readonly object _lock = new object();

        //En son hangi sunucuya yönlendirme işlemi gerçekleştirildiyse index'ine karşılık gelmektedir.
        int _last;
        public CustomLoadBalancer(Func<Task<List<Service>>> services)
        {
            _services = services;
        }

        //Özel algoritma
        public async Task<Response<ServiceHostAndPort>> Lease(HttpContext httpContext)
        {
            var services = await _services();
            lock (_lock)
            {
                _last = _last >= services.Count ? 0 : _last;
                //İsteğin yönlendirileceği sunucu seçilmektedir.
                var next = services[_last];
                _last++;
                //Seçilen sunucu OkResponse nesnesi olarak return edilmektedir.
                return new OkResponse<ServiceHostAndPort>(next.HostAndPort);
            }
        }
        //Yönlendirmenin yapılacağı host ve port bilgilerini getiren Release metodu.
        public void Release(ServiceHostAndPort hostAndPort)
        {

        }
    }

Yukarıda sıralı olarak sunuculara istekleri yönlendiren farazi bir algoritma eşliğinde örneklendirme yapılmıştır. Nihayetinde geliştirilen bu custom algoritmayı sisteme dahil edebilmek için ‘Startup.cs’ dosyasında aşağıdaki konfigürasyonun yapılması gerekmektedir.

    public class Startup
    {
        .
        .
        .
        public void ConfigureServices(IServiceCollection services)
        {
            Func<IServiceProvider, DownstreamRoute, IServiceDiscoveryProvider, CustomLoadBalancer> loadBalancerFactoryFunc = (serviceProvider, Route, serviceDiscoveryProvider) => new CustomLoadBalancer(serviceDiscoveryProvider.Get);

            services.AddOcelot(Configuration)
                .AddCustomLoadBalancer(loadBalancerFactoryFunc);
            .
            .
            .
        }
        .
        .
        .
    }

Yukarıdaki kod bloğuna göz atarsanız eğer, 8. satır‘da IServiceProvider, DownstreamRoute ve IServiceDiscoveryProvider türlerinden parametreler alan ve geriye CustomLoadBalancer türünden nesne döndüren loadBalancerFactoryFunc adından bir fonksiyon oluşturulmaktadır. İlgili fonksiyonda içerik olarak geriye, serviceDiscoveryProvider.Get komutundan aldığı sunucuların bilgileri eşliğinde bir CustomLoadBalancer nesnesi döndürmekte ve 11. satır‘da çağrılarak bu operasyon gerçekleştirilmektedir. serviceDiscoveryProvider.Get komutu, edineceği sunucu bilgilerini ‘ocelot.json’ dosyasından okumaktadır.

Artık geliştirilmiş ve sisteme dahil edilmiş bu algoritmayı kullanabilmek için ‘ocelot.json’ dosyasında aşağıdaki gibi load balancer type değeri olarak verilmesi yeterlidir.

      .
      .
      .
      "LoadBalancerOptions": {
        "Type": "CustomLoadBalancer"
      }
      .
      .
      .

Bu operasyondan sonra ilgili sunuculara bu algoritma eşliğinde request yönlendirmesi gerçekleştirilecektir.

Nihai olarak;
Bu makalede load balancing işlemi için Ocelot kütüphanesinden nasıl istifade edebileceğimizi incelemiş olduk. Basit ama etkili olan bu kütüphane sayesinde birçok işlemi gerçekleştirebildiğimiz gibi load balancer görevini de icra edebilmekte ve temel düzeyde bu operasyona karşı ihtiyacımızı giderebilmekteyiz. Bu konuya dair, derin bir şekilde yaklaşım sergileyebilmek ve profesyonel bir sunucu üzerinden load balancing sağlayabilmek için sonraki yazılarımda NGINX üzerinden tematik bir makale klavyeye alıyor olacağım… O halde şimdilik keyifli okumalar diliyorum…

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Not : Örnek projeyi indirebilmek için buraya tıklayınız.

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir