65.9K
CodeProject 正在变化。 阅读更多。
Home

如何将 FastAPI 服务器连接到 PostgreSQL 并在 GCP Cloud Run 上部署

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2023 年 5 月 26 日

CPOL

15分钟阅读

viewsIcon

11921

一个关于如何将 FastAPI 应用程序与 PostgreSQL 数据库连接并将整个应用程序部署到 GCP 的深入教程。

在 Web 开发的世界中,强大的后端对于提供高质量的用户体验至关重要。构建后端的常用方法是使用 RESTful API,它允许客户端通过 HTTP 请求与服务器进行通信。

在我构建 Ballistic 时,我选择 FastAPI 作为我在 Python 中后端 API 的 Web 框架,因为它在 Python 社区中获得了巨大的关注。除了其性能(由 asyncio 库驱动)外,类型注解的核心集成和完善的文档也说服了我。请记住,良好的文档和庞大的社区可以为您节省大量的后端开发时间。

构建 Web 应用程序不仅仅是一个 Web 服务器;它还需要一个可靠且高效的数据库来存储和管理应用程序的状态数据。在可用的各种数据库选项中,PostgreSQL 脱颖而出,成为开发人员的热门选择。其庞大的社区和生态系统提供宝贵的支持,使解决问题变得更容易。PostgreSQL 的可靠性和稳定性声誉进一步巩固了其作为值得信赖的解决方案的地位。此外,PostgreSQL 对多个平台的兼容性确保了部署的灵活性。在这篇博文中,我们将探讨如何将我们的 FastAPI 服务器连接到 PostgreSQL 数据库。值得注意的是,该指南可以轻松地通过最少的修改适配到其他数据库,如 MySQL 或 MongoDB。

在部署 REST 服务器时,有许多选项可供选择。一个特别吸引人的选项是使用 Google Cloud Platform (GCP) 等云提供商,它们提供可伸缩的无状态服务,如 Cloud Run 和 App Engine。有了像 GCP 这样的云提供商,您就不必担心服务器 100% 的可用性,也无需担心高流量负载。您的初始投资也大大降低,因为您只需为消耗的资源付费,而不是预先购买整个服务器。

在这篇博文中,我们将探讨如何使用 FastAPI、PostgreSQL 和 GCP 构建 REST 服务器,并将其部署到 GCP Cloud Run 等无状态环境中。在博文的最后,我还会附上所有源代码,以帮助您跟随文中给出的解释。

为什么无状态 REST 服务器非常适合云部署

在深入了解使用 GCP 构建 REST 服务器的细节之前,让我们先回顾一下为什么无状态服务器非常适合云部署。

当服务器无状态时,这意味着它在请求之间不存储任何会话数据或状态信息。这使得扩展和部署更加容易,因为服务器的任何实例都可以处理任何传入的请求。这与有状态服务器相反,有状态服务器需要跟踪会话数据并确保每个请求都由同一个服务器实例处理。

无状态服务器还提供其他好处,例如提高可靠性和容错能力。由于服务器的任何实例都可以处理每个请求,因此可以通过启动新实例来快速检测和处理故障或崩溃。

虽然让您的应用程序跟踪用户数据至关重要,但请务必记住,这项责任在于数据库。数据库旨在存储和维护数据完整性,而 REST 服务器负责处理操作数据库中存储的数据的独立、独立的请求。通过分离这些职责,您的应用程序可以更高效,并且随着时间的推移更容易维护。

GCP Cloud Run 与 App Engine

在 GCP 上部署 REST 服务器时,主要有两种选择:Cloud Run 和 App Engine。

Cloud Run 是一个完全托管的、基于容器的平台,允许您在无服务器环境中运行无状态 HTTP 容器。这意味着您无需担心管理基础架构,只需为使用的资源付费。

另一方面,App Engine 是一个平台即服务 (PaaS) 产品,它允许您使用多种编程语言和框架来构建和部署 Web 应用程序和 API。它比 Cloud Run 是一个功能更齐全的平台,但它也需要更多的配置和管理。

Cloud Run 和 App Engine 都是部署 REST 服务器的好选择,具体选择很大程度上取决于您的具体用例和要求。

构建后端

设置新项目是构建任何应用程序的第一步。虽然有多种工具可用于管理包和依赖项,但我个人推荐使用 poetry,因为它简单易用。或者,您也可以使用普通的虚拟环境来设置您的项目。

假设您已在系统中安装了 poetry,您可以使用以下命令创建一个新项目

poetry new fastapi-gcp

这将创建一个名为 fastapi-gcp 的新目录,其中包含一些默认文件和基本项目结构。接下来,使用 cd fastapi-gcp 导航到新创建的目录,然后使用 poetry 安装 fastapi 包,如下所示

poetry add fastapi asyncpg

这将安装 fastapi 包及其依赖项,使其可在您的项目中使用。

构建 FastAPI 服务器

现在,让我们深入代码,开始构建我们的 FastAPI 服务器。只需几行代码,我们就可以创建一个简单的服务器来处理请求。创建一个 main.py 文件并添加以下代码

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI(title="fastapi-gcp")

@app.get("/", response_class=HTMLResponse)
def healthcheck():
    return "<h1>All good!</h2>"

在这里,我们只是使用一个最小的设置来创建一个服务器,当有人在浏览器中打开服务器的根 URL 时,它将返回一个 HTML 就绪的响应。要在 localhost:8080 上运行此服务器,请执行以下命令

uvicorn main:app --host 0.0.0.0 --port 8080

现在您的服务器正在运行,请确保在浏览器中打开 https://:8080 以查看“All good!”消息。

由于 FastAPI 基于 OpenAPI,此时,您也可以使用自动生成的文档。有多种选项,并且默认包含两个。通过访问以下 URL 来尝试它们

设置 PostgreSQL 数据库

现在我们的服务器已启动并运行,让我们也启动我们的 PostgreSQL 服务器。最简单的方法是利用 Docker 和 官方 PostgreSQL 镜像。如果您已安装 Docker,可以通过运行一个简单的单行命令快速实现此目的

docker run --name postgres-fastapi -e POSTGRES_PASSWORD=postgres-fastapi 
           --rm -p 5432:5432 postgres

在这里,我们启动一个名为“postgres-fastapi”的容器,密码为“postgres-fastapi”,暴露默认的 PostgreSQL 端口 5432。如果您想更深入地了解此主题,可以在本文中找到更详细的信息:如何使用 PostgreSQL Docker 官方镜像

设置管理

在将 FastAPI 服务器连接到 PostgreSQL 数据库之前,考虑如何管理我们的设置非常重要。将数据库访问凭证以纯文本形式存储在 Python 存储库中会带来重大的安全风险,因为黑客或恶意人员很容易发现它们。为了解决这个问题,最好将所有敏感凭证提取到一个外部环境文件中,例如 .env 文件,然后将这些值加载到我们的 FastAPI 服务器中。这种方法具有灵活性,因为只需使用不同的 .env 文件,我们就可以连接到不同的数据库。这在运行应用程序的不同版本时特别有用,例如在暂存环境中的一个版本和生产环境中的另一个版本。

由于 FastAPI 基于 Pydantic 构建,我们可以利用 BaseSettings 对象来促进我们的设置管理。首先,我们需要通过执行以下命令安装额外的依赖项

poetry add pydantic[dotenv]

让我们创建一个名为 settings.py 的新文件并添加以下代码

from pathlib import Path

from pydantic import BaseSettings

class Settings(BaseSettings):
    DB_NAME: str
    DB_HOST: str
    DB_USER: str
    DB_PASS: str

settings = Settings(_env_file=Path(__file__).parent/ ".env", _env_file_encoding="utf-8")

此代码假定您在此 settings.py 模块的父目录中有一个 .env 文件,其中包含您的数据库名称 (DB_NAME)、主机 (DB_HOST)、用户 (DB_USER) 和密码 (DB_PASS),如下所示

DB_NAME=postgres
DB_HOST=localhost
DB_USER=postgres
DB_PASS=postgres-fastapi

当然,此 .env 文件应包含在您的 .gitignore 文件中,以排除其上传到存储库。此时,您可以在 Python 代码中的任何位置轻松使用您的数据库设置,如下所示

from settings import settings

print(f"My database name is {settings.DB_NAME}")

将 FastAPI 连接到 PostgreSQL 数据库

现在我们的 FastAPI 和 PostgreSQL 服务器已启动并运行,并且我们的设置管理已就绪,我们可以继续在我们的 FastAPI 服务器和 PostgreSQL 数据库之间建立连接。

为此,我们可以使用 Tortoise-ORM。首先安装包

poetry add tortoise-orm

接下来,创建一个名为 database.py 的新文件,我们将在其中定义数据库连接的所有详细信息。这是一个示例

from fastapi import FastAPI
from tortoise import Tortoise
from tortoise.contrib.fastapi import register_tortoise

from settings import settings

Tortoise.init_models(["models"], "models")

TORTOISE_ORM = {
    "connections": {
        "default": f"postgres://{settings.DB_USER}:{settings.DB_PASS}
                               @{settings.DB_HOST}:5432/{settings.DB_NAME}",
    },
    "apps": {
        "models": {
            "models": ["models", "aerich.models"],
            "default_connection": "default",
        },
    },
    "use_tz": False,
}

def init_db(app: FastAPI) -> None:
    register_tortoise(
        app,
        config=TORTOISE_ORM,
        modules={"models": ["models", "aerich.models"]},
        generate_schemas=True,
        add_exception_handlers=True,
    )

在此 database.py 模块中,我们在 TORTOISE_ORM 对象中定义了 ORM 的核心配置,之后我们定义了一个名为 init_db 的函数,该函数实际上将 tortoise orm 注册到一个现有的 FastAPI 应用程序。

还要注意,我们正在使用 tortoise/aerich 工具来执行数据库迁移。数据库迁移是随着时间的推移管理数据库模式演变的关键方面。随着应用程序的发展,数据库模式通常需要进行修改以适应新功能、修复错误或优化性能。像 Aerich 这样的数据库迁移工具提供了一种系统化的方法来应用这些更改,同时保留现有数据。请确保通过运行以下命令安装 aerich 依赖项

poetry add aerich

现在,让我们在定义 FastAPI 应用程序的 main.py 模块中使用 init_db 函数

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI(title="fastapi-gcp")
init_db(app)

@app.get("/", response_class=HTMLResponse)
def healthcheck():
    return "<h1>All good!</h2>"

实现 init_db 函数后,您的 FastAPI 服务器就具备了连接到数据库的功能。但是,在继续之前,我们需要创建第一个数据库模型并初始化 Tortoise ORM 集成,首次运行 Aerich 迁移。让我们创建第一个 Note 模型,它将对应于 PostgreSQL 数据库中的 notes 表。此表将存储具有 filenametitlecontent 等属性的笔记集合。

from tortoise import fields
from tortoise.models import Model

class Note(Model):
    id = fields.IntField(pk=True)
    created_at = fields.DatetimeField(auto_now_add=True)
    updated_at = fields.DatetimeField(auto_now=True)
    filename = fields.CharField(max_length=256)
    title = fields.CharField(max_length=1000)
    content = fields.TextField(default="")

现在,让我们通过运行以下命令来初始化 aerich 迁移

aerich init -t database.TORTOISE_ORM
aerich init-db
aerich migrate
aerich upgrade

接下来,我们将扩展我们的 main.py 服务器模块,其中包含两个端点:一个用于在数据库中创建新笔记,一个用于通过提供标题来过滤现有笔记

from fastapi import FastAPI, HTTPException
from fastapi.responses import HTMLResponse
from pydantic import BaseModel

from database import init_db
from models import Note

app = FastAPI(title="fastapi-gcp")
init_db(app)

@app.get("/", response_class=HTMLResponse)
def healthcheck():
    return "<h1>All good!</h2>"

class CreateNotePayload(BaseModel):
    filename: str
    title: str
    content: str

@app.post("/notes")
async def create_note(payload: CreateNotePayload):
    note = await Note.create(**payload.dict())
    return {"message": f"Note created successfully with id {note.id}"}

@app.get("/notes/{title}")
async def get_note_by_title(title: str):
    if not (note := await Note.get_or_none(title=title)):
        raise HTTPException(status_code=404, detail="Note not found")

    return note.id

您可以通过使用 Swagger 或 Redoc 自动文档工具方便地测试端点和数据库连接。首先使用 POST 端点创建新笔记,然后使用 GET 端点检索最近创建笔记的 ID。如果此过程成功,恭喜您!您已成功在 FastAPI 服务器和 PostgreSQL 数据库之间建立了连接。

部署到 GCP

现在我们已经开发了后端并建立了 FastAPI 服务器与数据库之间的连接,让我们深入研究将应用程序部署到像 GCP (Google Cloud Platform) 这样的云提供商的过程。

将 PostgreSQL 数据库部署到 GCP

到目前为止,我们一直在使用本地 PostgreSQL 服务器来测试我们的应用程序。然而,当将应用程序部署到云提供商时,使用在云中运行的 PostgreSQL 服务器至关重要。幸运的是,您有多种选择可以实现这一点。

最直接的选择是利用 GCP 提供的 Cloud SQL 服务。Cloud SQL 是一项完全托管的数据库服务,可为您处理 PostgreSQL 实例。另一个选择是在计算引擎实例上部署 PostgreSQL 数据库,使用我们之前用于本地实例的相同 PostgreSQL Docker 镜像。

虽然在计算引擎实例上部署提供了更大的灵活性,但 Cloud SQL 作为完全托管的解决方案具有几个优势

  • 托管服务:使用 Cloud SQL,Google 会处理底层基础架构、维护和更新,让您无需承担这些责任。
  • 可伸缩性:Cloud SQL 提供自动可伸缩性以处理不同的工作负载。它允许您根据应用程序的需求轻松调整 CPU、内存和存储等资源。扩展可以在最小停机时间内执行,而无需预配额外的虚拟机。
  • 备份和恢复:Cloud SQL 提供数据库的自动备份,确保您数据的安全。您可以轻松地恢复备份或将时间点恢复到过去某个特定时刻。可以启用自动备份,并根据您的需求设置保留期。

需要注意的是,这些优点伴随着更高的成本。Cloud SQL 的定价取决于各种因素,包括负载、CPU 使用时间、磁盘使用和内存使用。对于较小的数据库,使用 Cloud SQL 的成本可能比在计算引擎实例上部署相同的数据库高出 10 倍。要估算不同选项的价格,您可以使用 GCP 定价计算器

无论您选择哪种部署选项,请确保更新您之前创建的 .env 文件中的数据库凭证,以匹配您已部署的 PostgreSQL 数据库的正确凭证。

容器化 FastAPI 服务器

为了便于在 GCP 上部署应用程序,建议使用 Docker 将其容器化。这包括创建一个 Dockerfile,该文件安装 Poetry,将 FastAPI 服务器代码复制到镜像中,并将 Uvicorn 服务器配置为入口点。以下是一个实现这些任务的 Dockerfile 示例

FROM python:3.11

WORKDIR /app
ENV PYTHONFAULTHANDLER 1
ENV PYTHONUNBUFFERED 1
ENV PIP_NO_CACHE_DIR off
ENV PIP_DISABLE_PIP_VERSION_CHECK on

RUN curl -sSL <https://install.python-poetry.org> | python3 -
ENV PATH="/root/.local/bin:${PATH}"

COPY poetry.lock .
COPY pyproject.toml .

RUN POETRY_VIRTUALENVS_CREATE=false poetry install --only main --no-interaction --no-ansi

COPY . /app

EXPOSE 8080

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]

在此 Dockerfile 中,我们从官方 Python 基础镜像开始,将工作目录指定为 /app,并安装 Poetry 来管理项目的依赖项。接下来,我们将 poetry.lockpyproject.toml 文件复制到容器中,并使用 Poetry 安装项目依赖项。然后,我们将 FastAPI 服务器代码从主机上的根目录复制到容器内的 /app 目录。

我们暴露端口 8080,假设 FastAPI 服务器将在该端口上监听。如果您的服务器使用不同的端口,请确保相应地更新 EXPOSE 语句。

最后,我们将入口点命令设置为使用适当的参数运行 Uvicorn 服务器。在此示例中,它运行 app 模块的 main FastAPI 实例,将其绑定到所有网络接口 (0.0.0.0),并在端口 8080 上监听。

您可以根据您的具体需求修改此 Dockerfile,例如添加其他依赖项或环境变量。

创建 Dockerfile 后,您可以使用以下命令构建镜像

docker build -t fastapi-gcp-backend:1.0 .

如果在构建过程中 poetry 安装步骤失败,请确保删除

packages = [{include = "fastapi_gcp"}]

行,如果您有的话,请从您的 pyproject.toml 文件中删除。这应该可以解决 poetry 安装在现有项目中的问题。

将镜像推送到 GCP Artifact Registry

构建成功完成后,您可以将镜像推送到 GCP Artifact Registry,并在创建 GCP Cloud Run 配置时选择它,以便轻松部署您的应用程序。

要将镜像推送到 GCP Artifact Registry,您应该使用适当的存储库名称对其进行标记,并在向存储库进行身份验证后推送镜像。您可以在此处了解有关向 GCP 存储库进行身份验证的更多信息。然后,您应该像这样标记您的镜像

LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY/IMAGE

在我们的例子中,这将是

us-east1-docker.pkg.dev/<your gcp project>/<your repo>/fastapi-gcp-backend

您应该在这里填入您的 GCP 项目和您的项目的存储库。我们可以像这样标记和推送我们之前构建的镜像

docker tag fastapi-gcp-backend:1.0 
us-east1-docker.pkg.dev/<your gcp project>/<your repo>/fastapi-gcp-backend:1.0
docker push us-east1-docker.pkg.dev/<your gcp project>/<your repo>/fastapi-gcp-backend:1.0

将镜像部署到 GCP Cloud Run

现在您的镜像已成功推送到 GCP Artifact Registry,您可以在 Cloud Run 配置中轻松使用此镜像。要配置新的 Cloud Run 服务,您可以使用 GCP Web 控制台执行以下步骤:https://console.cloud.google.com

  1. 通过单击相应的菜单选项导航到 Cloud Run 产品。
  2. 单击 创建服务 开始配置过程。
  3. 创建服务 页面上,您会找到一个指定容器镜像的部分。选择允许您从 Artifact Registry 中选择镜像的选项。
  4. 从可用选项中选择您刚刚推送到 Artifact Registry 的镜像。
  5. 根据您的要求配置其他设置,例如服务名称、区域、身份验证和扩展选项。
  6. 完成必要的配置后,单击 创建 以创建 Cloud Run 服务。

身份验证 部分,如果您正在创建公共 API,请务必选择 允许未经身份验证的调用

通过遵循这些步骤,您将能够配置一个使用 Artifact Registry 中的容器镜像的 Cloud Run 服务。这使您可以轻松地将您的 FastAPI 应用程序部署到 GCP 提供的可伸缩且受管理的环境中。

请注意,根据 GCP Web 控制台界面的任何更新,步骤可能会略有不同。始终参考 GCP 文档以获取最新的说明。

结论

总之,为应用程序构建强大的后端对于提供高质量的用户体验至关重要。RESTful API 提供了一种构建后端的有效方法,将它们部署在 Google Cloud Platform (GCP) 等云平台上可以为服务器提供可伸缩性和无状态性。在这篇博文中,我们探讨了在云部署中使用无状态 REST 服务器的好处,讨论了 GCP 上的 Cloud Run 和 App Engine 之间的区别,并学习了如何使用 FastAPI 和 PostgreSQL 构建 REST 服务器并将其部署到 GCP Cloud Run。我们还看到了如何使用对象关系映射 (ORM)(如 Tortoise ORM)将数据库表映射到 Python 对象,并提供了易于使用的 API 来与数据库交互。通过遵循本文概述的步骤,您可以构建和部署满足应用程序要求的可伸缩 RESTful API。

可能的改进

有几种方法可以优化此工作流程,建议您探索这些选项,如果其好处 outweigh 额外的努力。一些潜在的改进包括

  • 使用 Terraform 以声明方式定义 Cloud Run、Compute Engine 和 CloudSQL 等 GCP 资源。这允许自动化部署和标准化的基础架构配置,降低人为错误的几率并提高可见性。
  • 考虑探索 Tortoise-ORM 的替代方案。虽然 Tortoise-ORM 的文档很全面,但像 SQLModel 这样的异步 ORM 的更大社区可能更擅长帮助解决更具体的查询。

源代码

您可以在 https://github.com/GlennViroux/fastapi-postgres-gcp-blog 找到所有源代码。

如果您有任何意见、问题或建议,请告诉我!

编码愉快!:)

历史

  • 2023 年 5 月 29 日:初始版本
© . All rights reserved.