Ne eeettin Istio?(Sidecar Lifecycle Problemi) 🤔 ⛵️

developer-guy
Trendyol Tech
Published in
8 min readOct 21, 2020

--

Merhabalar, bu yazımıza diğerlerinden farklı olarak Dynamic Config & Secret Management sürecindeki yaşadığımız bir tecrübemiz ilham veriyor olacak, küçük bir not olarak eğer bu süreç hakkında bilgi sahibi değilseniz bu konuyu detaylıca işlediğimiz Türkçe ve İngilizce olarak paylaştığımız makalelerimizi okumanızı tavsiye ederim.

Bir tık konuya giriş yapalım , bilindiği üzere Trendyol’da Service Mesh implementasyonu olarak Istio’yu kullanıyoruz dolayısıyla geliştirdiğimiz Platform ürünleri tarafında bu konunun etkilerine de uygun olacak şekilde geliştirme yapmamız gerekebiliyor işte tam olarak da yazımızda bu konuda yaşadığımız bir “fail case”’inden bahsediyor olacağım.Yine küçük bir not Trendyol’da bahsetmiş olduğum bu Service Mesh’in kullanımına nasıl karar verildiği ve implementasyon olarak neden Istio’nun seçildiğine dair part 1 ve part 2 olacak şekilde detaylıca ele alındığı yazılarımızı da inceleyebilirsiniz.

Platform ürünlerini geliştirirken en temel amaç, domain ekiplere bu ürünlerin müşterisi gözüyle bakarak ekiplerin yaşadığı ortak problemlere getirdikleri farklı çözümleri, bir standart ve centralize hale getirip entegrasyon noktasında da kolaylık sağlamak. Dolayısıyla bizim ürünlerimizi domain ekipleri kullandığından bu ürünlerle ilgili feedbackleri alabilmek, yaşadıkları problemleri anlayabilmek için Platform ekibiyle iletişime geçebilecekleri iletişim kanallarımız mevcut , bizimde sorumluluklarımızdan bir diğeri bu feedbackleri , problemleri dinleyip ürünlerimizi daha da iyileştirmek.

Ekiplerin bu iletişim kanallarından Dynamic Config & Secret Management ürünü ile ilgili bize ilettikleri problemlerden bir tanesi Pod’larının nadiren de olsa CrashLoopBackOff state’ine düşürüyor olmamızdı.Biz de konu ile ilgili araştırmalarımıza devam ederken almış olduğumuz bir design kararının buna sebep olabileceğini düşünerek yola çıktık.

Dynamic Config & Secret Management tarafında application container’ı için konfigürasyonların sorumluluğu yönetecek olan bir adet config-init (initContainer) ve bir adet config-agent (sidecar) olmak üzere 2 adet container’ı ekiplerin Pod’ları içerisine inject ediyoruz, bu container’lardan config-init, uygulamanın startup time’da ihtiyaç duyabileceği dosyaların hazırlanmasını sağlarken , config-agent ise uygulamanın runtime sırasında değişebilecek olan konfigürasyon değerlerinin güncel tutulmasını sağlamakla görevlidir.

Dynamic Config & Secret Management ürününü geliştirirken dikkat etmemiz gereken en önemli nokta Secret Management tarafı için kullanmış olduğumuz Vault’a erişimin güvenli şekilde yapılmasıydı. Çünkü yukarıda bahsetmiş olduğumuz bu init ve sidecar container’ların bir şekilde Vault’a gidip application container’ı için Vault’dan bu değerleri almalıydı.Bu kısmın oldukça secure şekilde ilerlermesi gerekiyordu.Biz de Kubernetes native bir çözüm geliştirdiğimizden dolayı araştırmalarımız sonucu Vault K8S Auth Method’u fark ettik ve Vault üzerinden Token’ı bu auth method ile elde edebileceğimizi gördük.Daha sonra konu bu Token’ın secure bir şekilde bu init ve sidecar container ile nasıl paylaşabileceğimize geldi.

İşte dediğim design kararı bu noktada karşımıza çıkıyor , ilk yapıda Admission Webhook tarafında Pod için Vault’a init ve sidecar adına authenticate olup Token’ları Response Wrapping dediğimiz bir feature sayesinde Cubbyhole Secret Engine’de saklayıp , Token’a ulaşmak için kullanılabilecek single-use altını çiziyorum single-use -çünkü ekiplerin bize ilettiği CrashLoopBackOff state’ine Pod’un düşme sebebi bu- Response Wrapping Token’lar elde ediyorduk ve bu Response Wrapping Token’ları da inject ettiğimiz init ve sidecar container’a environment variable olarak geçiyorduk.Dolayısıyla kubectl gibi bir client ile Pod’un detayını görüntülediklerinde buradaki Token asıl Token yerine Response Wrapping Token olacak ayrıca Response Wrapping Token’lar single-use olduklarından dolayı init ve sidecar tarafından kullanıldığı anda invalid’e düşüyor olacak ve bu token ile de işlem yapmak da mümkün olmayacak, her şey yolunda gibi 😄👌 “gibi” diyorum çünkü şimdi gelelim bu yapının bize yaşattığı probleme.

Dediğim gibi dikkat çekmek istediğim noktalardan ilki Pod için Admission Webhook’un Vault’a authenticate olması diğer bir nokta Response Wrapping’in single-use olması.Aslında bahsetmiş olduğumuz designda yaşayabileceğimiz problemi tahmin etmiştik fakat sidecar containerımızın restart etmesine sebebiyet verebilecek bir durumun oluşmayacağını düşünüyorduk fakat kampanya dönemlerinde Kubernetes clusterları üzerindeki yük artışına bağlı olarak bazı ekiplerin HPA ile uygulamalarını scale ederken bizim sidecar containerının birden fazla kere çalıştırılmaya çalışıldığını gördük ne demiştik response wrapping token single-use bir token eğer bizim sidecar birden fazla kere çalıştırılmaya çalışılırsa aynı response wrapping invalid duruma düşeceğinden Pod’u CrashLoopBackOff state’ine düşürüyorduk çünkü yine dediğim bu yapıda sidecar container seviyesinde authenticate edip token elde etmek yerine Pod create isteği geldiği anda authenticate edip token elde ediyorduk dolayısıyla Pod CrashLoopBackOff state’ine düştükten sonra re-authenticate edip yeni bir Token elde edilebilmesi için silinmesinden başka çare yoktu.

Aslında böyle açık açık konuyu özetlediğimizde e o zaman sidecar container seviyesinde authenticate olun bu sayede container restart olsa bile tekrar authenticate olup Token elde edebilir dediğinizi duyar gibiyim 😅 Evet tam olarak da yeni yapıda böyle yapıyoruz ancak o an geliştirmelerin yoğunluğundan ve yapıyı yeni yeni tanıyıp kurguladığımızdan bunu atlamışız.

Problem

Konunun genel özetini anlattıktan sonra , gelelim şimdi sidecar container tarafında authenticate olmanın bize Istio ile beraber maliyetine ve bizim gözden kaçırdığımız noktaya.Temel problem şu Kubernetes sidecar container ve application container’ın lifecycle yönetimi konusunda biraz zayıf yani demek istediğim bu sidecar’ın application container’ı başlamadan başlatılabilmesi veyahut application container’i sonlanmadan sonlanmaması gibi senaryoları workaroundlar olmadan yönetemiyorsunuz.Bununla ilgili yazının sonunda çok güzel referanslar paylaşıyor olacağım o yüzden burada çok küçük detaylara değinip yazıyı çok uzatmadan geçiyor olacağım fakat mutlaka göz atmanızı tavsiye ederim.

Şimdi düşünelim, Istio benim container’ımın inbound/outbound trafiğini yönetmekten sorumlu data-plane componenti olan istio-proxy’in Pod’a inject edilmesi , control-plane’i sayesinde istio-proxy’lerin konfigüre edilmesi gibi süreçleri yönetiyor, dolayısıyla böyle hassas bir noktada görev alırken benim application container’ım ile ilgili problemlere yol açması da muhtemel.

Aslında tüm konunun özetini şu aşağıdaki cümle ile açıklamak istiyorum:

Soon after enabling an application for Istio we noticed that some would crash immediately upon startup and then work fine after a few container restarts. At first this was puzzling, but it became apparent that it only occurred on services that made outbound calls on startup. We figured out that this was occurring because the application container was starting before the istio-proxy container was ready which then caused outbound requests to fail. The thing with sidecar containers in Kubernetes is that you have no guarantee on the ordering of execution. To mitigate this, we just let the application fail and rely on Kubernetes to restart it while the istio-proxy becomes ready.
https://engineering.hellofresh.com/everything-we-learned-running-istio-in-production-part-2-ff4c26844bfb

Özetle Istio’ da aynı bizim çözümümüzdeki gibi istio-init initContainer ve istio-proxy Sidecar container’ı Pod içerisine inject ediyor ve ve ve ve bu container’lar ready olmadan outbound trafiğe izin vermiyor!(dediğim workaroundları yaparak buna müsade edebiliyorsunuz, yukarıda part 1 ve part 2 olarak belirttiğim yazıda çözümlerden birisi mevcut) 💥

Kubernetes’de init containerlar sequantial başlatıldığından bu problemi init container inject ederken istio-init’den önce config-init’i inject et gibi bir mantıkla çözdük neden buna ihtiyaç oldu , UNUTMAYIN artık Vault’a Admission Webhook seviyesinde değil container seviyesinde authenticate oluyoruz yani container ayağa kalkarken bir outbound trafiğe ihtiyaç duyuyor.Bunun workaround’unu yaptığımız kod parçacığını şöyle incelemeniz için paylaşıyorum.

Şimdi gelelim olay saatine, günlerden Cuma saat 10–11 civarı , ilgili featureı geliştirip clusterlara deploy ettiğimiz günün ertesi yani, entegre olduğumuz uygulamalardan birinde bir problem çıktı ve bunu incelemeye koyulduk ancak incelerken bir de fark ettik ki sidecar tarafı , authenticate olurken kullandığımız curl komutuna fail flag’ini eklemediğimizden ve bash scriptin başına fail fast için “set -e” koymadığımızdan dolayı uygulama ayağa kalkarken Vault’a gidip connection refused aldığında işini sonlandırmak yerine Token’ini alamadan başlamış, bu yüzden de sürekli *missing client token* hataları atmış, aslında gördüğünüz gibi kritik bir hata 😰

FAKAAT config-init işini doğru şekilde yapabildiği için application container’ının ayağa kalkması için gerekli olan dosyalar filesystem’de doğru şekilde oluşturulmuş dolayısıyla uygulamaların ayağa kalkmasına doğrudan etki etmiyor olsak dahi secret güncellemelerinin container’a yansıtamamamız söz konusu.Buradan lütfen test etmiyor musunuz vs gibi bir algı çıkmasın çünkü çok sıkı e2e test süreci uyguluyoruz hatta init ve sidecar olarak inject ettiğimiz container’ımızın dahi testini yapıyoruz fakat o ana kadar testlerimize Istio feature’ı eklememiştik ve Istio enabled bir Pod’da entegrasyon denerken anlık uygulamayı scale edip ayağa kalkabiliyor mu diye test etmiştk ki kalkıyordu dolayısıyla logları inceleyelim vs diye düşünmeden onay vermiştik dediğim gibi uygulama ayağa kalkabiliyordu çünkü config-init işini doğru şekilde yapabilmişti.

Çözüm

Hemen çözüm olarak yukarıda da belirtmiş olduğum bash script’e fail fast özelliği kazandırıp ardından curl komutuna fail flag’ini ekledik, ancak yeterli mi tabiki değil neden çünkü istio-proxy ready olana kadar bizim bir şekilde bu connection’ı denemeye devam etmemiz gerekiyor işte burada da curl’ün kendi içerisinde builtin desteklediği retry logic’den faydalanıyoruz.Hemen çözüm olarak uyguladığımız curl komutunu paylaşıyorum.

Bu sayede istio-proxy sidecar ayağa kalkana kadar Connection Refused hatalarında curl komutu 20 kere 1'er saniye aralıklarla request’i deneyecek ve başarılı olduğu durumda Token’ı elde edip işine devam edebilecekti.

Alternatif Çözümler

Bu noktada ilk fix’i çıktıktan sonra alternatif olarak neleri değerlendirebileceğimizi araştırmaya başladık bu noktada karşımıza
istio-proxy’nin 15021 yani health check’i aldığı endpoint’e (Istio 1.7) container’in depend etmesi çözümü çıktı yani istio-proxy’i healthy olduğu durumda bizim uygulamamız restart etmeden çalışmaya başlayacaktı.

containers:
- name: app
image: xxx
command:
lifecycle:
postStart: # same as istio-proxy readiness probe, when this hook exec failed, the app container will be restarted
httpGet:
path: /healthz/ready
port: 15021

Fakat Istio bağımlı bir çözüm geliştirmek istemediğimizden bunu tercih etmedik. Daha sonra bizim için asıl noktanın Vault’a connection kurmak olduğunu ve Vault’a connection’ı test eden bir script geliştirip ardından health checklerden exec probe’unu kullanarak da çalıştırabileceğimizi düşündük.

Bunu da tercih etmememizin nedeni ise eğer healthcheck’i koymuş olsaydık Vault’a connection runtime sırasında koparsa Pod’un state’i içerisindeki container’ların state’i ile bağlantılı olduğundan bizim uygulama fail edeceğinden Pod unhealthy duruma düşürecekti ve application container’ına giden trafiğin kesilmesine neden olacaktı , biz ise Vault’a olan connection runtime sırasında kesilse dahil sidecar tarafındaki retry logic sayesinde sonsuz kere connection kurmayı deneyip connection kurduğu anda da çalışmasına devam eden ve application container’ının trafiğine etki etmeyen bir yapıyı tercih ettik.

Özet

Her şey her zaman yolunda gitmeyebiliyor ama asıl önemli olan bu hatalardan ders çıkarmak , öğrenmek ve gelişmek . Umarım bu yazımızdaki yaşadığımız Istio case’i sizi böyle hatalar yaşamaktan kurtarır ayrıca sizin uyguladığınız benzer senaryolara karşı çözümleri de yorum olarak atabilirsiniz.

Saygılar..

Referanslar

--

--

developer-guy
Trendyol Tech

🇹🇷KCD Turkey Organizer🎖Best Sigstore Evangelist🐦SSCS Twitter Community Admin✍️@chainguard_dev Fan📦Container Addict📅Organizer at @cloudnativetr•@devopstr