Nginx چیست؟ آموزش کامل Reverse Proxy و Load Balancing برای FastAPI
در این آموزش عملی یاد میگیرید Nginx چیست و چگونه یک برنامه FastAPI را با Reverse Proxy، Load Balancing، Docker و Streaming در محیط واقعی اجرا کنید. پروژه کامل اتصال به API هوش مصنوعی درواره نیز ارائه شده است.
وقتی یک برنامه FastAPI، سرویس هوش مصنوعی یا REST API را روی سرور اجرا میکنید، معمولاً برنامه مستقیماً برای کاربران اینترنت قابل دسترسی نیست. در محیط توسعه ممکن است دستور زیر کافی باشد:
uvicorn app.main:app --host 0.0.0.0 --port 8000
اما در محیط عملیاتی یا Production، باید مسائل بیشتری را مدیریت کنید:
- اتصال دامنه به برنامه
- دریافت درخواست روی پورتهای استاندارد
- توزیع ترافیک بین چند نمونه برنامه
- مدیریت اتصالهای طولانی
- پشتیبانی از پاسخ جریانی یا Streaming
- ثبت لاگ و زمان پاسخ
- محدود کردن اندازه درخواست
- تحویل فایلهای استاتیک
- انتقال اطلاعات واقعی کاربر به Backend
- جداسازی برنامه داخلی از اینترنت
یکی از رایجترین ابزارها برای انجام این کارها Nginx است.
در این آموزش ابتدا با معماری و مفاهیم اصلی Nginx آشنا میشویم و سپس یک پروژه واقعی میسازیم که شامل دو نمونه FastAPI، یک Nginx Load Balancer، Docker Compose و اتصال به API هوش مصنوعی درواره است.
Nginx چیست؟
Nginx که معمولاً «انجیناکس» تلفظ میشود، یک وبسرور و Reverse Proxy پرکاربرد است. این نرمافزار میتواند درخواستهای ورودی کاربران را دریافت کند و آنها را به برنامههایی مانند FastAPI، Django، Laravel، Node.js یا Spring Boot بفرستد.
Nginx فقط یک وبسرور برای نمایش فایلهای HTML نیست. از آن میتوان برای کاربردهای زیر استفاده کرد:
- Web Server
- Reverse Proxy
- HTTP Load Balancer
- سرو فایلهای Static
- مدیریت Cache
- مسیریابی درخواستها
- مدیریت اتصالهای طولانی
- پشتیبانی از WebSocket
- قرار گرفتن جلوی REST API
- مدیریت پاسخهای Streaming و SSE
طبق مستندات رسمی، Nginx میتواند درخواستها را به یک سرور دیگر منتقل کند و با تعریف گروههای upstream، ترافیک را میان چند نمونه برنامه توزیع کند. مستندات Reverse Proxy در Nginx
Nginx در معماری API چه جایگاهی دارد؟
یک معماری ساده بدون Nginx ممکن است به شکل زیر باشد:
Client
|
v
FastAPI :8000
در این حالت پورت برنامه مستقیماً روی اینترنت باز است و همه درخواستها به یک نمونه FastAPI میرسند.
معماری مناسبتر به این شکل است:
Client
|
v
Nginx :80/:443
|
+----> FastAPI Instance 1 :8000
|
+----> FastAPI Instance 2 :8000
در این معماری، کاربر فقط با Nginx ارتباط دارد. Nginx درخواست را دریافت میکند و آن را به یکی از نمونههای FastAPI میفرستد.
سرویس FastAPI میتواند برای انجام پردازشهای هوش مصنوعی به API درواره متصل شود:
کاربر
|
v
Nginx
|
v
FastAPI
|
v
API درواره
|
v
مدل هوش مصنوعی
این جداسازی باعث میشود بتوانید تعداد نمونههای برنامه را افزایش دهید، پیکربندی شبکه را متمرکز کنید و سرویس Backend را مستقیماً در معرض اینترنت قرار ندهید.
تفاوت Forward Proxy و Reverse Proxy چیست؟
مفهوم Proxy معمولاً به دو دسته تقسیم میشود.
Forward Proxy
در Forward Proxy، پروکسی به نمایندگی از کاربر با سرویس مقصد ارتباط برقرار میکند:
User -> Forward Proxy -> Website
وبسایت مقصد معمولاً پروکسی را بهعنوان مبدأ اتصال مشاهده میکند.
Reverse Proxy
در Reverse Proxy، پروکسی جلوی سرورهای برنامه قرار میگیرد:
User -> Reverse Proxy -> Backend Server
کاربر معمولاً نمیداند کدام نمونه Backend درخواست را پردازش کرده است.
در این مقاله از Nginx بهعنوان Reverse Proxy استفاده میکنیم.
چرا FastAPI را مستقیماً روی اینترنت منتشر نکنیم؟
Uvicorn یک ASGI Server مناسب برای اجرای FastAPI است، اما قرار دادن مستقیم آن در لبه عمومی شبکه معمولاً انعطافپذیری معماری را کاهش میدهد.
Nginx میتواند لایه ورودی متمرکزی ایجاد کند و امکانات زیر را در اختیار برنامه قرار دهد:
- مسیریابی چند سرویس روی یک دامنه
- توزیع بار میان چند Backend
- مدیریت Timeout
- ثبت لاگ دسترسی
- تنظیم Headerهای Proxy
- پشتیبانی از پاسخهای Streaming
- تحویل فایلهای استاتیک
- مدیریت دامنه و پورت ورودی
برای مثال میتوانید مسیرهای مختلف را به سرویسهای جداگانه بفرستید:
location /api/ {
proxy_pass http://api_backend;
}
location /admin/ {
proxy_pass http://admin_backend;
}
location /static/ {
root /var/www/app;
}
اجزای اصلی تنظیمات Nginx
فایل پیکربندی Nginx معمولاً از چند Context اصلی تشکیل میشود:
events {
}
http {
upstream backend {
server app1:8000;
server app2:8000;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}
بخش events
این بخش تنظیمات مرتبط با اتصالها و Workerهای Nginx را در بر میگیرد:
events {
worker_connections 1024;
}
بخش http
تنظیمات مربوط به ترافیک HTTP، لاگها، Upstreamها و سرورها در این بخش قرار میگیرند.
بخش upstream
در upstream مجموعهای از Backendها تعریف میشود:
upstream fastapi_backend {
server app1:8000;
server app2:8000;
}
بخش server
هر بلوک server مشخص میکند Nginx روی چه پورت یا دامنهای درخواست دریافت کند:
server {
listen 80;
server_name api.example.com;
}
بخش location
بلوک location برای مسیریابی URL استفاده میشود:
location /api/ {
proxy_pass http://fastapi_backend;
}
دستور proxy_pass چه کاری انجام میدهد؟
دستور proxy_pass مقصد درخواست را مشخص میکند:
location / {
proxy_pass http://fastapi_backend;
}
در این مثال، درخواست دریافتی به گروه fastapi_backend فرستاده میشود.
یکی از نکات مهم، تفاوت استفاده از / در انتهای آدرس است.
نمونه اول:
location /api/ {
proxy_pass http://backend;
}
درخواست زیر معمولاً با همان URI به Backend منتقل میشود:
/api/users
نمونه دوم:
location /api/ {
proxy_pass http://backend/;
}
در این حالت بخش متناظر با location جایگزین میشود و Backend ممکن است مسیر زیر را دریافت کند:
/users
این تفاوت کوچک یکی از دلایل رایج خطاهای 404 هنگام تنظیم Nginx است. رفتار دقیق proxy_pass و نحوه انتقال URI در مستندات رسمی ماژول Proxy توضیح داده شده است.
انتقال Headerهای واقعی کاربر
Backend باید بتواند اطلاعاتی مانند Host، IP و پروتکل درخواست اصلی را تشخیص دهد. برای این منظور Headerهای زیر تنظیم میشوند:
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;
کاربرد آنها:
| Header | کاربرد |
|---|---|
Host | دامنهای که کاربر درخواست کرده است |
X-Real-IP | IP اتصال ورودی به Nginx |
X-Forwarded-For | زنجیره Proxyها و IPهای قبلی |
X-Forwarded-Proto | پروتکل اصلی مانند HTTP یا HTTPS |
اگر برنامه از این Headerها برای تصمیمگیری مهم استفاده میکند، فقط باید Proxyهای مورد اعتماد را معتبر در نظر بگیرد. دریافت مستقیم X-Forwarded-For از اینترنت لزوماً به معنی معتبر بودن آن نیست.
Load Balancing چیست؟
Load Balancing یا توزیع بار یعنی درخواستها بین چند نمونه از یک برنامه تقسیم شوند:
Request 1 -> App 1
Request 2 -> App 2
Request 3 -> App 1
Request 4 -> App 2
این کار میتواند ظرفیت پاسخگویی، دسترسپذیری و امکان توسعه افقی برنامه را بهبود دهد.
در Nginx یک گروه Upstream میسازیم:
upstream ai_backend {
server app1:8000;
server app2:8000;
}
اگر روش مشخصی انتخاب نشود، Nginx بهصورت پیشفرض از Round Robin استفاده میکند.
روشهای توزیع بار در Nginx
Round Robin
روش پیشفرض Nginx است و درخواستها را بهترتیب میان Backendها توزیع میکند:
upstream backend {
server app1:8000;
server app2:8000;
}
این روش برای درخواستهایی با زمان پردازش تقریباً مشابه مناسب است.
Least Connections
در این روش درخواست جدید به سروری فرستاده میشود که اتصال فعال کمتری دارد:
upstream backend {
least_conn;
server app1:8000;
server app2:8000;
}
برای APIهای هوش مصنوعی، بعضی درخواستها ممکن است چند ثانیه و بعضی دیگر مدت بیشتری طول بکشند. به همین دلیل least_conn در بسیاری از این سناریوها از Round Robin منطقیتر است.
Weighted Load Balancing
اگر یک سرور قدرت بیشتری دارد، میتوان وزن بالاتری به آن اختصاص داد:
upstream backend {
server app1:8000 weight=3;
server app2:8000 weight=1;
}
در این مثال سهم تقریبی app1 از درخواستها بیشتر است.
IP Hash
در این روش IP کاربر برای انتخاب Backend استفاده میشود:
upstream backend {
ip_hash;
server app1:8000;
server app2:8000;
}
این روش میتواند کاربر را تا حدی به یک Backend ثابت متصل کند، اما برای برنامههای Stateless معمولاً بهتر است وابستگی به یک نمونه خاص ایجاد نشود.
Nginx روشهایی مانند Round Robin، Least Connections و IP Hash را برای توزیع بار ارائه میکند. جزئیات این روشها در راهنمای رسمی HTTP Load Balancing آمده است.
معماری پروژه عملی
در پروژه این مقاله، اجزای زیر را اجرا میکنیم:
- یک Nginx روی پورت
8080 - دو نمونه یکسان FastAPI
- اتصال Async به API درواره
- یک مسیر معمولی برای دریافت پاسخ کامل
- یک مسیر Streaming برای دریافت تدریجی پاسخ
- یک مسیر تشخیص نمونه پردازشکننده
- Health Check برای Containerها
- لاگ زمان پاسخ Upstream
ساختار پروژه:
nginx-fastapi-ai/
├── app/
│ ├── __init__.py
│ └── main.py
├── nginx/
│ └── nginx.conf
├── .env
├── .gitignore
├── compose.yaml
├── Dockerfile
└── requirements.txt
مرحله اول: ساخت محیط پروژه
پوشهها را ایجاد کنید:
mkdir -p nginx-fastapi-ai/app nginx-fastapi-ai/nginx
cd nginx-fastapi-ai
touch app/__init__.py
مرحله دوم: تعریف وابستگیها
فایل requirements.txt را بسازید:
fastapi>=0.115,<1.0
uvicorn[standard]>=0.34,<1.0
openai>=1.68,<3.0
pydantic>=2.10,<3.0
SDK مورد استفاده با APIهای سازگار با OpenAI کار میکند. در این پروژه base_url روی آدرس API درواره تنظیم میشود.
مرحله سوم: ساخت برنامه FastAPI
فایل app/main.py را ایجاد کنید:
import json
import os
from contextlib import asynccontextmanager
from typing import AsyncIterator
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
from openai import AsyncOpenAI
from pydantic import BaseModel, Field
DARVAREH_API_KEY = os.environ["DARVAREH_API_KEY"]
DARVAREH_MODEL_ID = os.environ["DARVAREH_MODEL_ID"]
INSTANCE_NAME = os.getenv("INSTANCE_NAME", "unknown")
class ChatRequest(BaseModel):
message: str = Field(min_length=1, max_length=8000)
temperature: float = Field(default=0.3, ge=0, le=2)
max_tokens: int = Field(default=800, ge=1, le=4000)
class ChatResponse(BaseModel):
answer: str
instance: str
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.ai_client = AsyncOpenAI(
api_key=DARVAREH_API_KEY,
base_url="https://api.darvareh.ir/v1",
timeout=90.0,
max_retries=2,
)
yield
await app.state.ai_client.close()
app = FastAPI(
title="Nginx FastAPI AI Demo",
version="1.0.0",
lifespan=lifespan,
)
@app.get("/health")
async def health():
return {
"status": "ok",
"instance": INSTANCE_NAME,
}
@app.get("/instance")
async def instance():
return {
"instance": INSTANCE_NAME,
}
@app.post("/chat", response_model=ChatResponse)
async def chat(payload: ChatRequest, request: Request):
response = await request.app.state.ai_client.chat.completions.create(
model=DARVAREH_MODEL_ID,
messages=[
{
"role": "system",
"content": (
"شما یک دستیار فارسی دقیق هستید. "
"پاسخ را روشن، کاربردی و بدون ادعاهای اثباتنشده بنویس."
),
},
{
"role": "user",
"content": payload.message,
},
],
temperature=payload.temperature,
max_tokens=payload.max_tokens,
)
answer = response.choices[0].message.content or ""
return ChatResponse(
answer=answer,
instance=INSTANCE_NAME,
)
@app.post("/chat/stream")
async def chat_stream(payload: ChatRequest, request: Request):
async def generate() -> AsyncIterator[str]:
try:
stream = await request.app.state.ai_client.chat.completions.create(
model=DARVAREH_MODEL_ID,
messages=[
{
"role": "system",
"content": (
"شما یک دستیار فارسی دقیق هستید. "
"پاسخ را روشن، مرحلهبهمرحله و کاربردی بنویس."
),
},
{
"role": "user",
"content": payload.message,
},
],
temperature=payload.temperature,
max_tokens=payload.max_tokens,
stream=True,
)
async for chunk in stream:
if await request.is_disconnected():
break
content = chunk.choices[0].delta.content
if not content:
continue
data = json.dumps(
{
"type": "token",
"content": content,
"instance": INSTANCE_NAME,
},
ensure_ascii=False,
)
yield f"data: {data}\n\n"
done = json.dumps(
{
"type": "done",
"instance": INSTANCE_NAME,
},
ensure_ascii=False,
)
yield f"event: done\ndata: {done}\n\n"
except Exception:
error = json.dumps(
{
"type": "error",
"message": "پردازش درخواست با خطا مواجه شد.",
"instance": INSTANCE_NAME,
},
ensure_ascii=False,
)
yield f"event: error\ndata: {error}\n\n"
return StreamingResponse(
generate(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
},
)
چرا از AsyncOpenAI استفاده کردیم؟
FastAPI یک فریمورک Async است. استفاده از Client ناهمگام باعث میشود برنامه هنگام انتظار برای پاسخ مدل، Thread اصلی را بیدلیل مسدود نکند.
این موضوع بهخصوص زمانی اهمیت دارد که چند کاربر بهطور همزمان درخواست ارسال میکنند.
چرا Client را در lifespan ساختیم؟
ساخت Client برای هر درخواست باعث ایجاد سربار غیرضروری میشود. در این پیادهسازی، یک Client هنگام شروع هر نمونه برنامه ساخته و هنگام خاموش شدن آن بسته میشود.
چرا کلید API در Backend قرار دارد؟
کلید API نباید داخل JavaScript مرورگر، اپلیکیشن عمومی یا مخزن Git قرار بگیرد. کاربر با Backend شما ارتباط برقرار میکند و Backend درخواست را به درواره میفرستد.
مرحله چهارم: ساخت Dockerfile
فایل Dockerfile را بسازید:
FROM python:3.13-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /code
COPY requirements.txt /code/requirements.txt
RUN pip install \
--no-cache-dir \
--upgrade \
-r /code/requirements.txt
COPY app /code/app
EXPOSE 8000
CMD [
"uvicorn",
"app.main:app",
"--host",
"0.0.0.0",
"--port",
"8000"
]
ترتیب کپی فایلها مهم است. ابتدا requirements.txt کپی میشود تا در صورت تغییر نکردن وابستگیها، Docker بتواند از Cache لایه قبلی استفاده کند.
همچنین CMD به فرم Exec نوشته شده است. مستندات FastAPI این فرم را برای خاموش شدن صحیح برنامه و اجرای رویدادهای Lifespan توصیه میکنند. راهنمای رسمی اجرای FastAPI در Docker
مرحله پنجم: تنظیم Nginx
فایل nginx/nginx.conf را ایجاد کنید:
events {
worker_connections 2048;
}
http {
log_format upstream_timing
'$remote_addr - $request_id '
'"$request" $status $body_bytes_sent '
'request_time=$request_time '
'upstream_addr=$upstream_addr '
'upstream_status=$upstream_status '
'upstream_connect_time=$upstream_connect_time '
'upstream_header_time=$upstream_header_time '
'upstream_response_time=$upstream_response_time';
access_log /var/log/nginx/access.log upstream_timing;
error_log /var/log/nginx/error.log warn;
upstream ai_backend {
least_conn;
server app1:8000 max_fails=3 fail_timeout=15s;
server app2:8000 max_fails=3 fail_timeout=15s;
keepalive 32;
}
server {
listen 80;
server_name _;
client_max_body_size 1m;
location = /nginx-health {
access_log off;
default_type application/json;
return 200 '{"status":"ok","service":"nginx"}';
}
location /chat/stream {
proxy_pass http://ai_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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_set_header X-Request-ID $request_id;
proxy_buffering off;
proxy_cache off;
proxy_connect_timeout 5s;
proxy_send_timeout 120s;
proxy_read_timeout 300s;
add_header X-Request-ID $request_id always;
add_header X-Accel-Buffering no always;
}
location / {
proxy_pass http://ai_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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_set_header X-Request-ID $request_id;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 120s;
add_header X-Request-ID $request_id always;
}
}
}
تحلیل تنظیمات Upstream
قسمت زیر دو نمونه FastAPI را معرفی میکند:
upstream ai_backend {
least_conn;
server app1:8000 max_fails=3 fail_timeout=15s;
server app2:8000 max_fails=3 fail_timeout=15s;
keepalive 32;
}
least_conn
درخواست جدید به نمونهای فرستاده میشود که اتصال فعال کمتری دارد.
max_fails
تعداد خطاهایی را تعیین میکند که در بازه fail_timeout میتواند باعث شود Nginx یک Backend را موقتاً ناموفق در نظر بگیرد.
fail_timeout
بازه بررسی خطا و مدت کنار گذاشته شدن موقت Backend را کنترل میکند.
در نسخه متنباز Nginx، این رفتار یک Health Check غیرفعال یا Passive است؛ یعنی وضعیت Backend از نتیجه ترافیک واقعی تشخیص داده میشود. نباید آن را با Active Health Check دورهای اشتباه گرفت.
keepalive
تعدادی اتصال آماده میان Nginx و Backendها نگه میدارد تا برای هر درخواست مجبور به ایجاد اتصال کاملاً جدید نباشیم.
چرا برای Streaming تنظیم جداگانه داریم؟
در پاسخ معمولی، Nginx میتواند پاسخ Backend را Buffer کند. اما در Streaming میخواهیم هر بخش از پاسخ بلافاصله به کاربر برسد.
به همین دلیل از تنظیم زیر استفاده شده است:
proxy_buffering off;
وقتی Buffering غیرفعال باشد، Nginx داده دریافتی از Backend را بدون انتظار برای تکمیل کل پاسخ به Client منتقل میکند. این رفتار در مستندات proxy_buffering توضیح داده شده است.
همچنین Timeout خواندن پاسخ را افزایش دادهایم:
proxy_read_timeout 300s;
این عدد به معنی زمان کل درخواست نیست. proxy_read_timeout فاصله مجاز بین دو عملیات خواندن متوالی از Upstream را مشخص میکند. اگر Backend در این فاصله هیچ دادهای ارسال نکند، اتصال بسته میشود.
مرحله ششم: ساخت Docker Compose
فایل compose.yaml را ایجاد کنید:
services:
app1:
build:
context: .
environment:
DARVAREH_API_KEY: ${DARVAREH_API_KEY}
DARVAREH_MODEL_ID: ${DARVAREH_MODEL_ID}
INSTANCE_NAME: app1
expose:
- "8000"
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
]
interval: 10s
timeout: 3s
retries: 5
start_period: 10s
restart: unless-stopped
app2:
build:
context: .
environment:
DARVAREH_API_KEY: ${DARVAREH_API_KEY}
DARVAREH_MODEL_ID: ${DARVAREH_MODEL_ID}
INSTANCE_NAME: app2
expose:
- "8000"
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
]
interval: 10s
timeout: 3s
retries: 5
start_period: 10s
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
app1:
condition: service_healthy
app2:
condition: service_healthy
restart: unless-stopped
نکته مهم این است که پورت FastAPI با ports روی Host منتشر نشده است. دو Backend فقط داخل شبکه Docker در دسترساند و ورودی عمومی از Nginx عبور میکند.
مرحله هفتم: تنظیم متغیرهای محیطی
فایل .env را بسازید:
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID
کلید API را بعد از ثبتنام در درواره دریافت کنید.
شناسه مدل مورد نظر خود را نیز از صفحه مدلها و قیمتها بردارید. قیمت، محدودیت و دسترسپذیری مدلها ممکن است تغییر کند؛ بنابراین بهتر است اطلاعات فعلی را مستقیماً در این صفحه بررسی کنید.
فایل .gitignore:
.env
__pycache__/
*.pyc
.venv/
هیچگاه فایل .env واقعی را داخل مخزن عمومی Git قرار ندهید.
مرحله هشتم: اجرای پروژه
پروژه را Build و اجرا کنید:
docker compose up --build
وضعیت Containerها را ببینید:
docker compose ps
خروجی باید نشان دهد هر دو برنامه سالم هستند و Nginx روی پورت 8080 در دسترس است.
آزمایش Health Check
curl http://localhost:8080/health
نمونه خروجی:
{
"status": "ok",
"instance": "app1"
}
این درخواست از Nginx عبور کرده و به یکی از Backendها رسیده است.
Health Check خود Nginx:
curl http://localhost:8080/nginx-health
خروجی:
{
"status": "ok",
"service": "nginx"
}
تفاوت این دو مسیر مهم است:
/nginx-healthفقط آماده بودن Nginx را بررسی میکند./healthاز طریق Nginx به یکی از Backendها میرسد.
آزمایش Load Balancing
چند بار مسیر /instance را صدا بزنید:
for i in $(seq 1 10); do
curl -s http://localhost:8080/instance
echo
done
خروجی نمونه:
{"instance":"app1"}
{"instance":"app2"}
{"instance":"app1"}
{"instance":"app2"}
به دلیل استفاده از least_conn و کوتاه بودن درخواستها، توزیع دقیق همیشه الزاماً یکیدرمیان نیست. هدف این است که درخواستها براساس تعداد اتصالهای فعال میان نمونهها تقسیم شوند.
آزمایش API هوش مصنوعی
درخواست معمولی:
curl -X POST http://localhost:8080/chat \
-H "Content-Type: application/json" \
-d '{
"message": "Reverse Proxy را با یک مثال ساده توضیح بده.",
"temperature": 0.2,
"max_tokens": 500
}'
نمونه ساختار پاسخ:
{
"answer": "پروکسی معکوس سرویسی است که...",
"instance": "app2"
}
مقدار instance نشان میدهد کدام Backend درخواست را پردازش کرده است.
آزمایش پاسخ Streaming
برای جلوگیری از Buffer شدن خروجی در خود curl از گزینه -N استفاده کنید:
curl -N -X POST http://localhost:8080/chat/stream \
-H "Content-Type: application/json" \
-d '{
"message": "پنج کاربرد Nginx را مرحلهبهمرحله توضیح بده.",
"temperature": 0.3,
"max_tokens": 700
}'
خروجی بهصورت رویدادهای تدریجی دریافت میشود:
data: {"type":"token","content":"Nginx","instance":"app1"}
data: {"type":"token","content":" یک وبسرور","instance":"app1"}
event: done
data: {"type":"done","instance":"app1"}
اگر همه متن بعد از یک انتظار طولانی و یکجا نمایش داده شود، احتمالاً در یکی از لایهها Buffering فعال است.
مشاهده لاگهای Nginx
برای مشاهده لاگ زنده:
docker compose logs -f nginx
نمونه لاگ:
172.18.0.1 - abc123 "POST /chat HTTP/1.1" 200 542
request_time=2.418
upstream_addr=172.18.0.3:8000
upstream_status=200
upstream_connect_time=0.001
upstream_header_time=2.416
upstream_response_time=2.416
مقادیر مهم:
| مقدار | مفهوم |
|---|---|
request_time | کل زمان پردازش درخواست در Nginx |
upstream_addr | نمونه Backend انتخابشده |
upstream_status | کد وضعیت دریافتی از Backend |
upstream_connect_time | زمان برقراری اتصال با Backend |
upstream_header_time | زمان دریافت Header پاسخ |
upstream_response_time | زمان پاسخ Upstream |
این اطلاعات برای تشخیص اینکه کندی در Nginx، Backend یا سرویس بالادستی رخ داده مفید است.
تست خرابی یک Backend
ابتدا یکی از برنامهها را متوقف کنید:
docker compose stop app1
حالا چند بار درخواست بفرستید:
for i in $(seq 1 5); do
curl -s http://localhost:8080/instance
echo
done
درخواستهای سالم باید به app2 برسند:
{"instance":"app2"}
سپس نمونه اول را دوباره اجرا کنید:
docker compose start app1
پس از آماده شدن برنامه، Nginx میتواند دوباره درخواستها را میان نمونههای دردسترس توزیع کند.
تفاوت Timeoutهای مهم Nginx
proxy_connect_timeout
حداکثر زمان مورد انتظار برای برقراری اتصال با Backend:
proxy_connect_timeout 5s;
اگر برنامه داخلی در شبکه محلی است، اتصال معمولاً باید سریع برقرار شود.
proxy_send_timeout
فاصله زمانی مجاز میان عملیات ارسال درخواست به Backend:
proxy_send_timeout 60s;
proxy_read_timeout
فاصله زمانی مجاز میان خواندن دو بخش متوالی پاسخ:
proxy_read_timeout 120s;
برای مسیرهای Streaming میتوان مقدار بیشتری در نظر گرفت:
proxy_read_timeout 300s;
افزایش بیحساب Timeout راهحل همه مشکلات نیست. اگر پاسخ بیشازحد طول میکشد، باید زمان پردازش مدل، طول ورودی، تعداد توکن خروجی، Retryها و بار Backend را نیز بررسی کنید.
مدیریت درخواستهای POST و Retry
درخواست چت یک عملیات POST است. هنگام تنظیم Failover نباید بدون بررسی، Retry خودکار عملیات غیرقابل تکرار را فعال کنید.
در Nginx تنظیم زیر مشخص میکند چه خطاهایی باعث امتحان کردن Upstream بعدی شوند:
proxy_next_upstream error timeout;
از فعال کردن گزینه non_idempotent برای همه درخواستها خودداری کنید، مگر اینکه برنامه شما برای جلوگیری از اجرای تکراری درخواست طراحی شده باشد.
برای عملیات حساس به تکرار میتوان از شناسه درخواست یا Idempotency Key در سطح برنامه استفاده کرد.
آیا چند Container بهتر است یا چند Worker؟
دو روش رایج برای افزایش ظرفیت FastAPI وجود دارد.
چند Worker در یک Container
uvicorn app.main:app \
--host 0.0.0.0 \
--port 8000 \
--workers 4
چند Container جداگانه
Nginx
├── App Container 1
├── App Container 2
└── App Container 3
مقایسه کلی:
| روش | مزیت | محدودیت |
|---|---|---|
| چند Worker | راهاندازی ساده روی یک سرور | مدیریت هر Worker مستقل نیست |
| چند Container | جداسازی و توسعه افقی بهتر | تنظیمات و عملیات بیشتر |
| ترکیبی | ظرفیت بیشتر روی سرور قدرتمند | پیچیدگی و مصرف حافظه بیشتر |
برای یک سرور ساده، چند Worker میتواند کافی باشد. وقتی به جداسازی نمونهها، Restart مستقل و Load Balancing نیاز دارید، چند Container انتخاب انعطافپذیرتری است.
برنامه باید Stateless باشد
وقتی چند نمونه FastAPI دارید، درخواستهای یک کاربر ممکن است هر بار به Backend متفاوتی برسد.
بنابراین اطلاعاتی مانند موارد زیر نباید فقط در حافظه یک Process نگهداری شوند:
- Session کاربر
- تاریخچه مکالمه
- وضعیت Job
- محدودیت مصرف
- نتیجه پردازش موقت مهم
- اطلاعات احراز هویت سمت سرور
برای دادههای مشترک میتوان از پایگاه داده یا Cache مشترک استفاده کرد. در غیر این صورت ممکن است کاربر در درخواست دوم به نمونهای برسد که اطلاعات درخواست اول را ندارد.
تنظیم Nginx برای WebSocket
اگر برنامه شما مسیر WebSocket دارد، Headerهای Upgrade باید منتقل شوند:
map $http_upgrade $connection_upgrade {
default upgrade;
"" close;
}
server {
listen 80;
location /ws/ {
proxy_pass http://ai_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 300s;
}
}
این تنظیم را فقط برای مسیر WebSocket به کار ببرید. مسیرهای HTTP معمولی به Header Upgrade نیاز ندارند.
اضافه کردن دامنه
در سرور واقعی، مقدار server_name را تغییر دهید:
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://ai_backend;
}
}
رکورد DNS دامنه باید به IP سرور اشاره کند.
برای محیط Production بهتر است HTTPS نیز فعال باشد. نحوه صدور و تمدید گواهی به زیرساخت و روش استقرار شما بستگی دارد و باید براساس مستندات ارائهدهنده دامنه، سرور و گواهی انجام شود.
ارائه فایلهای Static با Nginx
اگر برنامه Frontend یا فایلهای ثابت دارد، Nginx میتواند آنها را مستقیماً ارائه کند:
server {
listen 80;
location /static/ {
alias /var/www/static/;
expires 7d;
}
location /api/ {
proxy_pass http://ai_backend;
}
}
این روش مانع مصرف ظرفیت FastAPI برای فایلهایی مانند موارد زیر میشود:
- CSS
- JavaScript
- تصویر
- فونت
- فایلهای عمومی
خطاهای رایج Nginx و FastAPI
خطای 502 Bad Gateway
معمولاً یعنی Nginx نتوانسته پاسخ معتبر از Backend دریافت کند.
موارد زیر را بررسی کنید:
docker compose ps
docker compose logs app1
docker compose logs app2
docker compose logs nginx
دلایل رایج:
- FastAPI اجرا نشده است.
- نام سرویس در
upstreamاشتباه است. - پورت Backend اشتباه تنظیم شده است.
- برنامه فقط روی
127.0.0.1گوش میدهد. - Containerها در شبکه مشترک نیستند.
- برنامه هنگام شروع Crash کرده است.
داخل Container باید Uvicorn روی این Host اجرا شود:
--host 0.0.0.0
خطای 504 Gateway Timeout
Nginx اتصال را برقرار کرده، اما در زمان مورد انتظار پاسخ مناسب دریافت نکرده است.
موارد زیر را بررسی کنید:
- زمان پاسخ مدل
- مقدار
max_tokens - Timeout کلاینت API
proxy_read_timeout- بار CPU و RAM
- تعداد درخواستهای همزمان
- Retryهای Backend
خطای 404 بعد از اضافه کردن Reverse Proxy
نحوه نوشتن location و / انتهای proxy_pass را بررسی کنید:
location /api/ {
proxy_pass http://backend/;
}
ممکن است Backend مسیر /users را دریافت کند، در حالی که شما انتظار /api/users داشتهاید.
پاسخ Streaming یکجا نمایش داده میشود
تنظیمات زیر را بررسی کنید:
proxy_buffering off;
proxy_cache off;
در FastAPI نیز Header زیر مفید است:
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
}
برای آزمایش با curl نیز از -N استفاده کنید:
curl -N http://localhost:8080/chat/stream
همه درخواستها به یک نمونه میروند
این رفتار همیشه نشانه خطا نیست. در روش least_conn ممکن است یک نمونه در لحظه اتصال کمتری داشته باشد.
همچنین موارد زیر را بررسی کنید:
- هر دو Container واقعاً اجرا شدهاند.
- هر دو در
upstreamتعریف شدهاند. - اتصالهای Keep-Alive یا Streaming طولانی وجود ندارند.
- یکی از نمونهها مرتباً خطا نمیدهد.
- یکی از Backendها به دلیل
max_failsموقتاً کنار گذاشته نشده است.
بهینهسازی برای APIهای هوش مصنوعی
APIهای هوش مصنوعی با APIهای معمولی چند تفاوت مهم دارند:
- زمان پاسخ میتواند متغیر باشد.
- خروجی ممکن است Streaming باشد.
- درخواستها ممکن است طولانیتر باشند.
- هزینه هر درخواست یکسان نیست.
- تعداد توکن خروجی روی زمان و هزینه اثر دارد.
- لغو اتصال کاربر باید در Backend تشخیص داده شود.
پیشنهادهای عملی:
Timeoutها را براساس داده واقعی تنظیم کنید
ابتدا زمان پاسخ واقعی درخواستها را اندازهگیری کنید. سپس Timeout را کمی بیشتر از محدوده منطقی قرار دهید.
خروجیهای طولانی را Stream کنید
Streaming زمان واقعی تولید پاسخ را لزوماً کاهش نمیدهد، اما کاربر زودتر اولین بخش پاسخ را مشاهده میکند.
درخواستهای طولانی را محدود کنید
در مدل Pydantic محدودیت طول ورودی تعریف کردیم:
message: str = Field(min_length=1, max_length=8000)
این مقدار باید براساس نوع برنامه، مدل انتخابی و سیاست محصول تنظیم شود.
شناسه درخواست ثبت کنید
در Nginx از $request_id استفاده کردیم:
proxy_set_header X-Request-ID $request_id;
همین شناسه را میتوان در لاگ Backend نیز ثبت کرد تا مسیر یک درخواست در لایههای مختلف قابل پیگیری باشد.
معیارهای فنی را پایش کنید
حداقل معیارهای زیر را اندازهگیری کنید:
- تعداد درخواست
- نرخ خطای 4xx و 5xx
- زمان پاسخ
- زمان دریافت اولین توکن
- زمان کامل شدن پاسخ
- تعداد اتصال فعال
- Backend انتخابشده
- تعداد Retry
- مصرف توکن
- هزینه تقریبی
- تعداد قطع اتصال کاربر
Nginx چه تفاوتی با API Gateway دارد؟
Nginx و API Gateway همپوشانیهایی دارند، اما همیشه یک مفهوم نیستند.
| قابلیت | Nginx | API Gateway تخصصی |
|---|---|---|
| Reverse Proxy | بله | بله |
| Load Balancing | بله | معمولاً بله |
| سرو فایل Static | بله | معمولاً هدف اصلی نیست |
| مدیریت دامنه و ورودی | بله | بله |
| مدیریت کلید API | نیازمند طراحی و تنظیم | معمولاً داخلی |
| سهمیهبندی مصرف | محدود یا نیازمند توسعه | معمولاً داخلی |
| گزارش مصرف مدل | نیازمند توسعه | ممکن است داخلی باشد |
| مسیریابی مدل هوش مصنوعی | نیازمند Backend | در AI Gateway ارائه میشود |
Nginx میتواند ورودی شبکه برنامه شما باشد، در حالی که درواره دسترسی API به مدلهای هوش مصنوعی را متمرکز میکند. این دو میتوانند در یک معماری کنار هم استفاده شوند:
Client
|
v
Nginx
|
v
FastAPI Business Logic
|
v
Darvareh AI API
چکلیست استقرار Nginx و FastAPI
پیش از انتشار نسخه Production این موارد را بررسی کنید:
- Backend مستقیماً روی اینترنت منتشر نشده باشد.
- کلید API فقط در Environment Variable قرار گرفته باشد.
- فایل
.envداخل Git نباشد. - مسیر Health Check تعریف شده باشد.
- Timeoutها براساس رفتار واقعی سرویس تنظیم شده باشند.
- برای Streaming، Buffering غیرفعال شده باشد.
- Headerهای Proxy بهدرستی منتقل شوند.
- شناسه درخواست در لاگ ثبت شود.
- برنامه تا حد امکان Stateless باشد.
- خطاهای سرویس بالادستی مدیریت شوند.
- اندازه ورودی و تعداد توکن خروجی محدود شوند.
- لاگها اطلاعات حساس را ذخیره نکنند.
- بازیابی پس از توقف یک Backend آزمایش شده باشد.
- مصرف CPU، RAM و تعداد اتصال پایش شود.
- قیمت و محدودیت مدل انتخابی بررسی شده باشد.
پرسشهای متداول
آیا Nginx برای FastAPI ضروری است؟
برای توسعه محلی ضروری نیست. در محیط عملیاتی، Nginx یکی از گزینههای رایج برای Reverse Proxy، مسیریابی، Load Balancing و مدیریت اتصال ورودی است. در بعضی زیرساختهای ابری ممکن است Load Balancer مدیریتشده جای آن را بگیرد.
آیا Nginx رایگان است؟
نسخه متنباز Nginx رایگان است. Nginx Plus یک محصول تجاری با امکانات تکمیلی محسوب میشود.
آیا Nginx میتواند چند برنامه را روی یک سرور اجرا کند؟
Nginx خود برنامهها را اجرا نمیکند، اما میتواند درخواستها را براساس دامنه یا مسیر به برنامههای مختلف بفرستد.
location /users/ {
proxy_pass http://user_service;
}
location /chat/ {
proxy_pass http://chat_service;
}
برای API هوش مصنوعی Round Robin بهتر است یا Least Connections؟
اگر زمان همه درخواستها تقریباً یکسان باشد، Round Robin ساده و مناسب است. اگر زمان پردازش درخواستها تفاوت زیادی دارد، least_conn میتواند توزیع متعادلتری ایجاد کند.
چرا Streaming پشت Nginx با تأخیر نمایش داده میشود؟
رایجترین دلیل فعال بودن Proxy Buffering است. مسیر Streaming را جدا کنید و proxy_buffering off قرار دهید. Buffering احتمالی Client یا لایههای واسط دیگر را نیز بررسی کنید.
آیا Nginx تاریخچه مکالمه کاربران را نگه میدارد؟
خیر. Nginx یک Reverse Proxy است و مدیریت تاریخچه گفتگو باید در Backend، پایگاه داده یا Cache انجام شود.
آیا میتوان کلید درواره را در Nginx قرار داد؟
از نظر فنی امکان افزودن Header ثابت وجود دارد، اما برای بیشتر برنامهها بهتر است منطق اتصال و کلید API داخل Backend نگهداری شود. این روش کنترل بیشتری بر ورودی، مدل، هزینه، خطا و سیاست استفاده میدهد.
چطور تعداد Backendها را افزایش دهیم؟
یک سرویس جدید اجرا و آن را به گروه Upstream اضافه کنید:
upstream ai_backend {
least_conn;
server app1:8000;
server app2:8000;
server app3:8000;
}
سپس پیکربندی Nginx را اعتبارسنجی و Reload کنید.
چگونه تنظیمات Nginx را قبل از اجرا بررسی کنیم؟
داخل یک سرور معمولی:
nginx -t
در Docker Compose:
docker compose exec nginx nginx -t
پس از موفق بودن تست:
docker compose exec nginx nginx -s reload
جمعبندی
Nginx یکی از کاربردیترین ابزارها برای قرار گرفتن جلوی برنامههای FastAPI و سرویسهای Backend است. این ابزار میتواند درخواستهای ورودی را دریافت کند، آنها را به Backend مناسب بفرستد و ترافیک را میان چند نمونه برنامه توزیع کند.
در پروژه این مقاله موارد زیر را بهصورت عملی پیادهسازی کردیم:
- ساخت API با FastAPI
- اتصال Backend به API هوش مصنوعی درواره
- اجرای دو نمونه برنامه با Docker
- استفاده از Nginx بهعنوان Reverse Proxy
- توزیع ترافیک با
least_conn - تنظیم Headerهای Forwarded
- ثبت زمان پاسخ Upstream
- مدیریت Timeout
- پشتیبانی از SSE و Streaming
- آزمایش توقف یک Backend
- بررسی خطاهای رایج 502 و 504
برای ساخت یک محصول هوش مصنوعی، بهتر است کلید API در Backend نگهداری شود و Nginx در لایه ورودی شبکه قرار بگیرد. Backend نیز میتواند با یک API سازگار و متمرکز به مدل مورد نظر متصل شود.
برای دریافت کلید API و شروع توسعه، وارد وبسایت درواره شوید. برای انتخاب مدل و مشاهده اطلاعات بهروز قیمتها نیز صفحه مدلهای درواره را بررسی کنید.
منابع
- مستندات رسمی Reverse Proxy در Nginx
- مستندات رسمی HTTP Load Balancing در Nginx
- مستندات ماژول ngx_http_proxy_module
- راهنمای رسمی اجرای FastAPI در Docker
مقالات مرتبط
- ساخت API آماده Production برای هوش مصنوعی
- API Gateway چیست؟ راهنمای کامل دروازه API
- آموزش Streaming API در هوش مصنوعی
- آموزش تست بار API با k6
- مانیتورینگ و Observability سرویسهای هوش مصنوعی
- آموزش اتصال API هوش مصنوعی به اپلیکیشن
- ساخت چتبات هوش مصنوعی با API درواره
برای مطالعه شرایط استفاده و محدودیتهای مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.