Sağlık Kontrolü Başarısız Oluyor ama Uygulama Çalışıyor
Uygulama yerelde sorunsuz çalışırken deployment sırasında sağlık kontrolünün başarısız olması genellikle beş nedenden kaynaklanır. En hızlı sonuca ulaşmak için binding, path, port, zamanlama ve bağımlılıkları bu sırayla kontrol edin.
Deployment sırasında sağlık kontrolünün başarısız olması nedeniyle her release'in engellendiği, uygulamanın ise ulaşabildiğiniz her ölçüte göre tamamen sorunsuz olduğu özel bir takılma durumu vardır. Uygulama yerelde çalışır. Yerel olarak Docker içinde de çalışır. Log'lar uygulamanın dinlediğini gösterir. Platform ise bazen art arda yirmi kez olmak üzere başarısızlık bildirmeye devam eder ve access log'unuzda hiçbir istek görünmez.
Bu son ayrıntı önemlidir ve olası nedenleri hemen daraltır. Uygulamanız isteği hiç log'lamadıysa kontrol uygulamanıza ulaşmamış demektir — dolayısıyla uygulama kodunuzda hiçbir şey bunu açıklayamaz.
İşte problemi en hızlı bulmanızı sağlayacak sırayla beş neden.
1. localhost'a bind ediyorsunuz
Bu, en yaygın nedendir ve "hiç trafik gelmiyor" belirtisini tam olarak açıklar.
Bir container içinde 127.0.0.1, yalnızca bu container'ın kendi loopback adresi anlamına gelir. Container dışından gelen bir sağlık kontrolü bu adrese ulaşamaz. Process dinliyor olabilir, log'larınız bunu gösterebilir; ancak socket, önemli olan hiçbir yerden erişilebilir değildir.
// Unreachable from outside the container
app.listen(3000, '127.0.0.1')
// Correct
app.listen(3000, '0.0.0.0')
Framework'lerin varsayılanları birbirinden farklıdır ve bazıları major sürümler arasında varsayılan değeri değiştirmiştir. Framework'ünüzün neye bind ettiğini hatırladığınız bilgiye göre değil, gerçekten kontrol edin.
# Confirm from inside the running container
dockup exec "ss -ltn || netstat -ltn" my-project/my-api
Dinleme adresi 0.0.0.0:3000 yerine 127.0.0.1:3000 ise sorunu buldunuz; bu listedeki diğer hiçbir şeyin önemi kalmaz.
2. Platformun probe ettiği port, servis verdiğiniz port değil
İşin içinde iki port vardır ve bunları birbirine karıştırmak kolaydır: process'in container içinde dinlediği port ve platformun yönlendirme yaptığı port. Uygulamanız ortamdan PORT okuyorsa ve bir Dockerfile içinde bir yerde 3000 değerini hardcode ettiyseniz, bu iki port sessizce birbirinden farklı olabilir.
Güvenilir yaklaşım, platformun size portu bildirmesine izin vermektir:
const port = process.env.PORT || 3000
app.listen(port, '0.0.0.0')
Ardından service'in portunu platformda tek bir yerde ayarlayın ve bu sayıyı iki farklı yerde yönetmeye son verin.
3. Path, success dışında bir yanıt döndürüyor
Bir sağlık kontrolü path'i birebir eşleştirilir ve şaşırtıcı sayıda hata bir redirect'ten kaynaklanır. Uygulamanız /healthz adresini /healthz/ adresine yönlendiriyorsa veya HTTPS'i 301 ile zorunlu kılıyorsa, yalnızca 2xx yanıtlarını başarı kabul eden bir checker her seferinde başarısız olur; tarayıcı ise redirect'i takip ederek çalışan bir sayfa gösterir.
Özellikle dikkat etmeniz gereken üç tuzak:
- Trailing slash redirect'leri.
/healthz→/healthz/bir 301'dır. - Zorunlu HTTPS. Dahili kontrol genellikle loopback üzerinden plain HTTP ile gelir. Koşulsuz bir HTTPS redirect'i kontrolü başarısız kılar.
- Auth middleware. Routing'den önce çalışan global bir authentication guard, health path'i için de 401 döndürür.
Health path'ini auth'tan ve HTTPS zorunluluğundan açıkça hariç tutun. Sıkıcı olması gereken tek route budur.
4. Check, cold start'tan daha hızlı
Check birkaç kez başarısız olduktan sonra geçiyorsa veya deploy sırasında başarısız olup yeniden denediğinizde geçiyorsa sorun configuration değil, zamanlamadır.
İhtiyacınız olan bütçe tek bir denemeden ibaret değildir — interval × retry sayısıdır. Veritabanına bağlanması ve cache'i ısıtması on iki saniye süren bir uygulama için toplam bütçenin on iki saniyeden uzun olması gerekir. Aksi hâlde her release'i başarısız kılar ve sonunda gate'i kapatırsınız; bu da bozuk bir build ile kullanıcılarınız arasındaki tek korumayı ortadan kaldırır.
dockup info my-project/my-api --json | grep -A6 healthCheck
Timeout değerini, geçerli tek bir denemenizin sürebileceği en uzun süreden daha yüksek ayarlayın. Retry sayısını ise interval × retry sayısı, geçerli boot sürenizi rahatça aşacak şekilde belirleyin. Tahmin etmek yerine boot süresini ölçün — log'larda timestamp'ler bulunur.
5. Uygulama gerçekten hazır değil
Son durum, check'in varlık nedenidir: uygulamanız başladı, bir dependency'ye ulaşamadı ve yeniden deniyor. Crash olmadığı için hiçbir şey onu yeniden başlatmıyor. Servis veremediği için check başarısız oluyor. Sistem tam olarak tasarlandığı gibi çalışıyor ve bu release'in trafik almaması gerektiğini size bildiriyor.
Bunu diğer dört durumdan ayırmanın yolu, uygulamanızın isteği log'lamış ve 2xx dışı bir yanıt vermiş olmasıdır. İstek log'larınızda görünüyorsa 1 ile 3 arasındaki nedenler elenmiş demektir.
Zaman kazandıran diagnostic sırası
# 1. Did the request reach the app at all?
dockup logs my-project/my-api --follow
# 2. What is the process actually bound to?
dockup exec "ss -ltn || netstat -ltn" my-project/my-api
# 3. Does the path answer from inside the container?
dockup exec "curl -si localhost:3000/healthz" my-project/my-api
# 4. What is the gate configured to expect?
dockup info my-project/my-api --json
- adım bu sorunların çoğunu çözer. Container içinden çalıştırılan bir
curl, tüm network değişkenlerini aynı anda ortadan kaldırır: Orada 200 döndüğü hâlde platform hâlâ başarısız oluyorsa sorun uygulamada değil, address veya port'tadır. 301 ya da 401 döndürüyorsa platforma hiç dokunmadan nedeni bulmuşsunuz demektir.
Gate'i korumaya neden değer
Dördüncü başarısız deploy'un ardından health check'i devre dışı bırakıp release'i yayına almak cazip gelebilir. Ancak neyi kapattığınızı hatırlamakta fayda var.
Dockup'ta health gate, bozuk bir release'i kullanıcılarınızdan uzak tutan mekanizmadır. Yeni sürüm build edilir ve başlatılırken mevcut sürüm hizmet vermeye devam eder; trafik yalnızca yeni sürüm yanıt vermeye başladığında aktarılır. Gate'i kapatırsanız başlayan ama çalışamayan bir container'ın sorunsuz olanın yerini aldığı failure mode'u yeniden etkinleştirmiş olursunuz.
Arka arkaya dört release'te başarısız olan bir check can sıkıcıdır. Koşulsuz olarak geçen bir check ise önemli olan deploy'u durduramayacak bir check'tir.
Sıkça sorulan sorular
Uygulama yerelde çalışırken health check neden başarısız oluyor?
Neredeyse her zaman nedeni, container'ın 0.0.0.0 yerine 127.0.0.1 adresine bind etmesidir. Yerelde aynı loopback üzerinden bağlanırsınız; container dışından bu adrese erişilemez.
Health endpoint authentication gerektirmeli mi? Hayır. Endpoint'i global auth middleware'den hariç tutun; aksi hâlde checker 401 alır ve uygulama sorunsuz olsa bile deploy başarısız olur.
Hangi timeout değerini kullanmalıyım? Geçerli tek bir denemenizin sürebileceği en uzun süreden daha uzun bir değer kullanın ve retry'ların en uzun geçerli cold start'ınızı kapsadığından emin olun. Tahmin etmek yerine boot süresini log'larınızdan okuyun.
Bir release'in önündeki engeli kaldırmak için health check'i devre dışı bırakmak güvenli mi? Release'in önündeki engeli kaldırır, ancak bozuk bir sürümün trafik almasını engelleyen korumayı da ortadan kaldırır. Bunun yerine check'i düzeltin — çoğu durumda neden bind address veya redirect'tir ve çözüm birkaç dakika sürer.
