FastAPI Uygulamasını Nginx ile Üretimde Çalıştırma

Geliştirme ortamında uvicorn main:app --reload komutuyla çalıştırdığın FastAPI uygulamasını üretime taşıma zamanı geldiğinde, işler biraz daha karmaşık hale geliyor. Nginx’i reverse proxy olarak kullanmak, Gunicorn ile worker yönetimi yapmak ve systemd servisi oluşturmak gibi adımlar seni bekliyor. Bu yazıda sıfırdan başlayarak, gerçek bir üretim ortamı kurulumunu adım adım yapacağız.

Neden Nginx + Gunicorn + Uvicorn Kombinasyonu?

FastAPI, ASGI tabanlı bir framework. Uvicorn da bu standartı destekleyen bir sunucu. Peki neden doğrudan Uvicorn’u internete açmıyoruz?

Birkaç temel neden var:

  • Statik dosya servisi: Nginx, statik dosyaları Uvicorn’dan çok daha verimli sunuyor
  • SSL/TLS sonlandırma: HTTPS işlemlerini Nginx üstleniyor, uygulama sunucunu yormuyorsun
  • Load balancing: Birden fazla worker process arasında yük dağılımı
  • Rate limiting ve güvenlik: DDoS koruması, bağlantı sınırlama
  • Buffer yönetimi: Yavaş istemcilerden gelen bağlantıları tamponluyor
  • Process yönetimi: Gunicorn, worker process’lerin hayatta kalmasını sağlıyor

Gunicorn ise Uvicorn worker’larını yöneten bir process manager olarak çalışıyor. Yani Gunicorn ana process olarak başlıyor, altında birden fazla Uvicorn worker açıyor. Bir worker çökerse Gunicorn yenisini başlatıyor.

Sunucu Hazırlığı ve Python Ortamı

Ubuntu 22.04 üzerinde çalışacağız. Önce sistemi güncelleyip gerekli paketleri kuralım.

sudo apt update && sudo apt upgrade -y
sudo apt install -y python3-pip python3-venv python3-dev build-essential nginx git

Uygulamamız için özel bir sistem kullanıcısı oluşturmak güvenlik açısından önemli. Root ile çalıştırmak tehlikeli, kendi kullanıcın ile çalıştırmak da gereksiz yetkiler veriyor.

sudo useradd --system --shell /bin/bash --home /var/www/myapi --create-home myapi
sudo su - myapi

Şimdi uygulama dizinini oluşturup sanal ortamı kuralım:

# myapi kullanıcısı olarak
mkdir -p /var/www/myapi/app
cd /var/www/myapi
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install fastapi gunicorn uvicorn[standard] python-multipart

Örnek FastAPI Uygulaması

Gerçek dünya senaryosuna yakın, basit ama işlevsel bir uygulama yazalım. Bir ürün API’si olsun:

# /var/www/myapi/app/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import uvicorn

app = FastAPI(
    title="Ürün API",
    description="Örnek üretim FastAPI uygulaması",
    version="1.0.0"
)

class Product(BaseModel):
    id: int
    name: str
    price: float
    stock: int
    description: Optional[str] = None

fake_db = [
    Product(id=1, name="Laptop", price=15000.0, stock=10, description="Gaming laptop"),
    Product(id=2, name="Mouse", price=250.0, stock=50, description="Wireless mouse"),
]

@app.get("/")
async def root():
    return {"message": "API çalışıyor", "status": "ok"}

@app.get("/health")
async def health_check():
    return {"status": "healthy"}

@app.get("/products", response_model=List[Product])
async def get_products():
    return fake_db

@app.get("/products/{product_id}", response_model=Product)
async def get_product(product_id: int):
    product = next((p for p in fake_db if p.id == product_id), None)
    if not product:
        raise HTTPException(status_code=404, detail="Ürün bulunamadı")
    return product

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

Requirements dosyasını oluştur:

pip freeze > /var/www/myapi/requirements.txt

Gunicorn Konfigürasyonu

Gunicorn’u komut satırından parametrelerle çalıştırabilirsin ama bir konfigürasyon dosyası kullanmak çok daha yönetilebilir. Özellikle worker sayısı, timeout ve log ayarlarını burada belirleyelim:

# /var/www/myapi/gunicorn.conf.py
import multiprocessing

# Bağlantı ayarları
bind = "unix:/var/www/myapi/myapi.sock"
backlog = 2048

# Worker ayarları
# Kural: (2 x CPU çekirdeği) + 1
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
worker_connections = 1000
timeout = 30
keepalive = 2

# Yeniden başlatma ayarları
max_requests = 1000
max_requests_jitter = 50
preload_app = True

# Log ayarları
accesslog = "/var/www/myapi/logs/access.log"
errorlog = "/var/www/myapi/logs/error.log"
loglevel = "info"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# Process adı
proc_name = "myapi"

Log dizinini oluştur:

mkdir -p /var/www/myapi/logs

Gunicorn’u test edelim, düzgün çalışıyor mu görelim:

cd /var/www/myapi
source venv/bin/activate
gunicorn -c gunicorn.conf.py app.main:app

Başka bir terminalde socket dosyasının oluştuğunu kontrol et:

ls -la /var/www/myapi/myapi.sock
curl --unix-socket /var/www/myapi/myapi.sock http://localhost/health

Eğer {"status":"healthy"} cevabını alıyorsan her şey yolunda.

Systemd Servis Dosyası

Gunicorn’u systemd ile yönetmek, sunucu yeniden başladığında otomatik başlamasını ve crash durumunda kendini restart etmesini sağlıyor:

# root kullanıcısına geri dön
exit
sudo nano /etc/systemd/system/myapi.service
[Unit]
Description=MyAPI FastAPI Gunicorn Servisi
After=network.target
Requires=network.target

[Service]
Type=notify
User=myapi
Group=myapi
WorkingDirectory=/var/www/myapi
Environment="PATH=/var/www/myapi/venv/bin"
ExecStart=/var/www/myapi/venv/bin/gunicorn -c /var/www/myapi/gunicorn.conf.py app.main:app
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=on-failure
RestartSec=5
StandardError=journal

[Install]
WantedBy=multi-user.target

Servisi etkinleştir ve başlat:

sudo systemctl daemon-reload
sudo systemctl enable myapi
sudo systemctl start myapi
sudo systemctl status myapi

Logları takip etmek için:

sudo journalctl -u myapi -f

Nginx Konfigürasyonu

Şimdi en kritik kısma geliyoruz. Nginx’i reverse proxy olarak yapılandıracağız. İki senaryo göstereceğim: HTTP ve HTTPS.

Önce HTTP konfigürasyonu, Let’s Encrypt sertifikasını almadan önce bu şekilde test ederiz:

# /etc/nginx/sites-available/myapi
upstream myapi_backend {
    server unix:/var/www/myapi/myapi.sock fail_timeout=0;
    # Birden fazla instance için:
    # server unix:/var/www/myapi/myapi1.sock fail_timeout=0;
    # server unix:/var/www/myapi/myapi2.sock fail_timeout=0;
}

server {
    listen 80;
    server_name api.siteadresi.com;

    # Güvenlik başlıkları
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;

    # İstek boyutu sınırı (dosya yüklemeleri için artırabilirsin)
    client_max_body_size 10M;

    # Zaman aşımı ayarları
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;

    # Genel API trafiği
    location / {
        proxy_pass http://myapi_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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_redirect off;
        proxy_buffering off;
    }

    # FastAPI docs - üretimde kapatmak isteyebilirsin
    location /docs {
        proxy_pass http://myapi_backend/docs;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Health check endpoint'i - rate limit uygulanmadan
    location /health {
        proxy_pass http://myapi_backend/health;
        access_log off;
    }

    # Nginx access log
    access_log /var/log/nginx/myapi_access.log;
    error_log /var/log/nginx/myapi_error.log;
}

Konfigürasyonu aktif et ve test et:

sudo ln -s /etc/nginx/sites-available/myapi /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

HTTPS ile Let’s Encrypt Sertifikası

Üretim ortamında HTTP ile çalışmak kabul edilemez. Certbot ile ücretsiz SSL sertifikası alalım:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d api.siteadresi.com

Certbot Nginx konfigürasyonunu otomatik güncelleyecek. Ama manuel yapılandırmayı tercih ediyorsan, sertifikalar /etc/letsencrypt/live/api.siteadresi.com/ dizinine kaydedildikten sonra Nginx konfigürasyonunu şöyle güncellersin:

server {
    listen 443 ssl http2;
    server_name api.siteadresi.com;

    ssl_certificate /etc/letsencrypt/live/api.siteadresi.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.siteadresi.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/api.siteadresi.com/chain.pem;

    # Modern SSL ayarları
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # HSTS
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # Geri kalan location blokları aynı...
    location / {
        proxy_pass http://myapi_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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_redirect off;
    }
}

# HTTP'yi HTTPS'e yönlendir
server {
    listen 80;
    server_name api.siteadresi.com;
    return 301 https://$host$request_uri;
}

Güvenlik Duvarı Ayarları

UFW ile yalnızca gerekli portları aç:

sudo ufw allow 22/tcp      # SSH
sudo ufw allow 80/tcp      # HTTP
sudo ufw allow 443/tcp     # HTTPS
sudo ufw deny 8000/tcp     # Uvicorn'u dışarıya kapatıyoruz
sudo ufw enable
sudo ufw status verbose

Deployment Güncellemeleri

Uygulamayı güncellemek için sıfır kesinti (zero-downtime) stratejisi kullanalım. Gunicorn’un HUP sinyali özelliği tam burada işe yarıyor:

#!/bin/bash
# /var/www/myapi/deploy.sh

set -e

APP_DIR="/var/www/myapi"
VENV_DIR="$APP_DIR/venv"
REPO_DIR="$APP_DIR/app"

echo "Deployment başlıyor..."

# Kodu güncelle (Git kullanıyorsan)
cd $REPO_DIR
git pull origin main

# Sanal ortamı aktif et ve bağımlılıkları güncelle
source $VENV_DIR/bin/activate
pip install -r $APP_DIR/requirements.txt --quiet

# Gunicorn'u graceful restart ile yeniden başlat
# Mevcut worker'lar isteklerini tamamlar, yeni worker'lar devreye girer
sudo systemctl reload myapi

echo "Deployment tamamlandı!"
echo "Servis durumu:"
sudo systemctl status myapi --no-pager
chmod +x /var/www/myapi/deploy.sh
sudo chown myapi:myapi /var/www/myapi/deploy.sh

Performans İzleme ve Log Analizi

Üretimde neyin döndüğünü bilmek için birkaç yararlı komut:

# Aktif bağlantı sayısı
sudo ss -tlnp | grep nginx

# Nginx erişim loglarında en çok istek atan IP'ler
sudo awk '{print $1}' /var/log/nginx/myapi_access.log | sort | uniq -c | sort -rn | head -20

# Son 100 satır hata logu
sudo tail -100 /var/log/nginx/myapi_error.log

# Gunicorn worker durumu
sudo systemctl status myapi
ps aux | grep gunicorn | grep -v grep

# Socket dosyası hakkında bilgi
sudo ls -la /var/www/myapi/myapi.sock

Gunicorn worker sayısını dinamik olarak değiştirmek için TTIN ve TTOU sinyallerini kullanabilirsin:

# Worker ekle
sudo kill -TTIN $(cat /var/run/myapi.pid)

# Worker çıkar
sudo kill -TTOU $(cat /var/run/myapi.pid)

Sık Karşılaşılan Sorunlar ve Çözümleri

502 Bad Gateway hatası: Gunicorn socket dosyasının izinleri yanlış olabilir.

# Socket dosyası izinlerini kontrol et
ls -la /var/www/myapi/myapi.sock

# Nginx'in myapi grubuna erişimi olduğundan emin ol
sudo usermod -aG myapi www-data
sudo systemctl restart nginx

Permission denied – socket bağlanamıyor: Gunicorn socket dizinine yazma izni yok.

sudo chown -R myapi:myapi /var/www/myapi
sudo chmod 755 /var/www/myapi

Worker timeout: Ağır işlemler yapıyorsan timeout değerini gunicorn.conf.py dosyasında artır.

  • timeout = 30: Normal API çağrıları için yeterli
  • timeout = 120: Dosya işleme veya uzun süreli görevler için
  • timeout = 0: Sonsuz bekleme, asla önerilmez

Memory leak: max_requests değeri sayesinde her worker belirli sayıda istek sonrası kendini yeniden başlatıyor. max_requests_jitter ise tüm worker’ların aynı anda restart etmesini önlüyor.

FastAPI docs üretimde görünüyor: Güvenlik açısından production ortamında API dokümantasyonunu kapatmak isteyebilirsin:

app = FastAPI(
    docs_url=None if os.getenv("ENVIRONMENT") == "production" else "/docs",
    redoc_url=None if os.getenv("ENVIRONMENT") == "production" else "/redoc"
)

Ortam Değişkenleri Yönetimi

Hassas bilgileri kod içine yazmak yerine ortam değişkeni olarak yönetmek gerekiyor. Systemd servis dosyasına environment dosyası ekleyelim:

sudo nano /etc/myapi/environment
ENVIRONMENT=production
DATABASE_URL=postgresql://user:password@localhost/mydb
SECRET_KEY=guclu-ve-uzun-bir-secret-key-buraya
API_KEY=harici-servis-api-key
DEBUG=false
sudo chmod 600 /etc/myapi/environment
sudo chown root:myapi /etc/myapi/environment

Systemd servis dosyasına ekle:

[Service]
EnvironmentFile=/etc/myapi/environment

Sonuç

FastAPI uygulamasını üretime taşıma süreci, birkaç bileşenin doğru şekilde bir araya gelmesini gerektiriyor. Özet olarak yapılan kurulumu şöyle sıralayabiliriz:

  • Sistem kullanıcısı: Uygulamayı ayrı bir kullanıcı altında çalıştırmak güvenlik için kritik
  • Sanal ortam: Bağımlılıkların izole edilmesi, sistem Python’una müdahale edilmemesi
  • Gunicorn + Uvicorn worker: Process yönetimi ve ASGI desteği
  • Systemd: Otomatik başlatma, crash recovery ve log entegrasyonu
  • Nginx: SSL sonlandırma, statik dosyalar, güvenlik başlıkları ve reverse proxy
  • UFW: Sadece gerekli portların dışarıya açılması

Bu kurulum, orta ölçekli bir üretim trafiğini rahatlıkla kaldırır. İleride yük arttıkça Redis ile caching, Celery ile async görev kuyruğu veya birden fazla uygulama sunucusu ekleyerek ölçekleyebilirsin. Ama başlangıç için bu yapı, sağlam ve yönetilebilir bir temel sunuyor.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir