Nginx ile Canary Deployment Yapılandırması

Yeni bir özelliği production’a alacaksın ve “ya bir şeyler ters giderse?” korkusu seni bir gecedir uyutmuyorsa, canary deployment tam da senin için. Bu yöntem sayesinde yeni versiyonu önce küçük bir kullanıcı grubuna sunuyor, risk almadan test edebiliyorsun. Nginx, bu süreci yapılandırmak için oldukça esnek bir altyapı sunuyor. Bu yazıda sıfırdan gerçek bir canary deployment kurulumunu nasıl yapacağını adım adım göreceğiz.

Canary Deployment Nedir ve Neden Nginx?

Canary deployment, yeni uygulama versiyonunu tüm kullanıcılara birden sunmak yerine önce küçük bir yüzdeye göndermek anacıyla kullanılan bir deployment stratejisidir. Adını maden ocaklarında kullanılan kanarya kuşlarından alır. Madenciler zehirli gaz tespiti için kanarya taşırdı. Kanarya hasta olursa tehlike var demekti. Burada da mantık aynı: yeni versiyon küçük bir grupta sorun çıkarırsa büyük kitleye yayılmadan geri çekebilirsin.

Nginx bu iş için neden tercih ediliyor? Birkaç temel sebep var:

  • upstream grupları ile birden fazla backend sunucusunu kolayca yönetebilirsin
  • split_clients modülü ile trafik yüzdelerini hassas biçimde belirleyebilirsin
  • map direktifi ile çok daha dinamik routing kuralları yazabilirsin
  • Konfigürasyon değişikliklerini nginx -s reload ile sıfır downtime ile uygulayabilirsin
  • Lua modülü (OpenResty) ile ileri seviye A/B testing senaryoları kurabilirsin

Ortamı Anlamak: Senaryo

Bu yazıda şu senaryoyu ele alacağız: Bir e-ticaret uygulamasının v1 versiyonu production’da çalışıyor. v2 versiyonunu geliştirdin. Önce trafiğin %10’unu v2’ye yönlendirip gözlemleyeceksin. Sorun yoksa %50’ye, ardından %100’e çıkacaksın.

Sunucu yapımız şöyle:

  • Nginx load balancer: 192.168.1.10
  • App v1 (stable): 192.168.1.21:8080 ve 192.168.1.22:8080
  • App v2 (canary): 192.168.1.31:8080

Temel Nginx Kurulumu ve Hazırlık

Önce Nginx’in güncel olduğundan emin ol. Ubuntu/Debian için:

sudo apt update
sudo apt install nginx -y
nginx -v
# nginx version: nginx/1.24.0

# Gerekli dizinleri oluştur
sudo mkdir -p /etc/nginx/conf.d
sudo mkdir -p /var/log/nginx/apps
sudo chown -R www-data:www-data /var/log/nginx/apps

CentOS/RHEL için:

sudo dnf install epel-release -y
sudo dnf install nginx -y
sudo systemctl enable nginx
sudo systemctl start nginx

Nginx konfigürasyon yapımızı modüler tutacağız. Ana nginx.conf dosyasına dokunmayacağız, her şeyi conf.d altında yöneteceğiz.

split_clients ile Temel Canary Yapılandırması

split_clients modülü Nginx’in core modüllerinden biri ve kurulum gerektirmiyor. Bu modül, belirlediğin bir değişkeni hash’leyerek trafiği yüzdelere böler. En basit ve en yaygın canary yöntemi bu.

sudo nano /etc/nginx/conf.d/canary.conf
# Upstream tanımları
upstream app_stable {
    server 192.168.1.21:8080 weight=1;
    server 192.168.1.22:8080 weight=1;
    keepalive 32;
}

upstream app_canary {
    server 192.168.1.31:8080 weight=1;
    keepalive 16;
}

# split_clients ile trafik bölme
# $remote_addr kullanarak IP bazlı tutarlı yönlendirme
split_clients "${remote_addr}AAA" $upstream_pool {
    10%     app_canary;
    *       app_stable;
}

server {
    listen 80;
    server_name shop.example.com;

    # Canary versiyonunu response header'a ekle (debug için)
    add_header X-App-Version $upstream_pool always;

    location / {
        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-Forwarded-Proto $scheme;
        
        proxy_connect_timeout 5s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
}

Burada dikkat edilmesi gereken bir nokta var: split_clients değişken olarak $remote_addr kullanıyor. Bu, aynı IP’den gelen tüm isteklerin her zaman aynı versiyona gitmesini sağlar. Kullanıcı deneyimi tutarlı olur. Ama bazı durumlarda IP bazlı bölme yeterli olmayabilir, çünkü CDN arkasındaki tüm kullanıcılar aynı IP’yi paylaşabilir.

Konfigürasyonu test et ve uygula:

sudo nginx -t
# nginx: configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

sudo nginx -s reload

Cookie Bazlı Canary Yönlendirmesi

IP bazlı yönlendirmenin CDN problemi var dedik. Daha iyi bir yöntem, kullanıcıya özel bir cookie set edip o cookie’ye göre yönlendirme yapmak. Böylece bir kullanıcı her zaman aynı versiyona gider, oturumu boyunca tutarlılık korunur.

sudo nano /etc/nginx/conf.d/canary-cookie.conf
upstream app_stable {
    server 192.168.1.21:8080;
    server 192.168.1.22:8080;
    keepalive 32;
}

upstream app_canary {
    server 192.168.1.31:8080;
    keepalive 16;
}

# Cookie değerine göre map tanımla
map $cookie_canary_group $target_upstream {
    "canary"    app_canary;
    default     app_stable;
}

# split_clients ile başlangıç ataması
split_clients "${remote_addr}${http_user_agent}" $initial_group {
    10%     "canary";
    *       "stable";
}

server {
    listen 80;
    server_name shop.example.com;

    location / {
        # Cookie yoksa split_clients ile grup belirle ve cookie set et
        set $canary_decision $target_upstream;
        
        if ($cookie_canary_group = "") {
            set $canary_decision "";
        }
        
        # Cookie set et (30 gün geçerli)
        add_header Set-Cookie "canary_group=$initial_group; Path=/; Max-Age=2592000; HttpOnly" always;

        proxy_pass http://$target_upstream;
        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-Version $cookie_canary_group;

        # Upstream bilgisini log'a ekle
        access_log /var/log/nginx/apps/canary_access.log;
    }
}

Bu yapıda bir kullanıcı ilk ziyaretinde cookie alıyor ve sonraki tüm ziyaretlerinde aynı versiyona gidiyor. Canary grubundan çıkarmak istersen cookie’yi silmek yeterli.

Gelişmiş: Başlık (Header) Bazlı Yönlendirme

QA ekibinin ya da belirli internal kullanıcıların her zaman canary versiyonunu görmesini isteyebilirsin. Bunun için özel bir HTTP header ile zorunlu yönlendirme yapılabilir. Bu özellikle test süreçlerinde çok işe yarıyor.

sudo nano /etc/nginx/conf.d/canary-header.conf
upstream app_stable {
    server 192.168.1.21:8080;
    server 192.168.1.22:8080;
    keepalive 32;
}

upstream app_canary {
    server 192.168.1.31:8080;
    keepalive 16;
}

# Öncelik sırası: Header > Cookie > split_clients
map $http_x_canary_force $forced_upstream {
    "true"      app_canary;
    "false"     app_stable;
    default     "";
}

map $cookie_canary_group $cookie_upstream {
    "canary"    app_canary;
    "stable"    app_stable;
    default     "";
}

split_clients "${remote_addr}" $split_upstream {
    15%     app_canary;
    *       app_stable;
}

# Öncelik zinciri
map "${forced_upstream}${cookie_upstream}" $final_upstream {
    ~^app_canary    app_canary;
    ~app_canary$    app_canary;
    ~^app_stable    app_stable;
    ~app_stable$    app_stable;
    default         $split_upstream;
}

server {
    listen 80;
    server_name shop.example.com;

    add_header X-Served-By $final_upstream always;

    location / {
        proxy_pass http://$final_upstream;
        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_connect_timeout 5s;
        proxy_read_timeout 60s;
    }
}

QA ekibi test ederken şu şekilde istek atacak:

curl -H "X-Canary-Force: true" https://shop.example.com/api/products
# Her zaman canary versiyonuna gider

curl -H "X-Canary-Force: false" https://shop.example.com/api/products
# Her zaman stable versiyonuna gider

Canary için Özel Log Yapılandırması

Canary sürecini izleyebilmek için ayrıntılı log formatı şart. Hangi istek hangi versiyona gitti, response süreleri nasıl, hata oranları ne durumda, bunları görmek gerekiyor.

sudo nano /etc/nginx/conf.d/log-format.conf
# Canary deployment için özel log formatı
log_format canary_json escape=json
    '{'
        '"timestamp":"$time_iso8601",'
        '"remote_addr":"$remote_addr",'
        '"method":"$request_method",'
        '"uri":"$uri",'
        '"status":$status,'
        '"bytes_sent":$bytes_sent,'
        '"request_time":$request_time,'
        '"upstream_addr":"$upstream_addr",'
        '"upstream_response_time":"$upstream_response_time",'
        '"upstream_status":"$upstream_status",'
        '"app_version":"$upstream_pool",'
        '"user_agent":"$http_user_agent",'
        '"canary_cookie":"$cookie_canary_group"'
    '}';

server {
    listen 80;
    server_name shop.example.com;

    # JSON formatında canary log
    access_log /var/log/nginx/apps/canary_access.log canary_json;
    error_log /var/log/nginx/apps/canary_error.log warn;

    location / {
        proxy_pass http://$final_upstream;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Bu JSON log formatı sayesinde log’ları Elasticsearch’e ya da Grafana Loki’ye kolayca gönderebilirsin. Anlık olarak izlemek için:

# Canary'e giden istekleri say
tail -f /var/log/nginx/apps/canary_access.log | grep '"app_version":"app_canary"' | wc -l

# jq ile parse ederek izle
tail -f /var/log/nginx/apps/canary_access.log | jq '. | select(.app_version == "app_canary") | {uri, status, request_time}'

# Hata oranlarını kontrol et
cat /var/log/nginx/apps/canary_access.log | jq -r '.status' | sort | uniq -c | sort -rn

Hızlı Geri Alma (Rollback) Mekanizması

Canary deployment’ın en önemli özelliği anında geri alabilmek. Nginx ile bu çok basit. İki yöntem var:

Yöntem 1: Yüzdeyi sıfıra çek

# split_clients konfigürasyonunu güncelle
sudo sed -i 's/10%     app_canary/0%     app_canary/' /etc/nginx/conf.d/canary.conf
sudo nginx -s reload
echo "Canary trafiği sıfırlandı"

Yöntem 2: Upstream’i devre dışı bırak

# Canary upstream'ini maintenance moduna al
sudo tee /etc/nginx/conf.d/canary-rollback.conf > /dev/null <<'EOF'
upstream app_canary {
    server 192.168.1.31:8080 down;
    server 192.168.1.21:8080 backup;
}
EOF

sudo nginx -s reload
echo "Rollback tamamlandı, tüm trafik stable'a yönlendi"

Daha pratik bir yaklaşım için bir shell script hazırlayalım:

sudo nano /usr/local/bin/canary-control.sh
#!/bin/bash

CANARY_CONF="/etc/nginx/conf.d/canary.conf"
ACTION=$1
PERCENTAGE=${2:-0}

usage() {
    echo "Kullanim: $0 [promote|rollback|set] [yuzde]"
    echo "  promote  : Canary yuzdesini artir"
    echo "  rollback : Canary'i tamamen kapat"
    echo "  set N    : Canary yuzdesini N'e ayarla (0-100)"
    exit 1
}

set_canary_percentage() {
    local pct=$1
    local remaining=$((100 - pct))
    
    cat > $CANARY_CONF <<EOF
upstream app_stable {
    server 192.168.1.21:8080 weight=1;
    server 192.168.1.22:8080 weight=1;
    keepalive 32;
}

upstream app_canary {
    server 192.168.1.31:8080 weight=1;
    keepalive 16;
}

split_clients "${remote_addr}AAA" $upstream_pool {
    ${pct}%     app_canary;
    *           app_stable;
}
EOF
    
    nginx -t && nginx -s reload
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Canary yüzdesi %${pct} olarak ayarlandi" | tee -a /var/log/nginx/canary-changes.log
}

case $ACTION in
    "rollback")
        set_canary_percentage 0
        echo "ROLLBACK: Tüm trafik stable'a yönlendirildi"
        ;;
    "promote")
        # Mevcut yüzdeyi oku ve artır
        CURRENT=$(grep -oP 'd+(?=%)' $CANARY_CONF | head -1)
        NEW_PCT=$((CURRENT + 10))
        [ $NEW_PCT -gt 100 ] && NEW_PCT=100
        set_canary_percentage $NEW_PCT
        echo "PROMOTE: Canary yüzdesi %${NEW_PCT}'e çıkarıldı"
        ;;
    "set")
        [ -z "$PERCENTAGE" ] && usage
        set_canary_percentage $PERCENTAGE
        ;;
    *)
        usage
        ;;
esac
sudo chmod +x /usr/local/bin/canary-control.sh

# Kullanım örnekleri
canary-control.sh set 10      # %10'dan başla
canary-control.sh promote     # %20'ye çıkar
canary-control.sh promote     # %30'a çıkar
canary-control.sh rollback    # Sorun çıktı, geri al

Nginx Plus Olmadan Aktif Health Check

Nginx open source versiyonunda aktif health check yok, ama passive health check yapılandırabiliriz. Canary sunucu yanıt vermezse otomatik olarak stable’a geçmesi için:

sudo nano /etc/nginx/conf.d/upstream-health.conf
upstream app_stable {
    server 192.168.1.21:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.22:8080 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

upstream app_canary {
    server 192.168.1.31:8080 max_fails=2 fail_timeout=20s;
    # Canary çökerse stable'a fallback
    server 192.168.1.21:8080 backup;
    server 192.168.1.22:8080 backup;
    keepalive 16;
}

server {
    listen 80;
    server_name shop.example.com;

    location / {
        proxy_pass http://$upstream_pool;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        
        # Hata durumunda otomatik failover
        proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
        proxy_next_upstream_tries 2;
        proxy_next_upstream_timeout 5s;
    }

    # Basit health check endpoint'i
    location /nginx-health {
        access_log off;
        return 200 "healthyn";
        add_header Content-Type text/plain;
    }
}

Canary Metriklerini İzleme

Deployment süreci boyunca iki versiyonu karşılaştırman gerekiyor. Basit bir monitoring scripti:

sudo nano /usr/local/bin/canary-monitor.sh
#!/bin/bash

LOG_FILE="/var/log/nginx/apps/canary_access.log"
INTERVAL=60  # saniye

while true; do
    echo "=== Canary Monitor - $(date) ==="
    
    # Son 1 dakikanın loglarını al
    RECENT_LOGS=$(tail -n 1000 $LOG_FILE | 
        awk -v cutoff="$(date -d '1 minute ago' '+%Y-%m-%dT%H:%M')" '$0 > cutoff')
    
    # Stable istatistikleri
    STABLE_TOTAL=$(echo "$RECENT_LOGS" | grep '"app_version":"app_stable"' | wc -l)
    STABLE_ERRORS=$(echo "$RECENT_LOGS" | grep '"app_version":"app_stable"' | grep -E '"status":[45][0-9][0-9]' | wc -l)
    
    # Canary istatistikleri
    CANARY_TOTAL=$(echo "$RECENT_LOGS" | grep '"app_version":"app_canary"' | wc -l)
    CANARY_ERRORS=$(echo "$RECENT_LOGS" | grep '"app_version":"app_canary"' | grep -E '"status":[45][0-9][0-9]' | wc -l)
    
    echo "Stable  -> Toplam: $STABLE_TOTAL | Hata: $STABLE_ERRORS"
    echo "Canary  -> Toplam: $CANARY_TOTAL | Hata: $CANARY_ERRORS"
    
    # Hata oranı hesapla
    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}"
        
        # %5'i aşarsa uyar
        if (( $(echo "$CANARY_ERROR_RATE > 5" | bc -l) )); then
            echo "UYARI: Canary hata oranı kritik seviyede! Rollback düşünün."
        fi
    fi
    
    echo ""
    sleep $INTERVAL
done
sudo chmod +x /usr/local/bin/canary-monitor.sh
# Arka planda çalıştır
nohup /usr/local/bin/canary-monitor.sh > /var/log/canary-monitor.log 2>&1 &

Gerçek Dünya İpuçları

Canary deployment yaparken öğrendiğim bazı önemli noktalar var:

  • Veritabanı migrasyonlarına dikkat et: v1 ve v2 aynı anda çalışıyorsa veritabanı şeması her ikisini de desteklemeli. Breaking schema change’i canary öncesinde tamamla.
  • Session yönetimi: Kullanıcı bir istekte v1’e, sonraki istekte v2’ye gidiyorsa session sorunları çıkabilir. Cookie bazlı yönlendirme bu sorunu büyük ölçüde çözer.
  • Yüzde artışını kademeli yap: %10 -> %25 -> %50 -> %100 şeklinde git, her adımda en az 1 saat gözlemle.
  • Canary sunucusu için ayrı alerting kur: Response time, error rate ve throughput için separate alarm eşikleri belirle.
  • split_clients hash tutarlılığı: Aynı kaynak IP her zaman aynı gruba gider, bu iyi bir şey. Ama IP değişen mobil kullanıcılar için cookie yöntemi daha sağlıklı.

Sonuç

Nginx ile canary deployment kurmak düşündüğünden çok daha az karmaşık. split_clients ile temel yüzde tabanlı yönlendirmeyi birkaç satır config ile halledebilirsin. Cookie ve header bazlı yönlendirmeyi ekleyince QA süreçlerin de çok daha kontrollü hale geliyor.

Bu yazıda anlattığım yapı production’da kullandığım, gerçekten test edilmiş bir yaklaşım. Büyük deployment’larda panik yaşamadan “bu versiyonda bir sorun var, geri alalım” diyebilmek, canary control scripti sayesinde tek bir komuta iniyor.

Bir sonraki adım olarak bu yapının üzerine Prometheus metrikleri ve Grafana dashboard’u eklemek, Slack’e otomatik alert göndermek istersen, o konuyu da başka bir yazıda ele alabiliriz. Ama temel canary altyapısı bu kadar. Artık yeni feature’ları deploy etmek o kadar da ürkütücü değil.

Yorum yapın