Rolling update kurdun, sağlık kontrolü yazdın, orkestratör "completed" dedi. Yine de erişim kayıtlarında her yayında yirmi otuz tane 502 var. Sıfır kesintili deploy dediğimiz şey işte tam olarak bu son yirmi otuz isteğin peşine düşmektir; çünkü stratejiyi doğru seçmek yetmez, sürecin en küçük ayrıntısı olan "eski süreç nasıl kapanıyor" sorusu cevaplanmadan kesinti bitmez.
Bu rehberde kesintinin gerçekte nerede oluştuğunu, uygulamanın SIGTERM sinyalini nasıl karşılaması gerektiğini, yük dengeleyiciden bağlantı boşaltmanın (connection draining) neden kapanmadan önce yapılması gerektiğini, Nginx'in kesintisiz reload mekanizmasını, veritabanı göçlerini ve dosya yükleme ile arka plan işleri gibi kolay unutulan parçaları anlatacağım. Sonunda elinde tek bir isteği bile düşürmeyen bir yayın akışı olacak.
Kesinti Aslında Nerede Oluşur#
"Kesintisiz" dediğimiz bir yayında hata üreten yerler neredeyse her zaman aynı dört noktadır ve hiçbiri seçtiğin yayın stratejisiyle ilgili değildir. Bunları tek tek görmeden hangi stratejiyi kurarsan kur, aynı 502'leri görmeye devam edersin.
| Kesinti kaynağı | Belirti | Çözüm |
|---|---|---|
Süreç SIGTERM ile anında ölüyor | Yayın anında 502 / bağlantı sıfırlama | Zarif kapanış (graceful shutdown) |
| Havuzdan çıkarma yok, doğrudan durduruluyor | Yük dengeleyici ölü kopyaya istek yolluyor | Önce boşalt, sonra durdur |
| Yeni kopya hazır olmadan havuza alınıyor | İlk isteklerde 500 ya da uzun gecikme | Gerçek sağlık kontrolü + hazırlık gecikmesi |
| Şema göçü kodla aynı anda gidiyor | Eski kopyalar sütun bulamıyor | Genişlet-daralt düzeni |
Bunların ilk üçü tek bir cümlede özetlenir: bir kopya, açık istekleri bitirmeden hizmet dışına çıkmamalı ve gerçekten hazır olmadan hizmete girmemeli. Bu iki koşulu sağladığında kesinti sorununun büyük bölümü ortadan kalkar. Dördüncüsü ise veri katmanıyla ilgilidir ve ayrı bir disiplin ister.
Zarif Kapanış: SIGTERM'i Doğru Karşılamak#
Bir konteyner durdurulduğunda Docker önce SIGTERM gönderir, ardından varsayılan olarak on saniye bekler ve süreç hâlâ yaşıyorsa SIGKILL ile öldürür. Uygulaman SIGTERM'i yakalamıyorsa çoğu çalışma zamanı süreci anında sonlandırır; o an işlenmekte olan isteklerin hepsi yarıda kalır. Zarif kapanışın görevi bu on saniyeyi doğru kullanmaktır.
Doğru kapanış sırası şudur:
SIGTERMalınır.- Sağlık uç noktası hemen başarısız dönmeye başlar (böylece yük dengeleyici bu kopyayı havuzdan düşürür).
- Kısa bir bekleme (
preStopgecikmesi) verilir; havuz güncellemesinin yayılması için gerekir. - Sunucu yeni bağlantı kabul etmeyi durdurur, açık istekleri bitirir.
- Veritabanı ve önbellek bağlantı havuzları kapatılır, arka plan işçileri durdurulur.
- Süreç sıfır çıkış koduyla sonlanır.
Node.js tarafında bunun asgari biçimi şöyle görünür:
let kapaniyor = false;
// Sağlık kontrolü, kapanış başladığı anda başarısız dönmeli
app.get("/healthz", (req, res) => {
if (kapaniyor) return res.status(503).send("shutting down");
res.status(200).send("ok");
});
const server = app.listen(8080);
process.on("SIGTERM", async () => {
kapaniyor = true; // 1) havuzdan düşmeye başla
await new Promise((r) => setTimeout(r, 5000)); // 2) yayılma için bekle
server.close(async () => { // 3) açık istekleri bitir
await db.end(); // 4) bağlantıları kapat
process.exit(0);
});
});
Docker tarafında bu sürenin sığabilmesi için kapanış zaman aşımını uzatman gerekir; varsayılan on saniye çoğu zaman yetmez:
services:
api:
image: registry.firmaniz.com/api:1.4.2
stop_grace_period: 45s # SIGTERM ile SIGKILL arasındaki süre
stop_signal: SIGTERM
Burada gözden kaçan bir tuzak var: konteynerinde uygulaman PID 1 değilse (örneğin CMD bir kabuk betiği çalıştırıyorsa) sinyal uygulamaya hiç ulaşmaz. CMD ["node", "server.js"] gibi exec biçimini kullan ya da docker run --init ile bir sinyal iletici ekle. Dockerfile tarafındaki bu ve benzeri kuralları Dockerfile en iyi pratikler yazısında topladım.
Bağlantı Boşaltma: Durdurmadan Önce Havuzdan Çıkar#
Zarif kapanış tek başına yetmez, çünkü yük dengeleyici bir kopyanın kapanmaya başladığını ancak sağlık kontrolünü tekrar sorduğunda öğrenir. Sağlık kontrolü aralığın on saniye ise, kopya SIGTERM aldıktan sonraki on saniye boyunca havuzda görünmeye devam eder ve o süre içinde yeni istek almaya devam eder. İşte 502'lerin asıl kaynağı burasıdır.
Çözüm, kapanış başladığı anda sağlık kontrolünü başarısız döndürmek ve sonra beklemektir. Bekleme süresi kabaca şu formülle hesaplanır:
bekleme = sağlık kontrolü aralığı × başarısızlık eşiği + emniyet payı
örnek = 5s × 2 + 5s = 15 saniye
Nginx tarafında sağlık kontrolüne göre otomatik havuz yönetimi için max_fails ve fail_timeout kullanılır:
upstream api_havuzu {
# 2 başarısız denemeden sonra bu kopya 10 saniye havuz dışı sayılır
server 127.0.0.1:8081 max_fails=2 fail_timeout=10s;
server 127.0.0.1:8082 max_fails=2 fail_timeout=10s;
server 127.0.0.1:8083 max_fails=2 fail_timeout=10s;
keepalive 32;
}
server {
listen 80;
server_name firmaniz.com;
location / {
proxy_pass http://api_havuzu;
proxy_http_version 1.1;
proxy_set_header Connection "";
# Bir kopya hata verirse isteği diğerine devret
proxy_next_upstream error timeout http_502 http_503;
proxy_next_upstream_tries 2;
proxy_connect_timeout 2s;
}
}
proxy_next_upstream satırı emniyet kemeridir: kopyanın kapanmasıyla havuzdan düşmesi arasındaki o kısa pencerede gelen bir istek, hata almak yerine sağlıklı kopyaya devredilir. Kubernetes kullanıyorsan aynı işi preStop kancası yapar:
lifecycle:
preStop:
exec:
# Endpoint listesinden düşmenin yayılması için bekle
command: ["/bin/sh", "-c", "sleep 15"]
terminationGracePeriodSeconds: 60
Bu sleep çirkin görünür ama gereklidir: Kubernetes, pod'a SIGTERM göndermekle Endpoint listesinden düşürmeyi paralel yapar, sıralı değil. Beklemeden kapanan bir pod, kube-proxy kuralları henüz güncellenmemişken istek almaya devam eder.
Nginx'i Kesintisiz Yeniden Yükleme#
Yapılandırma değiştirdiğinde systemctl restart nginx çalıştırmak dinleme soketini kapatır ve o milisaniyelerde gelen bağlantılar reddedilir. Doğru komut reload'dur: Nginx ana süreci yeni yapılandırmayla yeni işçi süreçleri başlatır, eski işçilere ise "yeni bağlantı alma, elindekini bitir" der. Dinleme soketi hiç kapanmaz.
# Önce söz dizimini doğrula; hatalıysa reload zaten yapma
nginx -t
# Kesintisiz yeniden yükle
systemctl reload nginx
# ya da doğrudan sinyalle
kill -HUP $(cat /run/nginx.pid)
# Eski işçilerin kapanmasını izle (shutting down olarak görünürler)
ps -o pid,etime,args -C nginx
Eski işçilerin uzun süren isteklerini bitirmesi için worker_shutdown_timeout değerini uygulamanın en uzun isteğinden büyük tut. Aksi halde bir dosya yükleme ya da rapor üretme isteği reload sırasında kesilir:
# Eski işçilere kapanmadan önce tanınan süre
worker_shutdown_timeout 60s;
Bir uyarı: nginx -t başarısızken reload çalıştırırsan Nginx yeni yapılandırmayı reddeder ve eskisiyle çalışmaya devam eder, yani site düşmez. Ama restart çalıştırırsan servis hiç açılmaz ve site tamamen kapanır. Bu tek fark, "test et, sonra reload et" alışkanlığını edinmen için yeterli sebeptir. Nginx ile Apache'nin bu konudaki davranış farklarını merak ediyorsan Apache ve Nginx karşılaştırması yazısına bakabilirsin.
Veritabanı Göçlerini Yayından Ayırmak#
Sıfır kesintili yayının en zor parçası kod değil veridir. Yayın süresince eski ve yeni sürüm aynı anda çalıştığından, şema her iki kodla da uyumlu olmak zorundadır. Bunu sağlamanın yolu, tek bir "göç ve yayınla" adımı yerine geriye uyumlu adımlar dizisi kurmaktır.
Uygulaman gereken kurallar kısadır ve istisnasızdır:
- Sütun eklemek güvenlidir; eski kod onu görmez.
- Sütun silmek ya da yeniden adlandırmak asla kodla aynı yayında yapılmaz.
- Yeni sütun ilk eklendiğinde
NULLkabul etmeli ya da varsayılan değeri olmalıdır. NOT NULLkısıtı, veri doldurulup tüm kopyalar yeni sürüme geçtikten sonra eklenir.- Uzun süren indeks ve tablo değişiklikleri kilit süresini kısaltan biçimlerle çalıştırılır.
-- Güvenli: eski kod etkilenmez
ALTER TABLE kullanicilar ADD COLUMN telefon_dogrulandi BOOLEAN DEFAULT FALSE;
-- Güvenli: yazma işlemlerini kilitlemeden indeks kur (PostgreSQL)
CREATE INDEX CONCURRENTLY idx_kullanicilar_telefon ON kullanicilar (telefon);
-- Tehlikeli: tüm kopyalar geçtikten günler sonra, ayrı bir yayında
ALTER TABLE kullanicilar DROP COLUMN eski_telefon;
Göçü uygulama açılışında otomatik çalıştırmak da yaygın bir tuzaktır: rolling update sırasında altı kopya aynı anda açılırsa aynı göçü altı kez çalıştırmaya kalkarlar. Göçü yayın hattında ayrı ve tek seferlik bir adım olarak çalıştır, uygulamanın açılış yolundan çıkar. Bu ayrımın nedenini ve genel ilkeyi Twelve-Factor App yazısındaki yönetim süreçleri başlığı altında da bulabilirsin.
Unutulan Parçalar: Arka Plan İşleri, Yüklemeler ve Oturumlar#
Web isteklerini kurtardın diyelim; kesintiyi hâlâ üç yerden yiyebilirsin. Birincisi arka plan işçileri. Kuyruktan iş alan bir işçi SIGTERM aldığında elindeki işi yarıda bırakırsa, o iş ya kaybolur ya da iki kez çalışır. Doğru davranış, yeni iş almayı durdurup elindekini bitirmek ve ancak sonra kapanmaktır. Kuyruk sisteminde "görünürlük zaman aşımı" ya da yeniden teslim mekanizması varsa, işlerin idempotent olması bu riski tamamen ortadan kaldırır.
İkincisi yerel diske yazılan dosyalar. Kullanıcı yüklemelerini konteyner içindeki bir dizine yazıyorsan, o kopya değiştiğinde dosyalar kaybolur; üstelik geçiş sırasında bazı kullanıcılar eski kopyaya, bazıları yeni kopyaya düştüğü için dosya "kâh var kâh yok" davranır. Yüklemeleri paylaşılan bir birime ya da nesne depolamaya taşı; kalıcı veri yönetiminin nasıl kurulacağını Docker volume ve veri yönetimi yazısında ayrıntılandırdım.
Üçüncüsü oturumlar ve istemci tarafındaki eski varlıklar. Oturumu süreç belleğinde tutuyorsan her yayında kullanıcılar çıkış yapar. Daha sinsi olanı ise tarayıcıda açık duran eski sayfanın, artık var olmayan bir JavaScript parçasını istemesidir; yeni yayında dosya adları değiştiği için 404 alır ve sayfa bozulur. Çözüm, eski varlık dosyalarını yeni yayından sonra bir süre daha sunmaktır:
# Yeni yayının varlıklarını üstüne kopyala, eskileri hemen silme
rsync -a --delete-after --exclude 'assets/*' ./dist/ /var/www/firmaniz.com/
rsync -a ./dist/assets/ /var/www/firmaniz.com/assets/
# Eski varlıkları en az bir gün sonra temizleyen ayrı bir görev çalıştır
Yayın Sonrası Doğrulama#
Kesintisiz olduğunu iddia ettiğin bir yayının gerçekten kesintisiz olduğunu ölçmeden bilemezsin. En pratik yöntem, yayın boyunca dışarıdan sabit aralıklı istek atıp hata sayan basit bir denetleyici çalıştırmaktır:
#!/usr/bin/env bash
# Yayın süresince her 200 ms'de bir istek at, hataları say
hata=0; toplam=0
son=$((SECONDS + 300))
while [ $SECONDS -lt $son ]; do
kod=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 https://firmaniz.com/healthz || echo 000)
toplam=$((toplam + 1))
[ "$kod" = "200" ] || { hata=$((hata + 1)); echo "$(date +%T) -> $kod"; }
sleep 0.2
done
echo "Toplam: $toplam, hata: $hata"
Çıktıda tek bir satır bile varsa yayının kesintisiz değildir ve hangi saniyede olduğu sana nedeni de söyler: kapanış anındaysa boşaltma eksiktir, açılış anındaysa sağlık kontrolü erken yeşile dönüyordur. Erişim kayıtlarında yayın penceresindeki durum kodlarını saymak da aynı bilgiyi verir:
# Yayın penceresindeki 5xx sayısını çıkar
awk '$4 >= "[25/Aug/2026:14:30" && $4 <= "[25/Aug/2026:14:40"' /var/log/nginx/access.log \
| awk '{print $9}' | sort | uniq -c | sort -rn
Sıkça Sorulan Sorular#
Sıfır kesintili deploy gerçekten mümkün mü#
Web katmanı için evet, tamamen mümkündür ve düzgün kurulmuş bir sistemde tek bir isteğin bile düşmemesi normaldir. Zorluk veri katmanındadır: bazı şema değişiklikleri kısa da olsa kilit gerektirir. Bunları küçük, geriye uyumlu adımlara bölerek ve kilitsiz biçimleri kullanarak fiilî kesintiyi sıfıra indirebilirsin, ama planlamayı ihmal edersen tek bir ALTER TABLE dakikalarca kesinti üretir.
Yayın sırasındaki 502 hatalarının sebebi nedir#
Neredeyse her zaman kapanan kopyanın havuzdan düşmeden önce durdurulmasıdır. Yük dengeleyici hâlâ o kopyaya istek yollarken süreç kapanmıştır ve bağlantı reddedilir. Çözüm, kapanış başlar başlamaz sağlık kontrolünü başarısız döndürmek, sonra havuz güncellemesinin yayılması için on beş saniye kadar beklemek ve ancak ondan sonra sunucuyu kapatmaktır.
stop_grace_period ne kadar olmalı#
En uzun isteğinin süresi artı boşaltma beklemesi kadar olmalıdır. Tipik bir API için otuz ile altmış saniye arası yeterlidir. Rapor üretimi ya da büyük dosya yükleme gibi uzun istekler varsa bu süreyi ona göre uzat. Süre yetmezse Docker SIGKILL gönderir ve elindeki tüm istekler kesilir; yani çok kısa bir değer zarif kapanış kodunu tamamen anlamsız kılar.
Nginx reload sırasında bağlantılar kopar mı#
Hayır. reload dinleme soketini kapatmaz; ana süreç yeni yapılandırmayla yeni işçiler başlatır ve eski işçiler mevcut bağlantılarını bitirir. Kopma yalnızca restart kullandığında ya da worker_shutdown_timeout süresi uzun süren bir istekten kısa olduğunda olur. Reload öncesinde mutlaka nginx -t çalıştır; hatalı yapılandırmada reload eskisiyle devam eder, restart ise servisi hiç açmaz.
Tek sunucuda sıfır kesintili yayın yapabilir miyim#
Evet. Uygulamanın iki kopyasını farklı portlarda çalıştırıp önlerine Nginx koyman yeterlidir; birini havuzdan çıkarıp güncellersin, sağlıklı olunca geri alırsın, sonra diğerine geçersin. Aynı mantığı tek sunucuda iki dizin ve bir sembolik bağla da kurabilirsin. Kritik olan, herhangi bir anda en az bir sağlıklı kopyanın istek karşılıyor olmasıdır.
Veritabanı göçünü ne zaman çalıştırmalıyım#
Göçü uygulamanın açılış yolundan çıkar ve yayın hattında ayrı, tek seferlik bir adım olarak çalıştır. Geriye uyumlu ekleme adımlarını yeni kodu yayınlamadan önce, yıkıcı temizlik adımlarını ise tüm kopyalar yeni sürüme geçtikten günler sonra çalıştır. Bu sıra, hem yayın sırasında hem de olası bir geri dönüşte şemanın çalışan kodla uyumlu kalmasını sağlar.
Kapanış#
Sıfır kesintili deploy bir araç değil, birkaç küçük alışkanlığın toplamıdır: uygulaman SIGTERM aldığında sağlığını hemen kırmalı ve açık istekleri bitirmeli, yük dengeleyici o kopyayı durmadan önce havuzdan düşürmeli, yeni kopya gerçekten hazır olmadan trafiğe girmemeli ve veritabanı değişiklikleri koddan bağımsız, geriye uyumlu adımlarla ilerlemeli. Bunları kurduktan sonra yayının kesintisiz olduğunu iddia etmekle kalmaz, dışarıdan istek atan basit bir betikle kanıtlarsın.
Bu düzeni kurmak için birden fazla kopyayı ve kendi yönlendirme katmanını çalıştırabileceğin bir ortama ihtiyacın var; VDS ve bulut sunucu paketlerimiz tam root erişimiyle bunu karşılar. Kurulumu, Nginx ayarlarını ve yayın hattını kendin üstlenmek istemiyorsan sunucu yönetimi hizmetimiz devreye girer; mevcut sitenizi kesintisiz taşımak söz konusuysa site taşıma sayfamıza göz atabilirsiniz.