Teknoloji

VPS’te Canary Dağıtımı Nasıl Tatlı Tatlı Kurulur? Nginx Ağırlıklı Yönlendirme, Sağlık Kontrolü ve Güvenli Rollback

Giriş: Küçük bir sürüm, büyük bir nefes

Hiç akşam üzeri küçük bir düzeltme göndereyim derken, bir anda beklenmedik bir dalgalanmayla tüm trafiği etkilediğin oldu mu? Ben bir keresinde, minik bir hatayı kapatayım diye aceleyle gönderdiğim sürüm yüzünden, geceyi log’ların başında kahveyle geçirmiştim. O gün karar verdim: Üretimi şansa bırakmak yok, önce ufak bir kitleye göster, gönlün rahat etsin. İşte canary dağıtımı tam olarak bu rahatlığın adı. Yeni sürümün önce ufak bir kısma görünür, bir süre gözlersin, işler yolundaysa yavaş yavaş herkese açarsın.

Bu yazıda VPS üzerinde canary dağıtımını, Nginx ile ağırlıklı yönlendirmeyi, pratik bir sağlık kontrolü yaklaşımını ve tek tuşla güvenli geri dönüş (rollback) düzenini adım adım anlatacağım. Masada abartı yok, tablo yok, uzun “kanıt” listeleri de yok; gerçek hayatta işe yarayan, akışkan ve kolayca hayata geçirilebilen bir kurgu var. Hepimizin sevdiği o “az risk, fazla kontrol” halini birlikte kuracağız. Hadi başlayalım.

Canary dağıtımı tam olarak ne? Neden VPS’te işleri kolaylaştırır?

Canary dağıtımı, yeni sürümü herkesin önüne bir anda koymak yerine, trafiğin küçük bir yüzdesine göstermektir. Düşün ki dükkânına yeni bir vitrin yerleştireceksin; önce bir köşede dener, yoldan geçen birkaç müşterinin bakışını izler, yerinde mi değil mi koklarsın. Beğeniler iyi, geri bildirimler temizse yavaş yavaş tüm vitrine yayarsın. Bu kadar basit ve bu kadar insan gibi bir yaklaşım.

VPS tarafında bunun güzelliği, kontrolün sende olmasında. Nginx ile gelen trafiği nasıl böleceğine sen karar veriyorsun. İstersen ekibindeki birkaç kişi için bir çerezle (cookie) canary’yi zorunlu kıl, istersen ağırlıkları değiştirerek yüzdeyi yumuşakça yükselt. Bir hata kokusu alırsan, ağırlığı sıfıra çekersin, konu kapanır. Üstelik bunu yaparken sistemin nefesini kesmiyorsun, trafik doğal akışına devam ediyor. Kısacası, “küçük adımlar, büyük güven.”

Mesela şöyle düşünün: Mutfakta yeni bir sos deniyorsun. İlk kaşığı kendin tadarsın, sonra arkadaşına uzatırsın, ardından masadakilere ikram edersin. Kimsenin damak tadını riske atmadan, sosa da haksızlık etmeden ilerlersin. Canary’nin ruhu bundan ibaret.

Nginx ile ağırlıklı yönlendirme nasıl kurulur?

İşin kalbi Nginx’te. Trafiği iki havuza ayıracağız: stable ve canary. Stable mevcut sürüm, canary yeni sürüm. Yük dengeleyici mantığıyla düşün; iki farklı backend var, Nginx onlara ağırlıklarına göre istekleri paylaştırıyor. Ağırlık arttıkça daha çok istek gidiyor. Nginx’in upstream ve weight yönergeleri bu işi çok temiz hallediyor.

Bir de kibar bir hilemiz var: Ekip arkadaşlarının tarayıcısına minik bir cookie bırakarak, önce yalnızca onların yeni sürümü görmesini sağlayacağız. Böylece test için staging’e muhtaç kalmadan, gerçek kullanıcı trafiğinin kenarında yürüyen bir mini sahne kuruyoruz. Bu da bize hem hız hem de gerçek ortam kokusu kazandırıyor. Hadi ayarları konuşalım.

Hedefli test: Cookie ile sadece sen ve ekibin görsün

Önce cookie tabanlı yönlendirmeyi kuralım. Bu kısımda, canary=1 çerezi olanlar doğrudan canary backend’ine gidecek. Diğerleri henüz ağırlıklı ortama düşecek, yani normal kullanıcılar hemen etkilenmeyecek.

# /etc/nginx/conf.d/app.conf (örnek)

upstream app_stable {
    server 127.0.0.1:8080 max_fails=3 fail_timeout=10s;
}

upstream app_canary {
    server 127.0.0.1:9080 max_fails=3 fail_timeout=10s;
}

# Canary cookie var mı, yok mu?
map $cookie_canary $upstream_pool {
    default   "weighted";   # cookie yoksa ağırlıklı havuz devrede
    "1"      "canary";     # cookie=1 ise direkt canary
}

# Ağırlıklı havuz (birazdan include edeceğiz)
# /etc/nginx/includes/app_weights.conf dosyasında tanımlayacağız

# $upstream_pool değerine göre hedef upstream ismi seç
map $upstream_pool $backend_upstream {
    canary   app_canary;
    default  app_weighted;
}

server {
    listen 80;
    server_name example.com;

    # İçeriden denemek için hızlı bir anahtar: ?canary=1 ile cookie bırak
    if ($arg_canary = "1") {
        add_header Set-Cookie "canary=1; Path=/; Max-Age=3600";
    }

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://$backend_upstream;
    }
}

Cookie ile hedefli test, canary’yi ilk olarak kendi gözlerinle görmene yarar. Üç beş kişiyle kısa bir tur atıp nefes kontrolü yaptıktan sonra, trafiği yavaş yavaş ağırlıkla geri kalanlara açacağız. Bu sayede ilk izlenimler taze taze elinde olur, gereksiz kahramanlık yapmadan ilerlersin.

Ağırlıklı trafik: Yüzde hesaplarıyla değil, ayar düğmesiyle

Şimdi weighted upstream’i dahil ediyoruz. Ağırlıkları tek bir dosyada tutmak, sürüm adımlarında işimizi kolaylaştıracak. Küçük bir script ile bu dosyayı güncelleyeceğiz, ardından Nginx’i hatasızca yeniden yükleyeceğiz. Map kullanımı da harika, çünkü koşullu yönlendirmeyi sadeleştiriyor; merak edenler map ile koşullu yönlendirme dokümanını inceleyebilir.

# /etc/nginx/includes/app_weights.conf
upstream app_weighted {
    server 127.0.0.1:8080 weight=10 max_fails=3 fail_timeout=10s;  # stable
    server 127.0.0.1:9080 weight=0  max_fails=3 fail_timeout=10s;  # canary (başta kapalı)
}

Bu tasarımın inceliği şu: Canary ağırlığını birden 0’dan 5’e, sonra 10’a çekebilirsin. Böylece trafiği yüzdelerle hesaplayıp zihnini dağıtmadan, “biraz daha ver, biraz daha az” hissiyle yönetirsin. Düğmeyi çevirmek gibi. Üstelik Nginx reload anında bağlantıları kesmeden, zarifçe yeni ayarlarla devam eder. İşte o an içinden bir “oh” gelir, bilirsin ki güvenle tırmanıyorsun.

Sağlık kontrolü: Pasif, aktif ve log’ların gözü

Canary’nin tadı sağlık kontrolüyle çıkar. Nginx’te pasif kontrol dediğimiz şey, başarısız bağlantıları izlemesi, belirli sayıda hata sonrası sunucuyu kısa süreli devre dışı bırakmasıdır. max_fails ve fail_timeout tam burada iş görüyor. Ama bir de gönül rahatlığı için aktif kontrol yapalım: Uygulama /healthz gibi hafif bir endpoint ile “iyiyim” diyecek, biz de küçük bir script ile buna bakacağız. Bir terslik görürsek canary ağırlığını anında sıfıra çekip konuyu kapatacağız.

Log tarafını da ihmal etmiyoruz. Canary isteğini log’larda etiketlemek, tek bakışta “bu hata canary’den mi geldi?” sorusunu cevaplamanı sağlar. Kafanı kaldırıp ekranı süzmen yeter, rengini anında veriyor.

Uygulamanın ağzından “iyiyim” demesi

Uygulamanın basit bir sağlık uç noktası olsun. Yanıtı küçük olsun, 200 dönsün, mümkünse sürüm bilgisini de taşısın. Teknolojiden bağımsız gidelim, fikir net olsun yeter.

# Örnek /healthz yanıtı (sade bir HTTP 200 ve kısa içerik yeterli)
HTTP/1.1 200 OK
Content-Type: text/plain

ok version=1.2.3

Ardından ufak bir kontrol script’i yazalım. Bu script belirli aralıklarla çalışabilir. Canary düşerse, ağırlığını 0 yapar, Nginx’i test edip zarifçe yeniden yükler. Hata olursa otomatik geri alır. Bu küçük güvenlik kemeri gece uykunu tatlı yapar.

# /usr/local/bin/canaryctl (örnek)
#!/usr/bin/env bash
set -euo pipefail

WEIGHTS_FILE="/etc/nginx/includes/app_weights.conf"
STABLE_W=${1:-10}   # stable ağırlık
CANARY_W=${2:-0}    # canary ağırlık

backup=$(mktemp)
cp "$WEIGHTS_FILE" "$backup"

cat > "$WEIGHTS_FILE" <<EOF
upstream app_weighted {
    server 127.0.0.1:8080 weight=${STABLE_W} max_fails=3 fail_timeout=10s;
    server 127.0.0.1:9080 weight=${CANARY_W} max_fails=3 fail_timeout=10s;
}
EOF

if nginx -t; then
  systemctl reload nginx
  echo "[canaryctl] Ağırlıklar güncellendi: stable=${STABLE_W}, canary=${CANARY_W}"
else
  echo "[canaryctl] Nginx test başarısız, geri alınıyor" >&2
  mv "$backup" "$WEIGHTS_FILE"
  exit 1
fi

Bir de aktif kontrolü otomatiğe bağlayan minik bir script yazalım. Canary iki denemenin ikisinde de yanıt vermezse, ağırlığını sıfıra çeksin ve haber bıraksın.

# /usr/local/bin/canary-health-guard
#!/usr/bin/env bash
set -euo pipefail

CANARY_URL="http://127.0.0.1:9080/healthz"
OK=0
for i in 1 2; do
  if curl -fsS --max-time 2 "$CANARY_URL" | grep -qi "ok"; then
    OK=$((OK+1))
  fi
  sleep 1
done

if [ "$OK" -lt 2 ]; then
  echo "[guard] Canary sağlıksız görünüyor, ağırlık sıfırlanıyor" >&2
  /usr/local/bin/canaryctl 10 0 || true
fi

Bu guard’ı bir sistem zamanlayıcısıyla düzenli aralıklarla koşturabilirsin. Hafif, anlaşılır ve tek işini yapıyor. Günün sonunda aradığımız da bu aslında.

Log’larda canary’yi boyamak

Log formatına ufak bir etiket ekleyelim. Böylece canary kaynaklı istekleri bir bakışta ayırt eder, sorun olduğunda nereden geldiğini hızlıca görürsün.

# /etc/nginx/nginx.conf
map $upstream_pool $canary_tag {
    canary   "canary";
    default  "stable";
}

log_format canary '$remote_addr - - [$time_local] "$request" $status $body_bytes_sent '
                  '"$http_referer" "$http_user_agent" pool=$canary_tag rt=$request_time';

access_log /var/log/nginx/access_canary.log canary;

Log’lara bakmayı günlük ritmine almak iyi bir alışkanlık. Birkaç dakikalık bakış, saatlerce sürecek bir arızayı başlamadan bitirebilir. Küçük notlar, büyük rahatlıklar.

Güvenli rollback: Geri dönüş yolunu baştan çizmek

Rollback işi, kriz anında alınan hızlı bir karar gibi görünür ama asıl sihri önceden yapılan hazırlıktadır. Dosya düzenini iki sürümü aynı anda tutacak şekilde kurarsın; current diye bir sembolik bağlantı olur, stable oraya bakar, canary ise yeni sürüme. Sorun çıkarsa current’ı eskiye çevirir, Nginx’i zarifçe reload edersin. İşlem biter, kullanıcı anlamaz bile.

Nginx tarafında da geri dönüş planın hazır: Canary ağırlığını 0 yap, stable’ı yukarı çek. Reload öncesi her zaman test et, test hatalıysa otomatik geri al. Bu kuralı cebine koyarsan, “reload korkusu” hayatından sessizce çıkar. İnce bir ayrıntı gibi ama gece 02:00’de altın değerinde.

Tek tuşla dönüş için bir komut alışkanlığı geliştirmek iyi oluyor. Mesela canaryctl ile ağırlıkları yönetiyorsun; aynı araç hatada saniyeler içinde geri alabiliyor. Bir koşu bandında hızını bir tık düşürmek gibi, ritim bozulmadan devam ediyorsun.

Tek komutla güvenlik: canaryctl ramp-up ve rollback

# Canary'yi %10'a denk gelecek şekilde artır (örnek: 9/1 ya da 10/1 gibi)
/usr/local/bin/canaryctl 9 1

# %30'a doğru yumuşak bir artış
/usr/local/bin/canaryctl 7 3

# Bir şeyler ters gidiyor, hemen geri dön
/usr/local/bin/canaryctl 10 0

Burada yüzdeye körü körüne takılma, genel trafik yoğunluğunu ve uygulamanın doğasını bilerek arttır. Kimisi 5’er 5’er gider, kimisi 10-10 atlar. Önemli olan, her artışın ardından nefes kontrolü yapman ve log’lara şöyle bir göz atman. Bazen bir satır log, bir saatin kaderini değiştirir.

Akşam planı: Küçük bir sürümü canary ile yayına alma

Akşam üstü sessizlik çökmüş, kahveni tazeledin ve mini bir sürüm bekliyor. Önce ekipten iki üç kişiye cookie veriyorsun. Tarayıcılarına ?canary=1 ile bir tur attır, sayfaların açılışında tuhaflık var mı bak. Bu arada /healthz tertemiz, gözün aydın. Nginx log’larında canary etiketiyle gelen istekleri izlerken, ufak tefek uyarılar görebilirsin; yeni bir endpoint biraz ağır kalmış mesela, notunu al.

Sonra ağırlıkları 9/1 yapıp bir on dakika bakıyorsun. Error rate kıpırdandı mı? Hızlarda gözle görülür bir düşüş var mı? Yoksa 7/3’e yükseltiyorsun. Adımlar arasında uygulamanın nefesini ölç, gereksiz hızla kaptırıp gitme. Trafiğin ritmini dinle, log’ların şarkısını duy. Bir yerde tını bozulursa, canary’i anında 0’a çekip nefeslendir, gerekirse hata ayıklamaya geri dön.

Bu sırada uygulama güncellemesi dil seviyesinde bir değişiklik içeriyorsa, örneğin bir PHP majör güncellemesi gibi, geriye uyumluluğu önceden tatlı tatlı kontrol etmek iyi fikir. Böyle durumlarda, PHP 8.x yükseltme kontrol listesinde pratik uyumluluk notları gibi bir yol haritasını elinin altında tutmak, canary’nin yükünü de azaltır. Çünkü bilirsin, bazı sorunlar canlıda değil, mutfakta çözüldüğünde en tatlısıdır.

Her şey yolundaysa final turu geliyor. Ağırlıkları 5/5’e getirip biraz daha izliyorsun, sonra yavaş yavaş 3/7, 2/8, 1/9 derken bir bakmışsın stable artık neredeyse boşa düşmüş. Bu noktada current bağlantını yeni sürüme alıyor, stable’ı yedek gibi tutmaya devam ediyorsun. Bir süre daha gözlem yap, sonra eski sürümü raflara kaldır. Küçük zaferlerin tadını çıkar, büyük fırtınalara gerek yok.

Ek notlar: TLS, reload ve küçük tehlikeler

Canary’nin teknik ritminde iki küçük detay hayat kurtarır. Birincisi, Nginx reload’un zarafeti; her zaman önce test et, sonra reload. Bu rutini ezberlersen, “ya dükkanı kapatırsam” endişesi çekip gider. nginx -t ve zarif reload üzerine kısa bir okuma bile bakış açını berraklaştırır. İkincisi, sertifika yenilemeleri. ACME otomasyonu ile sertifikalar taze kalır, reload aynı zarafetle sürer; bu sayede canary akışını kesmeden güvenliği de canlı tutarsın.

Bazen canary başarısız olduğunda paniğe kapılmak kolaydır. Oysa plan basit: Ağırlığı sıfırla, log’tan ipucunu topla, küçük bir düzeltme yap, tekrar küçük bir payla dene. Bu çevrim, duvarlara çarpmadan ilerleme sanatıdır. Her tur seni güçlendirir, her tur sistemi tanıtır. Yeter ki acele etme, izlemeyi bırakma, küçük adımların kıymetini bil.

Kapanış: Canary, küçük cesur adımların güvenliği

Bir uygulamayı büyütmenin yolu, ona sabırla eşlik etmekten geçiyor. Canary dağıtımı bu sabrın pratik hali; yeni olanı ufak ufak sahneye çıkarıyor, gözlerinle tartıyor, sonra alkışı veriyorsun. Nginx ile ağırlıklı yönlendirme bu akışı parmaklarının ucuna getiriyor; küçük bir script, basit bir sağlık kontrolü ve nazik bir reload ile işin kontrolünü tamamen eline alıyorsun. Üstelik her şey o kadar doğal akıyor ki, bir yerden sonra “başka türlüsü nasıl olurdu ki” demeye başlıyorsun.

Kapanışı birkaç pratik notla yapalım: Canary’yi önce kendin tat, ekibe tattır, sonra yavaş yavaş herkese aç. Log’lara kulak ver, sağlık kontrollerini hafif ama güvenilir tut. Rollback yolun her zaman açık kalsın, test etmeden reload etme. Ve lütfen, güzel giden bir akşamı gereksiz risklerle bozma. Umarım burada paylaştıklarım, bir sonraki küçük sürümünü huzurla yayına almanda sana yol olur. Bir dahaki yazıda görüşmek üzere, şimdilik alçakgönüllü canary’lere selam olsun.


Ek kaynak: Nginx upstream ve ağırlıklar için resmi doküman, koşullu yönlendirme için map modülü, güvenli yeniden yükleme için kontrol komutları.

Sıkça Sorulan Sorular

Yeni sürümü önce küçük bir kitleye açtığın için risk düşük oluyor. Hata olursa trafiği hemen eski sürüme çevirip toparlayabiliyorsun. Bu da soğukkanlı kalmanı sağlıyor.

Upstream’de weight değerleriyle trafiği paylaştırabilirsin. Mesela stable 9, canary 1 olduğunda kabaca 9’a 1 dağılım elde edersin. Ağırlıkları değiştirip zarifçe reload yapman yeter.

Uygulamaya hafif bir /healthz ekle, 200 ve kısa bir metin dönsün. Küçük bir script bu noktayı düzenli yoklasın. Yanıt bozulursa canary ağırlığını 0’a çeksin, sen de log’tan iz sür.