FastAPI + Nginx 部署 Python API 到云服务器的完整实践
背景
业务逻辑写完之后,真正让人头疼的往往不是代码本身,而是“怎么把它稳稳地放到云服务器上”。
我之前用 Flask 写过一个日志查询接口,直接用 gunicorn 挂在 8000 端口跑,局域网里访问正常,
但放到公网云主机上就接连踩坑:接口被直接裸暴露在公网、没有 TLS、进程崩了没人知道、
日志还把磁盘写满。最后花了两天时间重构,方案收敛成「FastAPI 应用 + Gunicorn 多进程 +
Nginx 反向代理 + systemd 托管」这一套。本文把整个过程和踩过的坑完整记录下来。
一、整体架构
最终的结构是这样的:
客户端 --HTTPS--> Nginx(443) --HTTP--> Gunicorn(127.0.0.1:8000) --> FastAPI 应用
关键点在于 Gunicorn 只监听 127.0.0.1,外部完全无法直接访问,所有流量必须经过 Nginx。
这样 TLS 证书、访问日志、限流、静态文件这些能力都交给 Nginx 处理,Python 进程只负责业务逻辑,
职责分离之后排查问题会清晰很多。
二、准备环境
云服务器选一台 2 核 4G 的 Linux 机器就完全够用了。以 Ubuntu 22.04 为例,先更新系统并装好依赖:
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-venv python3-pip nginx
项目代码不要放在 /root 或者 /tmp 这类目录,放到 /opt 下面,并用一个独立的系统用户运行,
避免用 root 跑业务进程:
sudo useradd -r -m -d /opt/apiapp -s /bin/bash apiapp
sudo -u apiapp python3 -m venv /opt/apiapp/venv
sudo -u apiapp /opt/apiapp/venv/bin/pip install fastapi uvicorn gunicorn pydantic
用 venv 而不是系统 pip 是为了隔离依赖。后面每次更新代码只要切到虚拟环境装包就行,
不会污染系统 Python,也不会和机器上其他项目打架。
三、编写最小可用的 FastAPI 应用
目录结构保持简单,一个主文件加一个配置就够起步了:
/opt/apiapp/
├── app
└── venv/
主程序的内容不复杂,重点是加上健康检查和请求 ID 中间件,方便后面在 Nginx 和日志里对账:
from fastapi import FastAPI, Query
import uuid, time
app = FastAPI(title='log-query', version='1.0.0')
@app.middleware('http')
async def add_request_id(request, call_next):
rid = str(uuid.uuid4())[:8]
start = time.time()
response = await call_next(request)
response.headers['X-Request-Id'] = rid
response.headers['X-Process-Time'] = f'{time.time() - start:.3f}'
return response
@app.get('/healthz')
async def healthz():
return {'status': 'ok'}
@app.get('/api/v1/logs')
async def query_logs(keyword: str = Query(..., min_length=1, max_length=64)):
# 实际业务里替换成查询逻辑
return {'keyword': keyword, 'items': []}
最后一定要有 /healthz 这类探活接口。后面配 Nginx、systemd 都会依赖它来判断服务是不是真的活着,
而不是仅仅端口在监听。
四、Nginx 反向代理配置
把默认站点改成我们的应用配置,假设对外域名是 api-example-com:
server {
listen 80;
server_name api-example-com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name api-example-com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
client_max_body_size 20m;
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;
proxy_read_timeout 60s;
}
location /healthz {
proxy_pass http://127.0.0.1:8000/healthz;
access_log off;
}
}
几个容易忽略的地方:proxy_set_header 那几行一定要写全,否则后端拿到的永远是 127.0.0.1,
客户端真实 IP 就丢了;/healthz 关掉 access_log 是因为探活请求频率很高,写日志会污染磁盘。
配置改完先检查语法再重载:
sudo nginx -t && sudo systemctl reload nginx
五、用 systemd 托管 Gunicorn
systemd 比 nohup 可靠得多,能自动重启、开机自启、日志统一交给 journald。
写一个 apiapp.service 放到 /etc/systemd/system/ 目录下:
[Unit]
Description=FastAPI Log Query Service
After=network.target
[Service]
User=apiapp
Group=apiapp
WorkingDirectory=/opt/apiapp
Environment='PATH=/opt/apiapp/venv/bin'
ExecStart=/opt/apiapp/venv/bin/gunicorn app:app \
--workers 3 \
--worker-class UvicornWorker \
--bind 127.0.0.1:8000 \
--timeout 60 \
--access-logfile - \
--error-logfile -
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
2 核 4G 的机器上开 3 个 worker 比较合适,开太多反而会因为内存不足被 OOM Killer 干掉。
启用之后:
sudo systemctl daemon-reload
sudo systemctl enable --now apiapp
sudo systemctl status apiapp
六、验证与常见问题排查
按顺序做四个检查,能快速定位九成问题:
- 进程是否在跑:systemctl status apiapp,看 Active 是不是 active (running)。
- 应用是否响应:在服务器上执行 curl -s 127.0.0.1:8000/healthz,应当返回 {“status”:“ok”}。
- Nginx 是否转发:curl -sI 127.0.0.1/healthz -H ‘Host: api-example-com’,看是不是 200。
- 公网是否可达:从外网机器访问 api-example-com 的 healthz 路径,拿到 200 才算真正跑通。
我踩过的坑主要有三个:一个是安全组没放行 443,Nginx 配得再对外也访问不了;
一个是忘记加 X-Forwarded-Proto,后端生成的重置密码链接全是 http,用户点不开;
还有一个是日志文件没做轮转,跑了一周把磁盘撑满,服务直接写不进去。
现在我会给 /var/log/nginx 加 logrotate 配置,保留 14 天压缩日志。
七、总结
这套组合的核心价值不在于技术多新,而在于每一层都做自己该做的事:Nginx 管入口和流量,
Gunicorn 管进程并发,FastAPI 管业务,systemd 管生命周期。出了问题看哪一层一目了然,
比把一个 Flask 应用直接扔到公网上裸跑要稳得多。
如果你的服务并发量不大,其实连 Gunicorn 都可以省掉,直接用 uvicorn 单进程跑也完全够用。
但只要涉及到多核利用和优雅重启,这套结构就是成本最低的稳妥方案。
后续还可以继续做的优化包括:接入 Prometheus + Grafana 做监控,给慢接口加 trace,
以及用 Docker Compose 把整套环境容器化,方便在多台机器之间迁移。有兴趣的话我会在后续文章里展开。
- 点赞
- 收藏
- 关注作者
评论(0)