Canary Release: Nginx ile Kademeli Yayınlama Stratejisi
Prodüksiyona yeni bir özellik atmak heyecan verici ama aynı zamanda korkutucu bir an. “Ya çöküyor mu? Ya kullanıcılar şikayete başlarsa?” diye düşünmeden edemiyorsun. İşte tam bu noktada canary release stratejisi devreye giriyor. Nginx ile kademeli yayınlama yaparak yeni versiyonunuzu önce küçük bir kullanıcı grubuna açabilir, sorun çıkmazsa trafiği kademeli olarak artırabilirsiniz. Bu yaklaşım, tam bir felaket yaşamadan önce sorunları yakalamanın en pratik yollarından biri.
Canary Release Nedir?
Canary release, yeni bir uygulamanın veya özelliğin tüm kullanıcılara değil, başlangıçta küçük bir kullanıcı grubuna sunulmasıdır. İsim, eski maden işçilerinin tünel içindeki hava kalitesini ölçmek için kanarya kullanmasından geliyor. Kanarya zehirli gazdan etkilenirse işçiler tehlikeden haberi olur.
Yazılım dünyasında da aynı mantık geçerli: Eğer yeni versiyonunuz %5 kullanıcıda patlarsa, %95’ini kurtarmış olursunuz. Sorun yoksa trafiği artırırsınız, sorun varsa hızla geri dönersiniz.
Nginx bu iş için mükemmel bir araç çünkü:
- upstream blokları ile birden fazla backend tanımlayabilirsiniz
- split_clients direktifi ile trafiği ağırlıklı olarak bölebilirsiniz
- map direktifi ile kullanıcı bazlı yönlendirme yapabilirsiniz
- Konfigürasyon değişiklikleri için servisi yeniden başlatmanıza gerek yoktur, reload yeterlidir
Temel Mimari
Senaryo şu: Bir e-ticaret API’niz var. v1 production’da çalışıyor, yeni v2’yi kademeli olarak devreye almak istiyorsunuz.
Internet --> Nginx (Load Balancer) --> v1 (stable, %90)
--> v2 (canary, %10)
Başlangıç için iki backend sunucunuz olduğunu varsayalım:
- v1 backend: 127.0.0.1:8080
- v2 backend: 127.0.0.1:8081
Yöntem 1: split_clients ile Ağırlıklı Trafik Dağılımı
split_clients direktifi, bir string’i hash’leyerek trafiği yüzdelik dilimlere böler. En basit ve etkili yöntem.
# /etc/nginx/nginx.conf veya /etc/nginx/conf.d/canary.conf
http {
# Kullanıcının IP adresine göre trafik böl
split_clients "${remote_addr}" $upstream_pool {
10% canary;
* stable;
}
upstream stable {
server 127.0.0.1:8080;
keepalive 32;
}
upstream canary {
server 127.0.0.1:8081;
keepalive 32;
}
server {
listen 80;
server_name api.example.com;
location /api/ {
proxy_pass http://$upstream_pool;
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_set_header X-Canary $upstream_pool;
}
}
}
Burada ${remote_addr} hash kaynağı olarak kullanılıyor. Yani aynı IP her zaman aynı backend’e gider. Bu tutarlılık (stickiness) kullanıcı deneyimi için kritik. Aynı kullanıcı sayfayı yenileyince farklı bir backend’e düşmez.
Konfigürasyonu test edip yeniden yüklemek için:
# Konfigürasyonu test et
sudo nginx -t
# Sorun yoksa reload et (downtime olmadan)
sudo systemctl reload nginx
# Reload sonrası log izle
sudo tail -f /var/log/nginx/access.log | grep "canary"
Yöntem 2: Cookie Bazlı Canary Yönlendirme
Bazı durumlarda IP bazlı yönlendirme yeterli olmaz. Örneğin NAT arkasındaki kurumsal kullanıcılar aynı IP’yi paylaşır. Cookie bazlı yönlendirme daha granüler kontrol sağlar.
http {
map $cookie_canary_group $upstream_pool {
"v2" canary;
default stable;
}
upstream stable {
server 127.0.0.1:8080;
keepalive 32;
}
upstream canary {
server 127.0.0.1:8081;
keepalive 32;
}
server {
listen 80;
server_name api.example.com;
location /api/ {
proxy_pass http://$upstream_pool;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Hangi gruba düştüğünü response header'da gönder
add_header X-Served-By $upstream_pool always;
}
# Canary grubuna kullanıcı eklemek için endpoint
location /enable-canary {
add_header Set-Cookie "canary_group=v2; Path=/; Max-Age=86400; HttpOnly";
return 200 "Canary aktif edildin";
}
# Canary'den çıkmak için
location /disable-canary {
add_header Set-Cookie "canary_group=; Path=/; Max-Age=0; HttpOnly";
return 200 "Canary deaktif edildin";
}
}
}
Bu yaklaşımda beta kullanıcılarınıza veya iç ekibinize /enable-canary endpoint’ine gitmelerini söyleyebilirsiniz. Cookie set edildiğinde otomatik olarak v2’ye yönlenirler.
Yöntem 3: HTTP Header Bazlı Yönlendirme
CI/CD pipeline’ınızdan veya internal servislerden gelen istekleri header ile yönlendirmek çok kullanışlı. Özellikle staging ve QA ekipleri için ideal.
http {
map $http_x_canary_version $upstream_pool {
"v2" canary;
default stable;
}
upstream stable {
server 127.0.0.1:8080;
keepalive 32;
}
upstream canary {
server 127.0.0.1:8081;
keepalive 32;
}
server {
listen 80;
server_name api.example.com;
location /api/ {
proxy_pass http://$upstream_pool;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
Test etmek için:
# Normal istek - stable backend'e gider
curl -i https://api.example.com/api/products
# Canary header ile - v2'ye gider
curl -i -H "X-Canary-Version: v2" https://api.example.com/api/products
# Hangi backend'e düştüğünü görmek için
curl -si https://api.example.com/api/health | grep "X-Served-By"
Yöntem 4: Gelişmiş Karma Strateji
Gerçek dünya senaryolarında genellikle birden fazla kriteri bir arada kullanmanız gerekir. Örneğin: “Beta kullanıcıları her zaman v2’ye gitsin, diğerlerinin %10’u da gitsin.”
http {
# Önce cookie kontrol et
map $cookie_canary_group $cookie_canary {
"v2" 1;
default 0;
}
# IP bazlı %10 trafik
split_clients "${remote_addr}AAA" $ip_canary {
10% 1;
* 0;
}
# İkisini birleştir
map "${cookie_canary}${ip_canary}" $upstream_pool {
"~1" canary; # Herhangi biri 1 ise canary
default stable;
}
upstream stable {
server 127.0.0.1:8080 weight=1;
keepalive 32;
}
upstream canary {
server 127.0.0.1:8081 weight=1;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/ssl/certs/api.example.com.crt;
ssl_certificate_key /etc/ssl/private/api.example.com.key;
# Detaylı logging - canary monitoring için kritik
log_format canary_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upstream=$upstream_pool '
'rt=$request_time '
'uct=$upstream_connect_time '
'urt=$upstream_response_time';
access_log /var/log/nginx/api_canary.log canary_log;
location /api/ {
proxy_pass http://$upstream_pool;
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_set_header X-Request-ID $request_id;
# Timeout değerleri
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# Header olarak hangi backend kullanıldığını ilet
add_header X-Served-By $upstream_pool always;
add_header X-Request-ID $request_id always;
}
# Health check endpoint'i
location /health {
proxy_pass http://$upstream_pool/health;
proxy_set_header Host $host;
access_log off;
}
}
}
Kademeli Trafik Artırma Scripti
Canary deployment’ın güzelliği kademelilik. Manuel olarak her seferinde konfigürasyon dosyasını düzenlemek yerine bir script kullanabilirsiniz:
#!/bin/bash
# /usr/local/bin/canary-traffic.sh
NGINX_CONF="/etc/nginx/conf.d/canary.conf"
CURRENT_PERCENT=$(grep -oP 'd+(?=%)' $NGINX_CONF | head -1)
usage() {
echo "Kullanim: $0 [percentage]"
echo "Ornek: $0 25"
echo "Mevcut canary trafigi: ${CURRENT_PERCENT}%"
exit 1
}
if [ -z "$1" ]; then
usage
fi
NEW_PERCENT=$1
if [ "$NEW_PERCENT" -lt 0 ] || [ "$NEW_PERCENT" -gt 100 ]; then
echo "Hata: Yuzde 0-100 arasinda olmali"
exit 1
fi
# Konfigürasyonu güncelle
sed -i "s/${CURRENT_PERCENT}%s*canary/${NEW_PERCENT}% canary/" $NGINX_CONF
# Test et
if nginx -t 2>/dev/null; then
systemctl reload nginx
echo "Basarili: Canary trafigi %${CURRENT_PERCENT} -> %${NEW_PERCENT} olarak guncellendi"
# Log'a yaz
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Canary traffic: ${CURRENT_PERCENT}% -> ${NEW_PERCENT}%"
>> /var/log/nginx/canary_changes.log
else
# Geri al
sed -i "s/${NEW_PERCENT}%s*canary/${CURRENT_PERCENT}% canary/" $NGINX_CONF
echo "Hata: Nginx konfigurasyonu gecersiz, degisiklik geri alindi"
exit 1
fi
Script’i çalıştırılabilir yapın ve kullanın:
chmod +x /usr/local/bin/canary-traffic.sh
# %5 ile başla
canary-traffic.sh 5
# Sorun yoksa artır
canary-traffic.sh 10
canary-traffic.sh 25
canary-traffic.sh 50
canary-traffic.sh 100
Monitoring ve Alerting
Canary deployment’ın başarısı izlemeye bağlı. Neyi izlemeniz gerektiğini bilin:
Log Analizi ile Hata Oranı Karşılaştırma
#!/bin/bash
# Canary vs Stable hata oranlarını karşılaştır
LOG_FILE="/var/log/nginx/api_canary.log"
THRESHOLD=5 # %5 hata eşiği
# Son 5 dakikanın loglarını al
RECENT_LOGS=$(awk -v d="$(date -d '5 minutes ago' '+%d/%b/%Y:%H:%M')"
'$0 ~ d' $LOG_FILE)
# Stable backend hata oranı
STABLE_TOTAL=$(echo "$RECENT_LOGS" | grep 'upstream=stable' | wc -l)
STABLE_ERRORS=$(echo "$RECENT_LOGS" | grep 'upstream=stable' | awk '$9 >= 500' | wc -l)
# Canary backend hata oranı
CANARY_TOTAL=$(echo "$RECENT_LOGS" | grep 'upstream=canary' | wc -l)
CANARY_ERRORS=$(echo "$RECENT_LOGS" | grep 'upstream=canary' | awk '$9 >= 500' | wc -l)
if [ "$CANARY_TOTAL" -gt 0 ]; then
CANARY_ERROR_RATE=$(echo "scale=2; $CANARY_ERRORS * 100 / $CANARY_TOTAL" | bc)
echo "Canary Hata Orani: %${CANARY_ERROR_RATE} (${CANARY_ERRORS}/${CANARY_TOTAL})"
# Eşik aşılırsa alert
if (( $(echo "$CANARY_ERROR_RATE > $THRESHOLD" | bc -l) )); then
echo "ALERT: Canary hata orani esigi asti! Rollback gerekebilir."
# Buraya Slack/PagerDuty notification ekleyebilirsiniz
fi
fi
if [ "$STABLE_TOTAL" -gt 0 ]; then
STABLE_ERROR_RATE=$(echo "scale=2; $STABLE_ERRORS * 100 / $STABLE_TOTAL" | bc)
echo "Stable Hata Orani: %${STABLE_ERROR_RATE} (${STABLE_ERRORS}/${STABLE_TOTAL})"
fi
# Ortalama yanıt süreleri
echo ""
echo "Ortalama Yanit Sureleri:"
echo "$RECENT_LOGS" | grep 'upstream=stable' |
awk -F'rt=' '{print $2}' | awk '{print $1}' |
awk '{sum+=$1; n++} END {printf "Stable: %.3fsn", sum/n}'
echo "$RECENT_LOGS" | grep 'upstream=canary' |
awk -F'rt=' '{print $2}' | awk '{print $1}' |
awk '{sum+=$1; n++} END {printf "Canary: %.3fsn", sum/n}'
Rollback Stratejisi
Her şey yolunda gitmeyebilir. Hızlı rollback için hazır olun:
#!/bin/bash
# /usr/local/bin/canary-rollback.sh
echo "[$(date)] ROLLBACK baslatiliyor..."
NGINX_CONF="/etc/nginx/conf.d/canary.conf"
# Tüm trafiği stable'a gönder
sed -i 's/[0-9]+%s*canary/0% canary/' $NGINX_CONF
if nginx -t 2>/dev/null; then
systemctl reload nginx
echo "[$(date)] ROLLBACK tamamlandi - Tum trafik stable backend'e yonlendirildi"
echo "[$(date)] ROLLBACK" >> /var/log/nginx/canary_changes.log
# Slack bildirimi (webhook URL'nizi ekleyin)
# curl -X POST -H 'Content-type: application/json'
# --data '{"text":"🚨 Canary rollback yapildi! Tum trafik stable backend e yonlendirildi."}'
# YOUR_SLACK_WEBHOOK_URL
else
echo "KRITIK: Rollback basarisiz! Manuel mudahale gerekiyor."
exit 1
fi
Gerçek Dünya Senaryosu: E-Ticaret API Deployment
Diyelim ki bir e-ticaret sitesinin ödeme API’sini güncelliyorsunuz. Bu kritik bir servis, dikkatli olmanız gerekiyor.
Deployment planınız şu olabilir:
- 1. Gün: Canary %2 ile başla. Sadece iç ekip ve beta kullanıcılar
- 2. Gün: Sorun yoksa %5’e çıkar. Hata oranı, latency takip et
- 3. Gün: %10’a çıkar. A/B metrikleri karşılaştır
- 5. Gün: %25, ardından %50, ardından %100
- Herhangi bir anda: Hata oranı %1’i geçerse otomatik rollback
# Ödeme API'si için özel canary konfigürasyonu
# Çok kritik olduğu için önce sadece internal IP'lere açıyoruz
geo $internal_user {
default 0;
10.0.0.0/8 1; # İç network
192.168.0.0/16 1; # VPN kullanıcıları
}
map "${internal_user}" $payment_upstream {
"1" canary_payment; # İç kullanıcılar v2'ye
default stable_payment; # Dışarıdakiler v1'e
}
upstream stable_payment {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
keepalive 16;
}
upstream canary_payment {
server 10.0.2.10:8081;
keepalive 8;
}
server {
listen 443 ssl http2;
server_name payment-api.example.com;
location /v2/payment/ {
proxy_pass http://$payment_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Ödeme işlemleri için daha uzun timeout
proxy_read_timeout 60s;
add_header X-Backend-Version $payment_upstream always;
}
}
Sık Yapılan Hatalar
Canary deployment yaparken dikkat etmeniz gerekenler:
- Session tutarsızlığı: Kullanıcı v1’de başlattığı sepeti v2’de bulamazsa kötü deneyim yaşar. Oturum verilerini Redis gibi merkezi bir yerde tutun
- Database migration uyumsuzluğu: v2’nin ihtiyaç duyduğu schema değişikliklerini önceden yapın. Her iki versiyon aynı DB’yi kullanıyorsa backward compatible migration şart
- Yetersiz monitoring: “Hata yok” demek yeterli değil. Latency, business metrikler (sipariş tamamlama oranı vb.) de takip edilmeli
- Çok hızlı geçiş: %5’ten direkt %100’e geçmek canary’nin amacını ortadan kaldırır
- Log ayrıştırmayı unutmak: Hangi isteğin hangi backend’e gittiğini loglamadan sorun analizi yapamassınız
Sonuç
Nginx ile canary release, ekstra bir araç veya servis gerektirmeden yapabileceğiniz güçlü bir deployment stratejisi. split_clients, map ve geo direktiflerini bir arada kullanarak hem basit hem de karmaşık senaryoları karşılayabilirsiniz.
Önemli olan birkaç nokta var: Monitoring olmadan canary deployment kör uçuştur. Hata oranı, yanıt süresi ve iş metriklerini her iki backend için ayrı ayrı takip edin. Rollback scriptinizi hazır tutun ve test edin. Deployment öncesinde değil, deployment sırasında ihtiyacınız olduğunu hatırlayın.
Kademeli geçişi atlamamak da kritik. %2, %5, %10, %25, %50, %100 adımları kulağa fazla gelebilir ama her adım sorun çıkma riskini önemli ölçüde azaltır. Prodüksiyona çıkmak artık korkulacak bir şey değil, kontrollü ve geri dönülebilir bir süreç haline gelir.
Son olarak: Bu konfigürasyonları mutlaka staging ortamında test edin. Nginx’in split_clients hash mekanizması deterministik çalışır ama beklediğiniz yüzdelere ne kadar yakın gerçekte ulaştığınızı, trafik hacminiz arttıkça göreceksiniz.
