Istio Canary Dağıtımlarında HTTP 502 Hatalarını Sıfırlamak: Envoy Draining ve Pod Lifecycle Vaka Çalışması
Blog'a Dön

Istio Canary Dağıtımlarında HTTP 502 Hatalarını Sıfırlamak: Envoy Draining ve Pod Lifecycle Vaka Çalışması

Buğra Şıkel

Istio Canary Dağıtımlarında HTTP 502 Hatalarını Sıfırlamak: Envoy Draining ve Pod Lifecycle Vaka Çalışması

Giriş

Saniyede 8.500 (8.5K RPS) işlem hacmine sahip bir ödeme geçidi (Payment Gateway) mikroservisinde, Argo Rollouts kullanarak yürüttüğümüz %5, %20, %50, %100 adımlı Canary dağıtımlarında kritik bir anomali tespit ettik: Her deployment adımında ortalama 18-24 arasında HTTP 502 Bad Gateway ve 503 Service Unavailable hatası alıyorduk. Servis Seviyesi Sözleşmesi (SLA) gereği %99.99 erişilebilirlik taahhüdümüz bulunduğundan, bu kesintiler doğrudan p99 gecikme sürelerini 1.2 saniyelere çıkarıyor ve monitoring sistemlerinde alarmları tetikliyordu.

Sorunun temeline indiğimizde, Kubernetes’in asenkron pod sonlandırma (termination) mekanizması ile Istio sidecar (Envoy proxy) yaşam döngüsünün senkronize çalışmadığını gördük. Geleneksel sistemlerde göz ardı edilebilecek 1-3 saniyelik iptables güncellenme gecikmesi, 8.5K RPS gibi bir yük altında binlerce isteğin Terminating durumundaki pod’lara yönlendirilmesine, daha kötüsü uygulama konteynerinin Envoy’dan önce kapanarak gelen trafiği karşılayamamasına neden oluyordu.

Aşağıdaki bölümlerde, Kubernetes 1.28.3 ve Istio 1.18.2 ortamında bu yarış durumunu (race condition) analiz edecek, pod yaşam döngüsüne (Pod Lifecycle) ve Envoy draining süreçlerine müdahale ederek 502 hatalarını p99 180ms gecikme ile nasıl sıfırladığımızı, karşılaştığımız trade-off’lar eşliğinde somut metriklerle inceleyeceğiz.

İçindekiler

  • Kök Neden Analizi: Pod Termination ve Asenkron Ağ Gecikmeleri
  • Istio Sidecar Yarış Durumu (Race Condition) Teşhisi
  • Çözüm Mimarisi: PreStop Hook ve Envoy Draining Entegrasyonu
  • Vaka Optimizasyon Verileri (Önce / Sonra Metrikleri)
  • Trade-off Analizi: PreStop vs Application-Level Graceful Shutdown
  • Pratik Öneriler / Production Notları
  • Sık Sorulan Sorular
  • Sonuç

Kök Neden Analizi: Pod Termination ve Asenkron Ağ Gecikmeleri

Bir Kubernetes pod’u sonlandırılmak istendiğinde (örneğin Canary deployment’ın bir adımı tamamlandığında veya scale down sırasında), kontrol düzlemi (control plane) süreci asenkron olarak iki koldan yürütür. Bu asenkron yapı, yüksek trafiğe sahip ortamlarda HTTP 502 hatalarının bir numaralı failidir.

Süreç 1: Kubelet ve SIGTERM
Kubelet, pod içindeki tüm konteynerlere (uygulama ve Istio proxy) eşzamanlı olarak SIGTERM sinyali gönderir. Konteynerlere, terminationGracePeriodSeconds (varsayılan: 30s) süresince kapanmaları için süre tanınır. Bu süre dolduğunda SIGKILL gönderilir.

Süreç 2: EndpointSlice ve Kube-Proxy
Aynı anda, Endpoint Controller pod’un IP adresini EndpointSlice objesinden çıkarır. Cluster içindeki tüm node’larda çalışan kube-proxy, bu güncellemeyi dinler ve kendi node’undaki iptables veya IPVS kurallarını günceller. Trafik yönlendirme kurallarının tüm node’lara yayılması (propagation) küme boyutuna ve API server yüküne bağlı olarak ortalama 1 ile 4 saniye sürer.

Kritik Kesişim: Kube-proxy iptables kurallarını güncelleyene kadar, diğer node’lardaki servisler (veya Ingress Gateway) trafiği halen kapanmakta olan pod’a göndermeye devam eder. Eğer uygulama konteyneri SIGTERM sinyalini alır almaz kapanırsa, ağ üzerinden gelmeye devam eden istekler içeride yanıtlayacak bir proses bulamaz. Sonuç: Connection Refused (TCP RST) ve dolayısıyla istemciye dönen HTTP 502 Bad Gateway.

Istio Sidecar Yarış Durumu (Race Condition) Teşhisi

Kubernetes mimarisine Istio eklendiğinde, durum daha kompleks bir hal alır. Pod içinde iki konteyner bulunur: app ve istio-proxy. SIGTERM sinyali her iki konteynere de aynı milisaniyede ulaşır.

  1. Senaryo A (Proxy Erken Kapanır): Envoy proxy, SIGTERM aldığında aktif bağlantıları (inflight requests) anında kesip kapanırsa, uygulama işlemeyi bitirse bile yanıtı istemciye ulaştıramaz.
  2. Senaryo B (Uygulama Erken Kapanır): Uygulama anında kapanır, ancak Envoy iptables güncellenene kadar gelen yeni istekleri kabul etmeye devam eder. Envoy, arka planda (localhost üzerinden) uygulamaya istek atmaya çalışır; uygulama kapalı olduğu için upstream_reset_before_response_started{connection_failure} hatası alır ve istemciye 502 döner.

Datadog ve Prometheus üzerinden yaptığımız analizde, istio_requests_total{response_code="502", response_flags="UC"} (Upstream Connection failure) sayılarının her pod sonlanmasında sıçrama yaptığını tespit ettik. Uygulamamız (Node.js/Express) SIGTERM sinyalini aldığında process.exit() tetikliyor, aktif veritabanı sorguları yarım kalıyor ve Istio’nun açık tuttuğu TCP bağlantıları kopuyordu.

Çözüm Mimarisi: PreStop Hook ve Envoy Draining Entegrasyonu

Sorunu sıfırlamak için pod kapanma sekansını deterministik (belirlenebilir) bir sıraya oturtmamız gerekiyordu. Uyguladığımız çözüm iki aşamalıdır: Kubernetes seviyesinde trafiği absorbe etmek ve Istio seviyesinde connection draining’i yönetmek.

1. Kubernetes Seviyesi: PreStop Hook ile iptables Senkronizasyonu

Uygulama konteynerinin SIGTERM almasını geciktirerek, kube-proxy’nin iptables güncellemelerini tamamlaması için zaman kazandırdık. Bunun için pod’un lifecycle.preStop kancasını kullandık.

2. Istio Seviyesi: Native Termination Draining

Istio 1.15 ve sonrası sürümlerde sunulan proxy.istio.io/config anotasyonu ile Envoy’un kapanma sürecini (draining) uygulama kapanana kadar uzattık. Envoy, terminationDrainDuration süresi boyunca mevcut bağlantıları açık tutar, ancak yeni bağlantı kabul etmez.

Konfigürasyon (Deployment YAML)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-gateway
  namespace: payments
spec:
  replicas: 20
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 10%
  template:
    metadata:
      labels:
        app: payment-gateway
      annotations:
        # Envoy'un mevcut istekleri tamamlaması için draining süresi
        proxy.istio.io/config: '{ "terminationDrainDuration": "25s" }'
    spec:
      terminationGracePeriodSeconds: 65 # preStop (15) + App Shutdown (20) + Buffer (30)
      containers:
      - name: payment-app
        image: payment-gateway:v2.4.1
        lifecycle:
          preStop:
            exec:
              # SIGTERM sinyalini 15 saniye geciktir.
              # Bu sürede kube-proxy iptables'ı günceller, pod yeni trafik almayı keser.
              command: ["/bin/sh", "-c", "sleep 15"]
        readinessProbe:
          httpGet:
            path: /health/readiness
            port: 8080
          periodSeconds: 5
          failureThreshold: 2

Bu konfigürasyon ile kapanma sekansı şu şekilde işler:

  1. Kubelet pod’u Terminating durumuna alır.
  2. Endpoint Controller pod’u trafik yönlendirmesinden çıkarır.
  3. Aynı anda preStop kancası çalışır ve 15 saniye boyunca uyur (sleep). Bu 15 saniye içinde uygulama normal şekilde çalışmaya ve gelen (gecikmeli) istekleri karşılamaya devam eder.
  4. 15 saniye bittiğinde kube-proxy çoktan tüm node’larda iptables kurallarını güncellemiştir; pod’a ağ üzerinden yeni istek gelmez.
  5. 15. saniyenin sonunda uygulamaya ve Envoy’a SIGTERM gider.
  6. Envoy, terminationDrainDuration: 25s kuralı gereği mevcut TCP bağlantılarını açık tutar.
  7. Uygulama kendi içindeki graceful shutdown (veritabanı bağlantılarını kapatma) işlemlerini yapar ve kapanır.

Vaka Optimizasyon Verileri (Önce / Sonra Metrikleri)

Uygulanan mimari değişiklik sonrası Argo Rollouts üzerinden yapılan 5 ardışık deployment verisi kayıt altına alınmıştır. Ölçümler, Prometheus istio_requests_total ve Datadog APM metriklerine dayanmaktadır.

Metrik / Kriter Optimizasyon Öncesi Optimizasyon Sonrası Net Değişim
HTTP 502/503 Hata Sayısı (Deploy Başına) 18 – 24 adet 0 adet %100 İyileşme (Sıfır Hata)
Deployment Sırası p99 Gecikme (Latency) 1250ms (Sıçramalı) 185ms (Stabil) 1065ms Düşüş
Pod Kapanma Süresi (Time to Terminate) 3s – 5s 18s – 22s +15s (Planlı Gecikme)
Toplam Rollout Süresi (4 Adım Canary) 4dk 10s 5dk 35s 1dk 25s Uzama (Trade-off)

Trade-off Analizi: PreStop vs Application-Level Graceful Shutdown

Mimaride karar alırken her zaman alternatif yolları ve bedellerini (trade-off) değerlendiririz. Bu vakada sleep 15 tabanlı platform seviyesi çözüm ile kod seviyesi (application-level) çözüm arasında bir tercih yapmamız gerekti.

Seçenek 1: Sadece Uygulama İçi (Application-Level) Graceful Shutdown

Uygulamanın Node.js tarafında process.on('SIGTERM', ...) ile sinyali yakalaması, HTTP server üzerinden server.close() çağırması (yeni bağlantı reddetme) ve aktif istekler bitene kadar beklemesi yöntemidir.

  • Avantajı: Kapanma süresi tam gerektiği kadardır. Bir pod 2 saniyede işini bitirirse 15 saniye beklemez, anında kaynakları (CPU/RAM) serbest bırakır.
  • Dezavantajı: Istio proxy bu durumdan haberdar olmaz. Envoy iptables kuralları yenilenene kadar trafiği uygulamaya atmaya çalışır. Uygulama server.close() tetiklediği için Envoy’dan gelen bağlantıları reddeder ve Connection Refused (503) hatası fırlatır. Bunu aşmak için uygulamanın sinyali aldığında yeni bağlantıları reddetmek yerine 10-15 saniye boyunca kabul etmeye devam etmesi (kodu karmaşıklaştırması) gerekir.

Seçenek 2: Platform Seviyesi (PreStop) Geciktirme (Seçilen Yol)

Kubernetes yaml dosyasında sleep 15 kullanmak.

  • Avantajı: Agnostiktir. İster Node.js, ister Go, ister Java kullanın; yazılımcının kodda özel bir sinyal yakalama/geciktirme mantığı kurmasına gerek kalmaz. Sorumluluk DevOps/Platform ekibindedir.
  • Dezavantajı (Trade-off): Kapanan her pod minimum 15 saniye yaşar. Kubernetes kümesinde kaynak tahsisi (Resource Allocation) bu süre boyunca kilitli kalır. Yüksek frekanslı (günde 100+ deployment) veya agresif Horizontal Pod Autoscaler (HPA) scale down senaryolarında, gereksiz yere yaşayan pod’lar CPU/Memory kotalarını işgal eder. Bizim vakamızda 8.500 RPS ödeme geçidinde veri bütünlüğü ve %99.99 SLA, HPA’in 15 saniye geç kaynak boşaltmasından çok daha kritik olduğu için bu maliyeti (trade-off) kabul ettik.

Pratik Öneriler / Production Notları

Production ortamında bu mimariyi devreye alacak ekiplerin aşağıdaki kontrol listesini (checklist) takip etmesi hayati önem taşır:

  • terminationGracePeriodSeconds Validasyonu: Bu değer her zaman PreStop sleep süresi + Uygulamanın en uzun kapanma süresi toplamından büyük olmalıdır. Aksi takdirde Kubernetes, preStop’un veya uygulamanın bitmesini beklemeden SIGKILL gönderir. Formülümüz: 15s (sleep) + 20s (app drain) + 30s (buffer) = 65s.
  • Prometheus Gözlem Noktaları: Envoy’un kapatma sürecindeki kayıplarını izlemek için envoy_cluster_upstream_cx_destroy_with_active_rq metriğine alarm (alert) kurulmalıdır. Bu metrik 0’dan büyükse, proxy’niz uygulamadan önce kapanıyor demektir.
  • Readiness Probe Davranışı: Pod Terminating durumuna geçtiğinde kubelet readiness probe kontrollerini durdurmaz ancak sonucunu dikkate almaz; pod zaten Endpoint listesinden çıkarılmıştır. Bu sebeple kapanma anında readiness probe’un hata vermesi (fail) süreci olumsuz etkilemez.
  • DB Bağlantı Havuzu (Connection Pool): Uygulamanız trafik almayı kesse bile arkadaki veritabanı işlemlerinin (commit/rollback) tamamlanması için pod’un kapanma sürecinde TCP bağlantılarını açık tuttuğundan emin olun.

Sık Sorulan Sorular

Istio’nun holdApplicationUntilProxyStarts özelliği kapanma (termination) sürecini etkiler mi?

Hayır. holdApplicationUntilProxyStarts konfigürasyonu sadece pod başlarken (Init / Startup fazı) Envoy’un uygulamadan önce ayağa kalkmasını ve ağ kurallarını hazır etmesini sağlar. Kapanma sekansında hiçbir işlevi yoktur. Kapanma sırasını yönetmek için Istio 1.15+ ile gelen terminationDrainDuration parametresini kullanmalısınız.

PreStop kancası çalışırken pod yeni trafik alır mı?

Evet ve hayır. API Server’a göre pod artık sistemden çıkarılmaktadır. Ancak diğer node’lardaki kube-proxy’lerin ağ haritasını (iptables) güncellemesi 1-4 saniye sürdüğü için, bu süre zarfında yönlendirilmiş olan paketler ağ üzerinden pod’a ulaşır. sleep 15 koymamızın yegane amacı, ağda süzülen bu son paketleri (tail traffic) güvenle içeri alıp işlemektir. 4. saniyeden sonra yeni paket gelmez.

Eski Istio sürümlerinde (1.14 ve altı) proxy’nin erken kapanmasını nasıl engellerim?

Eğer terminationDrainDuration desteklenmeyen eski bir sürüm kullanıyorsanız, uygulamanızın preStop kancasının sonuna Envoy’u manuel olarak kapatan bir curl komutu ekleyebilirsiniz. Örnek: curl -X POST http://localhost:15020/quitquitquit. Bu sayede uygulama tam olarak işini bitirdiğinde proxy’ye kapanma emri gönderir.

Gecikme süresini (sleep) 15 saniye yerine 5 saniye yapsam ne olur?

Cluster boyutunuza bağlıdır. 50-100 Node’lu orta ölçekli bir Kubernetes kümesinde kube-proxy’nin tüm Endpoint güncellemelerini işlemesi 2-3 saniye sürer; 5 saniye yeterli olabilir. Ancak 1000+ Node’lu devasa bir yapıda API Server yükü (throttle) nedeniyle bu süre 10 saniyenin üzerine çıkabilir. Production ortamlarında güvenlik marjı olarak 10 ile 15 saniye arası standart kabul edilir.

Sonuç

Canary dağıtımlarında sıklıkla karşılaşılan HTTP 502 Bad Gateway ve 503 Service Unavailable hataları, genellikle uygulamanın içindeki bir bug’dan değil, dağıtık sistemlerin asenkron ağ senkronizasyon eksikliklerinden kaynaklanır. Yürüttüğümüz vaka çalışmasında, pod’ların kapanma sekansına 15 saniyelik kontrollü bir bekleme (preStop) ekleyerek kube-proxy gecikmelerini elimine ettik ve Istio Envoy proxy’nin mevcut bağlantıları tahliye etme (drain) yeteneklerini yapılandırdık. Sonuç olarak, p99 gecikme sürelerini 1.2 saniyeden 180ms bandına çekerek deployment anındaki SLA ihlallerini tamamen ortadan kaldırdık.

Aksiyon Adımı: Eğer cluster’ınızda sık deployment yapıldığında 5xx hatalarında anlık spike’lar görüyorsanız, ilk iş olarak ingress/gateway loglarındaki upstream_reset_before_response_started metriklerini inceleyin. Ardından, kritik servislerinizin Deployment YAML dosyalarına minimum 10 saniyelik bir preStop sleep döngüsü ve terminationGracePeriodSeconds uyumlandırması ekleyerek test ortamınızda sonucu gözlemleyin.

Bunları da beğenebilirsiniz

Üretimde Kritik İş Yükleri İçin Geri Basınç (Backpressure) Destekli Kuyruk Mimarileri
9 Şubat 2026

Üretimde Kritik İş Yükleri İçin Geri Basınç (Backpressure) Destekli Kuyruk Mimarileri

Üretim ortamındaki kritik iş yükleri için kesintisiz performans ve sistem kararlılığı sağlamak amacıyla geri basınç destekli kuyruk mimarilerinin tasarım prensiplerini ve uygulama stratejilerini keşfedin. Bu makale, sistemlerin aşırı yüklenmesini önleyen ve kaynak kullanımını optimize eden sağlam kuyruk mimarileri oluşturmak için pratik bilgiler sunar.

Devamını Oku
PHP ile Verileri Şifreleme (Kriptografi) ve Şifre Çözme Fonksiyonları
14 Ekim 2022

PHP ile Verileri Şifreleme (Kriptografi) ve Şifre Çözme Fonksiyonları

Projelerimizde çoğu zaman şifrelemeler oluşturuyoruz. Bu şifrelemelerin çoğunu tek taraflı yapsak da bazı zamanlarda geri açılabilir şifreler kullanmamız gerekebiliyor. Aşağıdaki kod bloğunda php ile şifreleme…

Devamını Oku
Kurumsal Refactoring İçin Depo Seviyesinde AI Ajanları: İnsan-Döngüde (Human-in-the-Loop) Geri Bildirim Mekanizmaları Tasarlamak
9 Mart 2026

Kurumsal Refactoring İçin Depo Seviyesinde AI Ajanları: İnsan-Döngüde (Human-in-the-Loop) Geri Bildirim Mekanizmaları Tasarlamak

Kurumsal yazılım projelerinde teknik borcu azaltmak için depo seviyesinde otonom ajanların nasıl tasarlanacağını ve insan denetimiyle güvenli refactoring süreçlerinin nasıl işletileceğini inceleyin.

Devamını Oku
AI Asistan