# 部署概念

在部署 **FastAPI** 应用程序或任何类型的 Web API 时，有几个概念值得了解，通过掌握这些概念您可以找到**最合适的**方法来**部署您的应用程序**。

一些重要的概念是：

* 安全性 - HTTPS
* 启动时运行
* 重新启动
* 复制（运行的进程数）
* 内存
* 开始前的先前步骤

我们接下来了解它们将如何影响**部署**。

我们的最终目标是能够以**安全**的方式**为您的 API 客户端**提供服务，同时要**避免中断**，并且尽可能高效地利用**计算资源**（ 例如服务器CPU资源）。 🚀

我将在这里告诉您更多关于这些**概念**的信息，希望能给您提供**直觉**来决定如何在非常不同的环境中部署 API，甚至在是尚不存在的**未来**的环境里。

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

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

但现在，让我们仔细看一下这些重要的**概念**。 这些概念也适用于任何其他类型的 Web API。 💡

## 安全性 - HTTPS

在[上一章有关 HTTPS](https.md){.internal-link target=_blank} 中，我们了解了 HTTPS 如何为您的 API 提供加密。

我们还看到，HTTPS 通常由应用程序服务器的**外部**组件（**TLS 终止代理**）提供。

并且必须有某个东西负责**更新 HTTPS 证书**，它可以是相同的组件，也可以是不同的组件。


### HTTPS 示例工具

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

* Traefik
     * 自动处理证书更新 ✨
* Caddy
     * 自动处理证书更新 ✨
* Nginx
     * 使用 Certbot 等外部组件进行证书更新
* HAProxy
     * 使用 Certbot 等外部组件进行证书更新
* 带有 Ingress Controller(如Nginx) 的 Kubernetes
     * 使用诸如 cert-manager 之类的外部组件来进行证书更新
* 由云服务商内部处理，作为其服务的一部分（请阅读下文👇）

另一种选择是您可以使用**云服务**来完成更多工作，包括设置 HTTPS。 它可能有一些限制或向您收取更多费用等。但在这种情况下，您不必自己设置 TLS 终止代理。

我将在接下来的章节中向您展示一些具体示例。

---

接下来要考虑的概念都是关于运行实际 API 的程序（例如 Uvicorn）。

## 程序和进程

我们将讨论很多关于正在运行的“**进程**”的内容，因此弄清楚它的含义以及与“**程序**”这个词有什么区别是很有用的。

### 什么是程序

**程序**这个词通常用来描述很多东西：

* 您编写的 **代码**，**Python 文件**。
* 操作系统可以**执行**的**文件**，例如：`python`、`python.exe`或`uvicorn`。
* 在操作系统上**运行**、使用CPU 并将内容存储在内存上的特定程序。 这也被称为**进程**。

### 什么是进程

**进程** 这个词通常以更具体的方式使用，仅指在操作系统中运行的东西（如上面的最后一点）：

* 在操作系统上**运行**的特定程序。
     * 这不是指文件，也不是指代码，它**具体**指的是操作系统正在**执行**和管理的东西。
* 任何程序，任何代码，**只有在执行时才能做事**。 因此，是当有**进程正在运行**时。
* 该进程可以由您或操作系统**终止**（或“杀死”）。 那时，它停止运行/被执行，并且它可以**不再做事情**。
* 您计算机上运行的每个应用程序背后都有一些进程，每个正在运行的程序，每个窗口等。并且通常在计算机打开时**同时**运行许多进程。
* **同一程序**可以有**多个进程**同时运行。

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

例如，您可能会看到有多个进程运行同一个浏览器程序（Firefox、Chrome、Edge 等）。 他们通常每个tab运行一个进程，再加上一些其他额外的进程。

<img class="shadow" src="/img/deployment/concepts/image01.png">

---

现在我们知道了术语“进程”和“程序”之间的区别，让我们继续讨论部署。

## 启动时运行

在大多数情况下，当您创建 Web API 时，您希望它**始终运行**、不间断，以便您的客户端始终可以访问它。 这是当然的，除非您有特定原因希望它仅在某些情况下运行，但大多数时候您希望它不断运行并且**可用**。

### 在远程服务器中

当您设置远程服务器（云服务器、虚拟机等）时，您可以做的最简单的事情就是手动运行 Uvicorn（或类似的），就像本地开发时一样。

它将会在**开发过程中**发挥作用并发挥作用。

但是，如果您与服务器的连接丢失，**正在运行的进程**可能会终止。

如果服务器重新启动（例如更新后或从云提供商迁移后），您可能**不会注意到它**。 因此，您甚至不知道必须手动重新启动该进程。 所以，你的 API 将一直处于挂掉的状态。 😱


### 启动时自动运行

一般来说，您可能希望服务器程序（例如 Uvicorn）在服务器启动时自动启动，并且不需要任何**人为干预**，让进程始终与您的 API 一起运行（例如 Uvicorn 运行您的 FastAPI 应用程序） 。

### 单独的程序

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

### 启动时运行的示例工具

可以完成这项工作的工具的一些示例是：

* Docker
* Kubernetes
* Docker Compose
* Docker in Swarm Mode
* Systemd
* Supervisor
* 作为其服务的一部分由云提供商内部处理
* 其他的...

我将在接下来的章节中为您提供更具体的示例。


## 重新启动

与确保应用程序在启动时运行类似，您可能还想确保它在挂掉后**重新启动**。

### 我们会犯错误

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

作为开发人员，当我们发现这些bug并实现新功能（也可能添加新bug😅）时，我们会不断改进代码。

### 自动处理小错误

使用 FastAPI 构建 Web API 时，如果我们的代码中存在错误，FastAPI 通常会将其包含到触发错误的单个请求中。 🛡

对于该请求，客户端将收到 **500 内部服务器错误**，但应用程序将继续处理下一个请求，而不是完全崩溃。

### 更大的错误 - 崩溃

尽管如此，在某些情况下，我们编写的一些代码可能会导致整个应用程序崩溃，从而导致 Uvicorn 和 Python 崩溃。 💥

尽管如此，您可能不希望应用程序因为某个地方出现错误而保持死机状态，您可能希望它**继续运行**，至少对于未破坏的*路径操作*。

### 崩溃后重新启动

但在那些严重错误导致正在运行的**进程**崩溃的情况下，您需要一个外部组件来负责**重新启动**进程，至少尝试几次......

/// tip

...尽管如果整个应用程序只是**立即崩溃**，那么永远重新启动它可能没有意义。 但在这些情况下，您可能会在开发过程中注意到它，或者至少在部署后立即注意到它。

 因此，让我们关注主要情况，在**未来**的某些特定情况下，它可能会完全崩溃，但重新启动它仍然有意义。

///

您可能希望让这个东西作为 **外部组件** 负责重新启动您的应用程序，因为到那时，使用 Uvicorn 和 Python 的同一应用程序已经崩溃了，因此同一应用程序的相同代码中没有东西可以对此做出什么。

### 自动重新启动的示例工具

在大多数情况下，用于**启动时运行程序**的同一工具也用于处理自动**重新启动**。

例如，可以通过以下方式处理：

* Docker
* Kubernetes
* Docker Compose
* Docker in Swarm mode
* Systemd
* Supervisor
* 作为其服务的一部分由云提供商内部处理
* 其他的...

## 复制 - 进程和内存

对于 FastAPI 应用程序，使用像 Uvicorn 这样的服务器程序，在**一个进程**中运行一次就可以同时为多个客户端提供服务。

但在许多情况下，您会希望同时运行多个工作进程。

### 多进程 - Workers

如果您的客户端数量多于单个进程可以处理的数量（例如，如果虚拟机不是太大），并且服务器的 CPU 中有 **多个核心**，那么您可以让 **多个进程** 运行 同时处理同一个应用程序，并在它们之间分发所有请求。

当您运行同一 API 程序的**多个进程**时，它们通常称为 **workers**。

### 工作进程和端口

还记得文档 [About HTTPS](https.md){.internal-link target=_blank} 中只有一个进程可以侦听服务器中的端口和 IP 地址的一种组合吗？

现在仍然是对的。

因此，为了能够同时拥有**多个进程**，必须有一个**单个进程侦听端口**，然后以某种方式将通信传输到每个工作进程。

### 每个进程的内存

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

多个进程通常**不共享任何内存**。 这意味着每个正在运行的进程都有自己的东西、变量和内存。 如果您的代码消耗了大量内存，**每个进程**将消耗等量的内存。

### 服务器内存

例如，如果您的代码加载 **1 GB 大小**的机器学习模型，则当您使用 API 运行一个进程时，它将至少消耗 1 GB RAM。 如果您启动 **4 个进程**（4 个工作进程），每个进程将消耗 1 GB RAM。 因此，您的 API 总共将消耗 **4 GB RAM**。

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


### 多进程 - 一个例子

在此示例中，有一个 **Manager Process** 启动并控制两个 **Worker Processes**。

该管理器进程可能是监听 IP 中的 **端口** 的进程。 它将所有通信传输到工作进程。

这些工作进程将是运行您的应用程序的进程，它们将执行主要计算以接收 **请求** 并返回 **响应**，并且它们将加载您放入 RAM 中的变量中的任何内容。

<img src="/img/deployment/concepts/process-ram.svg">

当然，除了您的应用程序之外，同一台机器可能还运行**其他进程**。

一个有趣的细节是，随着时间的推移，每个进程使用的 **CPU 百分比可能会发生很大变化，但内存 (RAM) 通常会或多或少保持稳定**。

如果您有一个每次执行相当数量的计算的 API，并且您有很多客户端，那么 **CPU 利用率** 可能也会保持稳定（而不是不断快速上升和下降）。

### 复制工具和策略示例

可以通过多种方法来实现这一目标，我将在接下来的章节中向您详细介绍具体策略，例如在谈论 Docker 和容器时。

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

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

* **Gunicorn** 管理 **Uvicorn workers**
     * Gunicorn 将是监听 **IP** 和 **端口** 的 **进程管理器**，复制将通过 **多个 Uvicorn 工作进程** 进行
* **Uvicorn** 管理 **Uvicorn workers**
     * 一个 Uvicorn **进程管理器** 将监听 **IP** 和 **端口**，并且它将启动 **多个 Uvicorn 工作进程**
* **Kubernetes** 和其他分布式 **容器系统**
     * **Kubernetes** 层中的某些东西将侦听 **IP** 和 **端口**。 复制将通过拥有**多个容器**，每个容器运行**一个 Uvicorn 进程**
* **云服务** 为您处理此问题
     * 云服务可能**为您处理复制**。 它可能会让您定义 **要运行的进程**，或要使用的 **容器映像**，在任何情况下，它很可能是 **单个 Uvicorn 进程**，并且云服务将负责复制它。



/// tip

如果这些关于 **容器**、Docker 或 Kubernetes 的内容还没有多大意义，请不要担心。

 我将在以后的章节中向您详细介绍容器镜像、Docker、Kubernetes 等：[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。

///

## 启动之前的步骤

在很多情况下，您希望在**启动**应用程序之前执行一些步骤。

例如，您可能想要运行**数据库迁移**。

但在大多数情况下，您只想执行这些步骤**一次**。

因此，在启动应用程序之前，您将需要一个**单个进程**来执行这些**前面的步骤**。

而且您必须确保它是运行前面步骤的单个进程, *即使*之后您为应用程序本身启动**多个进程**（多个worker）。 如果这些步骤由**多个进程**运行，它们会通过在**并行**运行来**重复**工作，并且如果这些步骤像数据库迁移一样需要小心处理，它们可能会导致每个进程和其他进程发生冲突。

当然，也有一些情况，多次运行前面的步骤也没有问题，这样的话就好办多了。

/// tip

另外，请记住，根据您的设置，在某些情况下，您在开始应用程序之前**可能甚至不需要任何先前的步骤**。

 在这种情况下，您就不必担心这些。 🤷

///

### 前面步骤策略的示例

这将在**很大程度上取决于您部署系统的方式**，并且可能与您启动程序、处理重启等的方式有关。

以下是一些可能的想法：

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

/// tip

我将在以后的章节中为您提供使用容器执行此操作的更具体示例：[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。

///

## 资源利用率

您的服务器是一个**资源**，您可以通过您的程序消耗或**利用**CPU 上的计算时间以及可用的 RAM 内存。

您想要消耗/利用多少系统资源？ 您可能很容易认为“不多”，但实际上，您可能希望在不崩溃的情况下**尽可能多地消耗**。

如果您支付了 3 台服务器的费用，但只使用了它们的一点点 RAM 和 CPU，那么您可能**浪费金钱** 💸，并且可能 **浪费服务器电力** 🌎，等等。

在这种情况下，最好只拥有 2 台服务器并使用更高比例的资源（CPU、内存、磁盘、网络带宽等）。

另一方面，如果您有 2 台服务器，并且正在使用 **100% 的 CPU 和 RAM**，则在某些时候，一个进程会要求更多内存，并且服务器将不得不使用磁盘作为“内存” （这可能会慢数千倍），甚至**崩溃**。 或者一个进程可能需要执行一些计算，并且必须等到 CPU 再次空闲。

在这种情况下，最好购买**一台额外的服务器**并在其上运行一些进程，以便它们都有**足够的 RAM 和 CPU 时间**。

由于某种原因，您的 API 的使用量也有可能出现**激增**。 也许它像病毒一样传播开来，或者也许其他一些服务或机器人开始使用它。 在这些情况下，您可能需要额外的资源来保证安全。

您可以将一个**任意数字**设置为目标，例如，资源利用率**在 50% 到 90%** 之间。 重点是，这些可能是您想要衡量和用来调整部署的主要内容。

您可以使用“htop”等简单工具来查看服务器中使用的 CPU 和 RAM 或每个进程使用的数量。 或者您可以使用更复杂的监控工具，这些工具可能分布在服务器等上。


## 回顾

您在这里阅读了一些在决定如何部署应用程序时可能需要牢记的主要概念：

* 安全性 - HTTPS
* 启动时运行
* 重新启动
* 复制（运行的进程数）
* 内存
* 开始前的先前步骤

了解这些想法以及如何应用它们应该会给您足够的直觉在配置和调整部署时做出任何决定。 🤓

在接下来的部分中，我将为您提供更具体的示例，说明您可以遵循的可能策略。 🚀
