部署
部署FastAPI应用程序相对容易。
- 部署是什么意思
部署应用程序意味着执行必要的步骤以使其可供用户使用。
对于Web API来说,通常涉及将上传到云服务器中,搭配一个性能和稳定性都不错的服务器程序,以便你的用户可以高效地访问你的应用程序,而不会出现中断或其他问题。
这与开发阶段形成鲜明对比,在开发阶段,你不断更改代码、破坏代码、修复代码,来回停止和重启服务器等。
- 部署策略
根据你的使用场景和使用的工具,有多种方法可以实现此目的。
你可以使用一些工具自行部署服务器,你也可以使用能为你完成部分工作的云服务,或其他可能的选项。
关于FastAPI版本
FastAPI已在许多应用程序和系统的生产环境中使用。并且测试覆盖率保持在100%。但其开发进度仍在快速推进。
经常添加新功能,定期修复错误,并且代码仍在持续改进。
这就是为什么当前版本仍然是0.x.x
,这反映出每个版本都可能有Breaking changes。这遵循语义版本控制的约定。
你现在就可以使用FastAPI创建生产环境应用程序,你只需确保使用的版本可以与其余代码正确配合即可。
固定你的fastapi
版本
你应该做的第一件事是将你正在使用的FastAPI版本”固定“到你知道适用于你的应用程序的特定最新版本。
例如,假设你在应用程序中使用版本0.45.0
。
如果你使用requirements.txt
文件,你可以使用以下命令指定版本:
fastapi==0.45.0
这意味着你将使用版本0.45.0
。
或者你也可以将其固定为:
fastapi>=0.45.0,<0.46.0
这意味着你将使用0.45.0
或更高版本,但低于0.46.0
,例如,版本0.45.2
仍会被接受。
关于HTTPS
人们很容易认为HTTPS仅仅是”启用“或”未启用“的东西。
但实际情况笔者复杂得多。
要从用户的视角了解HTTPS的基础知识,请查看https://howhttps.works/。
现在,从开发人员的视角,在了解HTTPS时需要记住以下几点:
- 要使用HTTPS,服务器需要拥有由第三方生成的”证书(certificate)“。
-
- 这些证书实际上是从第三方获取的,而不是”生成“的。
- 证书有生命周期。
-
- 它们会过期。
-
- 然后它们需要更新,再次从第三方获取。
- 连接的加密发生在TCP层。
-
- 这是HTTP协议下面的一层。
-
- 因此,证书和加密处理是在HTTP之前完成的。
- TCP不知道域名。仅仅知道IP地址。
-
- 有关所请求的特定域名的信息位于HTTP数据中。
- HTTPS证书”证明“某个域名,但协议和加密发生在TCP层,在知道正在处理哪个域名之前。
- 默认情况下,这意味着你每个IP地址只能拥有一个HTTPS证书。
-
- 无论你的服务器有多大,或者服务器上的每个应用程序有多小。
-
- 不过,对此有一个解决方案。
- TLS协议(在HTTP之下的TCP层处理加密的协议)有一个扩展,称为SNI。
-
- SNI扩展允许一台服务器(具有单个IP地址)拥有多个HTTPS证书并提供多个HTTPS域名/应用程序。
- 为此,服务器上会有单独的一个组件(程序)侦听公共IP地址,这个组件必须拥有服务器中的所有HTTPS证书。
- 获得安全连接后,通信协议仍然是HTTP。
-
- 内容时加密过的,即使它们是通过HTTP协议发送的。
通常的做法是在服务器上运行一个程序/HTTP服务器并管理所有HTTPS部分:接收加密的HTTPS请求,将解密的HTTP请求发送到在同一服务器中运行的实际HTTP应用程序(在本例中那个为FastAPI应用程序),从应用程序中获取HTTP响应,使用适当的HTTPS证书对其进行加密并使用HTTPS将其发送回客户端。此服务器通常被称为TLS终止代理。
你可以用作TLS终止代理的一些选项包括:
- Traefik(也可以处理证书更新)
- Caddy(也可以处理证书更新)
- Nginx
- HAProxy
Let’s Encrypt
在Let’s Encrypt之前,这些HTTPS证书由受信任的第三方出售。
过去,获得这些证书的过程非常繁琐,需要大量的文书工作,而且证书非常昂贵。
但随后Let‘s Encrypt创建了。
它是Linux基金会的一个项目。它以自动方式免费提供HTTPS证书。这些证书可以使用所有符合标准的安全加密,并且有效期很短(大约3个月),因此安全性实际上更好,因为它们的生命周期缩短了。
域可以被安全地验证并自动生成证书。这还允许自动更新这些证书。
我们的想法是自动获取和更新这些证书,以便你可以永远免费拥有安全地HTTPS。
面向开发人员的HTTPS
这里有一个HTTPS API看起来是什么样的示例,我们会分布说明,并且主要关注对开发人员重要的部分。
域名
第一步我们要先获取一些域名(Domain Name)。然后可以在DNS服务器(可能是你的同一家云服务器提供的)中配置它。
你可能拥有一个云服务器(虚拟机)或类似的东西,并且它会有一个固定公共IP地址。
在DNS服务器中,你可以配置一条记录(”A记录“)以将你的域名指向你服务器的公共IP地址。
这个操作一般只需要在最开始执行一次。
DNS
现在让我们关注真正的HTTPS部分。
首先,浏览器将通过DNS 服务器查询域名的IP是什么,在本例中为someapp.example.com
。
DNS服务器会告诉浏览器使用某个特定的IP地址。这将是你在DNS服务器中为你的服务器配置的公共IP地址。
TLS握手开始
然后,浏览器将在端口443(HTTPS端口)上与该IP地址进行通信。
通信的第一部分只是建立客户端和服务器之间的连接并决定它们将使用的加密密钥等。
客户端和服务器之间建立TLS连接的过程称为TLS握手。
带有SNI扩展的TLS
服务器中只有一个进程可以侦听特定IP地址的特定端口。可能有其他进程在同一IP地址的其他端口上侦听,但每个IP地址和端口组合只有一个进程。
TLS(HTTPS)默认使用端口443
。这就是我们需要的端口。
由于只有一个进程可以监听此端口,因此监听端口的进程将是TLS终止代理。
TLS终止代理可以访问一个或多个TLS证书(HTTPS证书)。
使用上面讨论的SNI扩展,TLS终止代理将检查应该用于此连接的可用TLS(HTTPS)证书,并使用与客户端期望的域名相匹配的证书,
在这种情况下,它将使用someapp.example.com
的证书。
客户端已经信任生成该 TLS 证书的实体(在本例中为 Let’s Encrypt,但我们稍后会看到),因此它可以验证该证书是否有效。
然后,通过使用证书,客户端和 TLS 终止代理 决定如何加密 TCP 通信 的其余部分。 这就完成了 TLS 握手 部分。
此后,客户端和服务器就拥有了加密的 TCP 连接,这就是 TLS 提供的功能。 然后他们可以使用该连接来启动实际的 HTTP 通信。
这就是 HTTPS,它只是 安全 TLS 连接 内的普通 HTTP,而不是纯粹的(未加密的)TCP 连接。
HTTPS请求
现在客户端和服务器(特别是浏览器和 TLS 终止代理)具有 加密的 TCP 连接,它们可以开始 HTTP 通信。
接下来,客户端发送一个 HTTPS 请求。 这其实只是一个通过 TLS 加密连接的 HTTP 请求。
解密请求
TLS 终止代理将使用协商好的加密算法解密请求,并将(解密的)HTTP 请求传输到运行应用程序的进程(例如运行 FastAPI 应用的 Uvicorn 进程)。
HTTP响应
应用程序将处理请求并向 TLS 终止代理发送(未加密)HTTP 响应。
HTTPS响应
然后,TLS 终止代理将使用之前协商的加密算法(以someapp.example.com的证书开头)对响应进行加密,并将其发送回浏览器。
接下来,浏览器将验证响应是否有效和是否使用了正确的加密密钥等。然后它会解密响应并处理它。
客户端(浏览器)将知道响应来自正确的服务器,因为它使用了他们之前使用 HTTPS 证书 协商出的加密算法。
多个应用程序
在同一台(或多台)服务器中,可能存在多个应用程序,例如其他 API 程序或数据库。
只有一个进程可以处理特定的 IP 和端口(在我们的示例中为 TLS 终止代理),但其他应用程序/进程也可以在服务器上运行,只要它们不尝试使用相同的 公共 IP 和端口的组合。
这样,TLS 终止代理就可以为多个应用程序处理多个域名的 HTTPS 和证书,然后在每种情况下将请求传输到正确的应用程序。
证书更新
在未来的某个时候,每个证书都会过期(大约在获得证书后 3 个月)。
然后,会有另一个程序(在某些情况下是另一个程序,在某些情况下可能是同一个 TLS 终止代理)与 Let’s Encrypt 通信并更新证书。
TLS 证书 与域名相关联,而不是与 IP 地址相关联。
因此,要更新证书,更新程序需要向权威机构(Let’s Encrypt)证明它确实“拥有”并控制该域名。
有多种方法可以做到这一点。 一些流行的方式是:
- 修改一些DNS记录。
-
- 为此,续订程序需要支持 DNS 提供商的 API,因此,要看你使用的 DNS 提供商是否提供这一功能。
- 在与域名关联的公共 IP 地址上作为服务器运行(至少在证书获取过程中)。
-
- 正如我们上面所说,只有一个进程可以监听特定的 IP 和端口。
-
- 这就是当同一个 TLS 终止代理还负责证书续订过程时它非常有用的原因之一。
-
- 否则,你可能需要暂时停止 TLS 终止代理,启动续订程序以获取证书,然后使用 TLS 终止代理配置它们,然后重新启动 TLS 终止代理。 这并不理想,因为你的应用程序在 TLS 终止代理关闭期间将不可用。
通过拥有一个单独的系统来使用 TLS 终止代理来处理 HTTPS, 而不是直接将 TLS 证书与应用程序服务器一起使用 (例如 Uvicorn),你可以在 更新证书的过程中同时保持提供服务。
手动运行服务器-Uvicorn
在远程服务器计算机上运行FastAPI应用程序所需的主要东西是ASGI服务器程序,例如Uvicorn。
有3个主要可选方案:
- Uvicorn:高性能ASGI服务器。
- Hypercorn:与HTTP/2和Trio等兼容的ASGI服务器。
- Daphne:为Django Channels构建的ASGI服务器。
服务器主机和服务器程序
关于名称,有一个小细节需要记住。
”服务器“一词通常用于指远程/云计算机(物理机或虚拟机)以及在该计算机上运行的程序(例如Uvicorn)。
请记住,当您一般读到”服务器“这个名词时,它可能指的是这两者之一。
当提到远程主机时,通常将其称为服务器,但也称为机器(machine)、VM(虚拟机)、节点。这些都是指某种类型的远程计算机,通常运行Linux,您可以在其中运行程序。
安装服务器程序
您可以使用以下命令安装ASGI兼容服务器:
- Uvicorn,一个快如闪电ASGI服务器,基于uvloop和httptools构建。
$ pip install "uvicorn[standard]"
运行服务器程序
您可以按照之前教程中的相同方式运行应用程序,但不使用reload
选项,例如:
$ uvicorn main:app --host 0.0.0.0 --port 80
部署概念
这些示例运行服务器程序(例如Uvicorn),启动单个进程,在所有IP(0.0.0.0
)上监听预定义端口(例如80
)。
这是基本思路。但您可能需要处理一些其他事情,例如:
- 安全性-HTTPS
- 启动时运行
- 重新启动
- Replication(运行的进程数)
- 内存
- 开始前的步骤
在部署FastAPI应用程序或任何类型的Web API时,有几个概念值得了解,通过掌握这些概念您可以找到最合适的方法来部署您的应用程序。
安全性-HTTPS
见原文https://fastapi.tiangolo.com/zh/deployment/concepts/
程序和进程
什么是程序
程序这个词通常用来描述很多东西:
- 您编写的代码、Python文件。
- 操作系统可以执行的文件,例如:
python
、python.exe
或uvicorn
。 - 在操作系统上运行、使用CPU并将内容存储在内存上的特定程序。这也被称为进程。
什么是进程
进程 这个词通常以更具体的方式使用,仅指在操作系统中运行的东西(如上面的最后一点):
- 在操作系统上运行的特定程序。
-
- 这不是指文件,也不是指代码,它具体指的是操作系统正在执行和管理的东西。
- 任何程序,任何代码,只有在执行时才能做事。 因此,是当有进程正在运行时。
- 该进程可以由您或操作系统终止(或“杀死”)。 那时,它停止运行/被执行,并且它可以不再做事情。
- 您计算机上运行的每个应用程序背后都有一些进程,每个正在运行的程序,每个窗口等。并且通常在计算机打开时同时运行许多进程。
- 同一程序可以有多个进程同时运行。
如果您检查操作系统中的“任务管理器”或“系统监视器”(或类似工具),您将能够看到许多正在运行的进程。
例如,您可能会看到有多个进程运行同一个浏览器程序(Firefox、Chrome、Edge 等)。 他们通常每个tab运行一个进程,再加上一些其他额外的进程。
启动时运行
在大多数情况下,当您创建Web API时,您希望它始终运行、不间断,以便您的客户端始终可以访问它。 这是当然的,除非您有特定原因希望它仅在某些情况下运行,但大多数时候您希望它不断运行并且可用。
在远程服务器中
当您设置远程服务器(云服务器、虚拟机等)时,您可以做的最简单的事情就是手动运行 Uvicorn(或类似的),就像本地开发时一样。
它将会在开发过程中发挥作用并发挥作用。
但是,如果您与服务器的连接丢失,正在运行的进程可能会终止。
如果服务器重新启动(例如更新后或从云提供商迁移后),您可能不会注意到它。 因此,您甚至不知道必须手动重新启动该进程。 所以,你的 API 将一直处于挂掉的状态。
启动时自动运行
一般来说,您可能希望服务器程序(例如 Uvicorn)在服务器启动时自动启动,并且不需要任何人为干预,让进程始终与您的 API 一起运行(例如 Uvicorn 运行您的 FastAPI 应用程序)。
单独的程序
为了实现这一点,您通常会有一个单独的程序来确保您的应用程序在启动时运行。 在许多情况下,它还可以确保其他组件或应用程序也运行,例如数据库。
启动时运行的示例工具
可以完成这项工作的工具的一些示例是:
- Docker
- Kubernetes
- Docker Compose
- Docker in Swarm Mode
- Systemd
- Supervisor
- 作为其服务的一部分由云提供商内部处理
- 其他的…
重新启动
与确保应用程序在启动时运行类似,您可能还想确保它在挂掉后重新启动。
自动处理小错误
使用 FastAPI 构建 Web API 时,如果我们的代码中存在错误,FastAPI 通常会将其包含到触发错误的单个请求中。
对于该请求,客户端将收到 500 内部服务器错误,但应用程序将继续处理下一个请求,而不是完全崩溃。
更大的错误-崩溃
尽管如此,在某些情况下,我们编写的一些代码可能会导致整个应用程序崩溃,从而导致 Uvicorn 和 Python 崩溃。
尽管如此,您可能不希望应用程序因为某个地方出现错误而保持死机状态,您可能希望它继续运行,至少对于未破坏的路径操作。
崩溃后重新启动
但在那些严重错误导致正在运行的进程崩溃的情况下,您需要一个外部组件来负责重新启动进程,至少尝试几次…
在大多数情况下,用于启动时运行程序的同一工具也用于处理自动重新启动。
复制 - 进程和内存
对于 FastAPI 应用程序,使用像 Uvicorn 这样的服务器程序,在一个进程中运行一次就可以同时为多个客户端提供服务。
但在许多情况下,您会希望同时运行多个工作进程。
多进程 - Workers
如果您的客户端数量多于单个进程可以处理的数量(例如,如果虚拟机不是太大),并且服务器的 CPU 中有 多个核心,那么您可以让 多个进程 运行 同时处理同一个应用程序,并在它们之间分发所有请求。
当您运行同一 API 程序的多个进程时,它们通常称为 workers。
每个进程的内存
现在,当程序将内容加载到内存中时,例如,将机器学习模型加载到变量中,或者将大文件的内容加载到变量中,所有这些都会消耗服务器的一点内存 (RAM) 。
多个进程通常不共享任何内存。 这意味着每个正在运行的进程都有自己的东西、变量和内存。 如果您的代码消耗了大量内存,每个进程将消耗等量的内存。
服务器内存
例如,如果您的代码加载 1 GB 大小的机器学习模型,则当您使用 API 运行一个进程时,它将至少消耗 1 GB RAM。 如果您启动 4 个进程(4 个工作进程),每个进程将消耗 1 GB RAM。 因此,您的 API 总共将消耗 4 GB RAM。
如果您的远程服务器或虚拟机只有 3 GB RAM,尝试加载超过 4 GB RAM 将导致问题。
Server Workers-Gunicorn with Uvicorn
到目前为止,通过文档中的所有教程,您可能已经在单个进程上运行了像Uvicorn这样的服务器程序。
部署应用程序时,您可能希望进行一些进程复制,以利用多核并能够处理更多请求。
在这里时将Gunicorn与Uvicorn worker进程一起使用。
Gunicorn with Uvicorn Workers
Gunicorn主要是一个使用WSGI标准的应用服务器。这意味着Gunicorn可以为Flask和Django等应用程序提供服务。Gunicorn本身与FastAPI不兼容,因为FastAPI使用最新的ASGI标准。
但Gunicorn支持充当进程管理器并允许用户告诉它要使用哪个特定的worker类。然后Gunicorn将使用该类启动一个或多个worker进程。
Uvicorn有一个Gunicorn兼容的worker类。
使用这种组合,Gunicorn将充当进程管理器,监听端口和IP。它会将通信传输到运行Uvicorn类的worker进程。
然后与Gunicorn兼容的Uvicorn worker类将负责将Gunicorn发送的数据转换为ASGI标准以供FastAPI使用。
安装Gunicorn和Uvicorn
$ pip install "uvicorn[standard]" gunicorn
这将安装带有standard
扩展包(以获得高性能)的Uvicorn和Gunicorn。
Run Gunicorn with Uvicorn Workers
接下来你可以通过以下命令运行Gunicorn:
$ gunicorn main:app --workers 4 --worker-class
uvicorn.workers.UvicornWorker --bind 0.0.0.0:80
[19499] [INFO] Starting gunicorn 20.1.0
[19499] [INFO] Listening at: http://0.0.0.0:80 (19499)
[19499] [INFO] Using worker: uvicorn.workers.UvicornWorker
[19511] [INFO] Booting worker with pid: 19511
[19513] [INFO] Booting worker with pid: 19513
[19514] [INFO] Booting worker with pid: 19514
[19515] [INFO] Booting worker with pid: 19515
[19511] [INFO] Started server process [19511]
[19511] [INFO] Waiting for application startup.
[19511] [INFO] Application startup complete.
[19513] [INFO] Started server process [19513]
[19513] [INFO] Waiting for application startup.
[19513] [INFO] Application startup complete.
[19514] [INFO] Started server process [19514]
[19514] [INFO] Waiting for application startup.
[19514] [INFO] Application startup complete.
[19515] [INFO] Started server process [19515]
[19515] [INFO] Waiting for application startup.
[19515] [INFO] Application startup complete.
main:app
:这与Uvicorn使用的语法相同,main
表示名为"main
"的Python模块,因此是文件main.py
。app
是FastAPI应用程序的变量名称。
你可以想象main:app
相当于一个Pythonimport
语句,例如:
from main import app
因此,main:app
中的冒号相当于from main import app
中的Python import
部分。
--workers
:要使用的worker进程数量,每个进程将运行一个Uvicorn worker进程,在本例中为4个worker进程。--worker-class
:在worker进程中使用的与Gunicorn兼容的工作类。
这里我们传递了Gunicorn可以导入和使用的类:
import uvicorn.workers.UvicornWorker
--bind
:这告诉Gunicorn要监听的IP和端口,使用冒号(:
)分隔IP和端口。
如果您直接运行Uvicorn,则可以使用--host 0.0.0.0
和--port 80
,而不是--bind 0.0.0.0:80
(Gunicorn选项)。
在输出中,您可以看到它显示了每个进程的PID(进程ID)。
你可以看到:
- Gunicorn进程管理器以PID 19499开头(在您的情况下,它将是一个不同的数字)。
- 然后它开始
Listening at: http://0.0.0.0:80
。 - 然后它检测到它必须使用
uvicorn.workers.UvicornWorder
处的worker类。 - 然后它启动4个worker,每个都有自己的PID:19511、19513、19514和19515。
Gunicorn还将负责管理死进程和重新启动新进程。因此,这在一定程度上有助于上面列表中重启的概念。
Uvicorn with Workers
Uvicorn也有一个选项可以启动和运行多个worker进程。
然而,到目前为止,Uvicorn处理worker进程的能力比Gunicorn更有限。因此,如果您想拥有这个级别(Python级别)的进程管理器,那么最好尝试使用Gunicorn作为进程管理器。
$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4