Spring Boot ile Zero-Downtime Deployment Üzerine Notlar
Üretim ortamına dağıtım yapmak yıllardır değişmeyen bir gerilim alanıdır. Servisler güncellenir, pod’lar kapanır, yenileri açılır; ama kullanıcı o sırada sayfasını yenilediğinde hiçbir şey olmamış gibi devam etmelidir. Zero-downtime kavramı da tam bu beklentinin karşılığıdır: uygulama değişir ama sistemin ritmi bozulmaz. Bunun gerçekleşmesi için Spring Boot’un kapanış davranışıyla Kubernetes’in trafik yönlendirme mantığının aynı hizaya gelmesi gerekir.
Graceful Shutdown: Kapanmayı Yönetmek
Spring Boot bir SIGTERM aldığında hemen ölmek yerine elindeki işi toparlayabilir. Bu toparlama süresi düzgün tanımlanmadığında, özellikle uzun süren işlemler yarım kalır ve dışarıya hata olarak yansır. Basit ama etkili bir ayarla uygulamaya kendini düzgün kapatması için zaman tanıyoruz:
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 20s
Bu yapılandırma uygulamanın yeni istek kabul etmeyi bırakıp mevcut işlemlerini tamamlamasına imkân sağlar. Kapanış bir kesinti değil, kontrollü bir geçiş hâline dönüşür.
Liveness ve Readiness: Kubernetes’in Karar Mekanizması
Kubernetes açısından çalışan bir süreç ile trafiği kabul etmeye hazır bir süreç aynı şey değildir. Uygulamanın ayağa kalkması, veritabanı bağlantılarının stabil olması, cache ve thread havuzlarının kullanılabilir hâle gelmesi zaman alır. İşte readiness probe bu ayrımı yönetir:
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
Readiness hazır olmadığı sürece Kubernetes pod’a trafik yönlendirmez. Böylece daha açılışını tamamlamamış bir pod’a istek düşmesi engellenir.
Diğer tarafta liveness probe ise uygulamanın gerçekten yaşıyor olup olmadığını kontrol eder. Stuck bir thread, sonsuz döngü veya kilitlenmiş bir süreç varsa pod otomatik olarak yenisiyle değiştirilir. Bu iki probe yanlış ayarlanırsa dağıtım sırasında trafik beklenmedik şekilde hataya düşer.
RollingUpdate ve maxUnavailable Ayrıntısı
Kubernetes’in rolling update davranışı çoğu zaman güvenli kabul edilir; ancak yanlış konfigürasyon zero-downtime beklentisini kolaylıkla bozar. Sorunun kaynağı genellikle şudur: yeni pod henüz hazır olmadan eski pod devreden çıkar.
Bu yüzden maxUnavailable değeri sıfır olmalıdır:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 0
Bu ayar, sistemde her zaman minimum gerekli sayıda pod’un ayakta kalacağını garanti eder. Zero-downtime’ın temel taşlarından biri budur.
preStop: Kapanış Anını Yumuşatmak
Kubernetes bir pod’u kapatmaya karar verdiğinde, o pod’un trafiğin tamamen dışına alınması anlık gerçekleşmez. Endpoint güncellemeleri ve iptables değişiklikleri birkaç saniyelik gecikmeyle yayılır. Bu küçük gecikme, trafik hâlâ eski pod’a düşebileceği anlamına gelir.
Bu riski ortadan kaldırmak için pod’un kapanmadan hemen önce kısa bir bekleme süresi tanımlıyoruz:
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
Bu gecikme Kubernetes’in “bu pod artık yok” bilgisini kümeye tam olarak yayması için yeterlidir. Uygulama açısından da kapanış daha temiz ve iz bırakmayan bir hâle gelir.
Sonuç
Zero-downtime aslında sihirli bir özellik değil; doğru yapılandırılmış bir kapanış sürecinin, tutarlı probe ayarlarının ve Kubernetes’in trafik yönetimiyle uyumlu bir dağıtım stratejisinin doğal sonucudur. Uygulamanın davranışıyla Kubernetes’in beklentileri aynı noktada buluştuğunda, dağıtımlar sistemin günlük ritmi içinde sıradan bir operasyon hâline gelir. Yıllar içinde gördüğüm şey şu: stres, çoğunlukla belirsizlikten doğar. Bu ayarlar belirsizliği ortadan kaldırır ve dağıtımı güvenilir bir süreç hâline getirir.