Dört kopya halinde çalışan bir API'nin yeni sürümünü yayınlayacaksın. En kaba yöntem hepsini birden durdurup yenisini başlatmaktır; bu da imaj çekme, konteyner başlatma ve uygulamanın ısınma süresi boyunca servisin tamamen kapalı kalması demektir. Rolling update bu kaba yöntemin yerine geçer: kopyaları teker teker ya da küçük gruplar halinde yenilersin, her adımda yenisinin gerçekten sağlıklı olduğunu doğrularsın ve ancak ondan sonra bir sonrakine geçersin. Servis hiçbir an tamamen kapanmaz.
Rolling update'in kağıt üzerinde basit görünen ama sahada insanı yakan tarafı geri dönüştür. Yeni sürüm yarı yolda bozulursa elinde yarısı eski yarısı yeni, karışık bir küme kalır. Bu rehberde rolling update'in adım adım nasıl işlediğini, sağlık kontrolünün neden bu stratejinin can damarı olduğunu, Docker Swarm ve Kubernetes tarafındaki gerçek yapılandırmaları, maxSurge ile maxUnavailable ikilisinin ne anlama geldiğini, otomatik rollback'i ve veritabanı göçlerinin bu düzeni nasıl bozduğunu anlatacağım.
Rolling Update Nedir ve Adım Adım Nasıl İşler#
Rolling update, aynı uygulamanın N adet çalışan kopyası varken bu kopyaları hepsini birden değil, kontrollü bir sırayla yeni sürüme geçirme yöntemidir. Orkestratör bir kopyayı yük dengeleyici havuzundan çıkarır, durdurur, yeni imajla başlatır, sağlık kontrolünden geçmesini bekler, havuza geri alır ve bir sonrakine geçer. Bu döngü boyunca kalan kopyalar trafiği taşımaya devam ettiği için kullanıcı tarafında kesinti oluşmaz.
Tek bir adımın gerçek sırası şudur ve bu sıra bozulursa strateji anlamını yitirir:
- Yeni sürümün imajı düğüme çekilir (
docker pullya da orkestratörün kendi mekanizması). - Güncellenecek kopya yük dengeleyicinin havuzundan çıkarılır, yani yeni istek almaz.
- Açık isteklerin bitmesi için bir bekleme süresi tanınır (connection draining).
- Eski konteyner durdurulur, yeni sürüm başlatılır.
- Sağlık kontrolü art arda başarılı olana kadar beklenir.
- Kopya havuza geri alınır,
delaysüresi kadar beklenip bir sonraki kopyaya geçilir.
Bu yöntemin en büyük avantajı ekstra kaynak maliyetinin düşük olmasıdır. Blue-green deployment yönteminde iki tam ortam ayakta tutmak zorundasın; rolling update'te ise fazladan yalnızca bir veya iki kopyalık kapasite yeter. Buna karşılık geçiş süresince eski ve yeni sürüm aynı anda canlıdır ve bu durum, aşağıda ayrıntısına gireceğim uyumluluk kurallarını zorunlu kılar.
Sağlık Kontrolü Olmadan Rolling Update Yapılmaz#
Rolling update'in tamamı tek bir soruya dayanır: "yeni kopya gerçekten hizmet verebiliyor mu?" Orkestratör bu soruyu sağlık kontrolüyle sorar. Sağlık kontrolü tanımlanmamışsa orkestratör "süreç ayakta, demek ki sağlıklı" varsayımını yapar ve veritabanına bağlanamayan, yapılandırmayı okuyamayan bir konteyneri havuza alır. Sonuç, kesintisiz sanılan bir güncellemenin sessizce yüzde yirmi beş hata üretmesidir.
Uygulamanda ayrı bir sağlık uç noktası aç ve bunu ana iş mantığından bağımsız tut. İyi bir /healthz uç noktası kritik bağımlılıkları (veritabanı, önbellek) hafifçe yoklar ama ağır sorgu çalıştırmaz:
# Sağlık uç noktası dışarıdan böyle görünmeli
curl -fsS -o /dev/null -w "%{http_code} %{time_total}s\n" http://127.0.0.1:8080/healthz
# 200 0.012s
Docker tarafında sağlık kontrolünü ya imaja gömersin ya da compose dosyasında tanımlarsın:
services:
api:
image: registry.firmaniz.com/api:1.4.2
healthcheck:
# Uygulamanın kendi bağımlılıklarını da kontrol eden hafif uç nokta
test: ["CMD", "curl", "-fsS", "http://localhost:8080/healthz"]
interval: 10s # kontroller arası süre
timeout: 3s # tek kontrol için üst sınır
retries: 3 # üst üste 3 başarısızlık = unhealthy
start_period: 25s # açılış süresi; bu sürede başarısızlık sayılmaz
start_period en sık atlanan parametredir. JVM ya da .NET tabanlı bir uygulama ilk yirmi saniye boyunca meşguldür; bu süre tanınmazsa konteyner sağlıksız sayılır, yeniden başlatılır ve sonsuz bir döngüye girer. Konteynerin durmadan yeniden başladığı bu tabloyu daha önce yaşadıysan, konteyner sürekli yeniden başlıyor yazısındaki teşhis adımları buraya birebir uyar.
Docker Swarm ile Rolling Update Yapılandırması#
Docker Swarm, rolling update'i servis tanımının içine gömer; ayrı bir betik yazmana gerek kalmaz. Aşağıdaki deploy bloğu sahada güvenle kullanabileceğin bir başlangıç yapılandırmasıdır:
services:
api:
image: registry.firmaniz.com/api:1.4.2
deploy:
replicas: 4
update_config:
parallelism: 1 # aynı anda kaç kopya yenilensin
delay: 20s # kopyalar arası bekleme
order: start-first # önce yenisini başlat, sonra eskisini durdur
failure_action: rollback
monitor: 30s # başarı sayılmadan önce izleme süresi
max_failure_ratio: 0
rollback_config:
parallelism: 1
delay: 10s
order: start-first
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8080/healthz"]
interval: 10s
timeout: 3s
retries: 3
start_period: 25s
Burada iki parametre kaderi belirler. order: start-first, yeni kopya sağlıklı olmadan eskisini durdurmaz; yani kapasiten hiçbir an düşmez. Varsayılan olan stop-first ise önce durdurur ve o kısa aralıkta kapasiten bir kopya eksilir. Dört kopyalı bir serviste bu genelde tolere edilebilir, iki kopyalı bir serviste ise yükün yarısını tek makineye yıkar. monitor süresi ise yeni kopyanın "başarılı" sayılmadan önce izleneceği süredir; çok kısa tutarsan açılışta iyi görünüp otuzuncu saniyede çöken bir sürüm başarıyla yayılmış sayılır.
Güncellemeyi başlatmak ve izlemek için kullandığın komutlar şunlardır:
# Yeni sürüme geç
docker service update --image registry.firmaniz.com/api:1.4.3 api
# Adım adım hangi görevin hangi durumda olduğunu izle
docker service ps api --no-trunc
# Servisin güncelleme durumu (updating / completed / rollback_completed)
docker service inspect api --format '{{ .UpdateStatus.State }}: {{ .UpdateStatus.Message }}'
# Elle geri dön
docker service rollback api
Tek düğümlü bir kurulumda çalışıyorsan ve Swarm'a geçmek istemiyorsan, benzer davranışı docker compose up -d --no-deps --scale kombinasyonuyla elle kurgulaman gerekir; compose'un kendi başına sağlık kontrolüne bağlı kademeli değişimi yoktur. Compose tarafındaki temel kavramlara hâkim değilsen Docker Compose kullanımı yazısı iyi bir başlangıç noktasıdır.
Kubernetes Tarafında maxSurge ve maxUnavailable#
Kubernetes'te rolling update varsayılan stratejidir ve davranışı iki sayı belirler. maxSurge, istenen kopya sayısının üzerine geçici olarak kaç fazladan kopya açılabileceğini; maxUnavailable ise güncelleme sırasında en fazla kaç kopyanın hizmet dışı kalabileceğini söyler. İkisi de mutlak sayı ya da yüzde olarak yazılabilir.
spec:
replicas: 6
minReadySeconds: 15 # hazır sayılmadan önce bu kadar sabit kalmalı
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # 6 yerine geçici olarak 8 kopyaya çıkabilir
maxUnavailable: 0 # kapasite hiçbir an 6'nın altına düşmez
Bu iki değerin kombinasyonu doğrudan hız ile güvenlik arasındaki dengeyi belirler:
| maxSurge | maxUnavailable | Davranış | Ne zaman uygun |
|---|---|---|---|
| 0 | 1 | Önce durdur, sonra başlat; ek kaynak yok | Kaynak sıkışıksa, kritik olmayan servis |
| 1 | 0 | Önce başlat, sonra durdur; kapasite hiç düşmez | Üretimdeki varsayılan tercih |
| %25 | 0 | Aynı anda birçok kopya, kapasite korunur | Çok kopyalı, hızlı yayın istenen servis |
| %25 | %25 | En hızlı; kapasite geçici olarak düşer | Yükün düşük olduğu bakım penceresi |
maxUnavailable: 0 üretim için doğru varsayılandır ama tek koşulu vardır: kümede en az bir kopyalık boş kapasite bulunmalıdır. Yoksa yeni kopya Pending durumunda takılır ve güncelleme hiç ilerlemez. minReadySeconds ise sağlık kontrolünden hemen sonra çöken sürümleri yakalamak için kritik bir tampon sağlar.
İzleme ve geri dönüş komutları şunlardır:
# Güncellemeyi zaman aşımı ile izle; başarısızsa çıkış kodu 1 döner
kubectl rollout status deployment/api --timeout=180s
# Revizyon geçmişi
kubectl rollout history deployment/api
# Bir önceki revizyona dön
kubectl rollout undo deployment/api
# Belirli bir revizyona dön
kubectl rollout undo deployment/api --to-revision=7
# Yayını ortada dondur (canary benzeri gözlem için)
kubectl rollout pause deployment/api
kubectl rollout resume deployment/api
kubectl rollout status komutunun zaman aşımında sıfırdan farklı çıkış kodu döndürmesi, CI hattında otomasyon kurmanın en temiz yoludur: komut başarısız olduğu anda pipeline kubectl rollout undo çalıştırır.
Rollback: Geri Dönüşü Yayından Önce Planla#
Rollback'i "bir şeyler ters giderse yaparız" diye düşünmek en pahalı hatadır, çünkü o an panik anıdır ve düşünmek için vaktin yoktur. Geri dönüşü yayın öncesinde tanımla: hangi metrik hangi eşiği aşarsa geri döneceğini, kimin karar vereceğini ve komutun tam olarak ne olduğunu yaz. Pratikte üç şeyin önceden hazır olması yeterlidir.
Birincisi, değişmez sürüm etiketleri. latest etiketiyle çalışıyorsan geri dönecek bir yerin yoktur; çünkü latest, dün de bugün de aynı ismi taşıyan farklı bir imajdır. Her yapıya sürüm ya da commit hash'i içeren bir etiket ver:
# Değişmez etiket üret ve gönder
GIT_SHA=$(git rev-parse --short HEAD)
docker build -t registry.firmaniz.com/api:1.4.3-${GIT_SHA} .
docker push registry.firmaniz.com/api:1.4.3-${GIT_SHA}
İkincisi, son iyi bilinen sürümün kayıt altında olması. Her başarılı yayından sonra sürümü basit bir dosyaya ya da ortam değişkenine yaz; geri dönüş komutu bu değeri okusun. Üçüncüsü, rollback'in otomatik tetiklenmesi. Swarm'da bunu failure_action: rollback yapar, Kubernetes'te ise CI adımının kendisi yapar:
#!/usr/bin/env bash
set -euo pipefail
kubectl set image deployment/api api=registry.firmaniz.com/api:${NEW_TAG}
if ! kubectl rollout status deployment/api --timeout=180s; then
echo "Yayın doğrulanamadı, geri dönülüyor..." >&2
kubectl rollout undo deployment/api
kubectl rollout status deployment/api --timeout=120s
exit 1
fi
echo "Yayın tamam: ${NEW_TAG}"
Geri dönüş kararını insan sezgisine değil ölçüye bağlamak istiyorsan, hata oranı ve gecikme eşiklerinin nasıl belirlendiğini SLO ve error budget yazısında ayrıntılı anlattım.
Veritabanı Göçleri Rolling Update'i Nasıl Bozar#
Rolling update sırasında eski ve yeni sürüm dakikalarca aynı anda çalışır ve ikisi de aynı veritabanına bağlıdır. Bu tek cümle, şema değişikliklerinin neden bu stratejinin en riskli parçası olduğunu açıklar. Yeni sürüm bir sütunu yeniden adlandırdıysa, henüz güncellenmemiş eski kopyalar eski sütun adını sorgulamaya devam eder ve hata verir. Daha kötüsü, geri dönmek istediğinde şema artık eski koda uymaz; yani rollback'in de kırılır.
Çözüm, şema değişikliğini koddan ayırmak ve genişlet-daralt (expand-contract) düzeniyle üç yayına bölmektir:
| Aşama | Veritabanında yapılan | Kodda yapılan | Geri dönülebilir mi |
|---|---|---|---|
| 1. Genişlet | Yeni sütun eklenir, NULL kabul eder | Kod hâlâ eski sütunu okur | Evet |
| 2. Geçiş | Veri kopyalanır, iki sütun da yazılır | Yeni kod yeni sütunu okur | Evet |
| 3. Daralt | Eski sütun kaldırılır | Eski sütuna referans kalmaz | Hayır, bu adım tek yönlü |
Kural nettir: hiçbir yayın hem şemayı hem de o şemaya bağımlı kodu aynı anda değiştirmemeli. Bir örnek üzerinden bakalım:
-- 1. Genişlet: güvenli, geri dönülebilir. Eski kod bu sütunu görmez bile.
ALTER TABLE siparisler ADD COLUMN musteri_eposta VARCHAR(255) NULL;
CREATE INDEX CONCURRENTLY idx_siparisler_musteri_eposta ON siparisler (musteri_eposta);
-- 2. Geçiş: mevcut veriyi parça parça doldur, tabloyu uzun süre kilitleme
UPDATE siparisler SET musteri_eposta = eposta WHERE musteri_eposta IS NULL LIMIT 5000;
-- 3. Daralt: yalnızca tüm kopyalar yeni sürüme geçtikten ve birkaç gün geçtikten sonra
ALTER TABLE siparisler DROP COLUMN eposta;
CREATE INDEX CONCURRENTLY gibi kilit süresini kısaltan biçimleri tercih et; büyük bir tabloda düz CREATE INDEX yazmak, tüm yazma işlemlerini dakikalarca bekletir ve rolling update'i kesintiye çevirir. Daralt adımını en az bir başarılı yayın döngüsü sonrasına bırakmak da geri dönüş kapını açık tutar.
Sahada En Sık Yapılan Hatalar#
latest etiketiyle yayın yapmak. Orkestratör imaj etiketi değişmediği için hiçbir şeyi yenilemez; --force ile zorlarsan da hangi imaja döneceğini bilemezsin. Değişmez etiket kullanmak bu iki sorunu birden çözer.
Sağlık kontrolünü uygulamanın ana sayfasına bağlamak. / yolunu sağlık kontrolü yapmak, ağır bir ana sayfa sorgusunu her on saniyede bir çalıştırmak demektir; ayrıca ana sayfa önbellekten dönüyorsa veritabanı çökmüşken bile 200 döner. Ayrı, hafif ve gerçekten bağımlılık yoklayan bir uç nokta aç.
Kapatma sinyalini yok saymak. Uygulaman SIGTERM aldığında hemen ölürse, o an işlediği istekler yarıda kalır. Rolling update'in her adımında bu gerçekleşir; yani "kesintisiz" dediğin yayın, her adımda bir avuç 502 üretir. Zarif kapanışın nasıl kurulduğunu sıfır kesintili deploy yazısında adım adım anlattım.
Tek kopyayla rolling update denemek. Kopya sayısı 1 ise ya kapasite sıfıra düşer ya da start-first ile aynı porta iki kopya bağlanmaya çalışır. Bu strateji en az iki, tercihen üç kopyayla anlamlıdır.
Yayın sonrası izlememek. Güncelleme "completed" dedi diye iş bitmez; asıl bilgi sonraki on beş dakikadaki hata oranı ve gecikme eğrisindedir. Yayından sonraki kısa bir gözlem penceresi, gecenin ortasında uyanmaktan ucuzdur.
Sıkça Sorulan Sorular#
Rolling update ile blue-green arasındaki fark nedir#
Rolling update kopyaları teker teker yeniler, blue-green ise iki tam ortam tutup trafiği bir anda çevirir. Rolling update daha az kaynak ister çünkü ikinci bir ortamı ayakta tutmazsın, ama geçiş süresince eski ve yeni sürüm aynı anda çalışır ve bu uyumluluk zorunluluğu getirir. Blue-green'de geri dönüş saniyeler sürer; rolling update'te ise geri dönüş de kademelidir, yani birkaç dakika alır.
Rolling update ne kadar sürer#
Süre kabaca kopya sayısı çarpı tek kopyanın hazır olma süresi bölü paralellik kadardır. Altı kopyalı, her biri kırk saniyede hazır olan bir serviste parallelism: 1 ve delay: 20s ile yaklaşık altı dakika sürer. Paralelliği artırmak hızlandırır ama kötü bir sürüm daha fazla kullanıcıya aynı anda ulaşır.
Rollback sonrası veritabanı değişiklikleri ne olacak#
Kod geri döner ama veritabanı geri dönmez, işin en can alıcı noktası budur. Bu yüzden şema değişikliklerini her zaman geriye uyumlu tut: sütun ekle, sütun silme; alan adını değiştirmek yerine yenisini ekleyip eskiyi bir süre koru. Genişlet-daralt düzenine uyarsan rollback yaptığında eski kod hâlâ çalışan bir şemayla karşılaşır.
Tek sunucuda rolling update yapılabilir mi#
Evet, ama koşulu aynı uygulamadan birden fazla kopyanın farklı portlarda çalışması ve önlerinde bir yönlendirme katmanı bulunmasıdır. Nginx upstream havuzuna üç kopya tanımlarsın, birini havuzdan çıkarıp yenilersin, sağlıklıysa geri alırsın. Tek kopya varsa rolling update mümkün değildir; o durumda ya kısa bir kesintiyi kabul edersin ya da blue-green kurarsın.
Kubernetes rollout undo kaç sürüm geriye gidebilir#
Varsayılan olarak Deployment'ın revisionHistoryLimit değeri onudur; yani son on revizyonun ReplicaSet'i saklanır ve bunlardan herhangi birine dönebilirsin. kubectl rollout history ile listeyi görür, --to-revision ile istediğine geçersin. Bu sınırı düşürmek küme üzerindeki nesne sayısını azaltır ama geri dönüş seçeneklerini de daraltır.
Rolling update sırasında oturumlar bozulur mu#
Oturum bilgisini uygulama belleğinde tutuyorsan evet; bir kopya kapandığında o kopyadaki oturumlar kaybolur ve kullanıcı aniden çıkış yapmış olur. Çözüm, oturumu paylaşılan bir depoya (Redis, veritabanı) ya da imzalı çereze taşımaktır. Bu, durumsuz uygulama ilkesinin doğrudan pratik karşılığıdır ve rolling update yapabilmenin ön koşuludur.
Kapanış#
Rolling update ucuz, sade ve çoğu ekip için doğru varsayılandır; ama üç şey doğru değilse sessizce kesintiye dönüşür: gerçek bir sağlık kontrolün olmalı, uygulaman SIGTERM aldığında açık istekleri bitirip zarifçe kapanmalı ve veritabanı şeman geriye uyumlu ilerlemeli. Bunların üstüne değişmez sürüm etiketleri ve doğrulanamayan yayında otomatik geri dönen bir CI adımı eklediğinde, yayın günü artık heyecan verici bir olay olmaktan çıkar.
Bu düzeni kendi altyapında kurmak istiyorsan tam root erişimi gerekir; VDS ve bulut sunucu paketlerimizde birden fazla kopya çalıştırıp önlerine kendi yönlendirme katmanını koyabilirsin. Orkestrasyon kurulumu, sağlık kontrolleri ve yayın hattını kendin kurmak yerine devretmek istersen sunucu yönetimi hizmetimiz bu işi üstlenir; yayın öncesi geri dönüş noktası için de yedekleme çözümlerimize göz atabilirsin.