3 puan yazan GN⁺ 2025-05-06 | 1 yorum | WhatsApp'ta paylaş
  • Zarif kapanış (graceful shutdown), uygulama bir sonlandırma sinyali aldıktan sonra yeni istekleri engelleme, mevcut istekleri tamamlama ve kaynakları temizleme sürecinden oluşur
  • Go'da os/signal paketi kullanılarak SIGINT, SIGTERM gibi sonlandırma sinyalleri doğrudan işlenebilir; signal.NotifyContext ile context tabanlı kapanış kontrolü de mümkündür
  • HTTP sunucusu kapatılırken Server.Shutdown() çağrısından önce readiness probe'u başarısız duruma getirerek trafiği kesmek ve birkaç saniye bekledikten sonra shutdown uygulamak daha kararlıdır
  • Tüm handler'ların context kapanış sinyalini algılayıp sonlanabilmesi gerekir; bu, BaseContext veya middleware ile merkezi biçimde ele alınabilir
  • Sonlandırma sinyali alındıktan sonra veritabanı, mesaj broker'ı, cache gibi harici kaynaklar kasıtlı olarak temizlenmelidir; defer ile kaydedildiğinde kapanış sırasını yönetmek kolaylaşır

Graceful Shutdown nedir?

  • Zarif kapanış, uygulama kapanırken yeni istekleri engelleme, devam eden isteklerin tamamlanmasını bekleme ve kaynakları temizleme adımlarından geçen bir süreçtir
  • Bu yazı ağırlıklı olarak HTTP sunucuları ve container ortamlarını ele alsa da, tüm uygulamalara uygulanabilecek bir kavramdır

1. Sonlandırma sinyallerini işleme

  • Unix benzeri sistemlerde SIGTERM, SIGINT, SIGHUP gibi sinyaller sonlandırma sinyali olarak kullanılır
  • Go runtime'ı SIGTERM, SIGINT aldığında varsayılan olarak uygulamayı sonlandırır; ancak os/signal.Notify ile bu sinyaller doğrudan işlenebilir
  • Buffer'lı kanal (kapasite 1) kullanmak, başlatma sırasında sinyal kaybını önlemeye yardımcı olur
  • Go 1.16 sonrasında signal.NotifyContext kullanılarak context tabanlı sinyal yönetimi daha kolay hale gelmiştir

2. Kapanış süresini dikkate alma

  • Kubernetes'te varsayılan olarak 30 saniyelik bir kapanış lütuf süresi verilir (terminationGracePeriodSeconds)
  • Güvenli kapanış için %20 pay bırakıp işlemleri 25 saniye içinde tamamlamak tavsiye edilir

3. Yeni istek almayı durdurma

  • http.Server.Shutdown(), yeni bağlantıları engeller ve mevcut istekler tamamlanana kadar bekler
  • Kubernetes ortamında önce readiness probe'un başarısız dönmesi sağlanarak gelen trafiği kesmek, ardından kısa bir beklemeden sonra shutdown uygulamak gerekir
  • Readiness handler'ında genel bir değişken üzerinden kapanış durumu kontrol edilerek HTTP 503 döndürmek mümkün olabilir

4. İstek işlemeyi tamamlama

  • Kapanış için kullanılan context üzerinde uygun bir timeout tanımlamak gerekir (context.WithTimeout)
  • Shutdown context süresi dolarsa kalan bağlantılar zorla kapatılır
  • Tüm handler'lar, context.Context kullanarak kapanış sinyalini algılayıp durabilecek şekilde tasarlanmalıdır
  • Bunun için middleware veya BaseContext aracılığıyla tüm isteklere kapanış context'i enjekte edilebilir

5. Kaynak temizliği

  • Sonlandırma sinyali gelir gelmez kaynakları kapatmak, işlenmekte olan handler'larda sorunlara yol açabilir
  • Shutdown tamamlandıktan sonra veritabanı bağlantıları, mesaj broker'ları, cache'ler vb. temizlenmelidir
  • Go'daki defer kullanımı, başlatmanın ters sırasıyla kapanış rutinlerini çalıştırmayı sağlayarak bağımlılık yönetimini kolaylaştırır
  • Bellek, dosya tanımlayıcıları gibi işletim sisteminin otomatik temizlediği kaynakların yanı sıra veri flush etme, transaction rollback gibi açıkça kapatılması gereken kaynaklar da vardır

Tam örnek özeti

  • signal.NotifyContext ile sonlandırma sinyali alma
  • /healthz readiness endpoint'i uygulama
  • BaseContext ile tüm isteklere kapanış context'i enjekte etme
  • Readiness sonrası 5 saniye bekleyip shutdown uygulama
  • server.Shutdown çağrısı başarısız olursa zorunlu kapatma için fallback ekleme

Kaynakça ve ilgili kaynaklar

1 yorum

 
GN⁺ 2025-05-06
Hacker News görüşü
  • Kubernetes'te load balancer hedef IP güncellemesinin uzun sürdüğü durumlar olabiliyor. Sorunların %90'ı, trafiğin gerçekten drain edilip edilmediğini doğrulamakla ilgili

    • Global preStop hook'una 15 saniyelik bekleme süresi eklemek, HTTP 503 oranını belirgin biçimde iyileştirdi
    • Load balancer kaydının kaldırılması ile SIGTERM iletimi arasında zaman yaratarak uygulama tarafındaki işlemleri basitleştiriyor
  • log.Fatal kullanıldığında defer içindeki içerik çalışmaz

    • log.Fatal, os.Exit çağırdığı için hemen sonlanır
    • panic kullanılırsa defer içeriği çalışır
  • Prometheus /metrics endpoint'i periyodik olarak scrape edildiğinde, son scrape ile sürecin kapanması arasında kaydedilen metrikler yayılmayabilir

    • Servis kapanırken son birkaç saniyenin logları kaybolabilir
    • Log dosyası bir sidecar process tarafından izleniyorsa race condition oluşabilir
  • Dağıtık bir sistem istemcinin düzgün kapanmasına bağımlıysa, sistem ciddi şekilde arızalanabilir

  • Yeni servis instance'ı önceki instance'dan socket devraldığında, bağlantıları kesmeden uygulamayı yeniden başlatma yöntemine dair açıklama eksik

    • systemd'de bunu uygulamak nispeten basit
    • nginx bunu 20 yılı aşkın süredir destekliyor
    • Kubernetes ve Docker bunu desteklemiyor
  • Liveness konusundaki tartışma yetersiz

    • Liveness/readiness için aynı endpoint'i kullanan uygulamaları birçok kez gördüm
  • Bir program ctrl c gibi komutları temiz biçimde işleyemiyorsa kötü yazılmıştır

  • Elixir, process'leri küçük VM process'leri olarak tasarlayarak düzgün kapanış rutinlerini kasıtlı olarak yazma ihtiyacını ortadan kaldırıyor

  • Projede düzgün kapanışı ele almak için küçük bir kütüphane oluşturuldu

    • Farklı başlatma ve kapatma mekanizmalarına sahip servisleri birleştirmek için bir API sağlıyor
  • Readiness probe güncellendikten sonra sistemin yeni istek göndermemesi için birkaç saniye beklenmeli

    • Kapanmakta olan pod hazır durumda değildir
    • Servis, endpoint'leri kapanıyor olarak işaretler
    • SIGTERM'den sonra da küçük bir pencere olabilir, ancak bu büyük bir sorun değil
    • Yeni bağlantı kabul etmeden mevcut bağlantıları düzgün biçimde sonlandırmak önemlidir