Kubernetes’e Göç Hikayemiz

Ertugrul Aslan
Apinizer
Published in
9 min readAug 12, 2021

--

Bu makalede Apinizer ürümüzün Kubernetes üzerinde çalışabilmesi için yaptıklarımızı anlatmaya çalışacağım.

Öncelikle, bilmeyenler için; Apinizer, uçtan uca API Yaşam döngüsünü (Full API Life Cycle) yönetmeyi sağlayan API Yönetim ve Entegrasyon Platformu’dur (https://apinizer.com).

Apinizer’ın 2.x ve öncesi sürümleri Embedded Tomcat ve Undertow üzerinde çalışan bir yapıya sahipti.

Ana modüllerden ilki olan Yönetim Konsolu Web tabanlı bir uygulamaydı. JSF, Primefaces, Spring, Hibernate teknolojileri kullanılarak geliştirilmiş, konfigürasyon verileri için MySQL veritabanı kullanılmıştı ve Embedded Tomcat üzerinde, Java 1.8 versiyonu ile çalışmaktaydı.

İkinci ana modül ise tüm API isteklerinin karşılandığı ve politikaların işletildiği Gateway Engine modülüydü. Herhangi bir open source proje (Apache Camel gibi) üzerine inşa edilmemiş, pure Java ile Apinizer ekibi tarafından geliştirilmişti. Yönetim Konsolu ve Gateway Engine, birbirlerinden tamamen izole bir şekilde çalışmakta ve Gateway Engine kısmı Yönetim Konsolu’ndan yönetilmekteydi.

Gateway Engine paketlenmiş (basit bir container gibi düşünebilirsiniz) bir yapıya sahipti ve Active — Active Cluster olarak çalışabiliyordu. Yönetim konsolu üzerindeki Server Management arayüzleri üzerinden “Oluştur”, “Başlat”, “Durdur”, “Güncelle”, “Scale Et”, “Sil” gibi işlemler yapılabiliyor, Gateway Engine’ler sanki bir Container Orkestrasyon aracı kullanırmış gibi yönetilebiliyordu. Müşterilimizden de bu doğrultuda çok güzel geri bildirimler aldık :).

Yönetim Konsolu ve Gateway Engine kısmının ayrı olması bize çok ciddi esneklik sağladı ve her müşterinin topolojisine uygun kurulumları çok rahatlıkla gerçekleştirdik. Versiyon güncellemeleri yaparken bile güncellemelerimizi sıfır kesinti (Rolling Upgrade) ile müşterilerimizin sunucularından yapabiliyorduk.

Öyleyse ne oldu da stable çalışan, her türlü topolojide konfigüre edilebilen bir mimariyi yeniden tasarladık ve Kubernetes üzerinde çalışan bir yapıya dönüştürdük? Evet ben de kendime sormaya başladım aynı soruyu :).

Kubernetes’e Göç Hikayemizin Başlaması

Göç hikayemize başlamadan önce şunu özellikle belirtmek istiyorum: Apinizer’ın teknik olarak en iyi şekilde olması için araştırmalar ve geliştirmeler hiç bir zaman bitmedi, bitmiyor ve görünen o ki bitmeyecek :).

Yukarıda bahsettiğim gibi, Apinizer Yönetim Konsolu JSF teknolojisi ile geliştirilmişti. Ekibimiz JSF ve Primefaces’e oldukça hakim olduğu için teknik sorunlarla çok karşılaşmadık ve kısa sürede ürüne çok güzel özellikler kattık. Ama müşteri sayımız artıp, ekibe yeni kişiler dahil etmeye başladıkça, ne yazık ki kullandığımız teknolojilerde deneyimli birilerini bulamamak bizi zorlamaya başladı. Çünkü ortaya çıkan yeni gereksinimleri ya da güncellemeleri yeni arkadaşlarla hayata geçiremedik.

Buna ek olarak, arayüz testleri açısından JSF bizi çok yordu. Özellikle de bir ürün söz konusu ise testlerin ne kadar önemli olduğunu söylememe gerek yok sanırım. :)

Sonuç olarak teknolojimizi güncelleme kararı kaldık. 2015 yılında geliştirilmeye başlayan Apinizer, 2018 yılının başında çok önemli bir kararla yeni ekip kurularak güncel teknolojilerle yeniden yazılmaya başladı.

Apinizer’in yeni versiyonunda aşağıdaki teknolojileri kullandık.

  • Yönetim Konsolu için: Spring Boot, Angular, Typescript, SpringData, MongoDB
  • Gateway Engine: Undertow, EclipseJ9

Kim bilir belki de kafamızda, proje tecrübesi olmuş tüm yazılım mühendislerinin düşündüğü gibi “imkanım olsa, ben bunu yeniden çok daha güzel yaparım” düşüncesi vardı 😉.

İşte Kubernetes kararı da bu süreçte ortaya çıktı. Aslında çok uzun zamandır yol haritasında olması rağmen bir türlü başlayamadığımız Kubernetes maceramıza başladık ve Apinizer’i yeniden yazarken Kubernetes üzerinde çalışacak şekilde tasarladık. Bu arada bazı ürün özelliklerinde çok radikal değişiklikler yaptık (örneğin önceki sürümün en önemli bileşeni olan Server Management modülünü tamamen kaldırdık 😂). Sadece bununla da yetinmedik ve Kubernetes ile yapabilen birçok şeyi Apinizer’dan kaldırdık. Mesela önceki versiyonda bizim geliştirmiş olduğumuz, sunucu Health Check’leri, disk durumu kontrolü, açılıp kapanma durumunda bildirimlerde bulunma gibi özellikleri, Kubernetes tarafından otomatik olarak yapıldıkları için yeni sürüme taşımadık. Bunun yerine Kubernetes tarafından gerçekleştirilen bütün bu işleri Yönetim Konsolu üzerinden yönetilebilir hale getirdik.

Neden Kubernetes’e Göç Ettik

Özellikle mimariyi etkileyen bir teknoloji seçerken bunun tek bir sebebi olmaz. Birçok neden olmalı ki mimarinizde böyle bir değişiklik yapasınız. Bizim için de durum aynıydı. Aşağıdaki nedenlerden dolayı Apinizer’ı Kubernetes’e taşımaya karar verdik.

  • Bütün dünyanın kullandığı ve güvendiği bir Container yönetiminden faydalanmak.
  • Sunucularda oluşacak herhangi bir darboğazda müdahale etmesi ve gerekirse yeniden başlatması.
  • Imaj güncellemelerinin kolaylıkla yapılması.
  • Yatayda ölçeklendirmenin kolayca yapılabilmesi.
  • Birden çok Ortam (aşağıda detayını bulabilirsiniz) oluşturulabilmesi ve bunların farklı kaynaklar ayrılarak yönetilebilmesi.
  • Kubernetes’in müşterilerimiz tarafından daha yaygın olarak kullanılmaya başlaması ve bizden de Apinizer’i Kubernetes üzerinde çalıştırmamızı talep etmeleri.
  • Kubernetes’in popüler olması :)

Kubernetes üzerinde çalışan Apinizer Mimarisi

Aşağıda Apinizer’in yeni mimari yapısını görebilirsiniz.

Yaşadığımız Bazı Zorluklar

Kubernetes sizin geliştirme yaparak ya da bir sürü farklı teknolojiyi kullanarak çözdüğünüz problemleri tek bir platformda çözdüğü için size o kadar çok değer katıyor ki, belki de yaşadığınız zorlukları görmüyorsunuz bile 🙂. İlk olarak baştan sona Kubernetes’in özelliklerini öğrenmeye, bulabildiğimiz bütün dökümanları, tutorial’ları ve en önemlisi Production sistemde Kubernetes kullanan insanların makalelerini okumaya başladık. Tabi ki bu öğrenme süreci, özellikle API Yönetim Platformu gibi kurumların/şirketlerin omurgasında çalışacak bir sistemi koşturacağımızdan zahmetli ve uzun oldu. Çünkü mümkün olan tüm başarılı başarısız senaryoları öğrenip gerektiğinde ona göre aksiyonlar almanız gerekiyor.

Öğrenme sürecinde ilerledikten sonra Apinizer üzerinde gerçekleştirime başladık. Gerçekleştirimde karşılaştığımız en büyük zorluk, Apinizer projesinin başladığı ilk günden beri bağlı kaldığımız “Easy to” sloganımız ve kullanım kolaylığı ilkemiz oldu. (Zorluklara rağmen bunlara hep bağlı kaldık, kalıyoruz ve kalacağız 😉). Bu ilke doğrultusunda ilk sürümden itibaren kullanıcılarımızın herhangi bir teknoloji veya dil bilmesine gerek bırakmadan her şeyi Yönetim Konsolu üzerinden yapabilmelerini sağladık. Dolayısıyla bu gerçekleştirimde de amacımız ve bakış açımız hiç Kubernetes bilmeyen kişilerin bile Apinizer’ı kullanabilmesi ve bu sırada eğer istemiyorsa Kubernetes bileşenleri ile muhatap olmamasıydı. Dolayısıyla Gateway ve Cache Engine’lerinin oluşturulabilmesi, birden fazla Ortam (Environment) (Kubernetes’deki Namespace) tanımlanabilmesi, bunların ölçeklenebilmesi, monitor edilmesi, logların izlenmesi ve versiyon güncellemelerinin yapılması gibi (hepsi Kubernetes üzerinde birkaç komut ve yaml dosyaları ile rahatlıkla yapılabilen) yönetimsel işlemlerin hepsinin Apinizer’in Yönetim Konsolu üzerinden Kubernetes bilmeyen kişiler tarafından yapılabilmesi gerekiyordu. Bunu başardık! 😉.

Bu işlerin Apinizer Yönetim Konsolu üzerinden yapılabilmesi için Kubernetes entegrasyonlarına ihtiyaç vardı. Akla ilk gelen ve bizim de ilk uyguladığımız yöntem Kubernetes Management REST API’larını kullanmak oldu. İlk başta çok kolay ve kullanışlı geldi ancak daha sonra Kubernetes bileşenlerini (Namespace, Deployment, Replicaset, Pod, Service, Metric) yoğun olarak kullanmaya başlayınca Kubernetes API’ları ile entegre olmak development için zor olmaya başladı ve biz de yeni çözümler aramaya başladık. Araştırmalarımız sonucu çok başarılı ve istediğimiz her şeyi kolayca gerçekleştirmemize olanak veren “fabric8 Kubernetes Client” kütüphanesini (https://github.com/fabric8io/kubernetes-client) keşfettik. Sunduğu Java kütüphanesini kullanarak Apinizer’in Kubernetes entegrasyonlarını kolayca gerçekleştirdik. Burada şunu belirtmeliyim: Kubernetes API’ları ile bunları yapabiliyorsunuz, ancak API Request/Response değerlerini bilmek, Token yönetimini yapmak gibi bilinmesi gereken birçok konu var ve bu da azımsanacak bir iş değil. Fabric8 bir Java Client verdiği için bunları yapmak daha kolay. Aşağıdaki bir kaç kod örneği paylaştım.

Örneğin yandaki kod parçası ile, Apinizer Yönetim Konsolu’nun çalıştığı Kubernetes Cluster’a otomatik client olabilirsiniz, Token bilmeye gerek yok. 🙂

Yukarıdaki kod parçası ile de bir deployment bileşeni oluşturup ReplicaSet, Pod image vs gerekli tüm bilgilerini tanımlayabiliyorsunuz.

Fabric8 kütüphanesini öğrenince, ister istemez Kubernetes’deki her şeyi öğreniyorsuz 😉.

Apinizer yönetim konsolu üzerinden Kubernetes konfigürasyonu

Yukarıda Apinizer’ın mimarisini paylaşmıştım. Burada yine bir özet geçeyim.

Apinizer API Yönetim Platformu; Yönetim Konsolu, Gateway Server (Worker), Cache Server, Integration Server gibi tamamen birbirinden bağımsız uygulamalardan oluşmaktadır.

Yönetim Konsolu: API yönetimi ile ilgili tüm işlerin kolayca yapılmasını sağlayan Apinizer Yönetim Arayüzüdür.

Gateway Server (Worker): Tüm API istek/yanıtlarının üzerinden geçtiği, mesaj dönüşümleri, protokol dönüşümleri, trafik yönetimi, politika uygulama, loglama gibi işlerin yapıldığı, Apinizer’in en önemli bileşenidir.

Cache Server: Apinizer’da Cache ihtiyacı için gerekli tüm işlemlerin yapıldığı uygulamadır.

Integration Server: Apinizer’daki Görev Akışlarının (Task Flow) bağımsız olarak çalışmasını sağlamak için kullanılan uygulamadır.

Kubernetes ile Apinizer’ın entegre olduğu kısım bu 4 Java uygulamasının Kubernetes platformunda çalıştırılmasıdır. Apinizer Yönetim Konsolu üzerinde bu yapının yönetilmesi gerekmektedir.

Öncelikle bir apinizer-deployment.yaml dosyası ile Kubernetes Cluster’ımıza Yönetim Konsolu’nun deployment’ını yapıyoruz (https://docs.apinizer.com/apinizer-dokumantasyonu/kurulum-ve-konfiguerasyon-1180802.html). Daha sonra Kubernetes ile ilgili diğer tüm işleri Apinizer Yönetim Konsolu üzerinden yapabiliyorsunuz. API Proxy’lerinizi oluşturup yayınlayabilmek için en az bir Ortam (Environment) oluşturmanız gerekiyor.

Ortam (Environment): Apinizer’da tanımlanan API Proxy’ler ya da API Proxy Grupları, bir veya birden fazla Ortam üzerinde çalışabilir. Apinizer, istenildiği kadar Ortam oluşturulmasına olanak verir ve bu Ortamlar için farklı kaynaklar ayrılabilir. Böylece Development & Test, Sandbox ve Production ortamlarını birbirlerinden ayırmak mümkün olduğu gibi, farklı istemci gruplarına farklı kaynaklar sağlanarak değişen performans gereksinimleri karşılanabilir. Oluşturulan Ortamların hepsi bir Kubernetes Cluster içinde yer alır.

Aşağıdaki şekilde görüldüğü gibi, Apinizer’daki tüm API istekleri Ortam içindeki Gateway Engine’ler tarafından işlenir.

Aşağıdaki görsel, Apinizer’ın Yönetim Konsolu üzerinden kendi topolojiniz ve sistem kaynaklarınıza uygun Ortamlar oluşturabileceğiniz arayüze ait.

Bu arayüzdeki alanlar, Ortam’ın metadata bilgilerini ve ilgili Ortam’daki API trafiğinin hangi Elasticsearch Cluster’ına yazılacağını belirliyor. Burada önemli olan “Environment Name” bölümü. Çünkü “Environment Name” Kubernetes’deki Namespace kavramına denk geliyor. Yani Apinizer’da oluşturduğunuz her bir Environment için Kubernetes’de aynı isimde bir Namespace oluşturulur. Kubernetes Namespaces ile ilgili detaylı bilgiyi şurada bulabilirsiniz.

İşin heyecanlı kısmı işte burada başlıyor :). Apinizer Gateway Engine ve Cache Server’larının tanımlanması ve konfigüre edilmesi.

Gateway Engine ve Cache Server tanımlarken aslında Kubernetes Deployment bileşenini hazırlıyoruz. Şimdi bu değerleri ve arka tarafta gelişen işleri tek tek açıklayalım.

  • Count: Deployment’in ReplicaSet değeri, yani kaç adet Pod oluşturulacağıdır.
  • CPU: Her bir Pod’un max CPU Core sayısıdır.
  • Memory: Her bir Pod’un kullanacağı max Memory bilgisidir. Apinizer varsayılan Memory değerinin %75'ine kadar kullanılabilmesine göre ayarlamalar yapar. Ama siz isterseniz Additional Parameters değeri ile bu ayarı değiştirebilirsiniz.
  • Service Port: Oluşturulan Pod’lara dışarıdan erişim için gerekli olan NodePort tipinde Servis.
  • Additional Parameters: Gateway ve Cache Server için tanımlanabilecek parametreler.

Bu değerleri girip publish ettiğinizde Apinizer aşağıdaki adımları da ekleyerek Kubernetes üzerinde bir Namespace ve içinde yukarıda bahsedilen bileşenleri oluşturur.

Tanımladığınız değerlere ek olarak her bir deployment için Kubernetes’e aşağıdakileri ekleyerek Publish eder:

Liveness Probe, Readiness Probe, Startup Probe ve Deployment Strategy. Aşağıda kısaca açıklamaya çalışacağım ama detaylı bilgi için: Liveness, Readiness and Startup Probes.

Liveness Probe: Uygulamanızı Pod üzerinde konteyner olarak çalıştırdığınızı, ancak bazı nedenlerden dolayı (bellek sızıntısı, CPU kullanımı, uygulama kilitlenme vb.) uygulamaların isteklerimize yanıt vermediğini ve hata durumunda kaldığını varsayalım. Liveness Probe bu saydığımız durumları kontrol eder ve herhangi bir sıkıntı olması durumunda konteyneri yeniden başlatır.

Readiness Probe: Bazı durumlarda Liveness Probe problem saptamasa bile uygulamanızın isteklere cevap vermesi için örneğin bir dataseti doldurmak, başka bir hizmetin canlı olmasını beklemek gibi bir dizi gereksinimi vardır. Uygulamanızın istekleri bu gereksinimler tamamlanmadan alıp 500 gibi bir hata vermemesi için Readiness Probe kullanılır. Yük dolayısıyla ya da network dar boğazı gibi nedenlerle konteyner cevap veremiyorsa yine Readiness Probe devreye girip ilgili konteyneri “not ready” olarak işaretler. Böylece trafik her daim sorunsuz çalışan Podlara/konteynerlara yönlendirilmiş olur.

Startup Probe: Kısaca konteyner içindeki uygulamanın başlatılıp başlatılmadığını gösterir. Kubelet, bir konteyner uygulamasının ne zaman başladığını bilmek için Startup Probe’ları kullanır. Böyle bir Probe yapılandırılırsa, başarılı olana kadar Liveness ve Readiness olma kontrollerini devre dışı bırakır ve bu Probe’ların uygulama başlangıcına müdahale etmediğinden emin olur. Bu, yavaş başlayan uygulamalarda canlılık kontrollerini benimsemek ve çalışmaya başlamadan önce Kubelet tarafından öldürülmelerini önlemek için kullanılabilir.

Sonuç

Sonuç olarak diyebilirim ki, Apinizer’i Kubernetes’e taşımak çok doğru bir karardı. Yorulduk, ama değdi 🙂.

Son olarak, bir Ortam oluşturulduğunda Kubernetes’e deploy olan örnek bir konfigürasyon dosyasını paylaşarak yazıyı bitireyim.

https://raw.githubusercontent.com/apinizer/develop/main/example-env

Eğer siz de Apinizer’ı hızlıca denemek isterseniz demo ortamımızda bir hesap oluşturarak kullanmaya başlayabilirsiniz. https://cloud.apinizer.com

--

--