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.
