بلاگ/دیپلوی FastAPI روی سرور مجازی و اختصاصی: Gunicorn، Uvicorn Workers و Nginx برای Production
به‌روز شده

دیپلوی FastAPI روی سرور مجازی و اختصاصی: Gunicorn، Uvicorn Workers و Nginx برای Production

1405/08/100 بازدید
دیپلوی FastAPI روی سرور مجازی و اختصاصی: Gunicorn، Uvicorn Workers و Nginx برای Production

دیپلوی FastAPI روی سرور مجازی و اختصاصی: Gunicorn، Uvicorn Workers و Nginx برای Production

اگر با uvicorn main:app --reload اپلیکیشن را تست کرده‌اید و حالا می‌خواهید آن را روی یک سرور واقعی بالا بیاورید، این راهنما همان مسیر را قدم‌به‌قدم نشان می‌دهد. از انتخاب نوع سرور تا پیکربندی Gunicorn، سرویس systemd و ریورس پروکسی Nginx با HTTPS.

دیپلوی FastAPI روی سرور با Gunicorn و Nginx

چرا فقط Uvicorn کافی نیست

سرور توسعه‌ای که با دستور uvicorn main:app --reload اجرا می‌شود، تک‌پردازه است و با هر تغییر کد ری‌استارت می‌شود؛ برای کار واقعی روی اینترنت ساخته نشده. اگر همین حالت را روی production رها کنید، یک درخواست سنگین کل اپلیکیشن را کند می‌کند و اگر پردازه کرش کند، چیزی آن را دوباره بالا نمی‌آورد.

برای دیپلوی FastAPI روی سرور به سه چیز نیاز دارید: چند پردازه worker که درخواست‌ها را موازی پاسخ بدهند، یک process manager که در صورت کرش پردازه را دوباره اجرا کند، و یک وب‌سرور جلویی که SSL، فایل‌های استاتیک و چند دامنه را مدیریت کند. Gunicorn با Uvicorn Workers نقش اول و دوم را می‌گیرد، Nginx نقش سوم را.

انتخاب سرور: مجازی یا اختصاصی

برای شروع یک API با ترافیک معمولی، یک سرور مجازی (VPS) کافی است. اگر اپلیکیشن دیتابیس سنگین دارد یا چند سرویس همزمان روی یک ماشین اجرا می‌شود، سرور اختصاصی منابع ثابت‌تری می‌دهد. تفاوت اصلی را در این جدول می‌بینید:

معیارسرور مجازی (VPS)سرور اختصاصی
مناسب برایAPI در حال توسعه، MVP، ترافیک متوسطچند سرویس همزمان، دیتابیس سنگین، ترافیک بالا
مقیاس‌پذیریتغییر پلن در چند دقیقهنیاز به سفارش سرور جدید
هزینه شروعپایین‌تر، بیلینگ ساعتی یا ماهانهثابت و بالاتر، معمولا ماهانه
مدیریتهمان دسترسی root، بدون تفاوت در نصبهمان دسترسی root، بدون تفاوت در نصب

مراحل نصب Gunicorn، systemd و Nginx در هر دو حالت یکسان است؛ تفاوت فقط در منابعی است که به اپلیکیشن می‌دهید. برای اکثر پروژه‌ها شروع با یک سرور مجازی و ارتقا در صورت نیاز منطقی‌تر از خرید سرور اختصاصی از روز اول است. اگر هنوز سرور نخریده‌اید، مراحل خرید و راه‌اندازی سرور مجازی از انتخاب پلن تا اولین اتصال SSH را پوشش می‌دهد.

آماده‌سازی محیط روی سرور

بعد از اتصال با SSH، یک محیط مجزا برای پروژه بسازید تا پکیج‌های پایتون با بقیه سرور قاطی نشوند:

sudo apt update && sudo apt install -y python3-venv python3-pip nginx
mkdir -p /srv/myapp && cd /srv/myapp
python3 -m venv venv
source venv/bin/activate
pip install fastapi uvicorn gunicorn

کد اپلیکیشن را همین‌جا قرار بدهید یا با git clone بیاورید. مطمئن شوید فایل اصلی (مثلا main.py) شیء app را export می‌کند، چون Gunicorn دقیقا به همین اسم نیاز دارد.

پیکربندی Gunicorn با Uvicorn Workers

Gunicorn خودش پروتکل ASGI که FastAPI روی آن کار می‌کند را نمی‌فهمد، برای همین هر worker را با کلاس UvicornWorker اجرا می‌کنید. یک فایل gunicorn.conf.py کنار main.py بسازید:

bind = "127.0.0.1:8000"
workers = 4
worker_class = "uvicorn.workers.UvicornWorker"
timeout = 60
accesslog = "/srv/myapp/logs/access.log"
errorlog = "/srv/myapp/logs/error.log"

تعداد worker را با فرمول (2 × تعداد هسته CPU) + 1 حساب کنید، نه یک عدد دلخواه. روی یک سرور مجازی ۲ هسته‌ای این عدد ۵ است؛ بیشتر از این، worker ها برای یک منبع محدود رقابت می‌کنند و کندتر می‌شوند. اگر پروژه Django یا Go است نه FastAPI، همین الگوی Gunicorn یا systemd با جزئیات متفاوت در دیپلوی پروژه Django روی سرور اختصاصی و VPS و دیپلوی اپلیکیشن Go روی سرور اختصاصی و VPS با systemd و Nginx توضیح داده شده. اجرای دستی برای تست:

mkdir -p logs
gunicorn main:app -c gunicorn.conf.py

اگر اپلیکیشن بالا آمد و روی curl http://127.0.0.1:8000 پاسخ داد، آماده مرحله بعد هستید.

اجرای پایدار با سرویس systemd

اجرای دستی Gunicorn در ترمینال با بسته شدن SSH متوقف می‌شود. یک unit file در /etc/systemd/system/myapp.service بسازید:

[Unit]
Description=FastAPI app (Gunicorn + Uvicorn workers)
After=network.target

[Service]
User=www-data
WorkingDirectory=/srv/myapp
Environment="PATH=/srv/myapp/venv/bin"
ExecStart=/srv/myapp/venv/bin/gunicorn main:app -c gunicorn.conf.py
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

Restart=always همان بخشی است که یک کرش ناگهانی را به یک خاموشی چند ثانیه‌ای تبدیل می‌کند، نه یک قطعی کامل. فعال‌سازی:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp

از این به بعد با هر ری‌بوت سرور، اپلیکیشن خودش بالا می‌آید. برای دیدن لاگ‌های لحظه‌ای: journalctl -u myapp -f.

ریورس پروکسی با Nginx

Gunicorn فقط روی 127.0.0.1:8000 گوش می‌دهد، یعنی مستقیم از بیرون قابل دسترسی نیست — همین جا Nginx وارد می‌شود. یک فایل در /etc/nginx/sites-available/myapp بسازید:

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

    location / {
        proxy_pass http://127.0.0.1:8000;
        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;
    }
}

پیکربندی ریورس پروکسی Nginx برای FastAPI روی سرور مجازی

لینک و فعال‌سازی:

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

هدرهای X-Forwarded-* را حذف نکنید؛ بدون آن‌ها FastAPI آی‌پی واقعی کاربر را نمی‌بیند و همه درخواست‌ها از سمت Nginx به نظر می‌رسند. اگر بعد از این تنظیمات با صفحه خطا مواجه شدید، رفع خطای 502 Bad Gateway در Nginx علت‌های رایج را پوشش می‌دهد.

HTTPS رایگان با Let's Encrypt

با دامنه‌ای که به IP سرور اشاره می‌کند، گرفتن گواهی SSL چند دقیقه کار دارد:

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

Certbot خودش فایل Nginx را ویرایش می‌کند و ریدایرکت از HTTP به HTTPS را اضافه می‌کند. گواهی هر ۹۰ روز منقضی می‌شود اما تمدید خودکار از طریق یک تایمر سیستمی که certbot نصب می‌کند انجام می‌شود؛ برای اطمینان می‌توانید با sudo certbot renew --dry-run تست کنید.

آپدیت کد بدون قطعی سرویس

بعد از اولین دیپلوی، هر تغییر کد نیاز به یک روش امن برای اعمال دارد. ساده‌ترین حالت کشیدن کد جدید و ری‌استارت سرویس است:

cd /srv/myapp
git pull
source venv/bin/activate
pip install -r requirements.txt
sudo systemctl restart myapp

این روش چند ثانیه قطعی دارد چون همه worker ها همزمان بسته و دوباره باز می‌شوند. اگر این چند ثانیه مهم است، به‌جای restart از sudo systemctl reload myapp استفاده کنید؛ Gunicorn با سیگنال HUP worker های قدیمی را یکی‌یکی با نسخه جدید جایگزین می‌کند و در هر لحظه حداقل یک worker پاسخگو است. برای این کار سیگنال reload باید در unit file تعریف شده باشد:

ExecReload=/bin/kill -HUP $MAINPID

چک‌لیست قبل از رفتن به Production

قبل از اعلام آدرس به کاربران واقعی، این موارد را چک کنید:

  • فایروال فقط پورت‌های ۲۲، ۸۰ و ۴۴۳ را باز گذاشته باشد، نه پورت ۸۰۰۰.
  • لاگ‌های Gunicorn (error.log) بعد از چند درخواست آزمایشی خالی از خطا باشد.
  • متغیرهای حساس (کلید دیتابیس، توکن‌ها) در فایل .env باشند، نه داخل کد.
  • مصرف RAM با free -h در حد منطقی باشد؛ اگر هر worker به‌تنهایی رم زیادی می‌خورد، تعداد worker را کم کنید یا پلن سرور را ارتقا دهید. برای محدود کردن سخت‌گیرانه‌تر مصرف هر سرویس، محدود کردن مصرف CPU و RAM سرویس‌ها با systemd و cgroups در سرور لینوکس روش دقیق‌تری نشان می‌دهد.

یک مورد دیگر که راحت فراموش می‌شود، چرخش لاگ (log rotation) است. فایل access.log روی یک API پرترافیک در چند هفته به چند گیگابایت می‌رسد و می‌تواند دیسک سرور را پر کند. یک فایل کوچک در /etc/logrotate.d/myapp این را حل می‌کند:

/srv/myapp/logs/*.log {
    weekly
    rotate 4
    compress
    missingok
    notifempty
}

با این تنظیم، لاگ‌های قدیمی هر هفته فشرده می‌شوند و فقط ۴ نسخه آخر نگه داشته می‌شود؛ بقیه حذف می‌شوند.

اگر منابع سرور فعلی برای تعداد worker مورد نیاز کم است، ساده‌ترین راه ارتقای پلن سرور مجازی آریانت است؛ تغییر پلن بدون نصب دوباره هیچ‌کدام از مراحل بالا انجام می‌شود.

سؤالات متداول

آیا می‌توان به‌جای Gunicorn فقط از Uvicorn با چند worker استفاده کرد؟ بله، با uvicorn main:app --workers 4 هم چند پردازه بالا می‌آید، اما Gunicorn مدیریت worker ها (ری‌استارت خودکار یک worker کرش‌کرده بدون قطع بقیه) را بهتر انجام می‌دهد. برای production ترکیب Gunicorn و Uvicorn Workers رایج‌تر است.

چرا باید از Nginx جلوی Gunicorn استفاده کنم؟ Gunicorn برای مدیریت تعداد زیاد اتصال همزمان، فایل‌های استاتیک و SSL ساخته نشده. Nginx این کارها را سریع‌تر انجام می‌دهد و Gunicorn فقط روی اجرای کد پایتون تمرکز می‌کند.

روی سرور اختصاصی هم همین مراحل جواب می‌دهد؟ بله، تمام دستورات همین راهنما مستقل از نوع سرور است. تنها چیزی که تغییر می‌کند عدد workers در gunicorn.conf.py است، چون به تعداد هسته CPU سرور بستگی دارد.

اگر بعد از ری‌استارت سرور اپلیکیشن بالا نیامد چه باید کرد؟ با sudo systemctl status myapp وضعیت سرویس را ببینید و اگر فعال نیست با sudo systemctl enable myapp آن را برای اجرای خودکار در بوت فعال کنید. خطای دقیق‌تر معمولا در journalctl -u myapp است.