RealPython 中文系列教程(十八)

原文:RealPython

协议:CC BY-NC-SA 4.0

主持一个关于 Heroku 的 Django 项目

原文:https://realpython.com/django-hosting-on-heroku/

*立即观看**本教程有真实 Python 团队创建的相关视频课程。与书面教程一起观看,以加深您的理解: 在 Heroku 上主持您的 Django 项目

作为一名网络开发新手,你已经建立了你的文件夹应用,并在 GitHub 上分享了你的代码。也许,你希望吸引技术招聘人员来获得你的第一份编程工作。许多编码训练营的毕业生可能也在做同样的事情。为了让自己与众不同,增加被关注的机会,你可以开始在网上主持你的 Django 项目。

对于一个业余爱好 Django 项目,你会想要一个免费的托管服务,快速的设置,友好的用户界面,与你现有的技术很好的整合。虽然 GitHub Pages 非常适合托管静态网站和使用 JavaScript 的网站,但是您需要一个 web 服务器来运行您的 FlaskDjango 项目。

有几个主要的云平台提供商以不同的模式运营,但是在本教程中你将探索 Heroku 。它符合所有条件——免费、设置快速、用户友好,并且与 Django 很好地集成在一起——并且是许多初创公司最喜欢的云平台提供商。

在本教程中,您将学习如何:

  • 在几分钟内让你的 Django 项目上线
  • 使用 Git 将您的项目部署到 Heroku
  • 使用 Django-Heroku 集成库
  • 将你的 Django 项目挂接到一个独立的关系数据库上
  • 管理配置以及敏感数据

要继续学习,您可以通过单击下面的链接下载代码和其他资源:

获取源代码: 点击这里获取 Django 项目以及本教程中各个步骤的快照。

演示:您将构建什么

您将创建一个基本的 Django 项目,并直接从终端将它部署到云中。最后,你将拥有一个公共的、可共享的链接,链接到你的第一个 Heroku 应用。

这里有一个一分钟的视频演示了必要的步骤,从初始化一个空的 Git 存储库到在浏览器中查看您完成的项目。坚持看完,快速预览一下你将在本教程中发现的内容:

https://player.vimeo.com/video/552465720?background=1

除了上面截屏中显示的步骤之外,稍后你会发现更多的步骤,但是这应该足以让你对如何在本教程中使用 Heroku 有一个大致的了解。

Remove ads

项目概述

本教程并不是关于构建任何特定的项目,而是使用 Heroku 在云中托管一个项目。虽然 Heroku 支持各种语言和 web 框架,但你会坚持使用 Python 和 Django。如果您手头没有任何 Django 项目,也不用担心。第一步将带您搭建一个新的 Django 项目,让您快速入门。或者,您可以使用一个现成的示例项目,稍后您会发现。

一旦你准备好你的 Django 项目,你将注册一个免费的 Heroku 账户。接下来,您将下载一个方便的命令行工具,帮助您在线管理应用程序。正如上面的截图所示,命令行是使用 Heroku 的一种快捷方式。最后,您将在新配置的 Heroku 实例上完成一个已部署的 Django 项目。你可以把你的最终结果想象成你未来项目想法的占位符。

先决条件

在开始之前,请确保您已经熟悉了 Django web 框架的基础知识,并且已经习惯使用它来建立一个基本项目。

**注意:**如果你在 Flask 方面比 Django 更有经验,那么你可以看看类似的关于使用 Heroku 部署 Python Flask 示例应用程序的教程。

您还应该安装和配置一个 Git 客户端,以便您可以从命令行方便地与 Heroku 平台交互。最后,你应该认真考虑为你的项目使用一个虚拟环境。如果你还没有一个特定的虚拟环境工具,你很快就会在本教程中找到一些选项。

步骤 1:搭建 Django 项目来托管

要在云中托管 Django web 应用程序,您需要一个有效的 Django 项目。就本教程的目的而言,不必详细说明。如果你时间不够,可以随意使用你的一个爱好项目或者构建一个样本投资组合应用,然后跳到创建你的本地 Git 库。否则,留下来从头开始做一个全新的项目。

创建虚拟环境

通过创建一个不会与其他项目共享的隔离虚拟环境来开始每个项目是一个好习惯。这可以使您的依赖项保持有序,并有助于避免包版本冲突。一些依赖管理器和打包工具,如 Pipenvpoems自动创建和管理虚拟环境,让你遵循最佳实践。当你开始一个新项目时,许多ide比如 PyCharm 也会默认这样做。

然而,创建 Python 虚拟环境的最可靠和可移植的方式是从命令行手动完成。您可以使用外部工具如 virtualenvwrapper 或直接调用内置模块。虽然 virtualenvwrapper 将所有环境保存在预定义的父文件夹中,但是venv希望您为每个环境分别指定一个文件夹。

在本教程中,您将使用标准的venv模块。习惯上将虚拟环境放在项目的根文件夹中,所以让我们先创建一个,并将工作目录改为:

$ mkdir portfolio-project
$ cd portfolio-project/

你现在在portfolio-project文件夹中,这将是你的项目的家。要在这里创建一个虚拟环境,只需运行venv模块并为您的新环境提供一个路径。默认情况下,文件夹名称将成为环境的名称。如果你愿意,你可以用可选的--prompt参数给它一个自定义名称:

$ python3 -m venv ./venv --prompt portfolio

以前导点(.)开始的路径表示它相对于当前工作目录。虽然不是强制性的,但这个点清楚地表明了你的意图。不管怎样,这个命令应该会在您的portfolio-project根目录下创建一个venv子目录:

portfolio-project/
│
└── venv/

这个新的子目录包含 Python 解释器的副本以及一些管理脚本。现在,您已经准备好将项目依赖项安装到其中了。

安装项目依赖关系

大多数实际项目都依赖于外部库。Django 是一个第三方 web 框架,并不是 Python 自带的。您必须在项目的虚拟环境中安装它以及它自己的依赖项。

如果您还没有激活虚拟环境,请不要忘记激活它。为此,您需要执行虚拟环境的bin/子文件夹中的一个 shell 脚本中的命令。例如,如果您使用的是 Bash ,那么就可以获得activate脚本:

$ source venv/bin/activate

现在, shell 提示符应该显示一个带有虚拟环境名称的前缀,以表明它已被激活。您可以仔细检查特定命令指向的可执行文件:

(portfolio) $ which python
/home/jdoe/portfolio-project/venv/bin/python

上面的输出确认了运行python将执行位于您的虚拟环境中的相应文件。现在,让我们为您的 Django 项目安装依赖项

你需要一个相当新版本的 Django。根据您阅读本文的时间,可能会有更新的版本。为了避免潜在的兼容性问题,您可能希望指定与编写本教程时使用的版本相同的版本:

(portfolio) $ python -m pip install django==3.2.5

这将安装 Django 的 3.2.5 版本。包名是不区分大小写的,所以无论您键入django还是Django都没有关系。

**注意:**有时候,你会看到一个关于 pip 新版本可用的警告。忽略此警告通常是无害的,但是如果您在生产环境中,出于安全原因,您需要考虑升级:

(portfolio) $ python -m pip install --upgrade pip

或者,如果版本检查困扰您并且您知道可能的后果,您可以在配置文件中禁用版本检查。

安装 Django 带来了一些额外的传递依赖关系,您可以通过列出它们来揭示:

(portfolio) $ python -m pip list
Package    Version
---------- -------
asgiref    3.4.1
Django     3.2.5
pip        21.1.3
pytz       2021.1
setuptools 56.0.0
sqlparse   0.4.1

因为您希望其他人能够毫无问题地下载和运行您的代码,所以您需要确保可重复的构建。这就是冻结的目的。它以一种特殊的格式输出大致相同的依赖集及其子依赖集:

(portfolio) $ python -m pip freeze
asgiref==3.4.1
Django==3.2.5
pytz==2021.1
sqlparse==0.4.1

这些基本上是pip install命令的参数。然而,它们通常被封装在一个或多个需求文件中,这些文件pip可以一次性使用。要创建这样一个文件,您可以重定向freeze命令的输出:

(portfolio) $ python -m pip freeze > requirements.txt

这个文件应该提交到您的 Git 存储库中,这样其他人就可以通过以下方式使用pip安装它的内容:

(portfolio) $ python -m pip install -r requirements.txt

目前,您唯一的依赖项是 Django 及其子依赖项。但是,您必须记住,每次添加或删除任何依赖项时,都要重新生成并提交需求文件。这就是前面提到的包管理器可能派上用场的地方。

解决了这个问题,让我们开始一个新的 Django 项目!

Remove ads

Django 项目

每个 Django 项目都由遵循特定命名约定的相似文件和文件夹组成。您可以手动创建这些文件和文件夹,但是自动创建通常更快更方便。

当你安装 Django 时,它为管理任务提供了一个命令行工具,比如引导新项目。该工具位于虚拟环境的bin/子文件夹中:

(portfolio) $ which django-admin
/home/jdoe/portfolio-project/venv/bin/django-admin

您可以在 shell 中运行它,并传递新项目的名称以及创建默认文件和文件夹的目标目录:

(portfolio) $ django-admin startproject portfolio .

或者,您可以通过调用django模块来获得相同的结果:

(portfolio) $ python -m django startproject portfolio .

注意这两个命令末尾的点,它指示您当前的工作目录portfolio-project作为目标。如果没有它,该命令将创建另一个与您的项目同名的父文件夹。

如果你得到一个command not found错误或者ModuleNotFound 异常,那么确保你已经激活了安装 Django 的同一个虚拟环境。其他一些常见的错误是将项目命名为与一个内置对象相同,或者没有使用有效的 Python 标识符。

**注意:**使用管理工具从头开始一个新的 Django 项目既快速又灵活,但是需要大量的手工劳动。如果您计划托管生产级 web 应用程序,那么您需要配置安全性、数据源等等。选择一个遵循最佳实践的项目模板可能会为您省去一些麻烦。

之后,您应该有这样的目录布局:

portfolio-project/
│
├── portfolio/
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
│
├── venv/
│
├── manage.py
└── requirements.txt

您创建了一个名为portfolio管理应用程序,它包含项目级设置和带有 URL 模式的主文件,以及其他一些东西。您还创建了manage.py脚本,它方便地包装了django-admin并与您的项目挂钩。

现在,您已经有了一个基本的、可运行的 Django 项目。此时,您通常会启动一个或多个 Django 应用程序并定义它们的视图和模型,但是对于本教程来说它们不是必需的。

更新本地数据库模式(可选)

这一步是可选的,但是如果您想要使用 Django admin 视图或者定义定制应用和模型,那么您最终需要更新您的数据库模式。默认情况下,Django 带来了一个基于文件的 SQLite 数据库,方便测试和运行本地开发服务器。这样,你就不需要安装和设置一个像 MySQLPostgreSQL 这样的成熟数据库。

要更新数据库模式,运行migrate子命令:

(portfolio) $ python manage.py migrate

在成功应用所有未决的迁移之后,您将在项目根文件夹中找到一个名为db.sqlite3的新文件:

portfolio-project/
│
├── portfolio/
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
│
├── venv/
│
├── db.sqlite3 ├── manage.py
└── requirements.txt

您可以使用 sqlite3命令行实用程序、Python 内置的 sqlite3模块或者您喜欢的数据库管理工具来检查它的内容。到目前为止,这个文件应该包含一些负责身份验证、会话管理等的内部应用程序的表,以及一个跟踪应用的迁移的元表。

Remove ads

运行本地开发服务器

在将 Heroku 放在项目之上增加复杂性之前,在本地计算机上测试所有东西是有意义的。这可以让你省去很多不必要的调试。幸运的是,Django 附带了一个用于开发目的的轻量级 web 服务器,几乎不需要配置。

**注意:**从技术上讲,您可以利用 Heroku 上 Django 内置的相同开发服务器。然而,它不是为处理现实生活中的交通而设计的,也不安全。你最好使用像 Gunicorn 这样的 WSGI 服务器。

要运行开发服务器,请在您之前激活虚拟环境的终端窗口中键入以下命令:

(portfolio) $ python manage.py runserver

默认情况下,它将在本地主机端口 8000 上启动服务器。如果另一个应用程序已经在使用 8000,您可以调整端口号。服务器将继续监视项目源文件中的变化,并在必要时自动重新加载它们。当服务器仍在运行时,在 web 浏览器中导航到该 URL:

http://127.0.0.1:8000/

主机127.0.0.1代表虚拟本地网络接口上的 IP 地址之一。如果一切顺利,并且您没有更改默认的项目设置,那么您应该进入 Django 欢迎页面:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Django Welcome Page on Localhost

万岁!火箭已经起飞,您的 Django 项目已经准备好部署到云中。

步骤 2:创建一个本地 Git 存储库

现在,您已经有了一个可以工作的 Django 项目,是时候采取下一步措施在云中托管它了。在本节中,您将探索在 Heroku 平台上构建和部署应用程序的可用选项。如果您还没有为您的项目创建一个本地 Git 存储库,那么您还需要创建一个本地 Git 存储库。在这一步结束时,您将准备好深入 Heroku 工具链。

Heroku 提供了至少五种不同的方式来部署您的项目:

  1. Git: 将提交推送到 Heroku 上的远程 Git 存储库
  2. GitHub: 合并拉取请求时自动触发部署
  3. Docker:Docker 图像推送到 Heroku 容器注册表
  4. API: 以编程方式自动化您的部署
  5. Web: 从 Heroku 仪表板手动部署

最直接和以开发人员为中心的方法是第一种。许多软件开发人员已经在日常生活中使用 Git,所以 Heroku 的入门门槛相当低。git命令让您在 Heroku 中完成很多工作,这就是为什么您将在本教程中使用 Git。

初始化一个空的 Git 存储库

使用组合键 Ctrl + CCmd + C 停止您的开发服务器,或者打开另一个终端窗口,然后在您的项目根文件夹中初始化一个本地 Git 存储库:

$ git init

您的虚拟环境是否活跃并不重要。它应该创建一个新的.git子文件夹,其中包含 Git 跟踪的文件的历史。名称以点开头的文件夹在 macOS 和 Linux 上是隐藏的。如果您想检查您是否成功地创建了它,那么使用ls -a命令来查看这个文件夹。

指定未跟踪的文件

告诉 Git 忽略哪些文件很有用,这样 Git 就不会再跟踪它们了。有些文件不应该是存储库的一部分。您通常应该忽略 IDE 和代码编辑器设置、包含密码等敏感数据的配置文件、Python 虚拟环境之类的二进制文件、缓存文件以及 SQLite 数据库之类的数据。

当您检查新 Git 存储库的当前状态时,它会列出工作目录中的所有文件,并建议将它们添加到存储库中:

$ git status
On branch master

No commits yet

Untracked files:
 (use "git add <file>..." to include in what will be committed)
 .idea/
 __pycache__/
 db.sqlite3
 manage.py
 portfolio/
 requirements.txt
 venv/

nothing added to commit but untracked files present (use "git add" to track)

不是添加所有这些文件和文件夹,而是让 Git 忽略其中的一些,例如:

  • .idea/
  • __pycache__/
  • db.sqlite3
  • venv/

.idea/文件夹是 PyCharm 特有的。如果你使用的是 Visual Studio Code 或其他编辑器,那么你需要将它们对应的文件和文件夹添加到这个列表中。在前面包含更多的文件名模式将让其他贡献者安全地使用他们选择的编辑器和 ide,而不必过于频繁地更新列表。

Git 寻找一个名为.gitignore的特殊文件,它通常放在您的存储库的根文件夹中。每行包含一个具体的文件名或一个要排除的通用文件名模式。你可以手动编辑这个文件,但是使用 gitignore.io 网站从一组预定义的组件中创建一个要快得多:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你会注意到,在地址栏中键入 gitignore.io 会将浏览器重定向到 Toptal 拥有的一个更详细的域。

在这里,您可以选择正在使用的编程语言、库和工具。当你对你的选择满意时,点击创建按钮。然后,将结果复制并粘贴到文本编辑器中,并将其作为.gitignore保存在您的项目根文件夹中,或者记下 URL 并在命令行中使用 cURL 来下载文件:

$ curl https://www.toptal.com/developers/gitignore/api/python,pycharm+all,django > .gitignore

如果您发现自己在重复输入这个 URL,那么您可以考虑在您的 shell 中定义一个别名命令,这应该是最容易记住的:

$ git ignore python,pycharm+all,django > .gitignore

实现同一个目标通常有多种方式,了解不同的选择可以教会你很多。不管怎样,在创建了.gitignore文件之后,您的存储库状态应该如下所示:

$ git status
On branch master

No commits yet

Untracked files:
 (use "git add <file>..." to include in what will be committed)
 .gitignore
 manage.py
 portfolio/
 requirements.txt

nothing added to commit but untracked files present (use "git add" to track)

创建本地 Git 存储库的剩余步骤是暂存您的更改,并在第一次提交时保存它们。

Remove ads

进行第一次提交

请记住,要通过 Git 使用 Heroku,您必须将代码推送到远程 Git 存储库。为此,您需要在本地存储库中至少有一个提交。首先,将新文件添加到暂存区,这是工作树和本地存储库之间的缓冲区。然后,重新检查状态以确认您没有遗漏任何内容:

$ git add .
$ git status
On branch master

No commits yet

Changes to be committed:
 (use "git rm --cached <file>..." to unstage)
 new file:   .gitignore
 new file:   manage.py
 new file:   portfolio/__init__.py
 new file:   portfolio/asgi.py
 new file:   portfolio/settings.py
 new file:   portfolio/urls.py
 new file:   portfolio/wsgi.py
 new file:   requirements.txt

这些文件已经准备好提交,所以让我们获取它们的快照,并将其保存在本地存储库中:

$ git commit -m "Initial commit"

提供一个描述性的提交消息来帮助您浏览变更历史总是一个好主意。根据经验,你的信息应该解释为什么你做出了改变。毕竟,任何人都可以查看 Git 日志来找出到底对做了什么更改。

好吧,到目前为止你学到了什么?您知道在 Heroku 平台上部署新版本通常需要将本地提交推送到 Git 远程。您已经创建了一个本地 Git 存储库,并进行了第一次提交。接下来,你需要创建你的免费 Heroku 账户。

第三步:创建一个免费的 Heroku 账户

现在,你已经准备好注册一个免费的 Heroku 账户,并根据你的喜好进行配置。

Django 标榜自己是有期限的完美主义者的网络框架。Heroku 采取了类似的自以为是的方法在云中托管 web 应用程序,旨在减少开发时间。它是一种高级安全的平台即服务(PaaS) ,可以减轻您的基础设施管理负担,让您专注于对您最重要的事情——编写代码。

有趣的事实: Heroku 基于亚马逊网络服务(AWS) ,另一个流行的云平台,主要以基础设施即服务(IaaS) 模式运行。它比 Heroku 灵活得多,价格也更实惠,但需要一定的专业知识。

许多初创公司和较小的公司在早期发展阶段没有一支熟练的 DevOps 工程师团队。就投资回报而言,Heroku 可能是这些公司的一个方便的解决方案。

报名

要开始使用 Heroku,请访问 Heroku 注册页面,填写注册表格,然后等待带有链接的电子邮件来确认您的帐户。它会将您带到密码设置页面。配置完成后,您将能够进入新的 Heroku 仪表板。您需要做的第一件事是阅读并接受服务条款。

启用多因素身份验证(可选)

这一步完全是可选的,但 Heroku 可能会让你注册多因素认证(MFA) 以增加对你的帐户的保护,确保其安全。这项功能也被称为双因素认证 (2FA),因为它通常只包含两个阶段来验证您的身份。

有趣的事实:我的网飞个人账户曾一度遭到黑客攻击,有人可以使用我的信用卡,甚至在我取消订阅很久之后。从那以后,我在所有的在线服务中启用了双因素认证。

当登录到你的 Heroku 仪表盘时,点击右上角的忍者头像,选择账户设置,然后向下滚动,直到你可以看到多重身份验证部分。点击标有设置多重身份验证的按钮,选择您的验证方法:

  • Salesforce 认证器
  • 一次性密码生成器
  • 安全密钥
  • 内置认证器
  • 恢复代码

您应该选择哪种验证方法?

Salesforce 是 2010 年收购 Heroku 的母公司,这就是为什么他们宣传他们的专有移动应用程序是你的首选。然而,如果你已经在其他地方使用了另一个验证器应用程序,那么选择一次性密码生成器选项,用你的应用程序扫描二维码。

安全密钥需要一个外部硬件 USB 令牌,而内置认证器方法可以利用你的设备的指纹读取器,例如,如果它带有一个的话。

最后,恢复码可以作为额外的密码。即使你只打算在手机上使用验证程序,你也应该下载恢复代码作为备份。如果没有其他方法来验证您的身份,如果您的手机丢失、损坏或升级,您将无法再次登录您的 Heroku 帐户。相信我,我也经历过!

Heroku 曾经提供另一种验证方法,通过发送短信到你的手机,但是他们停止了这种方法,因为安全问题。

Remove ads

添加付款方式(可选)

如果你不愿意和 Heroku 分享你的信用卡号码,那也没关系。在合理的限制下,这项服务将继续免费运行。然而,即使你不打算花一分钱在云端托管你的 Django 项目,你仍然可以考虑连接你的支付细节。原因如下。

在写这篇教程的时候,你每月只能获得 550 个小时的免费帐号。这相当于每天 24 小时使用单个计算机实例的 22 天。当你用信用卡验证你的账户时,这个数字会上升到每月 1000 小时。

**注意:**无论您是否验证您的帐户,在 30 分钟窗口内没有接收到任何 HTTP 流量的免费层上的 web 应用程序都会自动进入睡眠状态。这可以节省你的空闲时间,但如果你的应用程序没有正常的流量,会让用户体验变得更糟。当有人想在待机模式下使用你的网络应用程序时,需要几秒钟才能再次启动。

验证你的账户的其他好处包括可以使用免费的插件如关系数据库,建立一个自定义域等等。请记住,如果您决定与 Heroku 共享您的账单信息,那么启用多因素身份认证是一项值得做的工作。

到目前为止,你一直通过 Heroku 的网络界面与他们互动。虽然这无疑是方便和直观的,但是在线托管 Django 项目的最快方式是使用命令行。

步骤 4:安装 Heroku CLI

在终端中工作是任何开发人员的基本技能。起初,输入命令可能看起来令人生畏,但是在看到它的威力后,它变成了第二天性。为了获得无缝的开发体验,您需要安装 Heroku 命令行界面(CLI)

Heroku CLI 将允许您直接从终端创建和管理 web 应用程序。在这一步中,您将学习一些基本命令以及如何显示它们的文档。首先,按照针对您的操作系统的安装说明进行操作。完成后,使用以下命令确认安装成功:

$ heroku --version

如果找到了heroku命令,并且您使用了最新版本的 Heroku CLI,那么您可以在您的 shell 中启用自动完成。当您按下 Tab 键时,它会自动完成命令及其参数,节省时间并防止输入错误。

**注意:**该工具需要一个 Node.js 服务器,大多数安装方法都捆绑了这个服务器。它也是一个开源项目,这意味着你可以在 GitHub 上看看它的源代码

Heroku CLI 有一个模块化的插件架构,这意味着它的特性是独立的,并且遵循相同的模式。要获得所有可用命令的列表,请在终端中键入heroku help或简单地键入heroku:

$ heroku
CLI to interact with Heroku

VERSION
 heroku/7.56.0 linux-x64 node-v12.21.0

USAGE
 $ heroku [COMMAND]

COMMANDS
 access          manage user access to apps
 addons          tools and services for developing, extending, (...)
 apps            manage apps on Heroku
 auth            check 2fa status
(...)

有时,一个命令的名字可能不会泄露它的用途。如果您想找到关于某个特定命令的更多细节并查看用法的快速示例,那么使用--help标志:

$ heroku auth --help
check 2fa status

USAGE
 $ heroku auth:COMMAND

COMMANDS
 auth:2fa     check 2fa status
 auth:login   login with your Heroku credentials
 auth:logout  clears local login credentials and invalidates API session
 auth:token   outputs current CLI authentication token.
 auth:whoami  display the current logged in user

这里,您通过使用--help标志来请求关于auth命令的更多信息。您可以看到,auth后面应该跟一个冒号(:和另一个命令。通过键入heroku auth:2fa,您要求 Heroku CLI 检查您的双因素身份验证设置的状态:

$ heroku auth:2fa --help
check 2fa status

USAGE
 $ heroku auth:2fa

ALIASES
 $ heroku 2fa
 $ heroku twofactor

COMMANDS
 auth:2fa:disable  disables 2fa on account

Heroku CLI 命令是分层的。它们通常有一个或多个子命令,您可以在冒号后指定,就像上面的例子一样。此外,这些子命令中的一些可能在命令层级的顶层有一个可用的别名。例如,键入heroku auth:2faheroku 2faheroku twofactor具有相同的效果:

$ heroku auth:2fa
Two-factor authentication is enabled

$ heroku 2fa
Two-factor authentication is enabled

$ heroku twofactor
Two-factor authentication is enabled

所有三个命令都给出相同的结果,这让您可以选择更容易记住的命令。

在这一小段中,您在计算机上安装了 Heroku CLI,并熟悉了它的语法。您已经看到了一些方便的命令。现在,为了充分利用这个命令行工具,您需要登录您的 Heroku 帐户。

Remove ads

第五步:使用 Heroku CLI 登录

即使不创建 Heroku 帐户,也可以安装 Heroku CLI。但是,你必须验证你的身份,证明你有一个相应的 Heroku 账号,才能用它做一些有意义的事情。在某些情况下,您甚至可能有多个帐户,因此登录允许您指定在给定时刻使用哪个帐户。

稍后您将了解到,您不会永久保持登录状态。这是一个好习惯,登录以确保您有访问权限,并确保您使用正确的帐户。最直接的登录方式是通过heroku login命令:

$ heroku login
heroku: Press any key to open up the browser to login or q to exit:

这将打开您的默认网络浏览器,如果您以前登录过 Heroku 仪表板,就可以很容易地获得您的会话 cookie。否则,您需要提供您的用户名、密码,如果您启用了双因素身份验证,还可能需要另一个身份证明。成功登录后,您可以关闭选项卡或浏览器窗口并返回到终端。

**注意:**您也可以使用无头模式登录,方法是在命令后面附加--interactive标志,它会提示您输入用户名和密码,而不是启动 web 浏览器。但是,这在启用多因素身份认证的情况下不起作用。

当您使用 CLI 登录时,您的会话 cookies 的暴露是暂时的,因为 Heroku 会生成一个新的授权令牌,它将在有限的时间内有效。它将令牌存储在您的主目录中的标准.netrc文件中,但是您也可以使用 Heroku 仪表板或heroku authheroku authorizations插件来检查它:

$ heroku auth:whoami
jdoe@company.com

$ heroku auth:token
 ›   Warning: token will expire today at 11:29 PM
 ›   Use heroku authorizations:create to generate a long-term token
f904774c-ffc8-45ae-8683-8bee0c91aa57

$ heroku authorizations
Heroku CLI login from 54.239.28.85  059ed27c-d04a-4349-9dba-83a0169277ae  global

$ heroku authorizations:info 059ed27c-d04a-4349-9dba-83a0169277ae
Client:      <none>
ID:          059ed27c-d04a-4349-9dba-83a0169277ae
Description: Heroku CLI login from 54.239.28.85
Scope:       global
Token:       f904774c-ffc8-45ae-8683-8bee0c91aa57
Expires at:  Fri Jul 02 2021 23:29:01 GMT+0200 (Central European Summer Time) (in about 8 hours)
Updated at:  Fri Jul 02 2021 15:29:01 GMT+0200 (Central European Summer Time) (1 minute ago)

在撰写本教程时,到期策略似乎有点小故障。官方文档规定,默认情况下,它应该保持一年的有效性,而 Heroku CLI 显示大约一个月,这也对应于会话 cookie 到期。使用 Heroku web 界面手动重新生成令牌可以将时间减少到大约八个小时。但是如果你测试实际的失效日期,你会发现它完全不同。如果您在学习本教程时对到期策略感到好奇,请自行探索。

无论如何,heroku login命令只用于开发。在生产环境中,您通常会使用authorizations插件生成一个永远不会过期的长期用户授权。通过 Heroku API ,它可以方便地用于脚本和自动化目的。

第六步:创建一个 Heroku 应用程序

在这一步,您将创建您的第一个 Heroku 应用程序,并了解它如何与 Git 集成。最后,你将为你的项目拥有一个公开可用的域名地址

在 Django 项目中,应用程序是封装可重用功能的独立代码单元。另一方面, Heroku apps 工作起来就像可扩展的虚拟计算机,能够托管你的整个 Django 项目。每个应用程序都由源代码、必须安装的依赖项列表和运行项目的命令组成。

最起码,每个项目都有一个 Heroku 应用程序,但拥有更多应用程序并不罕见。例如,您可能希望同时运行项目的开发试运行生产版本。每个都可以连接到不同的数据源,并具有不同的特性集。

注意: Heroku pipelines 让你按需创建、提升和销毁应用,以促进连续交付工作流。你甚至可以连接 GitHub,这样每个特性分支都会收到一个临时的测试应用。

要使用 Heroku CLI 创建您的第一个应用程序,请确保您已经登录 Heroku,并运行heroku apps:create命令或其别名:

$ heroku create
Creating app... done, ⬢ polar-island-08305
https://polar-island-08305.herokuapp.com/ | https://git.heroku.com/polar-island-08305.git

默认情况下,它会随机选择一个保证唯一的应用名称,比如polar-island-08305。你也可以选择自己的域名,但是它必须在整个 Heroku 平台上是唯一的,因为它是你免费获得的域名的一部分。您会很快发现它是否已被占用:

$ heroku create portfolio-project
Creating ⬢ portfolio-project... !
 ▸    Name portfolio-project is already taken

如果你想想有多少人使用 Heroku,那么有人已经创建了一个名为portfolio-project的应用程序就不足为奇了。当您在 Git 存储库中运行heroku create命令时,Heroku 会自动添加一个新的远程服务器到您的.git/config文件中:

$ tail -n3 .git/config
[remote "heroku"]
 url = https://git.heroku.com/polar-island-08305.git
 fetch = +refs/heads/*:refs/remotes/heroku/*

Git 配置文件中的最后三行定义了一个名为heroku的远程服务器,它指向您唯一的 Heroku 应用程序。

通常,在克隆存储库之后,您的 Git 配置中会有一个远程服务器——例如 GitHub 或 Bitbucket。然而,在一个本地存储库中可以有多个 Git remotes 。稍后您将使用该功能发布新的应用程序并部署到 Heroku。

**注意:**有时候,使用 Git 会变得很混乱。如果您注意到您意外地在本地 Git 存储库之外或通过 web 界面创建了一个 Heroku 应用程序,那么您仍然可以手动添加相应的 Git remote。首先,将您的目录更改为项目根文件夹。接下来,列出您的应用程序以找到所需的名称:

$ heroku apps
=== jdoe@company.com Apps
fathomless-savannah-61591
polar-island-08305 sleepy-thicket-59477

在您确定了您的应用程序的名称(在本例中为polar-island-08305)之后,您可以使用git remote add命令或 Heroku CLI 中相应的git插件来添加一个名为heroku的遥控器:

$ heroku git:remote --app polar-island-08305
set git remote heroku to https://git.heroku.com/polar-island-08305.git

这将添加一个名为heroku的远程服务器,除非另有说明。

当你创建一个新的应用程序时,它会告诉你它在.herokuapp.com域中的公共网址。在本教程中,公共网址是https://polar-island-08305.herokuapp.com,但是您的将会不同。尝试将您的 web 浏览器导航到您的独特域,看看接下来会发生什么。如果你不记得确切的网址,只需在项目根文件夹中,在终端中键入heroku open命令。它将打开一个新的浏览器窗口并获取正确的资源:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Empty Heroku App

干得好!你的 Heroku 应用已经响应 HTTP 请求。然而,它目前是空的,这就是为什么 Heroku 显示一个通用的占位符视图,而不是你的内容。让我们将您的 Django 项目部署到这个空白应用程序中。

Remove ads

步骤 7:将 Django 项目部署到 Heroku

至此,您已经具备了在 Heroku 上开始托管 Django 项目所需的一切。然而,如果您现在尝试将您的项目部署到 Heroku,它会失败,因为 Heroku 不知道如何构建、打包和运行您的项目。它也不知道如何安装需求文件中列出的特定 Python 依赖项。你现在会解决的。

选择一个构建包

Heroku 自动化了许多部署步骤,但它需要了解您的项目设置和技术堆栈。构建和部署项目的方法被称为构建包。已经有一些官方的构建包可以用于许多后端技术,包括 Node.js、Ruby、Java、PHP、Python、Go、Scala 和 Clojure。除此之外,你可以为不太流行的语言如 c 找到第三方的构建包

您可以在创建新应用程序时手动设置一个,或者让 Heroku 根据存储库中的文件检测它。Heroku 识别 Python 项目的一种方法是在项目根目录中查找requirements.txt文件。确保您已经创建了一个,这可能是在设置您的虚拟环境时用pip freeze完成的,并且您已经将它提交到本地存储库。

其他一些有助于 Heroku 识别 Python 项目的文件是Pipfilesetup.py。Heroku 也将认可 Django web 框架并为其提供特殊支持。所以如果你的项目包括requirements.txtPipfilesetup.py,那么通常不需要设置构建包,除非你在处理一些边缘情况。

选择 Python 版本(可选)

默认情况下,Heroku 将选择一个最新的 Python 版本来运行您的项目。但是,您可以通过在项目根目录中放置一个runtime.txt文件来指定不同版本的 Python 解释器,记住要提交它:

$ echo python-3.9.6 > runtime.txt
$ git add runtime.txt
$ git commit -m "Request a specific Python version"

请注意,您的 Python 版本必须包含语义版本的所有major.minor.patch组件。虽然 Python 只有少数几个支持的运行时,但你通常可以调整补丁版本。还有对 PyPy 的测试版支持。

指定要运行的流程

既然 Heroku 知道了如何构建您的 Django 项目,它需要知道如何运行它。一个项目可以由多个组件组成,比如 web 组件、后台工作人员、关系数据库、 NoSQL 数据库、计划作业等等。每个组件都在单独的进程中运行。

有四种主要的流程类型:

  1. web:接收 HTTP 流量
  2. worker:在后台执行工作
  3. clock:执行预定的工作
  4. release:部署前运行任务

在本教程中,您将只查看 web 过程,因为每个 Django 项目至少需要一个。您可以在名为Procfile的文件中定义它,该文件必须放在您的项目根目录中:

portfolio-project/
│
├── .git/
│
├── portfolio/
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
│
├── venv/
│
├── .gitignore
├── db.sqlite3
├── manage.py
├── Procfile ├── requirements.txt
└── runtime.txt

Procfile是一种单一的、与语言无关的格式,用于定义组成项目的过程。它将指导 Heroku 如何运行你的网络服务器。虽然在生产环境中运行 Django 项目不推荐使用内置的开发服务器,但是您可以在本练习中使用它:

$ echo "web: python manage.py runserver 0.0.0.0:\$PORT" > Procfile
$ git add Procfile
$ git commit -m "Specify the command to run your project"

为了让 Heroku cloud 之外的世界可以访问服务器,您将地址指定为0.0.0.0而不是默认的localhost。它会将服务器绑定在一个公共网络接口上。Heroku 通过PORT环境变量提供端口号。

现在,您可以通过使用 Heroku CLI 在本地运行 Django 项目来测试这个配置:

$ heroku local

默认情况下,如果没有明确指定流程类型,它将运行web流程。heroku local命令与heroku local web相同。同样,如果你没有用--port标志设置端口号,那么它将使用默认端口5000

现在,您已经指定了希望 Heroku 运行的进程。当您在 web 浏览器中打开 URL http://localhost:5000/时,您应该会在 Django 欢迎页面上再次看到熟悉的火箭。然而,要在http://0.0.0.0:5000/通过公共接口访问相同的资源,您需要调整 Django 配置,否则您将收到一个错误请求错误。

Remove ads

配置 Django

您之前构建了一个基本的 Django 项目,现在是时候配置它了,这样它就可以在您的 Heroku 实例上运行了。配置一个 Django 项目可以让您微调各种设置,从数据库凭证到模板引擎

要通过非本地网络地址访问 Django 项目,您需要在项目设置中指定ALLOWED_HOSTS。除此之外,Django buildpack for Python 会为您运行collectstatic命令,这需要定义STATIC_ROOT选项。不管您是否使用 Heroku,在部署 Django 项目时,还有一些配置选项需要更改,但在这个阶段它们不是强制性的。

不用手动配置 Django,您可以走捷径,安装一个方便的 django-heroku 包来完成所有的工作。

**注:**不再维护django-heroku包,对应的 GitHub 库已存档。如果您只想尝试将 Django 项目部署到 Heroku,这可能不是问题。然而,对于生产级应用程序,您可以尝试一个名为 django-on-heroku 的分支,这是 Adam 在下面的评论部分建议的。或者,你可以使用 Eric Matthes 在他的博客上描述的实验性构建包。

在继续之前,请确保您处于正确的虚拟环境中,并记住在完成后刷新您的需求文件:

(portfolio) $ python -m pip install django-heroku
(portfolio) $ python -m pip freeze > requirements.txt

这将用项目的最新依赖项替换您的需求文件的内容。接下来,将这两行 Python 代码添加到您的portfolio/settings.py文件中,并且不要忘记之后返回到项目根文件夹:

(portfolio) $ pushd portfolio/
(portfolio) $ echo "import django_heroku" >> settings.py
(portfolio) $ echo "django_heroku.settings(locals())" >> settings.py
(portfolio) $ popd

或者,如果命令cd portfolio/cd ..在您的 shell 中不起作用,请使用它们来代替 pushdpopd 命令。

因为您在上面用追加重定向操作符(>>)追加了echo命令的输出,所以现在在 Django 设置文件的最底部有两行代码:

# portfolio/settings.py

# ...

import django_heroku
django_heroku.settings(locals())

这将使用基于项目布局和环境变量的值来更新本地名称空间中的变量。最后,不要忘记将您的更改提交到本地 Git 存储库:

(portfolio) $ git commit -am "Automatic configuration with django-heroku"

现在,您应该能够使用0.0.0.0主机名访问您的 Django web 服务器了。没有它,你将无法通过公共 Heroku 域访问你的应用程序。

配置 Heroku App

您为项目选择了一个构建包和一个 Python 版本。您还指定了 web 进程来接收 HTTP 流量,并配置了您的 Django 项目。将 Django 项目部署到 Heroku 之前的最后一个配置步骤需要在远程 Heroku 应用程序上设置环境变量。

不管你的云提供商是谁,关注配置管理是很重要的。特别是,敏感信息,如数据库密码或用于加密签名 Django 会话的密钥,不能存储在代码中。你还应该记得禁用调试模式,因为这会让你的网站容易受到黑客攻击。但是,在本教程中保持原样,因为您不会有任何自定义内容要显示。

传递这种数据的一种常见方式是环境变量。Heroku 让你通过heroku config命令管理应用程序的环境变量。例如,您可能想从环境变量中读取 Django 密钥,而不是将其硬编码到settings.py文件中。

既然安装了django-heroku,就可以让它处理细节。它检测SECRET_KEY环境变量,并使用它来设置用于加密签名的 Django 秘密密钥。确保密钥的安全至关重要。在portfolio/settings.py中,找到 Django 定义SECRET_KEY变量的自动生成行,并将其注释掉:

# SECURITY WARNING: keep the secret key used in production secret!
# SECRET_KEY = 'django-insecure-#+^6_jx%8rmq9oa(frs7ro4pvr6qn7...

除了注释掉SECRET_KEY变量,您还可以将它一起移除。但是现在不要着急,因为你可能马上就要用到它。

当你现在尝试运行heroku local时,它会抱怨 Django 秘密密钥不再被定义,并且服务器不会启动。要解决这个问题,您可以在当前的终端会话中设置变量,但是更方便的是创建一个名为 .env 的特殊文件,其中包含所有用于本地测试的变量。Heroku CLI 将识别该文件并加载其中定义的环境变量。

注意: Git 不应该跟踪你刚刚创建的.env文件。只要您遵循前面的步骤并使用 gitignore.io 网站,它应该已经列在您的.gitignore文件中了。

生成随机密钥的一种快速方法是使用 OpenSSL 命令行工具:

$ echo "SECRET_KEY=$(openssl rand -base64 32)" > .env

如果您的计算机上没有安装 OpenSSL,而您使用的是 Linux 机器或 macOS,那么您也可以使用 Unix 伪随机数生成器来生成密钥:

$ echo "SECRET_KEY=$(head -c 32 /dev/urandom | base64)" > .env

这两种方法中的任何一种都将确保一个真正随机的密钥。您可能会尝试使用一个不太安全的工具,比如md5sum,并用当前日期作为种子,但是这并不真正安全,因为攻击者可能会枚举可能的输出。

如果上面的命令在您的操作系统上都不起作用,那么暂时取消对portfolio/settings.py中的SECRET_KEY变量的注释,并在您的活动虚拟环境中启动 Django shell:

(portfolio) $ python manage.py shell

在那里,您将能够使用 Django 的内置管理工具生成一个新的随机密钥:

>>> from django.core.management.utils import get_random_secret_key
>>> print(get_random_secret_key())
6aj9il2xu2vqwvnitsg@!+4-8t3%zwr@$agm7x%o%yb2t9ivt%

抓住那个键,用它来设置您的.env文件中的SECRET_KEY变量:

$ echo 'SECRET_KEY=6aj9il2xu2vqwvnitsg@!+4-8t3%zwr@$agm7x%o%yb2t9ivt%' > .env

heroku local命令自动选择在.env文件中定义的环境变量,所以它现在应该像预期的那样工作。如果取消注释,记得再次注释掉SECRET_KEY变量!

最后一步是为远程 Heroku 应用程序指定 Django 密钥:

$ heroku config:set SECRET_KEY='6aj9il2xu2vqwvnitsg@!+4-8t3%zwr@$agm7x%o%yb2t9ivt%'
Setting SECRET_KEY and restarting ⬢ polar-island-08305... done, v3
SECRET_KEY: 6aj9il2xu2vqwvnitsg@!+4-8t3%zwr@$agm7x%o%yb2t9ivt%

这将在远程 Heroku 基础设施上永久设置一个新的环境变量,该变量将立即可供您的 Heroku 应用程序使用。您可以在 Heroku 仪表板中或使用 Heroku CLI 显示这些环境变量:

$ heroku config
=== polar-island-08305 Config Vars
SECRET_KEY: 6aj9il2xu2vqwvnitsg@!+4-8t3%zwr@$agm7x%o%yb2t9ivt%

$ heroku config:get SECRET_KEY
6aj9il2xu2vqwvnitsg@!+4-8t3%zwr@$agm7x%o%yb2t9ivt%

稍后,您可以用另一个值覆盖它或将其完全删除。轮换机密通常是减轻安全威胁的好主意。一旦秘密泄露,你应该迅速改变它,以防止未经授权的访问和限制损害。

Remove ads

发布一个应用程序

您可能已经注意到,用heroku config:set命令配置环境变量会在输出中产生一个特殊的"v3"字符串,类似于版本号。这不是巧合。每次你通过部署新代码或改变配置来修改你的应用,你都在创建一个新的版本,它增加了你之前看到的那个 v 编号。

要按时间顺序列出您的应用发布历史,请再次使用 Heroku CLI:

$ heroku releases
=== polar-island-08305 Releases - Current: v3
v3  Set SECRET_KEY config vars  jdoe@company.com  2021/07/02 14:24:29 +0200 (~ 1h ago)
v2  Enable Logplex              jdoe@company.com  2021/07/02 14:19:56 +0200 (~ 1h ago)
v1  Initial release             jdoe@company.com  2021/07/02 14:19:48 +0200 (~ 1h ago)

列表中的项目从最新到最早排序。版本号总是递增的。即使你将你的应用程序回滚到以前的版本,它也会创建一个新的版本来保存完整的历史。

使用 Heroku 发布新的应用程序可以归结为将代码提交到您的本地 Git 存储库,然后将您的分支推送到远程 Heroku 服务器。但是,在这样做之前,一定要仔细检查git status中是否有任何未提交的更改,并在必要时将它们添加到本地存储库中,例如:

$ git status
On branch master
Changes not staged for commit:
 (use "git add <file>..." to update what will be committed)
 (use "git restore <file>..." to discard changes in working directory)
 modified:   portfolio/settings.py

no changes added to commit (use "git add" and/or "git commit -a")

$ git add .
$ git commit -m "Remove a hardcoded Django secret key"

虽然您可以推送任何本地分支,但是必须将它推送到特定的远程分支才能让部署工作。Heroku 仅从远程 mainmaster 分支部署。如果您已经使用git init命令创建了您的存储库,那么您的默认分支应该被命名为master。或者,如果你在 GitHub 上创建了它,那么它将被命名为main

因为mainmaster分支都存在于远程 Heroku 服务器上,所以您可以使用一种简单的语法来触发构建和部署:

$ git push heroku master

这里,master指的是您的本地和远程分支机构。如果您想推送一个不同的本地分支,那么指定它的名称,比如bugfix/stack-overflow,后跟一个冒号(:)和远程目标分支:

$ git push heroku bugfix/stack-overflow:master

现在让我们将默认分支推到 Heroku,看看接下来会发生什么:

$ git push heroku master
(...)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Building on the Heroku-20 stack
remote: -----> Determining which buildpack to use for this app
remote: -----> Python app detected
remote: -----> Using Python version specified in runtime.txt
remote: -----> Installing python-3.9.6
remote: -----> Installing pip 20.2.4, setuptools 47.1.1 and wheel 0.36.2
remote: -----> Installing SQLite3
remote: -----> Installing requirements with pip
(...)
remote: -----> Compressing...
remote:        Done: 60.6M
remote: -----> Launching...
remote:        Released v6
remote:        https://polar-island-08305.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/polar-island-08305.git
 * [new branch]      master -> master

将代码推送到 Heroku 就像推送到 GitHub、Bitbucket 或其他远程 Git 服务器一样。然而,除此之外,它还开始了构建过程。Heroku 将根据您的项目文件确定正确的构建包。它将使用您的runtime.txt文件中指定的 Python 解释器,并从requirements.txt安装依赖项。

在实践中,更方便的做法是将您的代码只推送到您选择的 Git 服务器,比如 GitHub,并让它通过一个 webhook 触发 Heroku 上的构建。如果你想进一步探索,你可以在 Heroku 的官方文档中阅读关于 GitHub 集成的内容。

**注意:**第一次向 Heroku 推送代码时,可能需要一段时间,因为平台需要启动一个新的 Python 环境,安装依赖项,并为其容器构建一个映像。但是,后续部署会更快,因为安装的依赖项已经被缓存。

您可以在浏览器中导航至 Heroku 应用程序的公共 URL。或者,在您的终端中键入heroku open命令将为您完成这项工作:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Django Project Hosted on a Public Domain

恭喜你!您刚刚公开了您的项目。

Remove ads

步骤 8:建立一个关系数据库

干得好!你几乎已经完成了在 Heroku 上为你的 Django 项目设置主机的工作。这是等式的最后一部分,所以坚持一两分钟。

到目前为止,您一直使用 Django 预先配置的基于文件的 SQLite 数据库。它适合在你的本地计算机上测试,但不能在云中运行。Heroku 有一个临时文件系统,它会忘记自上次部署或服务器重启以来的所有更改。您需要一个独立的数据库引擎来将您的数据保存在云中。

在本教程中,您将使用 Heroku 提供的免费 PostgreSQL 实例作为完全托管的数据库服务。如果需要,可以使用不同的数据库引擎,但是 PostgreSQL 通常不需要额外的配置。

调配 PostgreSQL 服务器

当 Heroku 在你的项目中检测到 Django 框架时,它会自动旋转出一个免费但有限的 PostgreSQL 实例。它用应用程序数据库的公共 URL 设置了DATABASE_URL环境变量。首次部署应用时会进行配置,这可以通过检查启用的附加组件和配置变量来确认:

$ heroku addons

Add-on                                            Plan       Price  State
────────────────────────────────────────────────  ─────────  ─────  ───────
heroku-postgresql (postgresql-trapezoidal-06380)  hobby-dev  free   created
 └─ as DATABASE

The table above shows add-ons and the attachments to the current app (...)

$ heroku config
=== polar-island-08305 Config Vars
DATABASE_URL: postgres://ytfeiommjakmxb...amazonaws.com:5432/dcf99cdrgdaqba
SECRET_KEY:   6aj9il2xu2vqwvnitsg@!+4-8t3%zwr@$agm7x%o%yb2t9ivt%

通常,您需要在portfolio/settings.py中显式地使用该变量,但是因为您安装了django-heroku模块,所以不需要指定数据库 URL 或者用户名和密码。它会自动从环境变量中获取数据库 URL 并为您配置设置。

此外,您不必安装一个数据库驱动程序来连接到 Heroku 提供的 PostgreSQL 实例。另一方面,最好针对生产环境中使用的同一类型的数据库进行本地开发。它促进了您的环境之间的对等性,并让您利用给定数据库引擎提供的高级特性。

当您安装django-heroku时,它已经获取了psycopg2作为一个可传递的依赖项:

(portfolio) $ pip list
Package         Version
--------------- -------
asgiref         3.4.1
dj-database-url 0.5.0
Django          3.2.5
django-heroku   0.3.1
pip             21.1.3
psycopg2        2.9.1 pytz            2021.1
setuptools      56.0.0
sqlparse        0.4.1
whitenoise      5.2.0

psycopg2是 PostgreSQL 数据库的 Python 驱动。由于驱动程序已经存在于您的环境中,您可以立即开始在您的应用程序中使用 PostgreSQL。

在免费的爱好发展计划上,Heroku 设置了一些限制。您最多可以有 10,000 行,必须适合 1 GB 的存储空间。到数据库的连接不能超过 20 个。没有缓存,性能受到限制,还有许多其他限制。

您可以随时使用heroku pg命令查看 Heroku 提供的 PostgreSQL 数据库的详细信息:

$ heroku pg
=== DATABASE_URL
Plan:                  Hobby-dev
Status:                Available
Connections:           1/20
PG Version:            13.3
Created:               2021-07-02 08:55 UTC
Data Size:             7.9 MB
Tables:                0
Rows:                  0/10000 (In compliance) - refreshing
Fork/Follow:           Unsupported
Rollback:              Unsupported
Continuous Protection: Off
Add-on:                postgresql-trapezoidal-06380

这个简短的摘要包含有关当前连接数、数据库大小、表和行数等信息。

在接下来的小节中,您将了解如何在 Heroku 上使用 PostgreSQL 数据库做一些有用的事情。

更新远程数据库模式

当您在 Django 应用程序中定义新模型时,您通常会创建新的迁移文件,并将它们应用于数据库。要更新远程 PostgreSQL 实例的模式,只需在 Heroku 环境中运行与之前相同的迁移命令。稍后您将看到推荐的方法,但是现在,您可以手动运行适当的命令:

$ heroku run python manage.py migrate
Running python manage.py migrate on ⬢ polar-island-08305... up, run.1434 (Free)
Operations to perform:
 Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
 Applying contenttypes.0001_initial... OK
 Applying auth.0001_initial... OK
(...)

run插件启动一个名为一次性 dyno 的临时容器,它类似于一个 Docker 容器,可以访问你的应用程序的源代码及其配置。因为 dynos 运行的是 Linux 容器,所以您可以在其中一个容器中执行任何命令,包括交互式终端会话:

$ heroku run bash
Running bash on ⬢ polar-island-08305... up, run.9405 (Free)
(~) $ python manage.py migrate
Operations to perform:
 Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
 No migrations to apply.

在临时 dyno 中运行 Bash shell 是检查或操作 Heroku 应用程序状态的常见做法。您可以将其视为登录到远程服务器。唯一的区别是,您启动的是一个一次性虚拟机,它包含项目文件的副本,并接收与您的动态 web dyno 相同的环境变量。

然而,这种运行数据库迁移的方式并不是最可靠的,因为您可能会忘记它或者在以后的道路上犯错误。您最好在Procfile中通过添加突出显示的行来自动完成这一步:

web: python manage.py runserver 0.0.0.0:$PORT
release: python manage.py migrate

现在,每次您发布新版本时,Heroku 都会负责应用任何未完成的迁移:

$ git commit -am "Automate remote migrations"
$ git push heroku master
(...)
remote: Verifying deploy... done.
remote: Running release command...
remote:
remote: Operations to perform:
remote:   Apply all migrations: admin, auth, contenttypes, sessions
remote: Running migrations:
remote:   No migrations to apply.
To https://git.heroku.com/polar-island-08305.git
 d9f4c04..ebe7bc5  master -> master

它仍然让您选择是否真正进行任何新的迁移。当您正在进行可能需要一段时间才能完成的大型迁移时,请考虑启用维护模式,以避免在用户使用您的应用时损坏或丢失数据:

$ heroku maintenance:on
Enabling maintenance mode for ⬢ polar-island-08305... done

Heroku 将在维护模式下显示此友好页面:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Heroku App in the Maintenance Mode

完成迁移后,别忘了用heroku maintenance:off禁用它。

填充数据库

通过应用迁移,您已经为 Django 模型创建了数据库表,但是这些表大部分时间都是空的。你迟早会想得到一些数据。与数据库交互的最佳方式是通过 Django admin 接口。要开始使用它,您必须首先远程创建一个超级用户:

$ heroku run python manage.py createsuperuser
Running python manage.py createsuperuser on ⬢ polar-island-08305... up, run.2976 (Free)
Username (leave blank to use 'u23948'): admin
Email address: jdoe@company.com
Password:
Password (again):
Superuser created successfully.

记得在相应的命令前加上heroku run,在连接到你的远程 Heroku 应用程序的数据库中创建超级用户。在为超级用户提供了唯一的名称和安全密码之后,您将能够登录到 Django admin 视图,并开始向您的数据库添加记录。

您可以通过访问位于您唯一的 Heroku 应用程序域名后的/admin路径来访问 Django 管理视图,例如:

https://polar-island-08305.herokuapp.com/admin/

登录后应该是这样的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Django Admin Site on Heroku

直接操纵远程数据库的一个选择是从 Heroku 获取DATABASE_URL变量,并解密它的各个组件,通过您最喜欢的 SQL 客户机进行连接。或者,Heroku CLI 提供了一个方便的psql插件,它像标准 PostgreSQL 交互终端一样工作,但不需要安装任何软件:

$ heroku psql
--> Connecting to postgresql-round-16446
psql (10.17 (Ubuntu 10.17-0ubuntu0.18.04.1), server 13.3 (Ubuntu 13.3-1.pgdg20.04+1))
WARNING: psql major version 10, server major version 13.
 Some psql features might not work.
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

polar-island-08305::DATABASE=> SELECT username, email FROM auth_user;
 username |      email
----------+------------------
 admin    | jdoe@company.com
(1 row)

请注意,heroku psql命令如何将您连接到 Heroku 基础设施上的正确数据库,而不需要任何细节,如主机名、用户名或密码。此外,您不必安装 PostgreSQL 客户端来使用 SQL 查询其中一个表。

作为 Django 开发人员,您可能习惯于依赖它的对象关系映射器(ORM) ,而不是手动输入 SQL 查询。您可以通过在远程 Heroku 应用程序中启动交互式 Django shell 来再次使用 Heroku CLI:

$ heroku run python manage.py shell
Running python manage.py shell on ⬢ polar-island-08305... up, run.9914 (Free)
Python 3.9.6 (default, Jul 02 2021, 15:33:41)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)

接下来,导入内置的User模型,并使用其管理器从数据库中检索相应的用户对象:

>>> from django.contrib.auth.models import User
>>> User.objects.all()
<QuerySet [<User: admin>]>

您应该会看到您之前创建的超级用户。使用 Django shell 可以让您用面向对象的 API 查询连接的数据库。如果你不喜欢默认 shell,那么你可以安装一个替代 Python REPL 比如 IPython 或者 bpython ,Django 会识别它。

好吧,就这样!您在 Heroku 上托管了一个成熟的 Django 项目,并连接了一个关系数据库。例如,你现在可以在 GitHub 上的 README 文件中分享它的公共链接,让全世界欣赏你的作品。

结论

现在,您知道如何将您的想法转化为您的朋友和家人会喜欢的实时 web 应用程序。也许,人力资源部门的人会偶然发现你的一个项目并给你一份工作。注册一个免费的 Heroku 账户来托管你的 Django 代码是进入云计算世界的最好方式之一。

在本教程中,您已经学会了如何:

  • 在几分钟内让你的 Django 项目上线
  • 使用 Git 将您的项目部署到 Heroku
  • 使用 Django-Heroku 集成库
  • 将你的 Django 项目挂接到一个独立的关系数据库上
  • 管理配置以及敏感数据

您可以通过下面的链接下载最终的源代码以及各个步骤的快照:

获取源代码: 点击这里获取 Django 项目以及本教程中各个步骤的快照。

接下来的步骤

本教程仅仅触及了 Heroku 的皮毛。它有意掩盖了许多细节,但 Heroku 提供了更多,即使是有限的免费帐户。如果你想让你的项目更上一层楼,这里有一些想法可以考虑:

  • **配置一个 WSGI 服务器:**在公开您的项目之前,首先要做的是用更安全、更高效的东西替换内置的 Django 开发服务器,比如 Gunicorn。Django 提供了一个方便的部署清单,里面有你可以浏览的最佳实践。

  • **启用日志记录:**在云中运行的应用程序不在您的直接控制之下,这使得调试和故障排除比在本地机器上运行更困难。因此,您应该使用 Heroku 的一个附加组件来启用日志记录。

  • **提供静态文件:**使用外部服务,如亚马逊 S3 或内容交付网络(CDN)来托管静态资源,如 CSS、JavaScript 或图片。这可能会大大减轻您的 web 服务器的负载,并利用缓存加快下载速度。

  • **提供动态内容:**由于 Heroku 的临时文件系统,用户提供给你的应用程序的数据不能作为本地文件保存。使用关系数据库甚至 NoSQL 数据库并不总是最有效或最方便的选择。在这种情况下,你可能想使用外部服务,如亚马逊 S3。

  • **添加自定义域:**默认情况下,你的 Heroku 应用托管在.herokuapp.com域上。虽然它对于业余爱好项目来说既快速又有用,但您可能希望在更专业的环境中使用自定义域。

  • **添加 SSL 证书:**当您定义自定义域时,您必须提供相应的 SSL 证书,以通过 HTTPS 公开您的应用。这是当今世界的必备工具,因为一些网络浏览器供应商已经宣布,他们未来不会显示不安全的网站。

  • **与 GitHub 挂钩:**当一个 pull 请求被合并到主分支时,您可以通过允许 GitHub 触发一个新的构建和发布来自动化您的部署。这减少了手动步骤的数量,并保证了源代码的安全。

  • 使用 Heroku 管道: Heroku 鼓励您以最小的努力遵循最佳实践。它通过可选地自动化测试环境的创建,提供了一个连续的交付工作流。

  • **启用自动扩展:**随着应用的增长,它将需要面对不断增长的资源需求。大多数电子商务平台每年圣诞节前后都会经历一次流量高峰。这个问题的当代解决方案是水平扩展,它将你的应用程序复制成多个副本来满足需求。自动缩放可以在任何需要的时候响应这样的峰值。

  • **拆分成微服务:**当你的项目由多个独立的微服务组成时,水平伸缩效果最好,可以单独伸缩。这种架构可以加快开发速度,但也带来了一些挑战。

  • **从 Heroku 迁移:**一旦你接触了 Heroku,你可能会考虑迁移到另一个云平台,如谷歌应用引擎甚至底层亚马逊基础设施,以降低你的成本。

继续探索 Heroku 网站上的官方文档Python 教程,以找到关于这些主题的更多细节。

立即观看本教程有真实 Python 团队创建的相关视频课程。与书面教程一起观看,以加深您的理解: 在 Heroku 上主持您的 Django 项目*********

Django 迁移:入门

原文:https://realpython.com/django-migrations-a-primer/

*立即观看**本教程有真实 Python 团队创建的相关视频课程。和书面教程一起看,加深理解: 姜戈大迁徙 101

从版本 1.7 开始,Django 就内置了对数据库迁移的支持。在 Django 中,数据库迁移通常与模型密切相关:每当您编写一个新模型时,您还会生成一个迁移以在数据库中创建必要的表。然而,迁移可以做得更多。

通过四篇文章和一个视频,您将了解 Django 迁移是如何工作的,以及如何充分利用它们:

在本文中,您将熟悉 Django 迁移,并了解以下内容:

  • 如何在不编写任何 SQL 的情况下创建数据库表
  • 如何在更改模型后自动修改数据库
  • 如何恢复对数据库所做的更改

免费奖励: 点击此处获取免费的 Django 学习资源指南(PDF) ,该指南向您展示了构建 Python + Django web 应用程序时要避免的技巧和窍门以及常见的陷阱。

迁移解决的问题

如果您是 Django 或 web 开发的新手,您可能不熟悉数据库迁移的概念,也不清楚为什么这是个好主意。

首先,让我们快速定义几个术语,以确保每个人都在同一页上。Django 被设计为使用关系数据库 T1,存储在关系数据库管理系统中,如 T2 的 PostgreSQL T3、T4 的 MySQL T5 或 T6 的 SQLite T7。

在关系数据库中,数据组织在表中。数据库表有一定数量的列,但它可以有任意数量的行。每一列都有一个特定的数据类型,比如某个最大长度的字符串或正整数。对所有表及其列和各自数据类型的描述称为数据库模式。

Django 支持的所有数据库系统都使用 SQL 语言来创建、读取、更新和删除关系数据库中的数据。SQL 还用于创建、更改和删除数据库表本身。

直接使用 SQL 可能很麻烦,所以为了让您的生活更轻松,Django 附带了一个对象关系映射器,简称 ORM。ORM 将关系数据库映射到面向对象编程的世界。你不用 SQL 定义数据库表,而是用 Python 编写 Django 模型。您的模型定义了数据库字段,这些字段对应于它们的数据库表中的列。

下面是一个 Django 模型类如何映射到数据库表的例子:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是仅仅在 Python 文件中定义一个模型类并不能让一个数据库表神奇地凭空出现。创建数据库表来存储 Django 模型是数据库迁移的工作。此外,每当您对模型进行更改时,比如添加一个字段,数据库也必须进行更改。迁移也能解决这个问题。

以下是 Django 迁移让您的生活更轻松的几种方式。

Remove ads

在没有 SQL 的情况下更改数据库

如果没有迁移,您将不得不连接到您的数据库,键入一堆 SQL 命令,或者使用类似于 PHPMyAdmin 的图形工具,在每次您想要更改您的模型定义时修改数据库模式。

在 Django 中,迁移主要是用 Python 编写的,所以除非你有非常高级的用例,否则你不需要了解任何 SQL。

避免重复

创建一个模型,然后编写 SQL 来为它创建数据库表,这将是重复的。

迁移从您的模型中生成,确保您不会重复自己

确保模型定义和数据库模式同步

通常,您有多个数据库实例,例如,团队中的每个开发人员有一个数据库,一个用于测试的数据库和一个包含实时数据的数据库。

如果没有迁移,您将不得不在每个数据库上执行任何模式更改,而且您将不得不跟踪对哪个数据库已经进行了哪些更改。

使用 Django 迁移,您可以轻松地将多个数据库与您的模型保持同步。

在版本控制中跟踪数据库模式变更

像 Git 这样的版本控制系统对于代码来说非常优秀,但是对于数据库模式来说就不那么好了。

因为在 Django 中,迁移是普通的 Python,所以您可以将它们放在版本控制系统中,就像任何其他代码一样。

到目前为止,您有望相信迁移是一个有用且强大的工具。让我们开始学习如何释放这种力量。

建立 Django 项目

在本教程中,您将使用一个简单的比特币追踪器应用程序作为示例项目。

第一步是安装 Django。下面是如何在 Linux 或 macOS X 上使用虚拟环境实现这一点:

$ python3 -m venv env
$ source env/bin/activate
(env) $ pip install "Django==2.1.*"
...
Successfully installed Django-2.1.3

现在,您已经创建了一个新的虚拟环境并激活了它,还在该虚拟环境中安装了 Django。

请注意,在 Windows 上,您将运行env/bin/activate.bat而不是source env/bin/activate来激活您的虚拟环境。

为了更容易阅读,从现在开始,控制台示例将不包括提示的(env)部分。

安装 Django 后,您可以使用以下命令创建项目:

$ django-admin.py startproject bitcoin_tracker
$ cd bitcoin_tracker
$ python manage.py startapp historical_data

这给了你一个简单的项目和一个名为historical_data的应用。您现在应该有这样的目录结构:

bitcoin_tracker/
|
├── bitcoin_tracker/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
|
├── historical_data/
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations/
│   │   └── __init__.py
|   |
│   ├── models.py
│   ├── tests.py
│   └── views.py
|
└── manage.py

bitcoin_tracker目录中,有两个子目录:bitcoin_tracker用于项目范围的文件,而historical_data包含您创建的应用程序的文件。

现在,为了创建一个模型,在historical_data/models.py中添加这个类:

class PriceHistory(models.Model):
    date = models.DateTimeField(auto_now_add=True)
    price = models.DecimalField(max_digits=7, decimal_places=2)
    volume = models.PositiveIntegerField()

这是跟踪比特币价格的基本模型。

另外,不要忘记将新创建的应用程序添加到settings.INSTALLED_APPS。打开bitcoin_tracker/settings.py并将historical_data追加到列表INSTALLED_APPS中,就像这样:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'historical_data',
]

这个项目的其他设置都很好。本教程假设您的项目被配置为使用默认的 SQLite 数据库

Remove ads

创建迁移

创建了模型之后,您需要做的第一件事就是为它创建一个迁移。您可以使用以下命令来完成此操作:

$ python manage.py makemigrations historical_data
Migrations for 'historical_data':
 historical_data/migrations/0001_initial.py
 - Create model PriceHistory

**注意:**指定应用程序的名称historical_data,是可选的。关闭它会为所有应用程序创建迁移。

这将创建迁移文件,指导 Django 如何为应用程序中定义的模型创建数据库表。让我们再看一下目录树:

bitcoin_tracker/
|
├── bitcoin_tracker/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
|
├── historical_data/
│   ├── migrations/
│   │   ├── 0001_initial.py │   │   └── __init__.py
|   |
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
|
├── db.sqlite3
└── manage.py

如您所见,migrations目录现在包含了一个新文件:0001_initial.py

**注意:**您可能会注意到,运行makemigrations命令还会创建文件db.sqlite3,其中包含您的 SQLite 数据库。

当您试图访问一个不存在的 SQLite3 数据库文件时,它将被自动创建。

这种行为是 SQLite3 特有的。如果你使用任何其他数据库后端,如 PostgreSQL 或 MySQL ,你必须在运行makemigrations之前自己创建数据库。

您可以使用 dbshell管理命令来查看数据库。在 SQLite 中,列出所有表的命令很简单.tables:

$ python manage.py dbshell
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> .tables
sqlite>

数据库仍然是空的。当您应用迁移时,这种情况将会改变。键入.quit退出 SQLite shell。

应用迁移

您现在已经创建了迁移,但是要在数据库中进行任何实际的更改,您必须使用管理命令migrate来应用它:

$ python manage.py migrate
Operations to perform:
 Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
 Applying contenttypes.0001_initial... OK
 Applying auth.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 contenttypes.0002_remove_content_type_name... 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 historical_data.0001_initial... OK
 Applying sessions.0001_initial... OK

这里发生了很多事情!根据输出,您的迁移已经成功应用。但是所有其他的迁移来自哪里呢?

还记得INSTALLED_APPS的设定吗?这里列出的一些其他应用程序也带有迁移功能,默认情况下,migrate管理命令会为所有已安装的应用程序应用迁移功能。

再看一下数据库:

$ python manage.py dbshell
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> .tables
auth_group                    django_admin_log
auth_group_permissions        django_content_type
auth_permission               django_migrations
auth_user                     django_session
auth_user_groups              historical_data_pricehistory
auth_user_user_permissions
sqlite>

现在有多个表。他们的名字让你知道他们的目的。您在上一步中生成的迁移已经创建了historical_data_pricehistory表。让我们使用.schema命令对其进行检查:

sqlite> .schema --indent historical_data_pricehistory
CREATE TABLE IF NOT EXISTS "historical_data_pricehistory"(
 "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
 "date" datetime NOT NULL,
 "price" decimal NOT NULL,
 "volume" integer unsigned NOT NULL
);

.schema命令打印出您将执行来创建表的CREATE语句。参数--indent很好地格式化了它。即使您不熟悉 SQL 语法,也可以看出historical_data_pricehistory表的模式反映了PriceHistory模型的字段。

每个字段都有一列,主键有一个额外的列id,Django 会自动创建这个列,除非您在模型中明确指定主键。

如果再次运行migrate命令,会发生以下情况:

$ python manage.py migrate
Operations to perform:
 Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
 No migrations to apply.

没什么!Django 会记住哪些迁移已经被应用,并且不会尝试重新运行它们。

值得注意的是,您还可以将migrate管理命令限制到单个应用程序:

$ python manage.py migrate historical_data
Operations to perform:
 Apply all migrations: historical_data
Running migrations:
 No migrations to apply.

如您所见,Django 现在只为historical_data应用程序应用迁移。

当您第一次运行迁移时,最好应用所有迁移,以确保您的数据库包含您可能认为理所当然的功能(如用户验证和会话)所需的表。

Remove ads

更换型号

你的模型不是一成不变的。随着 Django 项目获得更多特性,您的模型将会改变。您可以添加或删除字段,或者更改它们的类型和选项。

当您更改模型的定义时,用于存储这些模型的数据库表也必须更改。如果您的模型定义与您当前的数据库模式不匹配,您很可能会遇到django.db.utils.OperationalError

那么,如何改变数据库表呢?通过创建和应用迁移。

在测试你的比特币追踪器时,你意识到你犯了一个错误。人们正在出售比特币的一部分,所以字段volume应该是类型DecimalField而不是PositiveIntegerField

让我们将模型更改为如下所示:

class PriceHistory(models.Model):
    date = models.DateTimeField(auto_now_add=True)
    price = models.DecimalField(max_digits=7, decimal_places=2)
 volume = models.DecimalField(max_digits=7, decimal_places=3)

如果没有迁移,您将不得不找出将PositiveIntegerField转换成DecimalField的 SQL 语法。幸运的是,姜戈会帮你处理的。只需告诉它进行迁移:

$ python manage.py makemigrations
Migrations for 'historical_data':
 historical_data/migrations/0002_auto_20181112_1950.py
 - Alter field volume on pricehistory

**注意:**迁移文件的名称(0002_auto_20181112_1950.py)是基于当前时间的,如果您在您的系统上执行,名称会有所不同。

现在,您将此迁移应用到您的数据库:

$ python manage.py migrate
Operations to perform:
 Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
 Applying historical_data.0002_auto_20181112_1950... OK

迁移已成功应用,因此您可以使用dbshell来验证更改是否有效:

$ python manage.py dbshell
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> .schema --indent historical_data_pricehistory
CREATE TABLE IF NOT EXISTS "historical_data_pricehistory" (
 "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
 "date" datetime NOT NULL,
 "price" decimal NOT NULL,
 "volume" decimal NOT NULL );

如果您将新的模式与您之前看到的模式进行比较,您会注意到volume列的类型已经从integer更改为decimal,以反映模型中的volume字段从PositiveIntegerField更改为DecimalField

列出迁移

如果您想知道 Django 项目中存在哪些迁移,您不必深究您安装的应用程序的migrations目录。您可以使用showmigrations命令:

$ ./manage.py showmigrations
admin
 [X] 0001_initial
 [X] 0002_logentry_remove_auto_add
 [X] 0003_logentry_add_action_flag_choices
auth
 [X] 0001_initial
 [X] 0002_alter_permission_name_max_length
 [X] 0003_alter_user_email_max_length
 [X] 0004_alter_user_username_opts
 [X] 0005_alter_user_last_login_null
 [X] 0006_require_contenttypes_0002
 [X] 0007_alter_validators_add_error_messages
 [X] 0008_alter_user_username_max_length
 [X] 0009_alter_user_last_name_max_length
contenttypes
 [X] 0001_initial
 [X] 0002_remove_content_type_name
historical_data
 [X] 0001_initial
 [X] 0002_auto_20181112_1950
sessions
 [X] 0001_initial

这将列出项目中的所有应用程序以及与每个应用程序相关联的迁移。此外,它会在已经应用的迁移旁边加上一个大的X

对于我们的小例子来说,showmigrations命令并不特别令人兴奋,但是当您开始在现有的代码基础上工作或者在一个团队中工作,而您并不是唯一一个添加迁移的人时,它就很方便了。

Remove ads

不应用迁移

现在,您知道了如何通过创建和应用迁移来更改数据库模式。有时,您可能希望撤消更改并切换回以前的数据库模式,因为您:

  • 想测试一个同事写的迁移吗
  • 意识到你所做的改变是个坏主意
  • 并行处理具有不同数据库更改的多个特征
  • 想要恢复数据库仍有旧模式时创建的备份

幸运的是,迁移不一定是单行道。在许多情况下,可以通过取消应用迁移来撤消迁移的效果。要取消应用迁移,您必须在您想要取消应用的迁移的之前,使用应用程序的名称和迁移的名称调用migrate

如果您想在您的historical_data应用程序中恢复迁移0002_auto_20181112_1950,您必须将0001_initial作为参数传递给migrate命令:

$ python manage.py migrate historical_data 0001_initial
Operations to perform:
 Target specific migration: 0001_initial, from historical_data
Running migrations:
 Rendering model states... DONE
 Unapplying historical_data.0002_auto_20181112_1950... OK

迁移尚未应用,这意味着对数据库的更改已被撤销。

取消应用迁移不会删除其迁移文件。下次运行migrate命令时,将再次应用迁移。

**注意:**不要将未应用的迁移与您习惯使用的文本编辑器中的撤销操作相混淆。

并非所有数据库操作都可以完全还原。如果您从模型中删除一个字段,创建一个迁移,并应用它,Django 将从数据库中删除相应的列。

不应用该迁移将重新创建该列,但不会恢复存储在该列中的数据!

当您处理迁移名称时,Django 不会强迫您拼出迁移的全名,从而为您节省了一些击键时间。它只需要足够的名称来唯一地标识它。

在前面的例子中,运行python manage.py migrate historical_data 0001就足够了。

命名迁移

在上面的例子中,Django 根据时间戳为迁移起了一个名字,类似于*0002_auto_20181112_1950。如果您对此不满意,那么您可以使用--name参数来提供一个自定义名称(不带.py扩展名)。

要尝试这一点,您首先必须删除旧的迁移。您已经取消应用了它,因此您可以安全地删除该文件:

$ rm historical_data/migrations/0002_auto_20181112_1950.py

现在,您可以用一个更具描述性的名称重新创建它:

$ ./manage.py makemigrations historical_data --name switch_to_decimals

这将创建与之前相同的迁移,只是使用了新名称0002_switch_to_decimals

结论

您在本教程中涉及了相当多的内容,并且学习了 Django 迁移的基础知识。

概括一下,使用 Django 迁移的基本步骤如下:

  1. 创建或更新模型
  2. 运行./manage.py makemigrations <app_name>
  3. 运行./manage.py migrate迁移所有应用程序,或运行./manage.py migrate <app_name>迁移单个应用程序
  4. 必要时重复

就是这样!这个工作流程在大多数情况下是可行的,但是如果事情没有按预期进行,您也知道如何列出和取消应用迁移。

如果您以前使用手写的 SQL 创建和修改数据库表,那么现在通过将这项工作委托给 Django 迁移,您会变得更加高效。

在本系列的下一篇教程中,您将深入探讨这个主题,并了解 Django 迁移如何在幕后工作。

免费奖励: 点击此处获取免费的 Django 学习资源指南(PDF) ,该指南向您展示了构建 Python + Django web 应用程序时要避免的技巧和窍门以及常见的陷阱。

干杯!

Remove ads

视频

https://www.youtube.com/embed/7PiyO-N6Pho?autoplay=1&modestbranding=1&rel=0&showinfo=0&origin=https://realpython.com

立即观看**本教程有真实 Python 团队创建的相关视频课程。和书面教程一起看,加深理解: 姜戈大迁徙 101******

使用 Gunicorn、Nginx 和 HTTPS 安全部署 Django 应用程序

原文:https://realpython.com/django-nginx-gunicorn/

*立即观看**本教程有真实 Python 团队创建的相关视频课程。配合文字教程一起看,加深理解: 用 Gunicorn 和 Nginx 部署一个 Django App

将一个 Django 应用从开发到生产是一个要求很高但很值得的过程。本教程将带您一步一步地完成这一过程,提供一个深入的指南,从一个简单的 Django 应用程序开始,并添加了 GunicornNginx域注册和关注安全性的 HTTP 头。阅读完本教程后,您将更好地准备将您的 Django 应用程序投入生产并向全世界提供服务。

在本教程中,您将学习:

  • 如何将 Django 应用从开发带入生产
  • 你如何在现实世界的公共领域托管你的应用程序
  • 如何将 GunicornNginx 引入请求和响应链
  • HTTP headers 如何加强你网站的 HTTPS 安全

为了充分利用本教程,您应该对 Python 、、Django 以及 HTTP 请求的高级机制有一个初级的理解。

您可以通过下面的链接下载本教程中使用的 Django 项目:

获取源代码: 点击此处获取本教程中使用的配套 Django 项目

从 Django 和 WSGIServer 开始

您将使用 Django 作为 web 应用程序的核心框架,使用它进行 URL 路由、HTML 呈现、认证、管理和后端逻辑。在本教程中,您将使用另外两个层来补充 Django 组件, GunicornNginx ,以便可伸缩地服务于应用程序。但是在这之前,您需要设置您的环境,并让 Django 应用程序自己启动并运行。

Remove ads

设置云虚拟机

首先,您需要启动并设置一个虚拟机(VM) ,web 应用程序将在其上运行。您应该熟悉至少一家基础设施即服务(IaaS) 云服务提供商来供应虚拟机。这一部分将在较高层次上引导您完成这一过程,但不会详细介绍每一步。

使用虚拟机为 web 应用提供服务是 IaaS 的一个例子,在这种情况下,您可以完全控制服务器软件。除了 IaaS 之外,还存在其他选择:

  • 一个无服务器架构允许你只编写 Django 应用程序,让一个独立的框架或云提供商处理基础设施方面。
  • 一种容器化的方法允许多个应用程序在同一个主机操作系统上独立运行。

不过,对于本教程,您将使用直接在 IaaS 上服务 Nginx 和 Django 的可靠方法。

虚拟机的两个流行选项是 Azure VMsAmazon EC2 。要获得启动实例的更多帮助,您应该参考云提供商的文档:

Django 项目和本教程中涉及的所有东西都位于运行 Ubuntu Server 20.04 的一个 t2.micro Amazon EC2 实例上。

虚拟机设置的一个重要组成部分是入站安全规则。这些是控制实例入站流量的细粒度规则。为初始开发创建以下入站安全规则,您将在生产中修改这些规则:

参考类型草案端口范围来源
one习俗传输控制协议(Transmission Control Protocol)Eight thousandmy-laptop-ip-address/32
Two习俗全部全部security-group-id
three传输控制协议(Transmission Control Protocol)Twenty-twomy-laptop-ip-address/32

现在,您将一次浏览一个:

  1. 规则 1 允许 TCP 从你的个人电脑的 IPv4 地址通过端口 8000,允许你在通过端口 8000 开发 Django 应用程序时向它发送请求。
  2. 规则 2 使用安全组 ID 作为来源,允许来自分配给同一安全组的网络接口和实例的入站流量。这是默认 AWS 安全组中包含的一个规则,您应该将它绑定到您的实例。
  3. 规则 3 允许你从个人电脑通过 SSH 访问你的虚拟机。

您还需要添加一个出站规则来允许出站流量做一些事情,比如安装包:

类型草案端口范围来源
习俗全部全部0.0.0.0/0

综上所述,您的初始 AWS 安全规则集可以由三个入站规则和一个出站规则组成。这些权限依次来自三个独立的安全组——默认组、HTTP 访问组和 SSH 访问组:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Initial security group rule set

然后,您可以从本地计算机 SSH 进入实例:

$ ssh -i ~/.ssh/<privkey>.pem ubuntu@<instance-public-ip-address>

这个命令让您以用户ubuntu的身份登录到您的虚拟机。这里,~/.ssh/<privkey>.pem是到私有密钥的路径,私有密钥是您绑定到虚拟机的一组安全凭证的一部分。VM 是 Django 应用程序代码所在的地方。

至此,您应该已经准备好继续构建您的应用程序了。

Remove ads

创建一个千篇一律的 Django 应用程序

在本教程中,您并不关心用复杂的 URL 路由或高级数据库特性来制作一个花哨的 Django 项目。相反,您需要简单、小巧、易懂的东西,让您能够快速测试您的基础设施是否正常工作。

为此,您可以采取以下步骤来设置您的应用程序。

首先,SSH 到您的虚拟机,并确保您安装了 Python 3.8 和 SQLite3 的最新补丁版本:

$ sudo apt-get update -y
$ sudo apt-get install -y python3.8 python3.8-venv sqlite3
$ python3 -V
Python 3.8.10

在这里,Python 3.8 就是系统 Python ,或者 Ubuntu 20.04 (Focal)自带的python3版本。升级发行版可确保您从最新的 Python 3.8.x 版本中获得错误和安全修复。可选地,您可以安装另一个完整的 Python 版本——比如python3.9—以及系统范围的解释器,您需要以python3.9的身份显式调用它。

接下来,创建并激活一个虚拟环境:

$ cd  # Change directory to home directory
$ python3 -m venv env
$ source env/bin/activate

现在,安装 Django 3.2:

$ python -m pip install -U pip 'django==3.2.*'

现在,您可以使用 Django 的管理命令来引导 Django 项目和应用程序:

$ mkdir django-gunicorn-nginx/
$ django-admin startproject project django-gunicorn-nginx/
$ cd django-gunicorn-nginx/
$ django-admin startapp myapp
$ python manage.py migrate
$ mkdir -pv myapp/templates/myapp/

这将创建 Django 应用程序myapp以及名为project的项目:

/home/ubuntu/
│
├── django-gunicorn-nginx/
│    │
│    ├── myapp/
│    │   ├── admin.py
│    │   ├── apps.py
│    │   ├── __init__.py
│    │   ├── migrations/
│    │   │   └── __init__.py
│    │   ├── models.py
│    │   ├── templates/
│    │   │   └── myapp/
│    │   ├── tests.py
│    │   └── views.py
│    │
│    ├── project/
│    │   ├── asgi.py
│    │   ├── __init__.py
│    │   ├── settings.py
│    │   ├── urls.py
│    │   └── wsgi.py
|    |
│    ├── db.sqlite3
│    └── manage.py
│
└── env/  ← Virtual environment

使用终端编辑器,如 VimGNU nano ,打开project/settings.py并将您的应用程序添加到INSTALLED_APPS:

# project/settings.py
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
 "myapp", ]

接下来,打开myapp/templates/myapp/home.html并创建一个简短的 HTML 页面:

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>My secure app</title>
  </head>
  <body>
    <p>Now this is some sweet HTML!</p>
  </body>
</html>

之后,编辑myapp/views.py来呈现 HTML 页面:

from django.shortcuts import render

def index(request):
    return render(request, "myapp/home.html")

现在创建并打开myapp/urls.py,将您的视图与 URL 模式关联起来:

from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),
]

之后,相应地编辑project/urls.py:

from django.urls import include, path

urlpatterns = [
    path("myapp/", include("myapp.urls")),
    path("", include("myapp.urls")),
]

你还可以做另外一件事,那就是确保用于加密签名的 Django 秘密密钥没有被硬编码到settings.py中,Git 很可能会跟踪它。从project/settings.py中删除以下行:

SECRET_KEY = "django-insecure-o6w@a46mx..."  # Remove this line

将其替换为以下内容:

import os

# ...

try:
    SECRET_KEY = os.environ["SECRET_KEY"]
except KeyError as e:
    raise RuntimeError("Could not find a SECRET_KEY in environment") from e

这告诉 Django 在您的环境中寻找SECRET_KEY,而不是将它包含在您的应用程序源代码中。

注意:对于较大的项目,请查看 django-environ 来配置 Django 应用程序的环境变量。

最后,在您的环境中设置密钥。下面是如何在 Ubuntu Linux 上使用 OpenSSL 将密钥设置为 80 个字符的字符串:

$ echo "export SECRET_KEY='$(openssl rand -hex 40)'" > .DJANGO_SECRET_KEY
$ source .DJANGO_SECRET_KEY

您可以从.DJANGO_SECRET_KEY的内容cat中看到 openssl 已经生成了一个密码安全的十六进制字符串密钥:

$ cat .DJANGO_SECRET_KEY
export SECRET_KEY='26a2d2ccaf9ef850...'

好的,你都准备好了。这就是拥有一个最低功能的 Django 应用程序所需要的一切。

Remove ads

在开发中使用 Django 的 wsgi server

在本节中,您将使用 httpie 测试 Django 的开发 web 服务器,这是一个非常棒的命令行 HTTP 客户端,用于测试从控制台到您的 web 应用程序的请求:

$ pwd
/home/ubuntu
$ source env/bin/activate
$ python -m pip install httpie

您可以创建一个别名,它将允许您使用httpie向您的应用程序发送一个GET请求:

$ # Send GET request and follow 30x Location redirects
$ alias GET='http --follow --timeout 6'

这用一些默认标志将GET别名为http调用。现在,您可以使用GET docs.python.org在 Python 文档的主页上查看响应头和主体。

在启动 Django 开发服务器之前,您可以检查您的 Django 项目是否存在潜在的问题:

$ cd django-gunicorn-nginx/
$ python manage.py check
System check identified no issues (0 silenced).

如果您的检查没有发现任何问题,那么告诉 Django 的内置应用服务器开始监听本地主机,使用默认端口 8000:

$ # Listen on 127.0.0.1:8000 in the background
$ nohup python manage.py runserver &
$ jobs -l
[1]+ 43689 Running                 nohup python manage.py runserver &

使用nohup <command> &在后台执行command,这样您就可以继续使用您的 shell。您可以使用jobs -l来查看进程标识符(PID) ,这将让您将进程带到前台或终止它。nohup标准输出(stdout)标准错误(stderr) 重定向到文件nohup.out

注意:如果出现nohup挂起,让你没有光标,按 Enter 可以找回你的终端光标和 shell 提示符。

Django 的 runserver 命令依次使用以下语法:

$ python manage.py runserver [address:port]

如果像上面那样不指定参数address:port,Django 将默认监听localhost:8000。您还可以使用lsof命令更直接地验证是否调用了python命令来监听端口 8000:

$ sudo lsof -n -P -i TCP:8000 -s TCP:LISTEN
COMMAND   PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
python  43689 ubuntu    4u  IPv4  45944      0t0  TCP 127.0.0.1:8000 (LISTEN)

在教程的这一点上,你的应用程序只监听本地主机,也就是地址127.0.0.1。它还不能从浏览器中访问,但是您仍然可以通过从 VM 本身的命令行向它发送一个GET请求来给它第一个访问者:

$ GET :8000/myapp/
HTTP/1.1 200 OK
Content-Length: 182
Content-Type: text/html; charset=utf-8
Date: Sat, 25 Sep 2021 00:11:38 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.10 X-Content-Type-Options: nosniff
X-Frame-Options: DENY

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>My secure app</title>
  </head>
  <body>
    <p>Now this is some sweet HTML!</p>
  </body>
</html>

标题Server: WSGIServer/0.2 CPython/3.8.10描述了生成响应的软件。在这种情况下,是 0.2 版本的WSGIServer的 CPython 3.8.10

WSGIServer只不过是由 Django 定义的实现 Python WSGI 协议的 Python 类。这意味着它遵循了 Web 服务器网关接口(WSGI) ,这是一个定义了 web 服务器软件web 应用交互方式的标准。

在我们到目前为止的例子中,django-gunicorn-nginx/项目是 web 应用程序。因为你正在开发应用程序,所以实际上没有单独的网络服务器。Django 使用了 simple_server 模块,该模块实现了一个轻量级的 HTTP 服务器,并将 web 服务器和应用服务器的概念融合到一个命令runserver中。

接下来,您将看到如何通过将您的应用程序与现实世界的域相关联,开始向大时代介绍您的应用程序。

Remove ads

用 Django、Gunicorn 和 Nginx 把你的网站放到网上

此时,您的站点可以在虚拟机上进行本地访问。如果你想让你的网站在一个真实的网址上被访问,你需要申请一个域名,并把它绑定到网络服务器上。这对于启用 HTTPS 也是必要的,因为一些证书颁发机构不会为您不拥有的空白 IP 地址或子域颁发证书。在本节中,您将看到如何注册和配置域。

设置静态公共 IP 地址

如果您可以将您的域配置指向一个保证不会改变的公共 IP 地址,那就太理想了。云虚拟机的一个次优属性是,如果实例处于停止状态,它们的公共 IP 地址可能会改变。或者,如果出于某种原因,您需要用新的实例替换现有的虚拟机,那么 IP 地址的变化将会带来问题。

这种困境的解决方案是将静态 IP 地址绑定到实例:

遵循云提供商的文档将静态 IP 地址与您的云虚拟机相关联。在本教程示例所用的 AWS 环境中,弹性 IP 地址50.19.125.152与 EC2 实例相关联。

注意:记住,这意味着您需要更改ssh的目标 IP,以便通过 SSH 进入您的虚拟机:

$ ssh [args] my-new-static-public-ip

更新目标 IP 后,您将能够连接到您的云虚拟机。

有了一个更稳定的公共 IP,您就可以链接到一个域了。

链接到一个域

在本节中,您将了解如何购买、设置域名,以及如何将域名链接到您现有的应用程序。

这些例子使用了 Namecheap ,但是请不要认为这是明确的认可。还有很多其他的选择,比如 domain.com 的的【GoDaddy】、的谷歌域名。就偏好而言,Namecheap 为成为本教程中的首选域名注册商支付了 0 美元。

警告:如果你想在DEBUG设置为True的公共域上为你的开发站点提供服务,你需要创建自定义的入站安全规则,只允许你的个人电脑和虚拟机的 IP 地址。你应该而不是0.0.0.0开放任何 HTTP 或 HTTPS 的入站规则,直到你至少关闭了 DEBUG

以下是您可以开始的方式:

  1. Namecheap 上创建一个账户,确保设置双因素认证(2FA)。
  2. 从主页开始搜索适合你预算的域名。你会发现顶级域名(TLD)和主机名的价格差别很大。
  3. 当你对选择满意时,购买域名

本教程使用了域supersecure.codes,但是你也有自己的域。

注意:当你阅读本教程时,请记住supersecure.codes只是一个示例域,并没有被主动维护。

当挑选自己的域名时,请记住,选择一个更深奥的网站名称和顶级域名(TLD)通常会导致更便宜的标价购买该域名。这对于测试尤其有用。

一旦你有了自己的域名,你会想要开启的私有域名保护,正式名称为 WhoisGuard 。当有人在您的域名上运行 whois 搜索时,这会屏蔽您的个人信息。下面是如何做到这一点:

  1. 选择账户→域名列表
  2. 选择您的域旁边的管理
  3. 启用保护隐私

接下来,是时候为您的站点设置 DNS 记录表了。每个 DNS 记录都将成为数据库中的一行,告诉浏览器一个完全合格的域名(FQDN) 指向的底层 IP 地址。在这种情况下,我们希望supersecure.codes路由到 50.19.125.152,即可以访问虚拟机的公共 IPv4 地址:

  1. 选择账户→域名列表
  2. 选择您的域旁边的管理
  3. 选择高级 DNS
  4. 主机记录下,为您的域添加两条 A 记录

如下添加 A 记录,用实例的公共 IPv4 地址替换50.19.125.152:

类型主持价值晶体管-晶体管逻辑。
一项记录@50.19.125.152自动的
一项记录www50.19.125.152自动的

一个 A 记录允许您将一个域名或子域与您为应用程序提供服务的 web 服务器的 IPv4 地址相关联。上面的字段应该使用 VM 实例的公共 IPv4 地址。

您可以看到主机字段有两种变体:

  1. 利用 @ 指向的根域,本例中为supersecure.codes
  2. 使用 www 意味着www.supersecure.codes将指向与刚才supersecure.codes相同的地方。从技术上讲,www的一个子域,它可以将用户发送到与更短的supersecure.codes相同的地方。

一旦您设置了 DNS 主机记录表,您需要等待 30 分钟路由才能生效。您现在可以终止现有的runserver进程:

$ jobs -l
[1]+ 43689 Running                 nohup python manage.py runserver &
$ kill 43689
[1]+  Done                    nohup python manage.py runserver

您可以通过pgrep或再次检查活动工单来确认流程已结束:

$ pgrep runserver  # Empty
$ jobs -l  # Empty or 'Done'
$ sudo lsof -n -P -i TCP:8000 -s TCP:LISTEN  # Empty
$ rm nohup.out

有了这些东西,你还需要调整 Django 设置, ALLOWED_HOSTS ,这是你让你的 Django 应用服务的域名集合:

# project/settings.py
# Replace 'supersecure.codes' with your domain
ALLOWED_HOSTS = [".supersecure.codes"]

前导点(.)是一个子域通配符,允许www.supersecure.codessupersecure.codes。保持这个列表以防止 HTTP 主机头攻击

现在,只需做一点小小的改动,就可以重新启动 WSGIServer 了:

$ nohup python manage.py runserver '0.0.0.0:8000' &

注意 address:port 参数现在是0.0.0.0:8000,而之前没有指定:

  • 指定 no address:port意味着在localhost:8000提供应用。这意味着应用程序只能从虚拟机内部访问。你可以从同一个 IP 地址通过调用httpie与它对话,但是你不能从外部世界访问你的应用程序。

  • 指定'0.0.0.0:8000'address:port使您的服务器对外界可见,尽管默认情况下仍然在端口 8000 上。 0.0.0.0是“绑定到这台计算机支持的所有 IP 地址”的简写在带有一个名为eth0网络接口控制器(NIC) 的现成云虚拟机的情况下,使用0.0.0.0充当机器的公共 IPv4 地址的替身。

接下来,打开来自nohup.out的输出,查看来自 Django 的 WSGIServer 的任何输入日志:

$ tail -f nohup.out

现在是关键时刻了。是时候让你的网站迎来第一个访问者了。从您的个人计算机,在 web 浏览器中输入以下 URL:

http://www.supersecure.codes:8000/myapp/

用您自己的域名替换上面的域名。您应该会看到该页面的快速响应:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由于您之前创建的入站安全规则,您可以访问此 URL,但其他人不可以。

如果您无法访问您的网站,可能有几个常见的原因:

  • 如果连接挂断,检查您是否已经打开了一个入站安全规则来允许my-laptop-ip-address/32使用TCP:8000
  • 如果连接显示为拒绝无法连接,请检查您是否调用了manage.py runserver 0.0.0.0:8000而不是127.0.0.1:8000

现在回到你的虚拟机的外壳。在tail -f nohup.out的连续输出中,你应该看到类似这样的线:

[<date>] "GET /myapp/ HTTP/1.1" 200 182

恭喜你,你已经朝着拥有自己的网站迈出了重要的第一步!然而,在这里暂停一下,注意 URL http://www.supersecure.codes:8000/myapp/中嵌入的几个大问题:

  • 该网站仅通过 HTTP 提供服务。如果不启用 HTTPS,如果你想在客户端和服务器之间传输任何敏感数据,你的网站就根本不安全。使用 HTTP 意味着请求和响应以纯文本形式发送。你很快就会解决的。

  • URL 使用非标准端口 8000 ,而不是标准的默认 HTTP 端口号 80。很不落俗套,有点碍眼,但是你还不能用 80。这是因为端口 80 是有特权的,非根用户不能也不应该绑定到它。稍后,您将引入一个工具,允许您的应用程序在端口 80 上可用。

如果你检查你的浏览器,你会看到你的浏览器地址栏暗示这一点。如果您使用的是 Firefox,将会出现一个红色锁图标,表示连接是通过 HTTP 而不是 HTTPS 进行的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

展望未来,你想让行动合法化。您可以开始通过 HTTP 的标准端口 80 提供服务。更好的是,开始服务 HTTPS (443)并将 HTTP 请求重定向到那里。您将很快看到如何完成这些步骤。

Remove ads

用 Gunicorn 替换 WSGIServer】

您是否希望开始将您的应用迁移到一个为外部世界做好准备的状态?如果是这样,那么你应该把 Django 内置的 WSGIServer,也就是manage.py runserver使用的应用服务器,换成一个单独的专用应用服务器。但是等一下:WSGIServer 似乎工作得很好。为什么要更换?

要回答这个问题,您可以阅读 Django 文档:

不要在生产环境中使用此服务器。它没有通过安全审计或性能测试。(这就是它将如何停留。我们从事的是制作 Web 框架的业务,而不是 Web 服务器,所以改进这个服务器以便能够处理生产环境超出了 Django 的范围。)(来源)

Django 是一个网络框架,而不是一个网络服务器,它的维护者想要清楚地区分这两者。在本节中,您将把 Django 的runserver命令替换为 Gunicorn 。Gunicorn 首先是一个 Python WSGI 应用服务器,而且是一个久经考验的服务器:

  • 它速度快,经过优化,专为生产而设计。
  • 它为您提供了对应用服务器本身更细粒度的控制。
  • 它有更完整和可配置的日志记录。
  • 它已经过测试,特别是作为应用服务器的功能。

您可以通过 pip 将 Gunicorn 安装到您的虚拟环境中:

$ pwd
/home/ubuntu
$ source env/bin/activate
$ python -m pip install 'gunicorn==20.1.*'

接下来,您需要做一些配置。一个 Gunicorn 配置文件的酷之处在于它只需要是有效的 Python 代码,变量名对应于参数。您可以在一个项目子目录中存储多个 Gunicorn 配置文件:

$ cd ~/django-gunicorn-nginx
$ mkdir -pv config/gunicorn/
mkdir: created directory 'config'
mkdir: created directory 'config/gunicorn/'

接下来,打开一个开发配置文件config/gunicorn/dev.py,并添加以下内容:

"""Gunicorn *development* config file"""

# Django WSGI application path in pattern MODULE_NAME:VARIABLE_NAME
wsgi_app = "project.wsgi:application"
# The granularity of Error log outputs
loglevel = "debug"
# The number of worker processes for handling requests
workers = 2
# The socket to bind
bind = "0.0.0.0:8000"
# Restart workers when code changes (development only!)
reload = True
# Write access and error info to /var/log
accesslog = errorlog = "/var/log/gunicorn/dev.log"
# Redirect stdout/stderr to log file
capture_output = True
# PID file so you can easily fetch process ID
pidfile = "/var/run/gunicorn/dev.pid"
# Daemonize the Gunicorn process (detach & enter background)
daemon = True

在启动 Gunicorn 之前,您应该暂停runserver进程。使用jobs找到它,使用kill停止它:

$ jobs -l
[1]+ 26374 Running                 nohup python manage.py runserver &
$ kill 26374
[1]+  Done                    nohup python manage.py runserver

接下来,确保上面 Gunicorn 配置文件中设置的值的日志和 PID 目录存在:

$ sudo mkdir -pv /var/{log,run}/gunicorn/
mkdir: created directory '/var/log/gunicorn/'
mkdir: created directory '/var/run/gunicorn/'
$ sudo chown -cR ubuntu:ubuntu /var/{log,run}/gunicorn/
changed ownership of '/var/log/gunicorn/' from root:root to ubuntu:ubuntu
changed ownership of '/var/run/gunicorn/' from root:root to ubuntu:ubuntu

使用这些命令,您已经确保了 Gunicorn 所需的 PID 和日志目录存在,并且它们可由ubuntu用户写入。

这样一来,您就可以使用-c标志启动 Gunicorn,从您的项目根目录指向一个配置文件:

$ pwd
/home/ubuntu/django-gunicorn-nginx
$ source .DJANGO_SECRET_KEY
$ gunicorn -c config/gunicorn/dev.py

这在后台运行gunicorn,带有您在上面指定的开发配置文件dev.py。和以前一样,现在可以监视输出文件,查看 Gunicorn 记录的输出:

$ tail -f /var/log/gunicorn/dev.log
[2021-09-27 01:29:50 +0000] [49457] [INFO] Starting gunicorn 20.1.0
[2021-09-27 01:29:50 +0000] [49457] [DEBUG] Arbiter booted
[2021-09-27 01:29:50 +0000] [49457] [INFO] Listening at: http://0.0.0.0:8000 (49457)
[2021-09-27 01:29:50 +0000] [49457] [INFO] Using worker: sync
[2021-09-27 01:29:50 +0000] [49459] [INFO] Booting worker with pid: 49459
[2021-09-27 01:29:50 +0000] [49460] [INFO] Booting worker with pid: 49460
[2021-09-27 01:29:50 +0000] [49457] [DEBUG] 2 workers

现在在浏览器中再次访问你的站点的 URL。您仍然需要 8000 端口:

http://www.supersecure.codes:8000/myapp/

再次检查您的虚拟机终端。您应该会在 Gunicorn 的日志文件中看到一行或多行,如下所示:

67.xx.xx.xx - - [27/Sep/2021:01:30:46 +0000] "GET /myapp/ HTTP/1.1" 200 182

这几行是访问日志,告诉您关于传入请求的信息:

成分意义
67.xx.xx.xx用户 IP 地址
27/Sep/2021:01:30:46 +0000请求的时间戳
GET请求方法
/myapp/path
HTTP/1.1草案
200响应状态代码
182响应内容长度

为了简洁起见,上面排除了用户代理,它也可能出现在您的日志中。下面是 macOS 上的 Firefox 浏览器的一个例子:

Mozilla/5.0 (Macintosh; Intel Mac OS X ...) Gecko/20100101 Firefox/92.0

随着 Gunicorn 的出现和收听,是时候将合法的 web 服务器也引入等式中了。

Remove ads

并入 Nginx

此时,您已经将 Django 的runserver命令换成了作为应用服务器的gunicorn。请求链中又多了一个玩家:像 T4 Nginx T5 这样的 T2 网络服务器 T3。

等等,你已经添加了 Gunicorn!为什么需要在画面中加入新的东西?之所以会这样,是因为 Nginx 和 Gunicorn 是两回事,它们是共存的,是作为一个团队工作的。

Nginx 将自己定义为高性能 web 服务器和反向代理服务器。这是值得的,因为这有助于解释 Nginx 与 Gunicorn 和 Django 的关系。

首先,Nginx 是一个 web 服务器,因为它可以向 web 用户或客户端提供文件。文件是文字文档:HTML、CSS、PNG、PDF——应有尽有。在过去,在 Django 等框架出现之前,网站基本上是作为文件系统的直接视图来运行的,这是很常见的。在 URL 路径中,斜线表示服务器文件系统中您可以请求查看的有限部分的目录。

请注意术语上的细微差别:

  • Django 是一个 web 框架。它允许您构建支持站点实际内容的核心 web 应用程序。它处理 HTML 呈现、认证、管理和后端逻辑。

  • Gunicorn 是一个应用服务器。它将 HTTP 请求翻译成 Python 可以理解的东西。Gunicorn 实现了 web 服务器网关接口(WSGI) ,这是 Web 服务器软件和 Web 应用程序之间的标准接口。

  • Nginx 是一个网络服务器。它是公共处理器,更正式的名称是反向代理,用于接收请求并扩展到数千个并发连接。

Nginx 作为网络服务器的一部分作用是它可以更有效地服务静态文件。这意味着,对于像图片这样的静态内容的请求,你可以省去 Django 这个中间人,让 Nginx 直接呈现文件。我们将在教程的后面到达这个重要的步骤。

Nginx 也是一个反向代理服务器,它位于外部世界和 Gunicorn/Django 应用程序之间。就像您可能使用代理发出出站请求一样,您可以使用 Nginx 这样的代理来接收它们:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image: Real Python

要开始使用 Nginx,请安装它并验证其版本:

$ sudo apt-get install -y 'nginx=1.18.*'
$ nginx -v  # Display version info
nginx version: nginx/1.18.0 (Ubuntu)

然后,您应该将您为端口 8000 设置的入站允许规则更改为端口 80。用以下内容替换TCP:8000的入站规则:

类型草案端口范围来源
超文本传送协议传输控制协议(Transmission Control Protocol)Eightymy-laptop-ip-address/32

其他规则,比如 SSH 访问规则,应该保持不变。

现在,启动nginx服务并确认其状态为running:

$ sudo systemctl start nginx
$ sudo systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
 Loaded: loaded (/lib/systemd/system/nginx.service; enabled; ...
 Active: active (running) since Mon 2021-09-27 01:37:04 UTC; 2min 49s ago
...

现在,您可以向一个熟悉的 URL 发出请求:

http://supersecure.codes/

这与你以前的情况相比有很大的不同。您不再需要 URL 中的端口 8000。相反,端口默认为端口 80,这看起来正常得多:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是 Nginx 的一个友好特性。如果您在零配置的情况下启动 Nginx,它会向您显示一个页面,表明它正在监听。现在试试下面网址的/myapp页面:

http://supersecure.codes/myapp/

记得把supersecure.codes换成自己的域名。

您应该会看到 404 响应,这没关系:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是因为您正在请求端口 80 上的/myapp路径,Nginx 而不是 Gunicorn 正在侦听端口 80。此时,您有了以下设置:

  • Nginx 正在监听端口 80。
  • Gunicorn 单独监听端口 8000。

在您指定之前,这两者之间没有任何联系。Nginx 不知道 Gunicorn 和 Django 有一些想让全世界看到的甜蜜 HTML。这就是它返回一个404 Not Found响应的原因。您还没有设置对 Gunicorn 和 Django 的代理请求:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image: Real Python

您需要给 Nginx 一些基本的配置,告诉它将请求路由到 Gunicorn,然后 guni corn 将请求提供给 Django。打开/etc/nginx/sites-available/supersecure,添加以下内容:

server_tokens  off; access_log  /var/log/nginx/supersecure.access.log; error_log  /var/log/nginx/supersecure.error.log; # This configuration will be changed to redirect to HTTPS later
server  { server_name  .supersecure.codes;   listen  80; location  /  { proxy_pass  http://localhost:8000; proxy_set_header  Host  $host; } }

请记住,您需要将文件名中的supersecure替换为您站点的主机名,并确保将.supersecure.codesserver_name值替换为您自己的域名,前缀为一个点。

注意:你可能需要sudo来打开/etc下的文件。

这个文件是 Nginx 反向代理配置的“Hello World”。它告诉 Nginx 如何操作:

  • 在端口 80 上监听使用主机的请求supersecure.codes及其子域。
  • 将这些请求传递给http://localhost:8000,Gunicorn 正在那里监听。

proxy_set_header 字段很重要。它确保 Nginx 通过终端用户发送的Host HTTP 请求头到达 Gunicorn 和 Django。Nginx 默认使用Host: localhost,忽略终端用户浏览器发送的Host头字段。

您可以使用nginx configtest验证您的配置文件:

$ sudo service nginx configtest /etc/nginx/sites-available/supersecure
 * Testing nginx configuration                                  [ OK ]

[ OK ]输出表明配置文件是有效的,可以被解析。

现在你需要符号链接这个文件到sites-enabled目录,用你的站点域替换supersecure:

$ cd /etc/nginx/sites-enabled
$ # Note: replace 'supersecure' with your domain
$ sudo ln -s ../sites-available/supersecure .
$ sudo systemctl restart nginx

在使用httpie向您的站点发出请求之前,您需要再添加一个入站安全规则。添加以下入站规则:

类型草案端口范围来源
超文本传送协议传输控制协议(Transmission Control Protocol)Eightyvm-static-ip-address/32

此安全规则允许来自虚拟机本身的公共(弹性)IP 地址的入站 HTTP 流量。乍一看,这似乎有些矫枉过正,但是您需要这样做,因为请求现在将通过公共互联网路由,这意味着使用安全组 ID 的自引用规则将不再足够。

现在它使用 Nginx 作为 web 服务器前端,重新向站点发送一个请求:

$ GET http://supersecure.codes/myapp/
HTTP/1.1 200 OK
Connection: keep-alive Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Mon, 27 Sep 2021 19:54:19 GMT
Referrer-Policy: same-origin
Server: nginx Transfer-Encoding: chunked X-Content-Type-Options: nosniff
X-Frame-Options: DENY

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>My secure app</title>
  </head>
  <body>
    <p>Now this is some sweet HTML!</p>
  </body>
</html>

现在 Nginx 位于 Django 和 Gunicorn 之前,这里有一些有趣的输出:

  • Nginx 现在将Server头返回为 Server: nginx ,表示 Nginx 是新的前端 web 服务器。将server_tokens设置为值off告诉 Nginx 不要发出它的精确版本,比如nginx/x.y.z (Ubuntu)。从安全角度来看,这将会泄露不必要的信息。
  • Nginx 使用chunked作为 Transfer-Encoding 报头,而不是广告Content-Length
  • Nginx 还要求保持与Connection: keep-alive的网络连接打开。

接下来,您将利用 Nginx 的一个核心特性:快速有效地提供静态文件的能力。

Remove ads

用 Nginx 直接提供静态文件

现在,您的 Django 应用程序上有了 Nginx 代理请求。重要的是,你还可以使用 Nginx 来直接服务静态文件。如果你在project/settings.py中有DEBUG = True,那么 Django 会渲染文件,但是这效率非常低,而且可能不安全。相反,您可以让您的 web 服务器直接呈现它们。

静态文件的常见例子包括本地 JavaScript、图像和 CSS——任何不需要 Django 来动态呈现响应内容的东西。

首先,在您的项目目录中,创建一个位置来保存和跟踪开发中的 JavaScript 静态文件:

$ pwd
/home/ubuntu/django-gunicorn-nginx
$ mkdir -p static/js

现在打开一个新文件static/js/greenlight.js并添加以下 JavaScript:

// Enlarge the #changeme element in green when hovered over (function  ()  { "use strict"; function  enlarge()  { document.getElementById("changeme").style.color  =  "green"; document.getElementById("changeme").style.fontSize  =  "xx-large"; return  false; } document.getElementById("changeme").addEventListener("mouseover",  enlarge); }());

如果鼠标悬停在上面,这段 JavaScript 将放大一块绿色大字体的文本。没错,就是一些前沿的前端工作!

接下来,将以下配置添加到project/settings.py,用您的域名更新STATIC_ROOT:

STATIC_URL = "/static/"
# Note: Replace 'supersecure.codes' with your domain STATIC_ROOT = "/var/www/supersecure.codes/static" STATICFILES_DIRS = [BASE_DIR / "static"]

你告诉 Django 的 collectstatic命令在哪里搜索和放置从多个 Django 应用程序聚合的静态文件,包括 Django 自己的内置应用程序,如admin

最后但同样重要的是,修改myapp/templates/myapp/home.html中的 HTML 以包含您刚刚创建的 JavaScript:

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>My secure app</title>
  </head>
  <body>
 <p><span id="changeme">Now this is some sweet HTML!</span></p> <script src="/static/js/greenlight.js"></script>  </body>
</html>

通过包含/static/js/greenlight.js脚本,<span id="changeme">元素将有一个附加的事件监听器。

注意:为了让这个例子简单明了,你将 URL 路径硬编码到greenlight.js,而不是使用 Django 的 static模板标签。您可能希望在更大的项目中利用这一特性。

下一步是创建一个目录路径,其中包含 Nginx 服务的项目静态内容:

$ sudo mkdir -pv /var/www/supersecure.codes/static/
mkdir: created directory '/var/www/supersecure.codes'
mkdir: created directory '/var/www/supersecure.codes/static/'
$ sudo chown -cR ubuntu:ubuntu /var/www/supersecure.codes/
changed ownership of '/var/www/supersecure.codes/static' ... to ubuntu:ubuntu
changed ownership of '/var/www/supersecure.codes/' ... to ubuntu:ubuntu

现在在项目目录中以非 root 用户的身份运行collectstatic:

$ pwd
/home/ubuntu/django-gunicorn-nginx
$ python manage.py collectstatic
129 static files copied to '/var/www/supersecure.codes/static'.

最后,在 Nginx 的站点配置文件/etc/nginx/sites-available/supersecure中为/static添加一个location变量:

server  { location  /  { proxy_pass  http://localhost:8000; proxy_set_header  Host  $host; proxy_set_header  X-Forwarded-Proto  $scheme; } location  /static  {  autoindex  on;  alias  /var/www/supersecure.codes/static/;  }  }

记住你的领域可能不是supersecure.codes,所以你需要定制这些步骤来为你自己的项目工作。

现在,您应该在project/settings.py中关闭项目中的DEBUG模式:

# project/settings.py
DEBUG = False

因为您在config/gunicorn/dev.py中指定了reload = True,所以 Gunicorn 将获得这一变化。

然后重启 Nginx:

$ sudo systemctl restart nginx

现在,再次刷新您的站点页面,并将鼠标悬停在页面文本上:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是 JavaScript 函数enlarge()发挥作用的明显证据。为了得到这个结果,浏览器必须请求/static/js/greenlight.js。这里的关键是浏览器直接从 Nginx 获取文件,而不需要 Nginx 向 Django 请求。

注意上面过程的不同之处:没有添加新的 Django URL 路由或视图来交付 JavaScript 文件。这是因为,在运行了collectstatic之后,Django 不再负责决定如何将 URL 映射到一个复杂的视图并呈现该视图。Nginx 可以直接把文件交给浏览器。

事实上,如果您导航到您的域的等同物https://supersecure.codes/static/js/,您将看到 Nginx 创建的传统文件系统树视图/static。这意味着更快、更有效地交付静态文件。

至此,您已经有了使用 Django、Gunicorn 和 Nginx 构建可伸缩站点的良好基础。另一个巨大的飞跃是为您的站点启用 HTTPS,这是您接下来要做的。

Remove ads

使用 HTTPS 软件让您的网站做好生产准备

再多走几步,你就可以让你的网站的安全性从好变得更好,包括启用 HTTPS 和添加一组帮助浏览器以更安全的方式使用你的网站的标题。启用 HTTPS 可以增加你站点的可信度,如果你的站点使用认证或者与用户交换敏感数据,这是必要的。

打开 HTTPS

为了允许访问者通过 HTTPS 访问你的网站,你需要一个位于你的网络服务器上的 SSL/TLS 证书。证书由证书颁发机构(CA)颁发。在本教程中,您将使用一个名为的免费 CA,让我们加密。要实际安装证书,您可以使用 Certbot 客户端,它会给出一系列完全无痛的逐步提示。

在开始使用 Certbot 之前,您可以预先告诉 Nginx 禁用 TLS 版本 1.0 和 1.1,支持版本 1.2 和 1.3。TLS 1.0 已停产(EOL),而 TLS 1.1 包含多个漏洞,TLS 1.2 已修复了这些漏洞。为此,打开文件/etc/nginx/nginx.conf。找到下面一行:

# File: /etc/nginx/nginx.conf
ssl_protocols  TLSv1  TLSv1.1  TLSv1.2;

用更新的实现替换它:

# File: /etc/nginx/nginx.conf
ssl_protocols  TLSv1.2  TLSv1.3;

你可以使用nginx -t来确认你的 Nginx 支持 1.3 版本:

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

现在您已经准备好安装和使用 Certbot 了。在 Ubuntu Focal (20.04)上,可以使用snap来安装 Certbot:

$ sudo snap install --classic certbot
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

参考 Certbot 的说明指南来查看不同操作系统和网络服务器的安装步骤。

在您可以使用certbot获得并安装 HTTPS 证书之前,您需要对 VM 的安全组规则进行另一项更改。因为 Let’s Encrypt 需要一个互联网连接来进行验证,所以你需要迈出重要的一步,向公共互联网开放你的站点。

修改您的入站安全规则以符合以下要求:

参考类型草案端口范围来源
one超文本传送协议传输控制协议(Transmission Control Protocol)Eighty0.0.0.0/0
Two习俗全部全部security-group-id
three传输控制协议(Transmission Control Protocol)Twenty-twomy-laptop-ip-address/32

这里的关键变化是第一条规则,它允许来自所有来源的 HTTP 流量通过端口 80。您可以删除将虚拟机的公共 IP 地址列入白名单的TCP:80的入站规则,因为这是多余的。其他两条规则保持不变。

然后,您可以再发出一个命令certbot来安装证书:

$ sudo certbot --nginx --rsa-key-size 4096 --no-redirect
Saving debug log to /var/log/letsencrypt/letsencrypt.log
...

这将创建一个 RSA 密钥大小为 4096 字节的证书。--no-redirect选项告诉certbot不要自动应用与自动 HTTP 到 HTTPS 重定向相关的配置。为了便于说明,您将很快看到如何自己添加它。

您将经历一系列的设置步骤,其中大部分是不言自明的,例如输入您的电子邮件地址。当提示输入您的域名时,输入域名和用逗号分隔的www子域:

www.supersecure.codes,supersecure.codes

完成这些步骤后,您应该会看到如下所示的成功消息:

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/supersecure.codes/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/supersecure.codes/privkey.pem
This certificate expires on 2021-12-26.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this
  certificate in the background.

Deploying certificate
Successfully deployed certificate for supersecure.codes
  to /etc/nginx/sites-enabled/supersecure
Successfully deployed certificate for www.supersecure.codes
  to /etc/nginx/sites-enabled/supersecure
Congratulations! You have successfully enabled HTTPS
  on https://supersecure.codes and https://www.supersecure.codes

如果您在相当于/etc/nginx/sites-available/supersecure的位置cat打开配置文件,您会看到certbot已经自动添加了一组与 SSL 相关的行:

# Nginx configuration: /etc/nginx/sites-available/supersecure
server  { server_name  .supersecure.codes; listen  80; location  /  { proxy_pass  http://localhost:8000; proxy_set_header  Host  $host; } location  /static  { autoindex  on; alias  /var/www/supersecure.codes/static/; } listen  443  ssl;  ssl_certificate  /etc/letsencrypt/live/www.supersecure.codes/fullchain.pem;  ssl_certificate_key  /etc/letsencrypt/live/www.supersecure.codes/privkey.pem;  include  /etc/letsencrypt/options-ssl-nginx.conf;  ssl_dhparam  /etc/letsencrypt/ssl-dhparams.pem;  }

确保 Nginx 接受这些更改:

$ sudo systemctl reload nginx

要通过 HTTPS 访问您的网站,您需要添加最后一项安全规则。您需要允许通过TCP:443的流量,这里 443 是 HTTPS 的默认端口。修改您的入站安全规则以符合以下要求:

参考类型草案端口范围来源
oneHTTPS传输控制协议(Transmission Control Protocol)Four hundred and forty-three0.0.0.0/0
Two超文本传送协议传输控制协议(Transmission Control Protocol)Eighty0.0.0.0/0
Two习俗全部全部security-group-id
three传输控制协议(Transmission Control Protocol)Twenty-twomy-laptop-ip-address/32

这些规则中的每一条都有特定的用途:

  1. 规则 1 允许来自所有来源的 HTTPS 流量通过端口 443。
  2. 规则 2 允许来自所有来源的 HTTP 流量通过端口 80。
  3. 规则 3 使用安全组 ID 作为来源,允许来自分配给同一安全组的网络接口和实例的入站流量。这是一个包含在默认 AWS 安全组中的规则,您应该将它绑定到您的实例。
  4. 规则 4 允许你从个人电脑通过 SSH 访问你的虚拟机。

现在,在浏览器中重新导航到你的站点,但是有一个关键的不同。将https指定为协议,而不是http:

https://www.supersecure.codes/myapp/

如果一切顺利,您应该会看到生命中最美丽的宝藏之一,那就是您的站点正在 HTTPS 上空交付:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果您使用 Firefox 并点击锁图标,您可以查看有关保护连接所涉及的证书的更多详细信息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你离安全网站又近了一步。此时,仍然可以通过 HTTP 和 HTTPS 访问该站点。那比以前好多了,但还是不理想。

Remove ads

将 HTTP 重定向到 HTTPS

您的网站现在可以通过 HTTP 和 HTTPS 访问。有了 HTTPS,你几乎可以关闭 HTTP——或者至少在实践中接近它。您可以添加几个功能来自动将任何试图通过 HTTP 访问您的站点的访问者路由到 HTTPS 版本。编辑您的/etc/nginx/sites-available/supersecure:

# Nginx configuration: /etc/nginx/sites-available/supersecure
server  { server_name  .supersecure.codes; listen  80; return  307  https://$host$request_uri;  } server  { location  /  { proxy_pass  http://localhost:8000; proxy_set_header  Host  $host; } location  /static  { autoindex  on; alias  /var/www/supersecure.codes/static/; } listen  443  ssl; ssl_certificate  /etc/letsencrypt/live/www.supersecure.codes/fullchain.pem; ssl_certificate_key  /etc/letsencrypt/live/www.supersecure.codes/privkey.pem; include  /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam  /etc/letsencrypt/ssl-dhparams.pem; }

添加的块告诉服务器将浏览器或客户端重定向到任何 HTTP URL 的 HTTPS 版本。您可以验证此配置是否有效:

$ sudo service nginx configtest /etc/nginx/sites-available/supersecure
 * Testing nginx configuration                                  [ OK ]

然后,告诉nginx重新加载配置:

$ sudo systemctl reload nginx

然后向应用程序的 HTTP URL 发送一个带有--all标志的GET请求,以显示任何重定向链:

$ GET --all http://supersecure.codes/myapp/
HTTP/1.1 307 Temporary Redirect Connection: keep-alive
Content-Length: 164
Content-Type: text/html
Date: Tue, 28 Sep 2021 02:16:30 GMT
Location: https://supersecure.codes/myapp/ Server: nginx

<html>
<head><title>307 Temporary Redirect</title></head>
<body bgcolor="white">
<center><h1>307 Temporary Redirect</h1></center> <hr><center>nginx</center>
</body>
</html>

HTTP/1.1 200 OK
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Tue, 28 Sep 2021 02:16:30 GMT
Referrer-Policy: same-origin
Server: nginx
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>My secure app</title>
  </head>
  <body>
    <p><span id="changeme">Now this is some sweet HTML!</span></p>
    <script src="/static/js/greenlight.js"></script>
  </body>
</html>

你可以看到这里实际上有两种反应:

  1. 初始请求接收到重定向到 HTTPS 版本的 307 状态码响应。
  2. 第二个请求是向同一个 URI 发出的,但是使用的是 HTTPS 方案而不是 HTTP。这一次,它用一个200 OK响应接收到它正在寻找的页面内容。

接下来,您将看到如何通过帮助浏览器记住该选择来超越重定向配置。

与 HSTS 更进一步

单独使用时,这种重定向设置存在一个小漏洞:

当用户手动输入 web 域(提供不带 http://或 https://前缀的域名)或访问普通 http://链接时,对网站的第一个请求将使用普通 http 以不加密的方式发送。

大多数安全的网站会立即发回重定向,将用户升级到 HTTPS 连接,但是精心策划的攻击者可以发起中间人(MITM)攻击来拦截初始 HTTP 请求,并从那时起控制用户的会话。(来源)

为了缓解这种情况,您可以添加一个 HSTS 策略来告诉浏览器优先选择 HTTPS,即使用户试图使用 HTTP。下面是仅使用重定向与在旁边添加 HSTS 标头之间的细微差别:

  • 通过从 HTTP 到 HTTPS 的普通重定向,服务器回答浏览器说,“再试一次,但是用 HTTPS。”如果浏览器发出 1000 次 HTTP 请求,它将被告知 1000 次重试 HTTPS。

  • 有了 HSTS 报头,浏览器做了有效的前期工作,在第一次请求后用 HTTPS 替换了 HTTP 的*。没有重定向。在第二个场景中,你可以把浏览器想象成升级连接。当用户要求他们的浏览器访问你网站的 HTTP 版本时,他们的浏览器会简短地回应,“不,我要带你去 HTTPS 版本。”*

要解决这个问题,您可以告诉 Django 设置 Strict-Transport-Security 头。将这些行添加到项目的settings.py:

# Add to project/settings.py
SECURE_HSTS_SECONDS = 30  # Unit is seconds; *USE A SMALL VALUE FOR TESTING!*
SECURE_HSTS_PRELOAD = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

请注意,SECURE_HSTS_SECONDS值是短暂的,只有 30 秒。在这个例子中,这是故意的。当您进入实际生产时,您应该增加这个值。 Security Headers 网站推荐的最小值为 2,592,000,相当于 30 天。

警告:在你增加SECURE_HSTS_SECONDS的值之前,先看看 Django 的对 HTTP 严格传输安全的解释。在将 HSTS 时间窗口设置为较大值之前,您应该首先确保 HTTPS 正在为您的站点工作。在看到标题后,浏览器不会轻易让你改变决定,而是坚持通过 HTTP 进行 HTTPS。

一些浏览器比如 Chrome 可能会让你忽略这种行为并编辑 HSTS 政策列表,但是你不应该依赖这种技巧。对于用户来说,这不会是一个非常流畅的体验。相反,为SECURE_HSTS_SECONDS保留一个小值,直到你确信你的站点没有在 HTTPS 上出现任何回归。

当您准备冒险尝试时,您需要再添加一行 Nginx 配置。编辑您的等效项/etc/nginx/sites-available/supersecure以添加一个proxy_set_header指令:

 location  /  { proxy_pass  http://localhost:8000; proxy_set_header  Host  $host; proxy_set_header  X-Forwarded-Proto  $scheme;   }

然后告诉 Nginx 重新加载更新的配置:

$ sudo systemctl reload nginx

这个添加的proxy_set_header的效果是 Nginx 向 Django 发送以下报头,这些报头包含在最初通过端口 443 上的 HTTPS 发送到 web 服务器的中间请求中:

X-Forwarded-Proto: https

这直接与您在上面的project/settings.py中添加的SECURE_PROXY_SSL_HEADER值挂钩。这是必要的,因为 Nginx 实际上向 Gunicorn/Django 发送普通的 HTTP 请求,所以 Django 没有其他方法知道原始请求是否是 HTTPS 的。由于上述 Nginx 配置文件中的location块是用于端口 443 (HTTPS)的,所有通过这个端口的请求应该让 Django 知道它们确实是 HTTPS 的。

Django 文档对此做了很好的解释:

但是,如果您的 Django 应用程序在代理后面,那么无论原始请求是否使用 HTTPS,代理都可能会“吞掉”。如果在代理和 Django 之间有一个非 HTTPS 连接,那么is_secure()将总是返回False——即使是最终用户通过 HTTPS 发出的请求。相反,如果代理和 Django 之间有 HTTPS 连接,那么is_secure()将总是返回True——即使请求最初是通过 HTTP 发出的。(来源)

如何测试该接头是否正常工作?这里有一个优雅的方式,让你留在你的浏览器:

  1. 在浏览器中,打开开发者工具。导航到显示网络活动的选项卡。在 Firefox 中,这是右键→检查元素→网络

  2. 刷新页面。首先,您应该将307 Temporary Redirect响应视为响应链的一部分。这是你的浏览器第一次看到Strict-Transport-Security标题。

  3. 将浏览器中的 URL 改回 HTTP 版本,并再次请求该页面。如果你使用的是 Chrome,你应该会看到一个307 Internal Redirect。在 Firefox 中,您应该会看到一个200 OK响应,因为您的浏览器会自动直接进入 HTTPS 请求,即使您试图告诉它使用 HTTP。虽然浏览器显示它们的方式不同,但这两种响应都表明浏览器执行了自动重定向。

如果您使用 Firefox,您应该会看到如下内容:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后,您还可以通过来自控制台的请求来验证标头是否存在:

$ GET -ph https://supersecure.codes/myapp/
...
Strict-Transport-Security: max-age=30; includeSubDomains; preload

这证明您已经使用project/settings.py中的相应值有效地设置了Strict-Transport-Security头。一旦你准备好了,你可以增加max-age的值,但是记住这将不可逆转地告诉浏览器在这段时间内升级 HTTP。

Remove ads

设置Referrer-Policy标题

Django 3.x 还增加了控制 Referrer-Policy 标头的能力。您可以在project/settings.py中指定SECURE_REFERRER_POLICY:

# Add to project/settings.py
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"

这个设置是如何工作的?当你跟随一个从页面 A 到页面 B 的链接时,你对页面 B 的请求在标题Referer下包含了页面 A 的 URL。一个设置Referrer-Policy头的服务器,你可以通过SECURE_REFERRER_POLICY在 Django 中设置这个头,控制什么时候以及有多少信息被转发到目标站点。SECURE_REFERRER_POLICY可以接受许多可识别的值,您可以在 Mozilla 文档中详细了解这些值。

举个例子,如果你使用了"strict-origin-when-cross-origin"并且用户的当前页面是https://example.com/page,那么Referer的页面头会受到以下方式的约束:

目标站点Referer表头
https://example.com/otherpagehttps://example.com/page
https://mozilla.orghttps://example.com/
http://example.org(HTTP 目标)[无]

假设当前用户的页面是https://example.com/page,下面是具体情况:

  • 如果用户跟随一个链接到https://example.com/otherpageReferer将包括当前页面的完整路径。
  • 如果用户跟随链接到单独的域https://mozilla.orgReferer将排除当前页面的路径。
  • 如果用户使用http://协议点击http://example.org的链接,Referer将为空白。

如果你将这一行添加到project/settings.py并重新请求你的应用主页,那么你会看到一个新成员:

$ GET -ph https://supersecure.codes/myapp/  # -ph: Show response headers only
HTTP/1.1 200 OK
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Tue, 28 Sep 2021 02:31:36 GMT
Referrer-Policy: strict-origin-when-cross-origin Server: nginx
Strict-Transport-Security: max-age=30; includeSubDomains; preload
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

在这一部分,您已经朝着保护用户隐私的方向迈出了又一步。接下来,您将看到如何锁定站点对跨站点脚本(XSS)和数据注入攻击的漏洞。

添加一个Content-Security-Policy (CSP)报头

一个更重要的 HTTP 响应头是 Content-Security-Policy (CSP) 头,它有助于防止跨站脚本(XSS) 和数据注入攻击。Django 本身不支持这个,但是你可以安装 django-csp ,Mozilla 开发的一个小型中间件扩展:

$ python -m pip install django-csp

要使用默认值打开标题,将这一行添加到现有MIDDLEWARE定义下的project/settings.py:

# project/settings.py
MIDDLEWARE += ["csp.middleware.CSPMiddleware"]

你如何测试这个?嗯,你可以在你的 HTML 页面中包含一个链接,看看浏览器是否允许它和页面的其他部分一起加载。

编辑myapp/templates/myapp/home.html处的模板,以包含一个到 Normalize.css 文件的链接,这是一个 css 文件,帮助浏览器更加一致地呈现所有元素,并符合现代标准:

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>My secure app</title>
 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.css" >  </head>
  <body>
    <p><span id="changeme">Now this is some sweet HTML!</span></p>
    <script src="/static/js/greenlight.js"></script>
  </body>
</html>

现在,在启用了开发人员工具的浏览器中请求页面。您将在控制台中看到如下错误:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

啊哦。你错过了规范化的力量,因为你的浏览器无法加载normalize.css。以下是它无法加载的原因:

  • 你的project/settings.py包括姜戈的MIDDLEWARE中的CSPMiddleware。包含CSPMiddleware将头设置为默认的Content-Security-Policy值,即default-src 'self',其中'self'表示你站点自己的域。在本教程中,那就是supersecure.codes
  • 你的浏览器遵守这个规则,禁止cdn.jsdelivr.net加载。CSP 是一个默认的拒绝 T2 策略。

您必须选择并明确允许客户端的浏览器加载您站点响应中嵌入的某些链接。要解决这个问题,将以下设置添加到project/settings.py:

# project/settings.py
# Allow browsers to load normalize.css from cdn.jsdelivr.net
CSP_STYLE_SRC = ["'self'", "cdn.jsdelivr.net"]

接下来,再次尝试请求您站点的页面:

$ GET -ph https://supersecure.codes/myapp/
HTTP/1.1 200 OK
Connection: keep-alive
Content-Encoding: gzip
Content-Security-Policy: default-src 'self'; style-src 'self' cdn.jsdelivr.net Content-Type: text/html; charset=utf-8
Date: Tue, 28 Sep 2021 02:37:19 GMT
Referrer-Policy: strict-origin-when-cross-origin
Server: nginx
Strict-Transport-Security: max-age=30; includeSubDomains; preload
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

注意,style-src'self' cdn.jsdelivr.net指定为Content-Security-Policy标题值的一部分。这意味着浏览器应该只允许来自两个域的样式表:

  1. supersecure.codes ( 'self')
  2. cdn.jsdelivr.net

style-src指令是可以成为Content-Security-Policy一部分的许多指令之一。还有很多其他的,比如img-src,指定图片和收藏夹图标的有效来源,还有script-src,定义 JavaScript 的有效来源。

这些中的每一个都有对应的django-csp设置。例如,img-srcscript-src分别由CSP_IMG_SRCCSP_SCRIPT_SRC设置。您可以查看 django-csp文档获取完整列表。

这里有一个关于 CSP 头文件的最后提示:尽早设置!当后来出现问题时,更容易查明原因,因为您可以更容易地隔离您添加的没有加载的特性或链接,因为您没有最新的相应 CSP 指令。

生产部署的最后步骤

现在,在准备部署应用程序时,您将经历最后几个步骤。

首先,确保您已经在项目的settings.py中设置了DEBUG = False,如果您还没有这样做的话。这确保了在 5xx 服务器端错误的情况下,服务器端调试信息不会泄露。

其次,编辑项目的settings.py中的SECURE_HSTS_SECONDS,将Strict-Transport-Security头的到期时间从 30 秒增加到建议的 30 天,相当于 2,592,000 秒:

# Add to project/settings.py
SECURE_HSTS_SECONDS = 2_592_000  # 30 days

接下来,使用生产配置文件重新启动 Gunicorn。在config/gunicorn/prod.py中增加以下内容:

"""Gunicorn *production* config file"""

import multiprocessing

# Django WSGI application path in pattern MODULE_NAME:VARIABLE_NAME
wsgi_app = "project.wsgi:application"
# The number of worker processes for handling requests
workers = multiprocessing.cpu_count() * 2 + 1
# The socket to bind
bind = "0.0.0.0:8000"
# Write access and error info to /var/log
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
# Redirect stdout/stderr to log file
capture_output = True
# PID file so you can easily fetch process ID
pidfile = "/var/run/gunicorn/prod.pid"
# Daemonize the Gunicorn process (detach & enter background)
daemon = True

在这里,您做了一些更改:

  • 你关闭了开发中使用的reload特性。
  • 您让工作线程的数量成为虚拟机 CPU 数量的函数,而不是硬编码它。
  • 你允许loglevel默认为"info",而不是更冗长的"debug"

现在您可以停止当前的 Gunicorn 进程并启动一个新的进程,用它的生产副本替换开发配置文件:

$ # Stop existing Gunicorn dev server if it is running
$ sudo killall gunicorn

$ # Restart Gunicorn with production config file
$ gunicorn -c config/gunicorn/prod.py

在做了这个更改之后,您不需要重启 Nginx,因为它只是将请求传递给同一个address:host,不应该有任何可见的更改。然而,从长远来看,随着应用规模的扩大,以面向生产的设置运行 Gunicorn 更健康。

最后,确保您已经完整地构建了 Nginx 文件。下面是完整的文件,包括到目前为止您添加的所有组件,以及一些额外的值:

# File: /etc/nginx/sites-available/supersecure
# This file inherits from the http directive of /etc/nginx/nginx.conf

# Disable emitting nginx version in the "Server" response header field
server_tokens  off; # Use site-specific access and error logs
access_log  /var/log/nginx/supersecure.access.log; error_log  /var/log/nginx/supersecure.error.log; # Return 444 status code & close connection if no Host header present
server  { listen  80  default_server; return  444; } # Redirect HTTP to HTTPS
server  { server_name  .supersecure.codes; listen  80; return  307  https://$host$request_uri; } server  { # Pass on requests to Gunicorn listening at http://localhost:8000
  location  /  { proxy_pass  http://localhost:8000; proxy_set_header  Host  $host; proxy_set_header  X-Forwarded-Proto  $scheme; proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for; proxy_redirect  off; } # Serve static files directly
  location  /static  { autoindex  on; alias  /var/www/supersecure.codes/static/; } listen  443  ssl; ssl_certificate  /etc/letsencrypt/live/www.supersecure.codes/fullchain.pem; ssl_certificate_key  /etc/letsencrypt/live/www.supersecure.codes/privkey.pem; include  /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam  /etc/letsencrypt/ssl-dhparams.pem; }

作为复习,与您的虚拟机相关的入站安全规则应该有一定的设置:

类型草案端口范围来源
HTTPS传输控制协议(Transmission Control Protocol)Four hundred and forty-three0.0.0.0/0
超文本传送协议传输控制协议(Transmission Control Protocol)Eighty0.0.0.0/0
习俗全部全部security-group-id
传输控制协议(Transmission Control Protocol)Twenty-twomy-laptop-ip-address/32

综上所述,最终的 AWS 安全规则集由四个入站规则和一个出站规则组成:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Final security group rule set

将上述内容与您的初始安全规则集进行比较。请注意,您已经放弃了对提供 Django 应用程序开发版本的TCP:8000的访问,并分别在端口 80 和 443 上通过 HTTP 和 HTTPS 开放了对互联网的访问。

您的网站现在可以开始展示了:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image: Real Python

现在您已经将所有组件放在一起,您的应用程序可以通过 Nginx over HTTPS 在端口 443 上访问。端口 80 上的 HTTP 请求被重定向到 HTTPS。Django 和 Gunicorn 组件本身并不向公共互联网公开,而是位于 Nginx 反向代理之后。

测试你网站的 HTTPS 安全性

您的站点现在比您开始学习本教程时安全多了,但是不要相信我的话。有几个工具可以给你一个网站安全相关特性的客观评级,重点是回复标题和 HTTPS。

第一个是安全头应用,它给从你的网站返回的 HTTP 响应头的质量打分。如果你一直在跟进,你的网站应该可以获得 A 级或更高的评分。

第二个是 SSL 实验室,它将对您的 web 服务器的配置进行深度分析,因为它与 SSL/TLS 相关。输入您站点的域名,SSL 实验室将根据与 SSL/TLS 相关的各种因素的强度返回一个等级。如果您使用--rsa-key-size 4096调用了certbot,并关闭了 TLS 1.0 和 1.1,转而使用 1.2 和 1.3,那么您应该可以很好地从 SSL 实验室获得 A+评级。

作为检查,您也可以从命令行请求您站点的 HTTPS URL,以查看您在整个教程中添加的更改的完整概述:

$ GET https://supersecure.codes/myapp/
HTTP/1.1 200 OK
Connection: keep-alive
Content-Encoding: gzip
Content-Security-Policy: style-src 'self' cdn.jsdelivr.net; default-src 'self'
Content-Type: text/html; charset=utf-8
Date: Tue, 28 Sep 2021 02:37:19 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
Strict-Transport-Security: max-age=2592000; includeSubDomains; preload
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>My secure app</title>
    <link rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.css"
    >
  </head>
  <body>
    <p><span id="changeme">Now this is some sweet HTML!</span></p>
    <script src="/static/js/greenlight.js"></script>
  </body>
</html>

那确实是一些可爱的 HTML。

结论

如果您已经阅读了本教程,那么您的站点作为一个羽翼未丰的独立开发 Django 应用程序,已经取得了长足的进步。您已经看到了 Django、Gunicorn 和 Nginx 如何联合起来帮助您安全地服务于您的站点。

在本教程中,您已经学会了如何:

  • 将你的 Django 应用从开发到生产
  • 在真实世界的公共领域托管你的应用程序
  • GunicornNginx 引入请求和响应链
  • 使用 HTTP 头来增加你的站点的 HTTPS 安全性

现在,您有了一组可重复的步骤来部署您的生产就绪的 Django web 应用程序。

您可以通过下面的链接下载本教程中使用的 Django 项目:

获取源代码: 点击此处获取本教程中使用的配套 Django 项目

延伸阅读

有了网站安全,你不可能 100%到达那里。您可以添加更多的功能来进一步保护您的站点,并生成更好的日志信息。

查看以下链接,了解您可以自己采取的其他步骤:

立即观看本教程有真实 Python 团队创建的相关视频课程。配合文字教程一起看,加深理解: 用 Gunicorn 和 Nginx 部署一个 Django App*********

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值