跳到内容

部署概念

在部署 FastAPI 应用程序,或者实际上任何类型的 Web API 时,有几个概念你可能非常关心,使用它们可以帮助你找到 最合适 的方法来 部署你的应用程序

一些重要概念包括

  • 安全性 - HTTPS
  • 启动时运行
  • 重启
  • 复制(运行的进程数量)
  • 内存
  • 启动前的准备步骤

我们将了解它们如何影响 部署

最终目标是能够以 安全 的方式 服务你的 API 客户端避免中断,并尽可能高效地利用 计算资源(例如远程服务器/虚拟机)。🚀

我将在这里向你介绍更多关于这些 概念 的知识,这应该能为你提供在不同环境(甚至可能是在 未来 尚不存在的环境)中部署 API 所需的 直觉

通过考虑这些概念,你将能够 评估和设计 部署 自己的 API 的最佳方式。

在接下来的章节中,我将为你提供更多部署 FastAPI 应用程序的 具体方法

但现在,让我们先来看看这些重要的 概念性想法。这些概念也适用于任何其他类型的 Web API。💡

安全 - HTTPS

上一章关于 HTTPS 的内容 中,我们了解了 HTTPS 如何为你的 API 提供加密。

我们还看到 HTTPS 通常由一个 外部于 你的应用程序服务器的组件提供,即 TLS 终止代理

并且必须有东西负责 续订 HTTPS 证书,这可能是同一个组件,也可能是另一个组件。

HTTPS 示例工具

你可以用作 TLS 终止代理的一些工具包括

  • Traefik
    • 自动处理证书续订 ✨
  • Caddy
    • 自动处理证书续订 ✨
  • Nginx
    • 配合 Certbot 等外部组件进行证书续订
  • HAProxy
    • 配合 Certbot 等外部组件进行证书续订
  • Kubernetes,配合 Nginx 等 Ingress Controller
    • 配合 cert-manager 等外部组件进行证书续订
  • 由云提供商作为其服务的一部分在内部处理(阅读下方 👇)

另一种选择是,你可以使用一个 云服务,它能完成更多工作,包括设置 HTTPS。它可能有某些限制或收取更高的费用等。但那样的话,你就无需自己设置 TLS 终止代理了。

我将在下一章中向你展示一些具体的例子。


接下来要考虑的概念都与运行实际 API 的程序(例如 Uvicorn)有关。

程序和进程

我们将大量讨论正在运行的“进程”,因此最好清楚它的含义,以及它与“程序”一词的区别。

什么是程序

程序”一词通常用来描述许多事物

  • 你写的 代码Python 文件
  • 可以被操作系统 执行文件,例如:pythonpython.exeuvicorn
  • 在操作系统上 运行 时,使用 CPU 并将数据存储在内存中的特定程序。这也称为 进程

什么是进程

进程”一词通常用于更具体的含义,仅指在操作系统中运行的事物(如上面最后一点)

  • 在操作系统上 运行 的特定程序。
    • 这不指文件,也不指代码,它 特指 由操作系统 执行 和管理的那个东西。
  • 任何程序、任何代码,只有在被执行时 才能做事情。所以,当有一个 进程运行时
  • 进程可以被你或操作系统 终止(或“杀死”)。届时,它停止运行/执行,并且 不能再做任何事情
  • 你计算机上运行的每个应用程序都有一个进程在背后运行,每个运行的程序、每个窗口等等。当计算机运行时,通常有许多进程 同时运行
  • 同一个程序 可以 同时运行多个进程

如果你查看操作系统中的“任务管理器”或“系统监视器”(或类似工具),你将能够看到许多正在运行的进程。

例如,你可能会看到运行相同浏览器程序(Firefox、Chrome、Edge 等)的多个进程。它们通常每个标签页运行一个进程,再加上一些额外的进程。


现在我们知道了 进程程序 之间的区别,让我们继续讨论部署。

开机自启

在大多数情况下,当你创建一个 Web API 时,你希望它 始终运行,不间断,以便你的客户端始终可以访问它。当然,除非你有特殊原因希望它仅在特定情况下运行,但大多数时候你希望它持续运行并 可用

在远程服务器上

当你设置一个远程服务器(云服务器、虚拟机等)时,你可以做的最简单的事情就是像在本地开发时一样,手动使用 fastapi run(它使用 Uvicorn)或类似的东西。

它会起作用,并且在 开发期间 有用。

但如果你的服务器连接断开,正在运行的进程 可能会终止。

如果服务器重新启动(例如,在更新后,或云提供商进行迁移),你可能 不会注意到。因此,你甚至不知道必须手动重启进程。所以,你的 API 将一直处于死亡状态。😱

自动开机自启

总的来说,你可能希望服务器程序(例如 Uvicorn)在服务器启动时自动启动,无需 人工干预,以便有一个进程始终运行你的 API(例如 Uvicorn 运行你的 FastAPI 应用)。

独立程序

为了实现这一点,你通常会有一个 独立的程序 来确保你的应用程序在启动时运行。在许多情况下,它还会确保其他组件或应用程序也运行,例如数据库。

开机自启的示例工具

可以执行此任务的一些工具示例是

  • Docker
  • Kubernetes
  • Docker Compose
  • Docker Swarm 模式
  • Systemd
  • Supervisor
  • 由云提供商作为其服务的一部分在内部处理
  • 其他...

我将在下一章中提供更多具体的例子。

重启

与确保应用程序在启动时运行类似,你也可能希望确保它在失败后 自动重启

我们会犯错

我们人类,总是会 犯错。软件几乎 总是 在不同的地方隐藏着 bug。🐛

而我们作为开发者,在我们发现这些 bug 和实现新功能(可能也会增加新的 bug 😅)时,会不断改进代码。

小错误自动处理

在使用 FastAPI 构建 Web API 时,如果我们的代码中出现错误,FastAPI 通常会将其限制在触发错误的单个请求中。🛡

客户端将收到该请求的 500 内部服务器错误,但应用程序将继续为后续请求工作,而不是完全崩溃。

较大的错误 - 崩溃

尽管如此,仍然可能出现我们编写的代码 使整个应用程序崩溃,导致 Uvicorn 和 Python 崩溃。💥

而且,你可能不希望应用程序因为一个地方出错而一直处于停止状态,你可能希望它至少能够 继续运行 对于那些没有损坏的 路径操作

崩溃后重启

但是,在那些导致正在运行的 进程 崩溃的非常严重的错误情况下,你希望有一个外部组件负责 重启 该进程,至少重启几次……

提示

……虽然如果整个应用程序 立即崩溃,那么无限期地重启它可能没有意义。但在那些情况下,你可能会在开发过程中注意到,或者至少在部署后立即注意到。

所以,让我们关注主要情况,即在某些特定情况 将来 可能完全崩溃,但仍然有意义去重启它。

你可能希望负责重启应用程序的组件是一个 外部组件,因为此时,Uvicorn 和 Python 所在的同一个应用程序已经崩溃了,所以同一个应用程序中的代码无法做任何事情。

自动重启的示例工具

在大多数情况下,用于 在启动时运行程序 的工具与用于处理自动 重启 的工具是相同的。

例如,这可以由以下方式处理

  • Docker
  • Kubernetes
  • Docker Compose
  • Docker Swarm 模式
  • Systemd
  • Supervisor
  • 由云提供商作为其服务的一部分在内部处理
  • 其他...

复制 - 进程和内存

对于 FastAPI 应用程序,使用像 fastapi 命令运行 Uvicorn 这样的服务器程序,在 一个进程 中运行一次就可以并发服务多个客户端。

但在许多情况下,你希望同时运行多个 worker 进程。

多进程 - Worker

如果你拥有的客户端数量超过单个进程的处理能力(例如,如果虚拟机不是太大的话),并且服务器 CPU 有 多个核心,那么你可以同时运行 多个进程 来运行相同的应用程序,并将所有请求分发给它们。

当你运行同一个 API 程序的 多个进程 时,它们通常被称为 Worker

Worker 进程和端口

还记得文档 关于 HTTPS 中提到的,在一个服务器上,一个 IP 地址和端口组合只能被一个进程监听吗?

这仍然是真的。

所以,要能够 同时运行多个进程,必须有一个 单个进程监听端口,然后以某种方式将通信传输给每个 worker 进程。

每个进程的内存

现在,当程序将某些东西加载到内存中时,例如,一个机器学习模型存储在一个变量中,或者一个大文件的内容存储在一个变量中,所有这些 都会消耗服务器一点点内存 (RAM)

多个进程通常 不共享内存。这意味着每个正在运行的进程都有自己的数据、变量和内存。如果你在代码中消耗大量内存,每个进程 都会消耗等量的内存。

服务器内存

例如,如果你的代码加载了一个 1 GB 大小 的机器学习模型,当你运行一个 API 进程时,它将至少消耗 1 GB 的 RAM。如果你启动了 4 个进程(4 个 worker),每个进程将消耗 1 GB 的 RAM。所以总共,你的 API 将消耗 4 GB 的 RAM

如果你的远程服务器或虚拟机只有 3 GB 的 RAM,尝试加载超过 4 GB 的 RAM 将导致问题。🚨

多进程 - 示例

在这个例子中,有一个 管理器进程 启动并控制两个 Worker 进程

这个管理器进程可能就是监听 IP 地址和端口 的那个。它会将所有通信传输给 worker 进程。

这些 worker 进程将运行你的应用程序,它们将执行主要计算来接收 请求 并返回 响应,并且它们会将你在变量中放入的任何内容加载到 RAM 中。

当然,同一台机器可能还会有 其他进程 在运行,除了你的应用程序之外。

一个有趣细节是,每个进程使用的 CPU 百分比 可能会随时间 变化很大,但 内存 (RAM) 通常会保持 相对稳定

如果你的 API 每次执行的计算量都差不多,而且你有很多客户端,那么 CPU 利用率 可能也会 相对稳定(而不是快速地上下波动)。

复制工具和策略示例

可以有几种方法来实现这一点,我将在接下来的章节中告诉你更多关于具体策略的信息,例如在讨论 Docker 和容器时。

需要考虑的主要限制是必须有一个 单一 组件处理 公共 IP 上的 端口。然后它必须有一种方法来 传输 通信到复制的 进程/Worker

以下是一些可能的组合和策略

  • Uvicorn 配合 --workers 参数
    • 一个 Uvicorn 进程管理器 将监听 IP 和端口,它将启动 多个 Uvicorn worker 进程
  • Kubernetes 和其他分布式 容器系统
    • Kubernetes 层面的某个东西将监听 IP 和端口。复制将通过拥有 多个容器 来实现,每个容器运行 一个 Uvicorn 进程
  • 云服务 为你处理
    • 云服务可能会 为你处理复制。它可能允许你定义 要运行的进程,或者 要使用的容器镜像,无论如何,它最有可能是一个 单一的 Uvicorn 进程,而云服务将负责复制它。

提示

如果关于 容器、Docker 或 Kubernetes 的这些项目现在还不太有意义,请不要担心。

我将在未来的章节中告诉你更多关于容器镜像、Docker、Kubernetes 等内容:容器中的 FastAPI - Docker

启动前的步骤

在许多情况下,你希望在 开始 运行应用程序之前执行一些步骤。

例如,你可能想运行 数据库迁移

但在大多数情况下,你只会想 执行一次 这些步骤。

所以,你将需要一个 单一进程 来执行这些 前期步骤,然后再启动应用程序。

你必须确保这些前期步骤是由一个单一进程运行的,即使 之后你启动了 多个进程(多个 worker)来运行应用程序本身。如果这些步骤是由 多个进程 运行的,它们将通过 并行 运行而 重复 工作,如果这些步骤是像数据库迁移这样敏感的操作,它们可能会导致冲突。

当然,有些情况下,多次运行前期步骤没有问题,那样的话处理起来就容易多了。

提示

另外,请记住,根据你的设置,在某些情况下,你 可能甚至不需要 在启动应用程序之前的任何前期步骤。

那样的话,你就无需担心这些问题了。🤷

启动前步骤策略示例

这将 严重取决于部署系统的方式,并且可能与你启动程序的方式、处理重启等方式相关。

以下是一些可能的想法

  • Kubernetes 中的“Init Container”,在你的应用程序容器之前运行
  • 一个 bash 脚本,运行前期步骤,然后启动你的应用程序
    • 你仍然需要一种方法来启动/重启 那个 bash 脚本,检测错误等。

提示

我将在未来的章节中提供更多关于使用容器进行此操作的具体例子:容器中的 FastAPI - Docker

资源利用率

你的服务器是一个 资源,你可以用你的程序来消耗或 利用 CPU 的计算时间和可用的 RAM 内存。

你想消耗/利用多少系统资源?可能很容易想到“不多”,但实际上,你可能希望消耗 尽可能多的资源而不崩溃

如果你支付了 3 台服务器的费用,但只使用了它们很少的 RAM 和 CPU,那么你可能 在浪费钱 💸,并且可能 浪费服务器电力 🌎,等等。

在这种情况下,最好只保留 2 台服务器并使用更高的资源百分比(CPU、内存、磁盘、网络带宽等)。

另一方面,如果你有 2 台服务器并且正在使用 100% 的 CPU 和 RAM,那么总有一天,一个进程会请求更多内存,服务器将不得不使用磁盘作为“内存”(这可能比 RAM 慢数千倍),甚至 崩溃。或者一个进程可能需要进行一些计算,而不得不等待 CPU 再次空闲。

在这种情况下,最好再 增加一台服务器,并在上面运行一些进程,以便它们都能拥有 足够的 RAM 和 CPU 时间

还有一种可能性是,由于某种原因,你的 API 使用量出现高峰。也许它火了,或者其他服务或机器人开始使用它。你可能希望在这种情况下拥有额外的资源来保证安全。

你可以设置一个 任意数字 作为目标,例如,50% 到 90% 之间的资源利用率。关键在于,这些很可能是你需要衡量并用来调整部署的主要因素。

你可以使用简单的工具,如 htop 来查看服务器上使用的 CPU 和 RAM,或每个进程使用的量。或者你可以使用更复杂的监控工具,这些工具可能分布在多台服务器上等。

总结

在这里,你已经阅读了一些你在决定如何部署应用程序时可能需要考虑的主要概念。

  • 安全性 - HTTPS
  • 启动时运行
  • 重启
  • 复制(运行的进程数量)
  • 内存
  • 启动前的准备步骤

理解这些想法以及如何应用它们,应该能让你获得必要的直觉,以便在配置和调整部署时做出任何决定。🤓

在接下来的部分,我将为你提供更多关于可能的策略的具体例子。🚀