• NGINX ile Layer 7 Load Balancing

    Yük dengeleme algoritmaları büyük web sunucularının verimliliğini yönetebilmek için inanılmaz derece de önemli bir araçtır. Bu algoritmalar sayesinde sunucuların verimliğini arttırabilmenin yanı sıra güvenliğini de sağlayabiliriz. Bu alanda kullanabileceğimiz iki dominant web sunucu bulunuyor. İlki OSS ve Plus versiyonlarıyla sunulan NGINX diğeri ise tamamen açık kaynak olan Apache2 web sunucusu.

    Yazımda Nginx ile yapılandırmayı dökümantasyon üzerinden ilerleyerek açıklayacağım. İlerleyen yazılarımda NGINX’te olmayan özellikleri hangi ek araçları kullanarak ekleyebileceğimizi anlatacağım.

    İlk olarak load balancing’in genel mantığını, Layer 7 load balance uygulamalarının neler olduğunu, uygulamaların nasıl çalıştığını vb. anlatacağım.

    Yük Dengeleyici Algoritmaların Mantığı

    Yük dengeleyici algoritmalar temelde statik ve dinamik olarak ayrılıyorlar. Statik algoritmalar isminden de anlaşılacağı üzere gelen bağlantıları önceden belirtilen kurala bağlı olarak back-end sunuculara aktarıyor. Dinamik algoritmalar duruma, koşullara vb. etkenlere göre istemcinin istediği adrese ulaşabilmesinde farklı back-end sunucuları tercih edebiliyorlar.

    Statik Load Balancing Algoritmaları

    Statik algoritmalar 2 başlık altında incelenebilir.

    • Round Robin ve Weighted Round Robin
    • Hash Algoritmaları

    Hash algoritmaları farklı şekillerde olabilir. Örneğin, IP hash, generic hash ve consistent hashing.

    Round Robin ~ Weighted Round Robin

    Round Robin NGINX’te varsayılan olarak sunulur. Her isteği sıradaki sunucuya gönderir.

    Özel bir konfigürasyon gerektirmez. Konfigürasyon dosyasının içeriğine kullanacağımız back-end sunucularının adreslerini girip kapatırız, ardından diğer tüm yöntemlerde de yapacağımız gibi NGINX’i yeniler ve işlemi tamamlarız.

    Örnek olarak şunu uygulayabiliriz:

    Öncelikle ana yapıdan bahsedeyim. Kullandığım ortamda 2 Debian 1 adet Rocky makine kullanacağım. Debian makinelerden birini Load Balancer olarak konfigure edeceğim. Diğer makinelerin de /var/www/html/index.html dosyalarını uygulamamızı ayırt edecek şekilde BACKEND-1 DEBİAN ve BACKEND-2 ROCKY olarak işaretleyeceğim.

    • BACKEND-1 DEBİAN makineye ait IP: 192.168.216.234
    • BACKEND-2 ROCKY makineye ait IP: 192.168.216.20

    Sonrasında load balancer makinemize gidip /etc/nginx/conf.d/lb.conf dosyasını oluşturup içerisine şu ayarı yapalım.

    • proxy_pass, gelen isteğin hangi sunucuya iletileceğini belirler.
    • proxy_set_header, NGINX’in değiştirdiği ya da göndermediği bazı HTTP header’larını back-end’e iletmek için kullanılır.

    Sunucuyu yeniledikten sonra göreceğimiz muhtemel görünüm,

    gibi olacaktır. Başlangıçta üç defa Debian sunucuya giden istekler sonrasında bir Debian bir Rocky olarak sıralı gitmeye başladı. Başarılı şekilde Round robin algoritması tamamlandı.

    Buna ek olarak konfigürasyon dosyasında sunucuların güçlerine bağlı olarak weighted (ağırlık katsayısı) da belirtebiliriz.

    Mesela az önce oluşturduğumuz lb.conf içerisinde şu değişikliği yaparsak,

    Bu sefer isteklerimiz muhtemel olarak 5 e 1 olarak dağılım gösterecek.

    Deneyelim,

    Görüldüğü üzere ağırlık Rocky makinenin üzerinde oldu.

    Hash Algoritmaları

    Hash algoritmaları temel matematiğinde hash % back-end sayısı yatar.

    Hash, IP hashing özelinde IP adresinin 19216811 (192.168.1.1) şeklinde yazılıp back-end sayısına göre modunun alınmasına göre belirlenir. Bu da aynı IP’nin her geldiğinde aynı sunucuya gitmesini sağlar.

    Generic hashing, verilen herhangi bir değeri hash’leyip ona göre sunucu atamasını yapar. Bu değerler,

    • $request_uri
    • $remote_addr ( ip hashing’e benzeyebilir bu şekilde)
    • $http_x_forwarded_for
    • $cookie_jsessionid

    … gibi olabilirler.

    Consistent Hashing, diğerlerinden farklı bir probleme parmak basıyor. Diğer hashing algoritmalarında back-end sunucu sayısı değiştiğinde ortalık karışabilirdi. Consistent algoritması buna yönelik olarak,

    1. Bir m değeri belirlenip 2m2^m ile toplam uzay belirlenir.
    2. Elimizdeki node’lar (back-end sunucuları) rastgele dağılım göstererek bu uzaya yerleştirilir.
    3. Key değerini hash’leyip uzay içerisinde değere saat yönünde en yakın sunucuya bağlantı sağlanır.

    Dağılım bu şekilde yapıldığında çok daha küçük bir grup sunucuların eklenmesinden veya çıkartılmasından etkilenir .(diğer hashing algoritmalarına nazaran). Bu beraberinde bazı problemleri de getirir. Mesela, rastgele dağılım yapıldığı için kimi Node çok büyük bir alanı kaplayabilir ve bizim matematiğimize ters düşebilir. Bu durumda,

    Consistent hashing, virtual node denen bir kavram ile birlikte gelir. Normaldeki Node’lara eş yeni sanal Node’lar üretilir ve aralıklara dağıtımlar yapılır. Bu şekilde gelen hash değerin dengesiz gitmesi önlenir.

    Bunu denemek istersek,

    (ip adreslerim yazıyı farklı zamanlarda yazdığım ve yeni lab’lar kurduğum için farklılık gösteriyor)

    lb.conf dosyamızı düzenlememiz ve consistent parametresini eklememiz yeterlidir. NGINX kendi özelinde default olarak her bir sunucuya 160 virtual node ataması yapar (Nginx Plus kullanıyor olsaydık bu varsayılan değeri değiştirebilirdik).

    Not: Weighted parametresini de ekleyip konfigürasyonu yaptığımızda yeni oluşturulacak sanal node’lar o orana göre üretilir.

    Dinamik Load Balancing Algoritmaları

    Dinamik algoritmalar 2 başlık altında incelenebilir.

    • Least Connections (en az bağlantı ~ kullandığımız OSS sürümü itibariyle statik olarak da sayılabilir.)
    • Random with Two Choices

    Least connections, pratikte sunuculardaki toplam bağlantı sayılarını kontrol ederek bağlantı sayısı en az olan sunucuya yeni isteği gönderir. Uygulaması da OSS sürümde de desteği olduğu için oldukça kolaydır.

    İlk önce lb.conf dosyamıza ait bu kısmı şu şekilde güncelliyoruz:

    Ardından nginx sunucumuzu yeniden başlatıp curl ile istemci sunuculardan eşzamanlı (sunuculardan tek tek curl atarsak bu algoritmayı tam olarak denemiş olmayız) istek attığımızda karşımıza çıkan durum hemen hemen şunun gibi olacak.

    Eş zamanlı istek atmak için şu komutu kullanalım.

    bash
    user@linux:~$ seq 1 50 | xargs -n1 -P10 -I{} curl -s http://192.168.216.130 > sayim.txt
    user@linux:~$ less sayim.txt

    Herhangi bir Weighted katsayısı belirtilmediği için iki sunucuda hemen hemen eşit dağılım göstermiş.

    Bir diğer dinamik algoritmamız olan Random with Two Choices, temelde back-end saydığımız tüm sunucular arasından 2 sunucu seçip bağlantı sayısı az olana yeni isteği gönderir.

    Algoritmayı uygulamak için lb.conf dosyamıza şu değişiklikleri yapalım:

    Dosyayı kaydettikten sonra nginx’i her zamanki gibi restart edip başarılı bir şekilde konfigürasyonu yapalım. Ben elimdeki 2 back-end sunucuyu buraya yazdım, bu çok sağlıklı bir örnek olmadı. Bunun yerine 3’ten fazla sunucuyla bu denemeleri yapmak daha doğru olurdu.

    Sunucu Sağlık Kontrolleri

    Back-end sunucuların olası çökme veya yavaşlama durumlarına karşı bir takım sağlık kontrolleri bulunduruluyor. Bunlar da kendi içerisinde dinamik ve statik olarak ikiye ayrılıyor. Kullandığımız nginx open source sürümü yalnızca iki adet statik sağlık kontrolünü destekliyor. Bunlar,

    • max_fails: İsteklerin maksimum kaç defa 5xx hatası alacağını belirleyen parametre. Yeterince hata alındığında sunucuyu down ederiz.
    • fail_timeout: Sunucunun ne kadar süre down olacağını belirleriz.

    ikisini birlikte kullanırız. Örneğin,

    Geriye kalan aktif sağlık kontrollerinin tamamı Nginx Plus sürümünde bulunuyor.

    Son olarak Nginx’in worker process çalışma mantığından bahsetmek istiyorum.

    Genel olarak NGINX çalışma yapısında, bir MASTER cpu çekirdeği kadar worker’ı hizmete açar. Client’lardan gelen istekleri worker’lar kabul eder ve kendi verilerine göre back-end sunuculara iletirler. Normal şartlar altında bu işleyiş sorunsuz çalışabilir ancak yük dengeleme algoritması kuruyorsak worker’lar birbirleriyle haberleşemediği durumlarda bazı kötü sonuçlar doğurur. Bu kötü sonuçları engellemek için NGINX’te zone kavramı sunulmuştur.

    Zone, worker’ların birbirleri arasında veri paylaşımı yapabileceği alanlardır. Yani paylaşımlı bellek alanıdır. Bu paylaşımlı bellek alanında,

    • Fail timeout sayaçları
    • Load balancing sayaçları
    • Weight bilgisi (runtime state olarak)
    • Fail state (down / up durumu)
    • Her backend için aktif connection sayısı

    Zone’u aktif etmek için lb.conf dosyasında,

    ayarını yapmamız yeterlidir. Oluşturduğumuz zone 64 kb boyutunda bir alan kapsıyor.

    Özetle,

    • Active health check
    • Runtime API (reload’suz upstream ekleme/çıkarma, weight değiştirme)
    • Slow start
    • Gelişmiş monitoring dashboard

    gibi pek çok özellik OS sürümünde bulunmayıp yalnızca plus sürümünde bulunuyor. Bu da profesyonel manada bir load balancing algoritmasının yapılmasını ciddi derecede zorlaştırıyor. Bu durumu çözebilmek için,

    1. Nginx Plus satın alabiliriz 🙁
    2. 3.taraf modülleri indirip nginx’e entegre edebiliriz
    3. Openresty kullanarak LUA programlama diliyle Nginx’i Plus sürümünden de ileriye taşıyabiliriz.
    4. Haproxy gibi farklı load balancer sunucularını tercih edebiliriz.