跳转至

第11章:生产部署

任何 Web API 项目的最终步骤都是部署。将其推送到生产环境与传统的 Django 部署非常相似,但有一些额外的关注点。在本章中,我们将介绍添加环境变量、配置我们的设置以更安全、在生产中切换到 PostgreSQL 数据库,并运行 Django 自己的部署检查清单以确保我们没有遗漏任何内容。

环境变量

环境变量可以在运行时加载到代码库中,但不存储在源代码中。这使它们成为在本地和生产设置之间切换的理想方式。它们也是存储不应出现在源代码控制中的敏感信息的好地方。

使用 Git 时,任何更改都存储在 Git 历史记录中,因此即使某些内容后来从代码库中删除,如果它曾在任何时候提交到提交中,如果有人知道如何查找,它将永远存在。

有多个包可以启用使用 Python 中的环境变量,但对于本项目,我们将使用 environs,因为它带有一个非常有用的额外 Django 配置。

首先安装 environs[django]。如果您使用 Zsh 作为终端 shell,则必须在包名称周围添加单引号 '',所以运行 python -m pip install 'environs[django]==9.3.5'

Shell

(.venv) > python -m pip install 'environs[django]==9.5.0'

django_project/settings.py 文件顶部附近添加三行导入。

代码

# django_project/settings.py
from pathlib import Path
from environs import Env # new
env = Env() # new
env.read_env() # new

然后在根项目目录中创建一个名为 .env 的新隐藏文件,它将存储我们的环境变量。现在它是空的,但将在下一节中使用。最后一步是将 .env 添加到我们现有的 .gitignore 文件中。如果使用环境变量但仍将其存储在 Git 中,那就没有意义了!

.gitignore

.venv/
.env

当我们更新文件时,我们不妨添加所有 *.pyc 文件和 __pycache__/ 目录。如果您在 Mac 上,无需跟踪存储有关文件夹设置信息的 .DS_Store。最后,将本地 db.sqlite3 提交到源代码控制并不是一个好主意。它包含整个数据库,因此任何有权访问的人都可以访问我们的所有数据。为方便起见,我们将继续在本地使用它,并很快看到如何在生产中使用 PostgreSQL。

这是最终的 .gitignore 文件应包含的内容:

.gitignore

.venv/
.env
__pycache__/
db.sqlite3
.DS_Store # Mac only

在提交之前,运行 git status 确认所有这些都按预期被忽略。然后添加我们的新工作并创建提交。

Shell

(.venv) > git status
(.venv) > git add -A
(.venv) > git commit -m "add environment variables"

DEBUG 和 SECRET_KEY

Django 的默认 settings.py 文件自动默认为本地生产设置,这使得项目易于启动,但有几个配置需要在部署到生产之前进行调整。如果您查看 django_project/settings.py 中的 DEBUG 配置,它当前设置为 True。这会生成非常详细的错误页面和堆栈跟踪。例如,使用 python manage.py runserver 启动本地 Web 服务器,并访问不存在的 API 端点,如 [[http://127.0.0.1:8000/99](http://127.0.0.1:8000/99](http://127.0.0.1:8000/99](http://127.0.0.1:8000/99`))。

我们希望 DEBUG 在本地开发时为 True,但在生产时为 False。如果在加载环境变量时遇到任何困难,我们希望 DEBUG 默认为 False,以便我们更加安全。要实现这一点,首先将 DEBUG=True 添加到 .env 文件中。

.env

DEBUG=True

然后在 django_project/settings.py 中,更改 DEBUG 设置以从 .env 文件读取变量 "DEBUG",但默认值为 False

代码

# django_project/settings.py
DEBUG = env.bool("DEBUG", default=False)

如果您刷新 [[http://127.0.0.1:8000/99](http://127.0.0.1:8000/99](http://127.0.0.1:8000/99](http://127.0.0.1:8000/99`)) 上的网页,您将看到完整的本地错误页面仍然存在。一切正常。

下一个要更改的设置是 SECRET_KEY,它是每次运行 startproject 时生成的随机 50 字符字符串。如果您查看 django_project/settings.py 中的当前值,它以 django-insecure 开头,表示当前值不安全。为什么不安全?因为很容易将 SECRET_KEY 提交到源代码控制,事实上,我们已经做到了。即使我们现在将当前值移动到环境变量中,它仍将保留在项目的 Git 历史记录中可见。

解决方案是生成一个新的密钥,并将该值存储在环境变量中,这样它就不会触及源代码控制。一种生成新密钥的方法是调用 Python 的内置 secrets 模块,通过在命令行运行 python -c 'import secrets; print(secrets.token_urlsafe())'

Shell

(.venv) > python -c "import secrets; print(secrets.token_urlsafe())"
KBl3sX5kLrd2zxj-pAichjT0EZJKMS0cXzhWI7Cydqc

将此新值复制并粘贴到 .env 文件中 SECRET_KEY 变量下。

.env

DEBUG=True
SECRET_KEY=KBl3sX5kLrd2zxj-pAichjT0EZJKMS0cXzhWI7Cydqc

最后,切换 django_project/settings.py 文件中的 SECRET_KEY 以从现在开始从环境变量读取。

代码

# django_project/settings.py
SECRET_KEY = env.str("SECRET_KEY")

要确认一切正常,请使用 python manage.py runserver 重新启动本地服务器,并刷新我们网站上的任何 API 端点。它应该正常工作。

ALLOWED_HOSTS

接下来是我们 django_project/settings.py 文件中的 ALLOWED_HOSTS 配置,它代表我们的 Django 项目可以服务的主机/域名。我们将在此处添加三个主机:.herokuapp.com 用于 Heroku 上的部署,以及 localhost127.0.0.1 用于本地开发。

代码

# django_project/settings.py
ALLOWED_HOSTS = [".herokuapp.com", "localhost", "127.0.0.1"] # new

如果您重新运行 python manage.py runserver 命令并刷新 [[http://127.0.0.1:8000/1,它应该在更改后正常显示。](http://127.0.0.1:8000/1,它应该在更改后正常显示。](http://127.0.0.1:8000/1,它应该在更改后正常显示。](http://127.0.0.1:8000/1`,它应该在更改后正常显示。))

DATABASES

我们当前的 DATABASES 配置是针对 SQLite 的,但我们希望能够在 Heroku 上切换到 PostgreSQL 进行生产。当我们之前安装 environs[django] 时,Django 的"好东西"包括优雅的 dj-database-url 包,它获取我们的数据库(SQLite 或 PostgreSQL)所需的所有数据库配置,并创建 DATABASE_URL 环境变量。

要实现这一点,请使用来自 environs[django]dj_db_url 更新 DATABASES 配置,以帮助解析 DATABASE_URL

代码

# django_project/settings.py
DATABASES = {
    "default": env.dj_db_url("DATABASE_URL") # new
}

就是这样!我们现在需要做的就是在 .env 文件中将 SQLite 指定为本地 DATABASE_URL 值。

.env

DEBUG=True
SECRET_KEY=KBl3sX5kLrd2zxj-pAichjT0EZJKMS0cXzhWI7Cydqc
DATABASE_URL=sqlite:///db.sqlite3

这看起来很神奇,不是吗?它起作用的原因是因为每当 Heroku 提供新的 PostgreSQL 数据库时,它会自动为其创建一个名为 DATABASE_URL 的配置变量。由于 .env 文件未提交到生产环境,我们在 Heroku 上的 Django 项目将改为使用此 PostgreSQL 配置。非常优雅,不是吗?

静态文件

正如我们在早期部署中看到的,需要配置静态文件才能使可浏览的 Web API 工作。首先,创建一个名为 static 的新项目级目录。

Shell

(.venv) > mkdir static

使用您的文本编辑器在 static 目录中创建一个空的 .keep 文件,以便它被 Git 拾取。然后安装 whitenoise 以在生产中处理静态文件。

Shell

(.venv) > python -m pip install whitenoise==5.3.0

White Noise 必须添加到 django_project/settings.py 中的以下位置: - 在 INSTALLED_APPS 中,whitenoise 位于 django.contrib.staticfiles 之上 - 在 MIDDLEWARE 中,WhiteNoiseMiddleware 位于 CommonMiddleware 之上 - 指向 White Noise 的 STATICFILES_STORAGE 配置

代码

# django_project/settings.py
INSTALLED_APPS = [
    ...
    "whitenoise.runserver_nostatic", # new
    "django.contrib.staticfiles",
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware", # new
    "corsheaders.middleware.CorsMiddleware",
    ...
]

STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR / "static"] # new
STATIC_ROOT = BASE_DIR / "staticfiles" # new
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" # new

最后一步是运行 collectstatic 命令,以便所有静态目录和文件都编译到一个位置以用于部署目的。

Shell

(.venv) > python manage.py collectstatic

Psycopg 和 Gunicorn

必须安装两个最终的包才能建立适当的生产环境。

Psycopg 是一个数据库适配器,允许 Python 应用程序与 PostgreSQL 数据库通信。如果您在 macOS 上,必须首先通过 Homebrew 安装 PostgreSQL,然后安装 psycopg2

Shell

# Windows
(.venv) > python -m pip install psycopg2==2.9.3

# macOS
(.venv) % brew install postgresql
(.venv) % python -m pip install psycopg2==2.9.3

我们可以使用这种方法,因为 Django 的 ORM(对象关系映射器)将我们的 models.py 代码从 Python 转换为所选的数据库后端。这几乎在所有时候都能正常工作,没有错误。可能会出现奇怪的错误,建议在专业项目上也在本地安装 PostgreSQL 以避免它们。

Gunicorn 是一个生产 Web 服务器,也必须安装以替换当前仅适用于本地开发的 Django Web 服务器。

Shell

(.venv) > python -m pip install gunicorn==20.1.0

requirements.txt

正如我们在本书前面看到的,需要一个 requirements.txt 文件来列出我们本地虚拟环境中安装的所有包。我们也可以用以下命令做到这一点。

Shell

(.venv) > python -m pip freeze > requirements.txt

这是我的 requirements.txt 文件的内容样子。您的看起来可能略有不同:例如,Django 很可能是 4.1.1 或更高版本,因为我们使用了 ==,这意味着安装了最新的 4.0.x 版本。

Procfile 和 runtime.txt

Heroku 依赖于一个名为 Procfile 的自定义文件,该文件描述了如何在生产中运行项目。必须在项目根目录中创建它,靠近 manage.py 文件。现在在您的文本编辑器中执行此操作,并添加以下行以使用 Gunicorn 作为生产 Web 服务器。

Procfile

web: gunicorn django_project.wsgi --log-file -

最后一步是通过 runtime.txt 文件指定应在 Heroku 上运行的 Python 版本。在您的文本编辑器中,在项目级别创建这个新的 runtime.txt 文件,这意味着它与 manage.pyProcfile 在同一目录中。我们要的 Python 版本是 3.10.2。

runtime.txt

python-3.10.2

部署检查清单

我们刚刚经历了很多步骤。对于大多数开发人员来说,太多而无法记住,这就是部署检查清单存在的原因。回顾一下,我们做了什么: - 通过 environs[django] 添加环境变量 - 将 DEBUG 设置为 False - 设置 ALLOWED_HOSTS - 对 SECRET_KEY 使用环境变量 - 更新 DATABASES 以在本地使用 SQLite,在生产中使用 PostgreSQL - 配置静态文件并安装 whitenoise - 安装 gunicorn 用于生产 Web 服务器 - 创建 requirements.txt 文件 - 为 Heroku 创建 Procfile - 创建 runtime.txt 以设置 Heroku 上的 Python 版本

除了为 Heroku 创建的 Procfile 文件外,这些部署步骤对于任何托管平台几乎都是相同的。

在实际部署项目之前,请确保所有这些更改都提交到 Git。

Shell

(.venv) > git status
(.venv) > git add -A
(.venv) > git commit -m "deployment checklist"

Heroku 部署

要在 Heroku 上部署,请确保您已通过终端 shell 登录。

Shell

(.venv) > heroku login

命令 heroku create 为我们的应用创建一个新容器来生活,默认情况下,Heroku 将分配一个随机名称。您可以指定自定义名称,就像我们在这里所做的那样,但它必须在 Heroku 上唯一。我的是 dfa-blog-api,所以这个名称已经被占用;您需要另一个字母和数字组合!

Shell

(.venv) > heroku create dfa-blog-api

到目前为止一切顺利。此时一个新步骤是在 Heroku 本身上创建一个 PostgreSQL 数据库,我们之前没有做过。Heroku 有自己的托管 PostgreSQL 数据库,我们可以使用,它们有多个层级。对于像这样的学习项目,免费的 hobby-dev 层级绰绰有余。

运行以下命令来创建这个新数据库。将 dfa-blog-api 替换为您自己的自定义名称。

Shell

(.venv) > heroku addons:create heroku-postgresql:hobby-dev -a dfa-blog-api

您看到 Heroku 创建了一个自定义 DATABASE_URL 来访问数据库了吗?对于我这里的,它是 postgresql-angular-74744。一旦我们部署,它就会在 Heroku 中作为配置变量自动可用。这就是为什么我们不需要为生产中的 DATABASE_URL 设置环境变量。我们也不需要将 DEBUG 设置为 False,因为这是我们的 django_project/settings.py 文件中的默认值。唯一要手动添加到 Heroku 的环境变量是 SECRET_KEY,因此请从您的 .env 文件复制其值并运行 config:set 命令,将 SECRET_KEY 本身的值放在双引号 "" 内。

Shell

(.venv) > heroku config:set SECRET_KEY="KBl3sX5kLrd2zxj-pAichjT0EZJKMS0cXzhWI7Cydqc"

现在是时候将我们的代码推送到 Heroku 本身并启动 Web 进程,以便我们的 Heroku dyno 正在运行。

Shell

(.venv) > git push heroku main
(.venv) > heroku ps:scale web=1

您新应用的 URL 将在命令行输出中,或者您可以运行 heroku open 来找到它。我们此 API 没有标准主页,因此您需要转到像 /api/v1/ 这样的端点。

如果您单击右上角的"登录"链接,它会响应 500 服务器错误消息!那是因为 PostgreSQL 数据库存在但尚未设置!

以前我们在生产中使用 SQLite,它是基于文件的,并且已在本地配置,然后推送到 Heroku。但是我们的这个 PostgreSQL 数据库是全新的!Heroku 拥有我们所有的代码,但我们还没有配置这个生产数据库。

必须再次遵循在本地使用的相同过程:运行迁移、创建超级用户帐户,并在管理员中输入博客文章。要在 Heroku 中运行命令,而不是在本地运行,请在其前面加上 heroku run

Shell

(.venv) > heroku run python manage.py migrate
(.venv) > heroku run python manage.py createsuperuser

您需要登录实时管理员站点以添加博客条目和用户,因为这是一个全新的数据库,与我们的本地 SQLite 无关。

刷新您的实时网站,它应该正常工作。请注意,由于生产服务器将在后台不断运行,您无需在 Heroku 上使用 runserver 命令。