前面的学习中,我们一直在本地开发环境中构建和打磨FastAPI应用,所有示例都默认你在终端里敲下uvicorn app.main:app --reload,浏览器里访问http://127.0.0.1:8000。
这种模式非常适合迭代业务逻辑和调试问题,但当我们要把应用真正放到生产环境时,就会发现有一整套新的问题需要考虑。
比如:进程到底应该有几个,能承受多少并发连接,如何让多个实例共享负载,如何接入HTTPS,如何让应用在服务器重启后自动拉起,如何在出现故障时快速回滚。
这一部分里,我们会把“部署与扩展”这件事拆开来讲。我们会先从最简单的生产启动方式开始,理解为什么不能直接用开发模式下的--reload,也不会只起一个单独的Uvicorn进程就交付给用户。
接着,我们会把应用装进Docker镜像,让它在任何支持容器的环境中都能以一致的方式运行。然后,我们会在容器前面加上一层反向代理,用Nginx或类似组件来处理HTTPS终止、静态资源分发和负载均衡。
最后,我们会站在“扩展性和可运维性”的角度,讨论如何在多核机器、多实例集群乃至Kubernetes环境中扩展FastAPI应用,让它从一个人的笔记本走向真正的线上系统。

在开发阶段,我们最常用的命令是加上--reload参数的Uvicorn,这个参数会在代码变更时自动重启进程,非常方便。但在生产环境中,--reload带来的额外监控开销和文件系统扫描并不必要,而且单进程模式本身也无法充分利用多核CPU。我们需要的是一种更稳健的启动方式,既要保持Uvicorn的高性能I/O能力,又要有多个工作进程可以并行处理请求。
一个非常常用的组合是“Gunicorn + UvicornWorker”。Gunicorn负责管理多个工作进程、平衡负载、处理进程信号和优雅关闭;UvicornWorker则把每个工作进程中的ASGI应用跑在高性能事件循环上。我们可以先在本机尝试这种启动方式,理解它的行为。
|gunicorn "app.main:app" \ -k uvicorn.workers.UvicornWorker \ -w 4 \ -b 0.0.0.0:8000
在这条命令里,-w 4表示我们启动4个工作进程,-b指定监听地址。实际部署时,我们可以根据机器CPU核心数来选择合理的工作进程数量,通常会用2 * CPU 核心数 + 1作为经验值起步,然后通过压测和监控来微调。重要的是,我们不再依赖一个孤零零的单进程来抗压,而是让操作系统的调度器帮助我们把请求分发到多个独立进程上。
在本机实验时,我们可以先用浏览器连上http://127.0.0.1:8000/docs,确认应用正常运行,再用简单的压测工具对一个典型端点施加压力,感受一下多进程带来的吞吐量提升。这样,当我们把相同模式搬到服务器上时,就不会是“盲目地抄一段教程里的命令”,而是心里有数地知道它背后在做什么。
现代后端部署的主流方式,是使用Docker这样的容器技术把应用及其运行时环境打包成一个镜像。这样做给我们带来的最大好处,是“环境可重复”和“部署方式统一”。无论是本地开发机、测试服务器还是生产集群,只要能跑Docker,就能以同样的方式拉起同一个镜像,不必再在每台机器上手动装Python、安装依赖、配置虚拟环境。
让我们一起写一个针对FastAPI应用的基础Dockerfile。假设我们的应用入口在app/main.py里,FastAPI实例名为app,依赖通过requirements.txt管理。
|FROM python:3.11-slim ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . ENV PORT=8000 CMD ["gunicorn", "app.main:app", \ "-k", "uvicorn.workers.UvicornWorker", \ "-w", "4", \ "-b", "0.0.0.0:8000"]
这个镜像非常朴素,却已经具备了在大多数环境中运行的能力。我们通过python:3.11-slim获得了一个较小体积的基础镜像,设置了一些环境变量来提升运行时表现,把依赖和应用代码拷贝进容器,然后用Gunicorn+UvicornWorker作为默认启动命令。实际项目中,我们可以根据需要进一步做镜像瘦身,比如使用多阶段构建,把编译依赖和运行时依赖分开。
构建镜像时,我们可以在项目根目录运行:
|docker build -t my-fastapi-app:latest .
然后通过以下命令在本机启动一个容器:
|docker run --rm -p 8000:8000 my-fastapi-app:latest
此时,通过浏览器访问http://localhost:8000/docs,你会发现界面和在本地直接启动Uvicorn时几乎没有区别,但这一次应用是跑在一个隔离的容器里。这一步小小的迁移,实际上已经为后续部署到云端、接入CI/CD流水线和与其他服务协作打下了坚实的基础。

虽然我们可以直接让容器监听80或443端口对外提供服务,但在实际生产环境中,我们通常会在应用前面加一层反向代理。Nginx是一种非常常见的选择,它可以负责TLS终止(也就是处理HTTPS握手和证书),可以做HTTP到HTTPS的重定向,可以负责处理静态资源,还可以作为多实例之间的负载均衡器。
设想我们已经有一台服务器,上面跑着一个或多个FastAPI容器,它们都监听在本机的某个端口上,比如8000和8001。我们可以在Nginx中配置一个虚拟主机,把来自外部的443端口请求转发到这些上游服务。
|http { upstream fastapi_app { server 127.0.0.1:8000; server 127.0.0.1:8001; } server { listen 80; server_name example.com; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_certificate_key /etc/nginx/certs/privkey.pem;
在这个配置里,我们用upstream fastapi_app声明了两个上游服务实例,Nginx会自动在它们之间分发请求。第一个server块负责把HTTP请求重定向到HTTPS,第二个server块负责终止TLS并把请求通过反向代理转发给FastAPI应用。这样的架构把“网络层的复杂性”和“应用层的业务逻辑”清晰地分开,让我们可以分别优化。
在容器化环境中,我们也可以把Nginx封装成单独的容器,与一个或多个FastAPI容器一起部署,通过Docker Compose或Kubernetes的Service和Ingress来完成同样的职责。关键在于:无论部署在什么平台,我们都遵循同一个原则,让应用专心做ASGI服务器,让反向代理负责TLS和流量调度。
当我们开始使用容器之后,一个新的问题随之而来:如何在不同环境中为同一个镜像提供不同的配置。例如,在开发环境中我们希望连接本地SQLite,而在生产环境中我们要连接云端托管的PostgreSQL;在测试环境中我们可能要打开额外的日志,而在生产环境中则希望日志更简洁。
我们在“应用配置管理”一章里会系统讨论这一点,但在部署与扩展的语境下,有一个特别重要的实践要提前强调:配置应该通过环境变量或外部配置文件提供,而不是硬编码在代码或镜像里。这样,我们就可以在构建一次镜像后,把它部署到不同环境,只通过环境变量切换行为。
在Docker中,我们可以在docker run时使用-e参数注入环境变量,或者在Compose文件中定义环境变量,然后在FastAPI应用里通过os.getenv或配置库(例如pydantic-settings)读取。
|docker run --rm \ -e DATABASE_URL="postgresql://user:pass@db:5432/mydb" \ -e ENVIRONMENT="production" \ -p 8000:8000 \ my-fastapi-app:latest
在应用代码中,我们可以这样读取配置:
|import os DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./dev.db") ENVIRONMENT = os.getenv("ENVIRONMENT", "development")
通过这种方式,我们避免了在镜像中写死任何特定环境的信息,也避免了把敏感配置(比如数据库密码和API密钥)直接放在代码库里。未来在讲到Kubernetes和云平台时,我们会看到如何用Secret和ConfigMap等机制进一步加强这一层的安全性和可管理性。

当应用在生产环境中开始接收真实用户请求时,总会有一天你会发现:单个实例的负载已经接近极限。此时,我们有两个方向可以考虑。一个是垂直扩展,也就是给现有机器增加更多CPU和内存,然后相应增加Gunicorn工作进程数量;另一个是水平扩展,把同一个镜像部署到多台机器或多个容器实例上,通过负载均衡器把流量分散过去。
在单机多进程的场景下,我们可以简单地把-w参数调大,让Gunicorn管理更多工作进程。每个进程都会有自己的Uvicorn事件循环和数据库连接池,系统整体的并发能力会随之增加。当然,这也会让内存占用增加,我们需要通过监控来找到一个合适的平衡点。
在多实例水平扩展的场景下,我们通常会在前面放一个负载均衡器(可能是云厂商提供的LB,也可能是自建的Nginx或Envoy),然后在后端挂多个运行相同镜像的容器或虚拟机。每个实例内部可以继续使用多进程,整个系统就变成了“多机 * 多进程 * 多事件循环”的结构。这种结构在处理大量独立HTTP请求时非常高效,但也要求我们在共享状态、会话管理和缓存一致性方面格外小心。
在FastAPI应用本身的代码里,我们要避免依赖进程内全局变量来保存业务状态,比如不能把登录用户列表或计数器直接存在模块级字典里,而应该通过数据库、缓存或外部存储来共享状态。只要我们坚持这一点,多进程和多实例扩展就不会对业务逻辑带来惊喜。
扩展之后,我们还需要关注另一个经常被忽视的问题:如何在不影响线上流量的情况下发布新版本,如何在实例关闭时优雅地处理已有连接。无论是使用Gunicorn、自建进程管理器,还是依赖Kubernetes等编排系统,我们都需要在应用里提供可靠的健康检查端点,并确保在收到停止信号时能够优雅地关闭。
健康检查通常是一个非常简单的端点,比如/healthz或/livez,它只需要返回一个表示服务健康的状态即可。在更复杂的场景下,这个端点还可以检查数据库连接、缓存服务和关键外部依赖的可用性,但在最小版本中,我们只需要保证应用进程启动成功且事件循环运行正常。
|from fastapi import FastAPI app = FastAPI() @app.get("/healthz") async def health_check(): return {"status": "ok"}
在部署系统中,我们可以配置负载均衡器或Kubernetes的探针定期访问这个端点,根据返回结果决定是否把实例加入或移出负载均衡池。这样,当我们滚动升级应用时,新实例只有在健康检查通过后才开始接收流量,旧实例在接收到终止信号后也可以先停止接受新连接,再处理完当前请求后再退出,从而避免用户在发布过程中遭遇大量失败请求。

在这一部分中,我们终于把视角从编辑器和本地终端,扩展到了完整的生产环境。我们从最基础的“Uvicorn开发模式”出发,理解了为什么在生产中需要一个负责多进程管理的Gunicorn,并在此基础上让应用能够更好地利用多核硬件。随后,我们用Docker把FastAPI应用与其运行环境一起打包,让部署从“在每台机器上配置Python和依赖”变成“在任何地方运行同一个镜像”。
我们又在应用前面加了一层反向代理,让Nginx或其他组件负责HTTPS终止和负载均衡,把网络层的复杂度从应用中抽离出来。同时,我们通过环境变量和外部配置把“同一个镜像在不同环境下的行为”变得可控,并讨论了多进程和多实例带来的扩展能力与对共享状态的约束。
在后面的学习中,我们会在这个部署与扩展的框架之上,进一步完善配置管理、监控、日志和安全策略。到那时,你会发现,这一整门FastAPI课程从一开始就围绕着“工程化的可持续演进”来组织,而部署与扩展这一部分则是把之前所有积累真正推向生产环境的关键一步。