Envoy, Bgp+Ecmp ve Frr ile Dağıtık Frontproxy Ortam Hazırlama

Sefa Pehlivan
hepsiburadatech
Published in
8 min readDec 7, 2020
Hepsiburada front proxy

Bu yazıdaki amacımız, envoy proxy’i distributed bir şekilde kullanarak en önde trafiği nasıl karşıladığımızı, bütün bu yapıyı merkezi bir yerden nasıl yönettiğimizi ve sonucunda neler elde ettiğimizi paylaşmak olacaktır.

Neden ihtiyaç duyduk?

Tahmin edebileceğiniz gibi enterprise loadbalancer’lar hem çok pahalı hem de yatayda büyüyemez. Eğer 10 Gbps throughput kapasiteli bir cihazınız var ise sınırınız budur, yatayda büyüyemezsiniz. Sektör gereği kasım ayı bizim için çok hareketli geçen bir dönem. Normal zamanda alınan trafik bu dönem de 5–10 katına çıkabiliyor. Dolayısıyla kullandığımız cihaz normal zamanları çok rahat atlatırken, kasım ayında sınırlarını zorlar hale geliyorduk.

Konumuza geçmeden önce proje de kullandığımız çözümlere ufak bir göz atalım.

Envoyproxy: Genellikle kubernetes içerisinde bir api gateway aracılığı ile sidecar proxy olarak kullanılan çok yetenekli open source bir üründür. Detay bilgiye buradan ulaşabilirsiniz.

Bgp&Ecmp: Bgp (Border gateway protocol), network katmanında rotaların dinamik bir şekilde öğrenilmesini sağlar. Ecmp (equal cost multi path) ise aynı rota eğer n tane yerden öğrenilmiş ise o n tane yere, trafiğin dağıtılmasından sorumludur.

Frr: Linux ve Unix platformları için IP yönlendirme protokolü paketidir.

Layer3 Load Balancing

İlk görselimizde en üstte bulunan üzerinde bgp/ecmp protokolleri çalışan iki adet aktif aktif switch bulunuyor. Onların da altında dört adet fiziksel sunucu üzerinde frr çalışıyor. Switch ile fiziksel sunucular üzerinde bulunan frr servisi, aralarında bgp komşuluğu kurarak, fiziksel sunucu üzerinde açılan tüm ip adreslerini dinamik bir şekilde switch’in öğrenmesini sağlıyor. Her sunucuda, ip adresi 1.1.1.1 olan bir servis ayağa kaldırdığımızda sunucular switchlere doğru bu ip adresini anons edecektir. Switch’de bulunan ecmp protokolü sayesinde örneğimizdeki dört sunucuyada eşit şekilde trafik dağılacaktır.

Bu proje için Engin EKEN’in yazdığı “BGP ve ECMP ile L3 Load Balancing — Envoy FrontProxy Ortamı” konu başlıklı yazıyı da okumanızı tavsiye ederim.

Açılan connection’lar dört sunucuya eşit şekilde dağılmaktadır.

Layer7 Load Balancing

İlk görselimizde fiziksel sunucular üzerinde çalışan her bir envoy servisi aslında bir domain’i işaret eder. x.hepsiburada.com için ayrı bir pid ve ip kullanılırken y.hepsiburada.com için ayrı bir pid ve ip kullanılmaktadır. Aynı ip adresini 4 farklı sunucuya verebilmek için her sunucu üzerinde dummy interface yarattık. Bunu loopback interface gibi düşünebilirsiniz. Her yarattığımız domain için hem envoy’da yeni bir konfigürasyon hemde linux’da bir dummy interface yaratarak frr’ın bu yeni ip adresini switch’e öğretmesini sağladık. Sonrasında 1.1.1.1 ip adresini envoy’a verdiğimiz’de envoy üzerindeki konfigürasyona göre bu ip üzerinden portları dinlemeye başlayacaktır.

Burada dikkat edilmesi gereken nokta; fiziksel sunucuların hepsinin aynı konfigürasyona sahip olması gerekir.

Management

İşin uğraş gerektiren en zor kısmı aslında burası böyle bir yapıyı kurup sonrasında güncel tutabilmek değişiklikleri anında uygulayabilmek ve bunları kesintisiz yapabilmek için yazdığımız controller yedi ayımızı aldı. Öncesinde bir şekilde bu n tane sunucu üzerinde çalışan envoy servislerini tek bir yerden yönetebilmek için hazır çözümler aradık malesef bulamadık, internetteki çoğu senaryo envoy’un kubernetes içerisinde nasıl konumlandırılacağı ile ilgili senaryolar olduğundan kendimiz iki bileşenden oluşan bir rest api yapı kurmaya karar verdik.

Gomes: Baremetal sunucular üzerinde çalışan, görevi sadece Leonardo’dan gelen servis control, conf create-update-delete, service status, server maintenance, olan request’lere cevap veren GO ile yazdığımız lightweight agent.

Leonardo: Envoy konfigürasyonları üzerinde view, create, update, delete operasyonlarını yürüten, python (Django REST framework) ile yazdığımız envoy controller.

Envoy’u bir çok şekilde yönetebilmek mümkün. Biz dosya sistemi aracılığı ile yönetmeyi tercih ettik. Envoy dosya sistemini “inotify” ile izler.

Inotify: Linux dosya sistemindeki değişiklikleri izlemek için bir mekanizma sağlar.

Envoy’un en önemli yeteneklerinden biri dosya sisteminde değişiklik yapıldığında konfigürasyonu saniyeler içerisinde live ederken bunu sıfır kesinti ile yapabilmesidir. Eğer gönderilen konfigürasyon hatalı ise dosya değişmiş olsa bile envoy bir önceki çalışan versiyonu sürdürmeye devam edecektir.

Envoy Leonardo xds relation

Konfigürasyonları envoy’a Leonardo aracılığı ile ilettiğimiz ve amacımız sıfır hata ile envoy konfigürasyonu yaratmak olduğu için gönderilen konfigürasyonun doğruluğunu controller tarafında envoy’un istediği şekilde kabul etmeye çalıştık. Tabi bunları sadece kullandığımız özellikler özelinde yaptık. Burada verilen bilgiler envoy’un tüm yeteneklerini içermiyor sadece kullandığımız belli başlı özelliklerden bahsediyor olacağım.

Bizim yapımızda envoy konfigürasyonu 4 adet bileşenden oluşmaktadır.

Bootstrap: Envoy servisi ayağa kalkarken ön yüklediği dosya anlamına gelir. Admin arayüz, ip:port bilgileri, LDS ve CDS’nin conf dosya yolları bu dosyanın içerisinde yer alır.

Bootstrap conf

LDS(Listener Discovery Service): Downstream’den gelen request’ler için trafiğin ilk uğradığı kısım. Burada network ve http için filtreler, rotalar, ssl, role base access control ve ratelimit konfigürasyonları yer alır.

LDS conf

CDS(Cluster Discovery Service): LDS’den, rotalara uyan trafik belirtilen CDS’lere aktarılır. CDS üzerinde upstream’lerin connection, healtcheck, circuit breaker ayarları ve EDS conf dosyası yer alır.

CDS conf

EDS(Endpoint Discovery Service): Upstream ip port bilgisi yer alır.

EDS conf

Projemizde bu dört başlığı ayrı ayrı ele alarak bir ilişkisel database oluşturduk. Akışı şu şekilde tasarladık; Yeni bir servis oluşturmak için önce Leonardo üzerinde bir EDS oluşturulur daha sonra bir CDS oluşturularak bu EDS, CDS üzerine bind edilir. Son adım olarak LDS yaratılır ve CDS bu LDS’ye bind edilir. Bir LDS’ye n tane CDS bağlanabilirken bir CDS’ye bir tane EDS bind edilebilir. Tüm bu ilişkiyi kurduktan sonra Leonardo üzerinde oluşturduğumuz deploy veya commit yapıları ile servis canlıya alınır.

Deploy: Hangi servis deploy edilecek ise o servisin daha önce Leonardo’nun yönettiği sunucularda deploy edilip edilmediği kontrol edilir. Deploy edilmemiş veya kısmen edilmiş ise config, LDS’den başlanarak toplanır, public ip havuzundan boşta olan bir ip seçilerek reserv edilir. Tüm bilgiler toplandıktan sonra jinja template aracılığı ile envoy’un anlayacağı yapıya çevrilir. Bu oluşturulan template Gomes agent’lara iletilir. Bu işlem her domain ve sunucu için bir kere çalıştırılır. Deploy ile birlikte Gomes agent’ların yaptığı işlemler; dummy interface, domain için linux üzerinde yeni servis, domaine özel klasör yapısı ve loglama kuralı oluşturmaktır.

Ortama yeni bir server katıldığında yapılması gereken tek işlem her servis için birer Deploy çalıştırmak olacaktır.

Commit: Deploy’dan farkı, değişiklikleri uygulamak için kullanılır. Bir servis üzerinde değişiklik yapıldığında sadece o servis için Commit aktif olur. Commit, Leonardo’nun yönettiği sunucuların hangilerinde Deploy edilmiş ise o sunucular da bulunan Gomes agent’lara değişiklikler gönderilerek servisin güncellenmesini sağlar.

Deploy ve Commit akış diagramı

Yazdığımız controller’da envoy’un bazı yeteneklerini birer module olarak ele aldık. Örn. x servisine ekstra bir route eklenecek ise önce module altında bir route yaratılmalı sonra ilgilli servisin LDS’sine bind edilmeli. Bunun gibi multi domain, ratelimit, rbac gibi yetenekleri de optional hale getirdik. Aşağıdaki görsel LDS üzerinde değişiklik yapabileceğimiz alanları gösteriyor CDS ve EDS özelinde de sahip oldukları yetenekleri update etmek mümkün. Bunları ihtiyaç oldukça arttırmak elimizde, tabiki herbir parametreyi kontrol ederek içeri almak kaydıyla. Leonardo içerisinde bahsedemediğim birçok özellik barındırıyor projeyi biraz daha geliştirdikten sonra community’e açmak planlarımız ararsında yer alıyor.

LDS parametreler.
Leonardo Controller UI

Ratelimit

Envoy internal olarak bu özelliği sunmuyor. Fakat burada bulunan GO ile yazılmış ratelimit uygulaması sayesinde GRPC üzerinden haberleşerek ratelimit yapabilme imkanı veriyor. Ratelimit yapımıza bakacak olursak iki adet fiziksel sunucu üzerinde birer adet standalone redis ve container olarak hizmet veren beşer adet ratelimit servisi bulunuyor. Biz burada saniye cinsinden ratelimit uygulamak için bir redis’e, dakika, saat ve gün cinsinden ratelimit uygulamak için diğer redis’e gönderecek şekilde ratelimit yapımızı oluşturduk. Yapabildiğimiz en yüksek loadtest sonucunda sorunsuz bir şekilde sınırlamamızın çalıştığını gözlemledik.

Ratelimit servis yapısı

Logging & Monitoring

Tüm bu uğraşın sonucunda benim işin eğlenceli kısmı olarak gördüğüm, loglamayı ELK ile, metric monitoring işini de prometheus-grafana ikilisi ile çözdüğümüz dinamik bir yapı kurguladık.

Logging: Envoy, üzerine gelen request’lerin access loglarını bizim belirlediğimiz bir dosyaya yazıyor, logları logstash’e belirli aralıklar ile yollayarak ELK üzerinde parse işlemini gerçekleştiriyoruz. Leonardo ve Gomes de hem PYTHON’da hemde GO’da bulunan logstash kütüphanelerinden yararlanarak log’ları direkt logstash’e gönderiyoruz.

Monitoring: Envoy’un admin arayüzünde prometheus için sağladığı “/stats/prometheus” url’i ile erişebileceğiniz bir prometheus exporter sayfası bulunuyor. Bu sayfa her envoy servisi için özel olarak ayağa kalkıyor. Leonardo üzerinde her açılan yeni servis, deploy edildiğinde ortamımızda bulunan consul’a bu servis için bir external servis kaydı giriyoruz. Daha sonra prometheus konfigürasyonumuz direkt consul’a baktığından yeni gelen servisi algılayıp metric’leri scrap etmeye başlıyor.

Prometheus consul conf

Consul üzerinden servislerin sağlık durumlarını /ready url’ine healtcheck yaptırarak kontrol edebilirsiniz. Burada dikkat etmeniz gereken consul’a servisinizi eğer agent’sız yapıda external servis olarak kayıt ederseniz consul-esm ile monitor etmeniz gerekecektir.

Envoy upstream metriclerini prometheus exporter içerisinde vermiyor fakat json olarak sunuyor. Bizim gibi sizde tüm her şeyi görmek geçmişe bakıp analiz etmek isterseniz GO ile veya istediğiniz herhangi bir dil ile upstream metriclerini json olarak çekerek time series herhangi bir database’e kendiniz gönderebilirsiniz.

Upstream metrics

Öğrenimler & Tavsiyeler

  • Envoy’u getenvoy paket yöneticisi ile kullanın. Böylelikle upgrade işlemleriniz kolay ve sancısız olacaktır.
  • Her bir envoy servisini ayağa kaldırırken LimitNOFILE değerini yükseltin. Varsayılan değer linux işletim sistemlerinde 1024 yeterli olmayacaktır.
  • Eğer trafiğiniz http1.1 ise ve arkaya baş harfleri büyük olan header bilgisi iletiyorsanız envoy header’ları küçülterek ileteceğinden uygulamanızda problem yaşayabilirsiniz buradaki dökümanı takip ederek çözümlenebilir.
  • Switch üzerindeki ecmp algoritmalarınının yük altında nasıl davrandığını gözlemleyin, yanlış algoritma veya konfigürasyon seçimi yük altında paketlerin drop edilmesine sebebiyet verecektir.
  • Hata yaparak veya konfigürasyon değiştirerek sunucuları sürekli yeniden kurmanız gerekecek bunun için ansible ile bir pipeline oluşturun. Yaptığınız her değişikliği bu pipeline’a ekleyin ki sonradan ortama dahil olacak yeni sunucu için sadece o pipeline’ı çalıştırmanız yeterli olsun.
  • Envoy LDS konfigürasyonunda http idle_timeout değerini 180 saniye olarak set edin. Bu 3 dakika boyunca işlem yapmamış kullanıcılar için connection’ı açık tutmayarak, sunucunuza doğru açılan tcp connection’ları minimumda tutmanıza yardımcı olacaktır. Default belirtilmez ise 1 saattir.
  • Envoy CDS konfigürasyonunda circuit breaker değerlerini kendinize uygun hale getirin. Default değerler yüksek trafik alan bir sistemde upstream’lere giden trafiği kesebilir.
  • CDS üzerinde tanımlanan healthy_panic_threshold parametresini kendinize göre ayarlayın detaylı bilgiyi buradan bulabilirsiniz.
  • Eğer bizim gibi kendiniz bir controller yazmak isterseniz bunun CI/CD sürecini tasarlayın. Sürekli geliştirme ihtiyacı her zaman olacaktır.
  • Eğer geleneksel UI üzerinden load balancer yönetmeye alışıksanız, controller’ınızın önüne bir arayüz yazın her şey daha görünür ve kolay olacaktır.
  • Sunuculara bakım yapmak istediğinizde frr servisini durdurmak yeterli olacaktır. Switch o sunucu özelinde komşuluğu kaybedeceğinden trafik artık o sunucuya gönderilmeyecektir. Burada dikkat edilmesi gereken konu sunucunun switch’e doğru olan 0.0.0.0/0 rotasının kaybolmaması gerekir, bu rota kaybolursa sunucu açık olan connectionları kapatmaya çalışırken rota bulamayacak ve cpu load yükselecektir.

Sonuç

Günün sonunda yüksek performanslı istediğimiz gibi scale edebileceğimiz bir ortam elde ettik. Burada envoy’u tercih etmemizdeki nedenler, dökümantasyonu çok iyi ve diğer rakiplerine oranla elma-elma testler yaptığımızda daha üstün response time’lar verdiğini gözlemledik. Yazının başında da belirttiğim gibi kat ve kat daha fazla trafik aldığımız kasım ayını, bu ortamımız ile sorunsuz bir şekilde tamamladık.

Tek domain çift DC grafiği yeşil ve mavi olan envoy
Çift DC Leonardo projesi

Projede kullandığımız teknolojiler ve diller.

Envoyproxy, Python, Django Rest Framework, GO, Gin, ELK Stack, Grafana, Prometheus, Consul, Influxdb, Ansible, Kubernetes, Docker, Jenkins, Frr, Redis, Postgresql, Js, Django, Bgp, Ecmp, Gitlab

Teşekkürler ❤

Bu projeyi beraber yürüttüğümüz takım arkadaşım Murat BABACAN’a bize her konuda destek çıkan iş ortağımız Çağrı ERSEN’e ve projeye olan inancıyla bizi sürekli teşvik eden yöneticimiz Kemal ERİŞEN’e teşekkür ederim.

--

--