第 16 章:部署¶
在本地 Django 开发环境中所期望的易用性与生产环境所需的安全性和性能之间存在着根本性的矛盾。Django 的设计目标是让 Web 开发者的生活更轻松,因此在首次运行 startproject 命令时,它默认采用本地配置。我们已经看到了这一点:使用 SQLite 作为基于本地文件的数据库、内置的 runserver 命令在 Web 浏览器中启动本地 Web 服务器,以及 settings.py 文件中的各种默认配置,包括将 DEBUG 设置为 True 和自动生成的 SECRET_KEY。
在生产环境中,情况就不同了。所有为开发环境易用性而优化的配置都需要转而关注安全性、性能和可扩展性。将 Django 网站部署到生产环境需要多个步骤——事实上步骤之多,以至于 Django 文档甚至专门提供了一个部署清单。这是一个非常有用的工具,但遗憾的是,它还不够全面。其他因素还包括各种托管选项、环境变量、数据库配置、静态文件处理等等。
在本章中,我们将创建一份部署清单,并使用 Heroku 部署 Newspaper 项目。所涉及的技术适用于几乎所有需要为生产环境做好准备的 Django 网站,无论使用何种托管平台。
托管选项¶
如果你问五位 Django 开发者最佳的托管选项是什么,你可能会得到五个不同的答案。每个人根据自己的经验和项目需求都有不同的偏好。但最终,我们可以将托管选项分为三大类:
- 1. 专用服务器(Dedicated Server):放置在数据中心、完全属于你的物理服务器。通常只有最大的公司才会采用这种方式,因为配置和维护需要大量的技术专长。
- 2. 虚拟专用服务器(VPS):一台服务器可以被分成多个使用相同硬件的虚拟机,这比专用服务器要实惠得多。这种方式还意味着你不必担心硬件维护。
- 3. 平台即服务(PaaS):一种托管的 VPS 解决方案,经过预先配置和维护,是部署和扩展网站最快的选择。它通常还附带托管数据库。缺点是开发者无法获得与 VPS 或专用服务器相同程度的自定义能力。在大规模使用时,PaaS 可能会变得相当昂贵。
这里的选择都是关于权衡取舍的。许多 Django 开发者和小型公司乐于使用 PaaS 来从根本上消除将代码投入生产环境时固有的许多困难。流行的 PaaS 选项包括 Heroku、Fly 和 Render 等众多选择。对于 VPS,Digital Ocean 对于独立开发者或小型团队拥有最简洁的界面,但对于企业级应用,选择通常在 AWS、Google 或 Microsoft 之间。
对于我们的 Newspaper 网站,我们将使用 PaaS,具体来说是 Heroku,因为它成熟、广泛使用,并且部署过程相对简单。不过,除了最后创建 Heroku 特有的 Procfile 文件外,本章中概述的步骤适用于任何 PaaS 提供商。
Web 服务器与 WSGI/ASGI 服务器¶
Django 提供的通过 runserver 运行的本地服务器承担了多项工作,而在生产环境中这些工作必须以不同的方式处理。首先,它充当 Web 服务器,即位于 Django 应用前面的软件,用于处理 HTTP 请求和响应。它还管理静态文件请求。在平台即服务出现之前,Web 开发者需要自行安装、配置和维护专用 Web 服务器,如 Nginx 或 Apache:这绝非易事,而且需要与 Web 开发完全不同的技能集。幸运的是,平台即服务知道我们正在部署网站,会自动捆绑一个 Web 服务器(通常是 Nginx),因此我们不必自己安装或管理它。
runserver 为我们提供的另一个角色是充当应用服务器,帮助 Django 生成动态内容。当请求到来时,runserver 驱动该请求经过 URL、视图、模型、数据库和模板,然后生成 HTTP 响应。换句话说,runserver 不仅充当 Web 服务器,还充当应用服务器。
应用服务器通常被称为"WSGI 服务器",因为它们使用 WSGI 将 Python Web 应用连接到服务器。在 Web 开发的早期,Web 框架在没有大量自定义的情况下无法与各种 Web 服务器良好配合。对于 Python Web 框架,这促使了 2003 年 Web 服务器网关接口(WSGI)的创建。WSGI 不是服务器或框架,而是一组规则,标准化了 Web 服务器应如何连接到任何 Python Web 应用。通过消除这一难题,它为更新的 Python Web 框架——如 2005 年首次发布的 Django——打开了大门,使其可以在任何 Web 服务器上运行,而不必担心过程中的这一步骤。常见的生产 WSGI 服务器包括 Gunicorn、uWSGI 和 Daphne。
传统上,Python 是一种同步编程语言:代码按顺序执行,意味着每段代码必须完成后才能开始下一段代码。因此,复杂任务可能需要较长时间。从 2012 年 Python 3.3 开始,通过 asyncio 模块向 Python 添加了异步编程。同步处理按特定顺序依次进行,而异步处理则并行发生。不依赖于其他任务的任务可以被卸载,与主操作同时执行,结果可以在完成后回报。
Django 自 2019 年的 3.0 版本以来一直在逐步添加异步支持。其中一个层面是引入了异步服务器网关接口(ASGI),顾名思义,它标准化了服务器应如何连接到同时支持同步和异步通信的 Python Web 应用。ASGI 旨在成为 WSGI 的最终继承者。
现在新的 Django 项目中同时包含了 ASGI 和 WSGI。当你运行 startproject 命令时,Django 会在项目级目录 django_project 中生成一个 wsgi.py 文件和一个 asgi.py 文件。
对整个 Django 技术栈的完整异步支持仍在开发中,但随着每个新的大版本发布而越来越近。鉴于本书面向初学者,重要的是认识到异步发展的存在,而不是深入探讨它们。虽然从技术角度来看它们令人兴奋,但要理解它们也具有挑战性,并且它们与需要"实时"功能的网站最为相关,如实时通知、聊天应用、实时数据更新和交互式仪表板。
部署清单¶
在一开始就看到完整的部署清单可能会让人感到不知所措,但它是本章的一个有用指南。以下是我们将要介绍的部署清单:
- 配置静态文件并安装 WhiteNoise
- 使用 environs 添加环境变量
- 创建
.env文件并更新.gitignore文件 - 更新
DEBUG、ALLOWED_HOSTS、SECRET_KEY和CSRF_TRUSTED_ORIGINS - 更新
DATABASES以在生产环境中运行 PostgreSQL 并安装 psycopg - 安装 Gunicorn 作为生产 WSGI 服务器
- 创建 Procfile
- 更新
requirements.txt文件 - 创建新的 Heroku 项目,将代码推送到其中,并启动 dyno web 进程
我们可以调整更多的生产设置,但这份清单涵盖了最关键的安全性和性能问题。
静态文件¶
静态文件是网站使用的图片、JavaScript 和 CSS。我们在第 6 章的 Blog 项目中已经使用过它们,在那里我们添加了自定义 CSS。对于本地使用,只要 settings.py 中的 DEBUG 设置为 True,文件就会由 runserver 命令自动提供服务。
Django 会自动在每个应用中查找名为"static"的文件夹中的静态文件,但一种常见的技术是将所有静态文件放在项目级的"static"文件夹中。我们将在这里这样做。使用 Control+c 退出本地服务器,在与 manage.py 文件相同的文件夹中创建一个新的 static 目录。在命令行中在其中添加 css、js 和 img 新文件夹。
(.venv) $ mkdir static
(.venv) $ mkdir static/css
(.venv) $ mkdir static/js
(.venv) $ mkdir static/img
Git 的一个怪癖是它不会跟踪空文件夹。如果文件夹中没有文件,Git 默认会忽略它。一种解决方案——我们将在这里采用——是在文本编辑器中向三个子文件夹各添加一个 .keep 文件:
static/css/.keepstatic/js/.keepstatic/img/.keep
对于本地使用,静态文件只需要两个设置:STATIC_URL 是提供静态文件服务的基础 URL,STATICFILES_DIRS 定义了内置的 staticfiles 应用在 app/static 文件夹之外查找静态文件的额外位置。
# django_project/settings.py
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"] # new
我们的本地 Django 服务器并非为在生产环境中托管静态文件而设计的。最佳实践是将所有静态文件打包到一个目录中,然后由生产 Web 服务器(而非 Django 服务器)来提供它们。Django 有一个管理命令 collectstatic 就是用于这个目的:它将所有静态文件复制到一个位置以便部署。我们需要做的一项配置是设置 STATIC_ROOT 来定义编译后静态文件的位置。按照惯例,我们将创建一个名为 staticfiles 的新项目级目录。
# django_project/settings.py
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
STATIC_ROOT = BASE_DIR / "staticfiles" # new
现在运行命令 python manage.py collectstatic,将所有静态文件编译到 staticfiles 文件夹中。
(.venv) $ python manage.py collectstatic
目前唯一的静态文件包含在内置的 admin 应用中,因此应该出现一个新的 staticfiles 目录,其中包含 admin 的部分。当将来添加更多静态文件时,它们也会被编译到这个目录中。
# staticfiles/
└──admin
├──css
├──img
├──js
我们需要使用 {% load static %} 模板标签来在模板中显示静态文件。现在将其添加到 base.html 文件的顶部。
<!-- templates/base.html -->
{% load static %}
<!DOCTYPE html>
...
如果我们使用专用服务器或 VPS 进行部署,那么编写代码让 Web 服务器(可能是 Nginx 或 Apache)来提供静态文件就是我们自己的事了。但由于我们使用的是 PaaS Heroku,我们可以利用流行的第三方包 WhiteNoise,它专为从 Django 中高效提供静态文件而优化。它支持额外的压缩和不可变文件存储,并设置适当的 HTTP 缓存头。简而言之,它使我们的部署过程更加顺畅。
使用 pip 安装最新版本的 WhiteNoise。
(.venv) $ python -m pip install whitenoise==6.7.0
然后,在 django_project/settings.py 文件中进行三项更改:
- 将
whitenoise添加到INSTALLED_APPS中,位于内置的staticfiles应用之上 - 在
MIDDLEWARE下,在第三行添加新的WhiteNoiseMiddleware - 更改
STORAGES以使用 WhiteNoise
# django_project/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"whitenoise.runserver_nostatic", # new
"django.contrib.staticfiles",
# 3rd Party
"crispy_forms",
"crispy_bootstrap5",
# Local
"accounts",
"pages",
"articles",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware", # new
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
...
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", # new
},
}
STORAGES 是 Django 4.2+ 中的新设置,定义了文件的存储方式。它在 settings.py 中是隐式设置的,但我们正在将 staticfiles 部分更改为使用 WhiteNoise 压缩。
再次运行 collectstatic 命令。提示符会警告覆盖现有文件,但这是我们有意为之:我们现在想使用 WhiteNoise 来编译它们。输入 yes 并按回车键继续:
(.venv) $ python manage.py collectstatic
搞定了!我们已经配置了静态文件以编译到一个位置用于生产环境,在 base.html 模板中添加了 static 模板标签,并安装了 WhiteNoise 来高效地提供它们。
中间件¶
添加 WhiteNoise 是我们第一次更新 Django 中间件,它是一个钩入 Django 请求/响应处理过程的钩子框架。它是一种添加功能的方式,如身份验证、安全性、会话等。在 HTTP 请求阶段,中间件按照在 MIDDLEWARE 中定义的顺序自上而下应用。这意味着 SecurityMiddleware 最先,然后是 SessionMiddleware,依此类推。
# django_project/settings.py
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", # |
"django.contrib.sessions.middleware.SessionMiddleware", # |
"whitenoise.middleware.WhiteNoiseMiddleware", # new # |
"django.middleware.common.CommonMiddleware", # |
"django.middleware.csrf.CsrfViewMiddleware", # |
"django.contrib.auth.middleware.AuthenticationMiddleware", # |
"django.contrib.messages.middleware.MessageMiddleware", # |
"django.middleware.clickjacking.XFrameOptionsMiddleware", # v
]
在 HTTP 响应阶段,即视图被调用之后,中间件按相反顺序应用,从下往上,首先是 XFrameOptionsMiddleware,然后是 MessageMiddleware,依此类推。描述中间件的传统比喻是像洋葱一样,每个中间件类都是包裹视图的一个"层"。
真正深入研究中间件是一个超出本书范围的高级主题。然而,从概念上了解 Django 架构中所有组件是如何组合在一起的很重要。
环境变量¶
真实的 Django 项目至少需要两个环境(本地和生产),但如果涉及多个测试服务器,通常会有更多。在同一项目中切换不同环境有两种方式:环境变量和多个设置文件。如今,最流行的方法是使用环境变量,我们也将这样做。环境变量是一个变量,其值在当前程序之外设置,可以在运行时加载。我们可以安全地存储这些变量,并根据需要将它们加载到 Django 项目中。
处理环境变量有多种方式,但在本项目中,我们将使用 environs,一个流行的第三方包,附带额外的 Django 特定功能。
使用 pip 添加 environs,并包含双引号 "" 来安装有用的 Django 扩展。
(.venv) $ python -m pip install "environs[django]"==11.0.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,其中包含我们的环境变量。我们已经知道,任何以句号开头的文件或目录都会被当作隐藏文件,在目录列表时默认不显示。但该文件仍然存在,需要添加到 .gitignore 文件中,以避免被添加到我们的 Git 源代码控制中。
# .gitignore
.venv/
__pycache__/
db.sqlite3
.env # new!
DEBUG 和 ALLOWED_HOSTS¶
我们将使用环境变量配置的第一个设置是 DEBUG。默认情况下,DEBUG 设置为 True,这对本地开发很有帮助,但如果部署到生产环境中则是一个重大的安全问题。例如,如果你使用 python manage.py runserver 启动本地服务器,并导航到一个不存在的页面,如 http://127.0.0.1:8000/debug,你将看到以下内容:

此页面列出了所有尝试的 URL 和加载的应用,对于任何试图入侵你网站的黑客来说,这就是一张藏宝图。你甚至会看到在错误页面的底部,它说如果 DEBUG=False,Django 将显示标准的 404 页面。这正是我们想要的!第一步是在 settings.py 文件中将 DEBUG 改为 False。
# django_project/settings.py
DEBUG = False
刷新网页 http://127.0.0.1:8000/debug,你会看到一个错误:网站根本无法加载。在命令行中,Django 通过 CommandError 向我们提供了解释,该错误针对严重问题而引发。
(.venv) $ python manage.py runserver
...
CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
在这种情况下,Django 告诉我们,如果没有设置 ALLOWED_HOSTS,就不能将 DEBUG 设为 False。那么什么是 ALLOWED_HOSTS 呢?它是一个字符串列表,代表我们的 Django 网站可以提供服务的域名/主机名。默认情况下,ALLOWED_HOSTS 设置为接受所有主机,这是不安全的!我们必须将其更新为接受本地端口(localhost 和 127.0.0.1)以及用于 Heroku 部署的 .herokuapp.com。我们可以将这三个路由都添加到配置中。
# django_project/settings.py
ALLOWED_HOSTS = [".herokuapp.com", "localhost", "127.0.0.1"] # new
现在我们已经设置了 ALLOWED_HOSTS,再试试 runserver 命令。

这就是我们希望在生产环境中显示的通用 Django 404 页面。它不会向潜在的黑客泄露任何信息。
手动为开发和生产环境设置配置并不理想。首先,这很麻烦,容易出错。其次,如果我们将应该保密的生产信息放入 settings.py 中并意外执行了 Git 提交,这是不安全的。
这就是环境变量发挥作用的地方。要向项目中添加任何环境变量,我们首先将其添加到 .env 文件中,然后更新 django_project/settings.py。
在 .env 文件中,创建一个名为 DEBUG 的新环境变量,并将其值设为 True。
# .env
DEBUG=True
然后在 django_project/settings.py 中,将 DEBUG 设置更改为从 .env 文件中读取变量 "DEBUG"。
# django_project/settings.py
DEBUG = env.bool("DEBUG", default=False)
env.bool 的语法表示从 .env 文件中加载一个布尔类型(即 true 或 false)的环境变量,名称为"DEBUG"。如果找不到环境变量,则将使用此处设置为 False 的默认值。最佳做法是默认为生产设置,因为它们更安全,如果我们的代码出了问题,我们不会默认暴露所有的秘密。
SECRET_KEY 和 CSRF_TRUSTED_ORIGINS¶
SECRET_KEY 是每次运行 startproject 时生成的随机 50 字符字符串。这个字符串在我们的 Django 项目中提供加密保护。在设置文件中,你会看到以 django-insecure 开头的当前值。以下是我的项目中 django_project/settings.py 的 SECRET_KEY 值。你的会不同。
# django_project/settings.py
SECRET_KEY = "django-insecure-3$k(g9eheqqbzr@#&tt)r6%ab-g1=!j@2c^y7*sl6+ltzys05!"
以下是去掉双引号后放在 .env 文件中的内容:
# .env
DEBUG=True
SECRET_KEY=django-insecure-3$k(g9eheqqbzr@#&tt)r6%ab-g1=!j@2c^y7*sl6+ltzys05! # new
更新 django_project/settings.py,使 SECRET_KEY 指向这个新的环境变量。它是一个字符串,所以语法是 env.str。
# django_project/settings.py
SECRET_KEY = env.str("SECRET_KEY")
SECRET_KEY 现在已经从设置文件中移出并安全了,对吗?实际上不是!因为我们之前的 Git 提交中包含了这个值,无论我们怎么做,它都存储在 Git 历史记录中。解决方案是创建一个新的 SECRET_KEY 并将其添加到 .env 文件中。生成新密钥的一种方法是在命令行中运行 python -c 'import secrets; print(secrets.token_urlsafe())' 来调用 Python 内置的 secrets 模块。
将这个新值复制粘贴到 .env 文件中。
# .env
DEBUG=True
SECRET_KEY=imDnfLXy-8Y-YozfJmP2Rw_81YA_qx1XKl5FeY0mXyY
现在使用 python manage.py runserver 重启本地服务器并刷新你的网站。它将使用从 .env 文件加载的新 SECRET_KEY 正常工作,而且由于 .env 在 .gitignore 文件中,所以不会被 Git 跟踪。
我们的 Newspaper 项目要求在生产网站上登录 admin 才能创建、读取、更新或删除文章。这意味着 CSRF_TRUSTED_ORIGINS 必须正确配置,因为它是用于不安全 HTTP 请求(如 POST)的可信来源列表。将其添加到 settings.py 文件的底部,并设置为匹配 Heroku 上的生产 URL https://*.herokuapp.com。我们将在本章末尾更新 ALLOWED_HOSTS 和 CSRF_TRUSTED_ORIGINS 以匹配我们的生产 URL。
# django_project/settings.py
CSRF_TRUSTED_ORIGINS = ["https://*.herokuapp.com"] # new
DATABASES¶
我们希望在本地使用 SQLite,在生产环境中使用 PostgreSQL。目前,我们的 DATABASES 设置文件只列出了 SQLite。ENGINE 指定使用什么类型的数据库,NAME 指向其位置。
# django_project/settings.py
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
大多数 PaaS 会自动设置一个 DATABASE_URL 环境变量,其灵感来自 Twelve-Factor App 方法,包含连接数据库所需的所有参数。以原始形式来看,对于 PostgreSQL,它看起来像这样:
postgres://USER:PASSWORD@HOST:PORT/NAME
换句话说,使用 postgres,这里是 USER、PASSWORD、HOST、PORT/NAME 的自定义值。虽然我们可以手动管理,但这种模式在 Django 社区中已经非常成熟,因此有一个专门的第三方包 dj-database-url 来为我们管理。方便的是,dj-database-url 已经安装了,因为它是 environs[django] 添加的辅助包之一。
这意味着我们只需一行代码就能解决所有这些问题。以下是对 django_project/settings.py 的简短更新,使我们的项目尝试访问 DATABASE_URL 环境变量。
# django_project/settings.py
DATABASES = {"default": env.dj_db_url("DATABASE_URL")}
我们还将在 .env 文件中为本地开发设置一个 DATABASE_URL 环境变量。
# .env
DEBUG=True
SECRET_KEY=imDnfLXy-8Y-YozfJmP2Rw_81YA_qx1XKl5FeY0mXyY
DATABASE_URL=sqlite:///db.sqlite3
让我们回顾一下这里发生的事情,因为一开始可能会让人困惑。对于本地开发,我们的项目会尝试查找包含环境变量的 .env 文件。如果找到了就会使用它们,这就是为什么它们是本地默认值。
在生产环境中,我们已经将 .env 文件包含在 .gitignore 文件中,所以 Heroku 不会知道它们,除非我们手动设置环境变量。Heroku 会自动创建一个 DATABASE_URL 环境变量,其中包含我们生产数据库的配置,因此该变量将被使用。
我们还需要安装 Psycopg,一个数据库适配器,让像我们这样的 Python 应用能够与 PostgreSQL 数据库通信。你可以在 Windows 上用 pip 安装它,但如果你使用的是 macOS,必须先通过 Homebrew 安装 PostgreSQL。
# Windows
(.venv) $ python -m pip install "psycopg[binary]"==3.2.1
# macOS
(.venv) $ brew install postgresql
(.venv) $ python3 -m pip install "psycopg[binary]"==3.2.1
我们使用的是二进制版本,因为它是开始使用 Psycopg 最快的方式。
Gunicorn 和 Procfile¶
由于 Django 的默认开发服务器 runserver 明确不是为生产环境设计的,我们必须选择一个可用于生产的 WSGI 服务器。Gunicorn 是最流行且最容易配置的选项之一。它可以同时处理多个请求,同时具有可扩展性、稳定性、可靠性,并与生产 Web 服务器兼容。
使用 pip 安装 Gunicorn。由于我们使用的是 PaaS,不需要额外的配置步骤。
(.venv) $ python -m pip install gunicorn==22.0.0
Heroku 依赖一个专有的 Procfile 文件,该文件提供在其技术栈上运行应用的指令。在你的文本编辑器中,在基础目录中创建一个名为 Procfile 的新文件。我们的项目只需要一行配置,告诉 Heroku 使用 Gunicorn 作为 WSGI 服务器,WSGI 配置文件的位置在 django_project.wsgi,最后,--log-file - 标志使任何日志消息对我们可见。
# Procfile
web: gunicorn django_project.wsgi --log-file -
requirements.txt¶
我们快要完成部署清单了。部署到 Heroku 之前的最后一步是更新 requirements.txt 文件。毕竟,为了部署,我们安装了以下新包:whitenoise、environs、psycopg 和 gunicorn。
使用 pip freeze 命令和 > 操作符将我们的虚拟环境信息输出到 requirements.txt 文件中。
(.venv) $ pip freeze > requirements.txt
requirements.txt 文件将出现在根目录中,包含所有已安装的包及其依赖项。我在撰写本书时的列表如下:
# requirements.txt
asgiref==3.8.1
black==24.4.2
click==8.1.7
crispy-bootstrap5==2024.2
dj-database-url==2.2.0
dj-email-url==1.0.6
Django==5.0.6
django-cache-url==3.4.5
django-crispy-forms==2.2
environs==11.0.0
gunicorn==22.0.0
marshmallow==3.21.3
mypy-extensions==1.0.0
packaging==24.1
pathspec==0.12.1
platformdirs==4.2.2
psycopg==3.2.1
psycopg-binary==3.2.1
python-dotenv==1.0.1
sqlparse==0.5.0
typing_extensions==4.12.2
whitenoise==6.7.0
我们可以使用 git status 来检查更改,添加新文件并提交。我们还可以推送到 GitHub 作为代码更改的在线备份。
(.venv) $ git status
(.venv) $ git add -A
(.venv) $ git commit -m "New updates for Heroku deployment"
(.venv) $ git push -u origin main
Heroku 设置¶
在 2022 年之前,Heroku 有慷慨的免费套餐,但遗憾的是,现在已不再如此。托管公司代表你启动虚拟服务器需要花费真金白银,因此现在很少有托管公司提供免费套餐。
Heroku 定价涉及多个功能层级,按小时计费,并设有每月上限。我们将在这里实施的部署设置如果一直开启,费用为 $12/月,但如果你注重成本并且纯粹出于教育目的进行部署,没有理由让网站一直"在线"。你可以部署网站、分享它,然后在几天后将其关闭,总费用应该只有 \(1-\)2。
在 Heroku 网站上注册一个账户。填写注册表单,等待一封包含确认账户链接的电子邮件。这将带你到密码设置页面。配置完成后,你将被引导到网站的仪表板部分。Heroku 现在要求注册多因素身份验证(MFA),可以使用 SalesForce 或 Google Authenticator 等工具完成。Heroku 现在要求添加信用卡用于账户验证和付款。
注册完成后,就该安装 Heroku 的命令行界面(CLI),这样我们就可以从命令行进行部署了。目前,我们在 Newspaper 项目的本地虚拟环境中运行。我们希望全局安装 Heroku,使其对所有项目都可用。一个简单的方法是打开一个新的命令行标签页——在 Windows 上是 Control+t,在 Mac 上是 Command+t——该标签页当前不在虚拟环境中运行。在这里安装的任何东西都将是全局的。
在 Windows 上,请参阅 Heroku CLI 页面以正确安装 32 位或 64 位版本。在 Mac 上,使用包管理器 Homebrew 进行安装。如果你的机器上还没有 Homebrew,请在 Homebrew 网站上复制长命令并粘贴到命令行中,然后按回车键进行安装。它看起来像这样:
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/\
install/HEAD/install.sh)"
接下来,将以下内容复制粘贴到命令行中并按回车键来安装 Heroku CLI。
$ brew tap heroku/brew && brew install heroku
安装完成后,你可以关闭新的命令行标签页,返回到初始标签页,其中 Newspaper 虚拟环境处于活动状态。
输入命令 heroku login 并按回车键。然后按任意键打开浏览器窗口,在那里你可以使用电子邮件、密码和刚刚设置的双因素身份验证登录。
(.venv) > heroku login
heroku: Press any key to open up the browser to login or q to exit:
Opening browser to https://cli-auth.heroku.com/auth/cli/browser/....

登录后,我们就可以开始了!
使用 Heroku 部署¶
与 Heroku 交互有两种方式:通过其 CLI(命令行界面)或网站。CLI 速度更快,我们将在这里使用它,但网站的可视化特性对于 Heroku 新手来说很有帮助。
第一步是从命令行使用 heroku create 创建一个新的 Heroku 应用。Heroku 将为我们的应用创建一个随机名称,在我的例子中是 fathomless-hamlet-26076。你的名称会不同。
(.venv) $ heroku create
Creating app... done, ⬢ afternoon-wave-82807
https://afternoon-wave-82807-b672795cd97e.herokuapp.com/ |
https://git.heroku.com/afternoon-wave-82807.git
heroku create 命令还为我们的应用创建了一个名为 heroku 的专用 Git 远程仓库。要查看这一点,输入 git remote -v。
(.venv) $ git remote -v
heroku https://git.heroku.com/afternoon-wave-82807.git (fetch)
heroku https://git.heroku.com/afternoon-wave-82807.git (push)
下一步是在 Heroku 上创建 PostgreSQL 数据库。有各种 Postgres 层级可用于不同的使用场景。五个计划层级分别是 Essential、Standard、Premium、Private 和 Shield。付费越多,停机时间越少。对于我们的使用场景,最低层级 Essential 就完全足够了。运行以下命令为我们的项目创建一个新的 Essential Postgres 数据库。
(.venv) $ heroku addons:create heroku-postgresql:essential-0
Creating heroku-postgresql:essential-0 on ⬢ afternoon-wave-82807...
~$0.007/hour (max $5/month)
Database should be available soon
postgresql-sinuous-77120 is being created in the background. The app will restart
when complete...
Use heroku addons:info postgresql-sinuous-77120 to check creation progress
Use heroku addons:docs heroku-postgresql to view documentation
数据库可能需要一些时间来配置,在这种情况下你可以等几分钟,然后运行命令来"检查创建进度"。确保数据库名称与你的项目匹配。
(.venv) $ heroku addons:info postgresql-sinuous-77120
=== postgresql-sinuous-77120
Attachments: afternoon-wave-82807::DATABASE
Installed at: Tue Jul 02 2024 10:57:17 GMT-0400 (Eastern Daylight Time)
Max Price: $5/month
Owning app: afternoon-wave-82807
Plan: heroku-postgresql:essential-0
Price: ~$0.007/hour
State: created
如果你运行 heroku config,它将显示在 Heroku 上设置的所有配置变量。目前,只有一个 DATABASE_URL,包含连接到生产 Postgres 数据库的信息。
(.venv) $ heroku config
=== afternoon-wave-82807 Config Vars
DATABASE_URL: postgres://u1k...us-east-1.rds.amazonaws.com:5432/d11ac0v0inabta
在 Heroku 网站仪表板上选择你的项目,然后点击导航栏中的"Settings"。在"Config Vars"下,你可以看到 DATABASE_URL 已经被设置。

我们的本地 .env 文件中还有另外两项,DEBUG 和 SECRET_KEY。我们需要在 Heroku 上手动设置这两个变量,可以在 Web 界面或命令行中操作。首先是 DEBUG,应该设为 False。
(.venv) $ heroku config:set DEBUG=False
Setting DEBUG and restarting ⬢ afternoon-wave-82807... done, v6
DEBUG: False
接下来是 SECRET_KEY。如果通过命令行操作,请确保用双引号 "" 包裹它。
(.venv) $ heroku config:set SECRET_KEY="SECRET_KEY=imDnfLXy-8Y-YozfJmP2Rw_81YA_qx1XKl5FeY0mXyY"
Setting SECRET_KEY and restarting ⬢ afternoon-wave-82807... done, v7
SECRET_KEY: SECRET_KEY=imDnfLXy-8Y-YozfJmP2Rw_81YA_qx1XKl5FeY0mXyY
最好再检查一下生产环境变量是否正确设置。在命令行中,这意味着使用 heroku config 命令。
(.venv) $ heroku config
=== afternoon-wave-82807 Config Vars
DATABASE_URL: postgres://u1k...us-east-1.rds.amazonaws.com:5432/d11ac0v0inabta
DEBUG: False
SECRET_KEY: SECRET_KEY=imDnfLXy-8Y-YozfJmP2Rw_81YA_qx1XKl5FeY0mXyY
你也可以在 Web 仪表板上查看。

现在是时候使用命令 git push heroku main 将代码推送到 Heroku 了。如果我们只输入 git push origin main,代码会被推送到 GitHub,而不是 Heroku。在命令中添加 heroku 会将代码发送到 Heroku。
(.venv) $ git push heroku main
Enumerating objects: 339, done.
Counting objects: 100% (339/339), done.
Delta compression using up to 10 threads
Compressing objects: 100% (333/333), done.
Writing objects: 100% (339/339), 798.15 KiB | 14.25 MiB/s, done.
Total 339 (delta 39), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (39/39), done.
remote: Updated 569 paths from 2ebafa9
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Building on the Heroku-22 stack
...
remote: https://afternoon-wave-82807-b672795cd97e.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/afternoon-wave-82807.git
* [new branch] main -> main
这个命令会从 Heroku 生成大量输出,第一次可能需要一段时间。我们正在将代码推送到 Heroku,它正在其服务器上重建我们的 Django 项目的生产版本。你会看到它从我们的 requirements.txt 文件中安装每个项目,以及其他操作。
最后一步是启动 Dyno,这是 Heroku 对我们应用的轻量级容器的称呼。我们至少需要一个正在运行的 dyno 来使我们的网站上线。如果我们开始看到流量激增,可以为项目添加更多 dyno,Heroku 会处理所有基础设施。对于这样的小项目,我推荐 Basic Dyno,每小时 $0.01,每月最高 $7。
我们将使用 Heroku CLI 启动一个 dyno,但 dyno 也可以通过 Web 界面管理。CLI 的通用语法是以 heroku 开头,ps 是一个影响 dyno 的多个命令的前缀,ps:scale 用于增加运行进程的 dyno 数量。因此,下面的命令告诉 Heroku 为我们的网站运行一个 dyno。
(.venv) $ heroku ps:scale web=1
Scaling dynos... done, now running web at 1:Basic

我们项目的总费用——如果我们让它一直运行——是每月 $12:Postgres 数据库 $5/月,Dyno $7/月。Heroku 按小时计费,所以你随时可以部署网站并在几天后将其关闭,费用应该只有几美分。
我们完成了!最后一步是确认我们的应用已上线。如果你使用 heroku open 命令,你的 Web 浏览器将打开一个新标签页,显示你应用的 URL:
(.venv) $ heroku open
Newspaper 网站已经上线,但如果你试用一下,很快就会发现一些问题。首先,没有文章和评论!这是因为我们还需要配置在 Heroku 上运行的生产 PostgreSQL 数据库。现在让我们来做。要在 Heroku 上而不是本地运行 Django 命令,我们使用 heroku run 前缀。因此,要使用初始设置迁移数据库,我们运行以下命令:
(.venv) $ heroku run python manage.py migrate
Running python manage.py migrate on ⬢ afternoon-wave-82807... up, run.2790 (Basic)
Operations to perform:
Apply all migrations: accounts, admin, articles, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0001_initial... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying accounts.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying articles.0001_initial... OK
Applying articles.0002_comment... OK
Applying sessions.0001_initial... OK
现在,创建一个超级用户账户来访问 admin。
(.venv) $ heroku run python manage.py createsuperuser
Running python manage.py createsuperuser on ⬢ afternoon-wave-82807... up, run.7422
(Basic)
Username: wsv
Email address: will@learndjango.com
Password:
Password (again):
Superuser created successfully.
导航到你部署网站的 admin 部分,使用超级用户凭据登录,并添加一些文章和评论。

它们将显示在实时网站上。你还可以创建用户账户,并通过重置密码来确认用户认证流程正常工作。
对于生产网站的后续更新,模式如下:
- 在本地进行代码更改并使用 Git 保存
- 使用
git push origin main将更改部署到 GitHub - 使用
git push heroku main将代码推送到 Heroku
如果你想删除一个托管网站,请登录你的 Heroku 仪表板并点击应用名称。点击顶部导航栏中的"Settings"链接,然后向下滚动到页面底部的"Delete App"部分,点击"Delete App…"按钮。系统会要求你再次输入完整的应用名称,以确认你要永久删除它。

另一个提示是,你可以随时按 Ctrl + d 退出 Heroku CLI。
额外的安全步骤¶
保护生产网站的安全程序清单几乎是无穷无尽的。我们的生产清单涵盖了基础知识,但如果你想采取额外措施,还有更多可以做的。
首先,更新 ALLOWED_HOSTS 和 CSRF_TRUSTED_ORIGINS 以使用你项目的确切生产 URL。
接下来,你可以运行 Django 的管理命令,它会在部署周围运行多个自动化检查。要运行该命令,你需要加上 heroku run 前缀,即 heroku run python manage.py check --deploy。你现在已经知道如何参考 Django 文档并更新本地和生产环境变量来通过这些检查。
结论¶
我们刚刚涵盖了很多新内容,所以你很可能会感到不知所措。这是正常的。为正确的部署配置网站涉及许多步骤,好消息是,这份生产设置清单几乎适用于每个 Django 项目。不要担心记住所有步骤;使用部署清单就好!
新手的另一个大绊脚石是适应本地环境和生产环境之间的差异。你很可能会忘记将代码更改推送到生产环境,然后花几分钟甚至几小时来思考为什么更改没有在你的网站上生效。更糟糕的是,你可能会更改本地的 SQLite 数据库,并期望它们神奇地出现在生产 PostgreSQL 数据库中。这是学习过程的一部分,但 Django 使它比本来要顺畅得多。你现在已经有足够的能力使用 PaaS 自信地在线部署任何 Django 项目了。