TestDriven.io 博客中文翻译(十二)

原文:TestDriven.io Blog

协议:CC BY-NC-SA 4.0

将 Django 应用程序部署到 Elastic Beanstalk

原文:https://testdriven.io/blog/django-elastic-beanstalk/

在本教程中,我们将逐步完成将生产就绪的 Django 应用程序部署到 AWS Elastic Beanstalk 的过程。

目标

本教程结束时,您将能够:

  1. 解释什么是弹性豆茎
  2. 初始化和配置弹性豆茎
  3. 对运行在 Elastic Beanstalk 上的应用程序进行故障排除
  4. 将弹性豆茎与 RDS 结合
  5. 为静态和媒体文件支持配置 S3
  6. 通过 AWS 证书管理器获取 SSL 证书
  7. 使用 SSL 证书在 HTTPS 上提供您的应用程序

什么是弹性豆茎?

AWS Elastic Beanstalk (EB)是一个易于使用的服务,用于部署和扩展 web 应用程序。它连接多个 AWS 服务,例如计算实例( EC2 )、数据库( RDS )、负载平衡器(应用负载平衡器)和文件存储系统( S3 ),等等。EB 允许您快速开发和部署 web 应用程序,而无需考虑底层基础设施。它支持用 Go、Java、.NET、Node.js、PHP、Python 和 Ruby。如果您需要配置自己的软件栈或部署用 EB 目前不支持的语言(或版本)开发的应用程序,EB 也支持 Docker。

典型的弹性豆茎设置:

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

AWS 弹性豆茎不另收费。您只需为应用程序消耗的资源付费。

要了解更多关于弹性豆茎的信息,请查看什么是 AWS 弹性豆茎?来自官方 AWS 弹性豆茎文档

弹性豆茎概念

在开始学习教程之前,让我们先来看看与 Elastic Beanstalk 相关的几个关键概念:

  1. 一个 应用 是弹性 Beanstalk 组件的逻辑集合,包括环境、版本和环境配置。一个应用程序可以有多个版本
  2. 一个 环境 是运行一个应用版本的 AWS 资源的集合。
  3. 一个 平台 是操作系统、编程语言运行时、web 服务器、应用服务器和弹性 Beanstalk 组件的组合。

这些术语将在整个教程中使用。

项目设置

在本教程中,我们将部署一个名为 django-images 的简单图像托管应用程序。

按照教程进行操作时,通过部署您自己的应用程序来检查您的理解。

首先,从 GitHub 上的库获取代码:

创建新的虚拟环境并激活它:

`$ python3 -m venv venv && source venv/bin/activate` 

安装需求并迁移数据库:

`(venv)$ pip install -r requirements.txt
(venv)$ python manage.py migrate` 

运行服务器:

`(venv)$ python manage.py runserver` 

打开您最喜欢的网络浏览器,导航到 http://localhost:8000 。使用右边的表格上传图像,确保一切正常。上传图像后,您应该会看到它显示在表格中:

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

弹性豆茎 CLI

在继续之前,请务必在注册一个 AWS 帐户。通过创建一个账户,你可能也有资格加入 AWS 免费等级

Elastic Beanstalk 命令行界面 (EB CLI)允许您执行各种操作来部署和管理您的 Elastic Beanstalk 应用程序和环境。

有两种安装 EB CLI 的方法:

  1. 通过 EB CLI 安装程序
  2. pip (awsebcli)

建议使用安装程序(第一个选项)全局安装 EB CLI(任何特定虚拟环境之外),以避免可能的依赖冲突。更多详情请参考本解释

安装 EB CLI 后,您可以通过运行以下命令来检查版本:

`$ eb --version

EB CLI 3.20.3 (Python 3.10.)` 

如果该命令不起作用,您可能需要将 EB CLI 添加到$PATH中。

EB CLI 命令列表及其描述可在 EB CLI 命令参考中找到。

初始化弹性豆茎

一旦我们运行了 EB CLI,我们就可以开始与 Elastic Beanstalk 交互了。让我们初始化一个新项目和一个 EB 环境。

初始化

在项目根目录(“django-images”)中,运行:

你会被提示一些问题。

默认区域

您的弹性 Beanstalk 环境的 AWS 区域(和资源)。如果您不熟悉不同的 AWS 区域,请查看 AWS 区域和可用区域。一般来说,你应该选择离你的客户最近的地区。请记住,资源价格因地区而异。

应用程序名称

这是您的弹性 Beanstalk 应用程序的名称。我建议按下回车键,使用默认设置:“django-images”。

平台和平台分支

EB CLI 将检测到您正在使用 Python 环境。之后,它会给你不同的 Python 版本和 Amazon Linux 版本供你使用。选择“运行在 64 位亚马逊 Linux 2 上的 Python 3.8”。

代码提交

CodeCommit 是一个安全的、高度可伸缩的、托管的源代码控制服务,托管私有的 Git 存储库。我们不会使用它,因为我们已经在使用 GitHub 进行源代码控制。所以说“不”。

为了稍后连接到 EC2 实例,我们需要设置 SSH。出现提示时,说“是”。

密钥对

为了连接到 EC2 实例,我们需要一个 RSA 密钥对。继续生成一个,它将被添加到您的“~/”中。ssh”文件夹。

回答完所有问题后,您会注意到项目根目录下有一个隐藏的目录,名为。elasticbeanstalk”。该目录应该包含一个 config.yml 文件,其中包含您刚才提供的所有数据。

`.elasticbeanstalk
└── config.yml` 

该文件应包含类似以下内容:

`branch-defaults: master: environment:  null group_suffix:  null global: application_name:  django-images branch:  null default_ec2_keyname:  aws-eb default_platform:  Python 3.8 running on 64bit Amazon Linux 2 default_region:  us-west-2 include_git_submodules:  true instance_profile:  null platform_name:  null platform_version:  null profile:  eb-cli repository:  null sc:  git workspace_type:  Application` 

创造

接下来,让我们创建弹性 Beanstalk 环境并部署应用程序:

同样,系统会提示您几个问题。

环境名称

这表示 EB 环境的名称。我建议坚持使用默认值:“django-images-env”。

└-env└-dev后缀添加到您的环境中被认为是一种很好的做法,这样您就可以很容易地将 EB 应用程序与环境区分开来。

DNS CNAME 前缀

您的 web 应用程序将在%cname%.%region%.elasticbeanstalk.com可访问。同样,使用默认值。

负载平衡

负载平衡器在您的环境实例之间分配流量。选择“应用程序”。

如果您想了解不同的负载平衡器类型,请查看适用于您的弹性 Beanstalk 环境的负载平衡器。

现货车队请求

Spot Fleet 请求允许您根据自己的标准按需启动实例。我们不会在本教程中使用它们,所以说“不”。

有了它,环境将会旋转起来:

  1. 你的代码将被压缩并上传到一个新的 S3 桶。
  2. 之后,将创建各种 AWS 资源,如负载平衡器、安全和自动伸缩组以及 EC2 实例。

还将部署一个新的应用程序。

这将需要大约三分钟,所以请随意拿一杯咖啡。

部署完成后,EB CLI 将修改*。elasticbeanstalk/config.yml* 。

您的项目结构现在应该如下所示:

`|-- .elasticbeanstalk
|-- config.yml
|-- .gitignore
|-- README.md
|-- core
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings.py
|   |-- urls.py
|-- wsgi.py
|-- db.sqlite3
|-- images
|   |-- __init__.py
|   |-- admin.py
|   |-- apps.py
|   |-- forms.py
|   |-- migrations
|   |   |-- 0001_initial.py
|   |-- __init__.py
|   |-- models.py
|   |-- tables.py
|   |-- templates
|   |-- images
|   |-- index.html
|   |-- tests.py
|   |-- urls.py
|-- views.py
|-- manage.py
└-- requirements.txt` 

状态

部署应用后,您可以通过运行以下命令来检查其状态:

`$ eb status

Environment details for: django-images-env
  Application name: django-images
  Region: us-west-2
  Deployed Version: app-93ec-220218_095635133296
  Environment ID: e-z7dmesipvc
  Platform: arn:aws:elasticbeanstalk:us-west-2::platform/Python 3.8 running on 64bit Amazon Linux 2/3.3.10
  Tier: WebServer-Standard-1.0
  CNAME: django-images-env.us-west-2.elasticbeanstalk.com
  Updated: 2022-02-18 16:00:24.954000+00:00
  Status: Ready
  Health: Red` 

您可以看到我们环境的当前健康状况是Red,这意味着出现了问题。暂时不要担心这个问题,我们将在接下来的步骤中解决它。

您还可以看到,AWS 为我们分配了一个 CNAME,这是我们的 EB 环境的域名。我们可以通过打开浏览器并导航到 CNAME 来访问 web 应用程序。

打开

此命令将打开您的默认浏览器并导航到 CNAME 域。你会看到502 Bad Gateway,我们将在这里很快修复它

安慰

该命令将在您的默认浏览器中打开 Elastic Beanstalk 控制台:

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

同样,您可以看到环境的健康状况是“严重的”,我们将在下一步中解决这个问题。

配置环境

在上一步中,我们尝试访问我们的应用程序,它返回了502 Bad Gateway。背后有几个原因:

  1. Python 需要PYTHONPATH来在我们的应用程序中找到模块。
  2. 默认情况下,Elastic Beanstalk 试图从不存在的 application.py 启动 WSGI 应用程序。
  3. Django 需要DJANGO_SETTINGS_MODULE知道使用哪些设置。

默认情况下,Elastic Beanstalk 为 Python 应用程序提供了 Gunicorn 。EB 在部署过程中自动安装 Gunicorn,因此我们不必将其添加到 requirements.txt 中。如果你想用别的东西替换 Gunicorn,看看用 Procfile 配置 WSGI 服务器的

让我们修复这些错误。

在项目根目录下创建一个名为“”的新文件夹。ebextensions”。在新创建的文件夹中创建一个名为 01_django.config 的文件:

`# .ebextensions/01_django.config option_settings: aws:elasticbeanstalk:application:environment: DJANGO_SETTINGS_MODULE:  "core.settings" PYTHONPATH:  "/var/app/current:$PYTHONPATH" aws:elasticbeanstalk:container:python: WSGIPath:  "core.wsgi:application"` 

注意事项:

  1. 我们将PYTHONPATH设置为 EC2 实例上的 Python 路径( docs )。
  2. 我们将DJANGO_SETTINGS_MODULE指向我们的 Django 设置( docs )。
  3. 我们将WSGIPath更改为我们的 WSGI 应用程序( docs )。

EB 如何*。config* 文件管用吗?

  1. 你想要多少就有多少。
  2. 它们按以下顺序加载:01_x、02_x、03_x 等。
  3. 您不必记住这些设置;您可以通过运行eb config列出您的所有环境设置。

如果您想了解更多关于高级环境定制的信息,请查看带有配置文件的高级环境定制

此时,您的项目结构应该如下所示:

`|-- .ebextensions
|-- 01_django.config
|-- .elasticbeanstalk
|-- config.yml
|-- .gitignore
|-- README.md
|-- core
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings.py
|   |-- urls.py
|-- wsgi.py
|-- db.sqlite3
|-- images
|   |-- __init__.py
|   |-- admin.py
|   |-- apps.py
|   |-- forms.py
|   |-- migrations
|   |   |-- 0001_initial.py
|   |-- __init__.py
|   |-- models.py
|   |-- tables.py
|   |-- templates
|   |-- images
|   |-- index.html
|   |-- tests.py
|   |-- urls.py
|-- views.py
|-- manage.py
└-- requirements.txt` 

在重新部署之前,我们必须做的另一件事是将我们的 CNAME 添加到 core/settings.py 中的ALLOWED_HOSTS:

`# core/settings.py

ALLOWED_HOSTS = [
    'xyz.elasticbeanstalk.com',  # make sure to replace it with your own EB CNAME
]` 

或者,对于测试,您可以只使用通配符:ALLOWED_HOSTS = ['*']。只是不要忘记在你完成测试后改变它!

将更改提交给 git 并部署:

`$ git add .
$ git commit -m "updates for eb"

$ eb deploy` 

您会注意到,如果您不提交,Elastic Beanstalk 不会检测到这些变化。这是因为 EB 与 git 集成,并且只检测提交的(更改的)文件。

部署完成后,运行eb open看看是否一切正常

哎哟。我们修复了以前的错误,但现在又出现了新的错误:

`NotSupportedError at /
deterministic=True requires SQLite 3.8.3 or higher` 

别担心。这只是 SQLite 的一个问题,无论如何都不应该在生产中使用。我们将在这里与 Postgres 交换它。

配置 RDS

Django 默认使用数据库 SQLite 。虽然这对于开发来说是完美的,但是对于生产来说,您通常会希望迁移到更健壮的数据库,比如 Postgres 或 MySQL。此外,由于版本依赖冲突,当前的 EB 平台不能很好地与 SQLite 一起工作。因为这两点,我们将用 SQlite 替换掉 Postgres

本地邮政汇票

首先,让 Postgres 在本地运行。您可以从 PostgreSQL Downloads 下载它,或者启动 Docker 容器:

`$ docker run --name django-images-postgres -p 5432:5432 \
    -e POSTGRES_USER=django-images -e POSTGRES_PASSWORD=complexpassword123 \
    -e POSTGRES_DB=django-images -d postgres` 

检查容器是否正在运行:

`$ docker ps -f name=django-images-postgres

CONTAINER ID   IMAGE      COMMAND                  CREATED              STATUS              PORTS                    NAMES
c05621dac852   postgres   "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:5432->5432/tcp   django-images-postgres` 

现在,让我们试着用 Django 应用程序连接它。在 core/settings.py 中,将DATABASE配置更改为以下内容:

`# core/settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'django-images',
        'USER': 'django-images',
        'PASSWORD': 'complexpassword123',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}` 

接下来,安装 Postgres 所需的 psycopg2-binary :

`(venv)$ pip install psycopg2-binary==2.9.3` 

添加到 requirements.txt :

`Django==4.0.2
Pillow==9.0.1
django-tables2==2.4.1
django-crispy-forms==1.14.0
psycopg2-binary==2.9.3` 

创建和应用迁移:

`(venv)$ python manage.py makemigrations
(venv)$ python manage.py migrate` 

运行服务器:

`(venv)$ python manage.py runserver` 

确保您仍然可以在 http://localhost:8000 上传图像。

如果得到一个DisallowedHost错误,将localhost127.0.0.1添加到 core/settings.py 内的ALLOWED_HOSTS中。

AWS RDS Postgres

要为生产设置 Postgres,首先运行以下命令打开 AWS 控制台:

单击左侧栏上的“配置”,向下滚动到“数据库”,然后单击“编辑”。

使用以下设置创建一个数据库,然后单击“应用”:

  • 引擎:postgres
  • 引擎版本:12.9(自 db.t2.micro 以来的旧 Postgres 版本在 13.1+版本中不可用)
  • 实例类:db.t2.micro
  • 存储:5 GB(应该绰绰有余)
  • 用户名:选择一个用户名
  • 密码:选择一个强密码

如果你想留在 AWS 免费层内,确保你选择 db.t2.micro. RDS 价格会根据你选择的实例类呈指数增长。如果你不想和micro一起去,一定要复习 AWS PostgreSQL 定价

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

环境更新完成后,EB 会自动将以下数据库凭证传递给我们的 Django 应用程序:

`RDS_DB_NAME
RDS_USERNAME
RDS_PASSWORD
RDS_HOSTNAME
RDS_PORT` 

我们现在可以使用 core/settings.py 中的这些变量来设置DATABASE:

`# core/settings.py

if 'RDS_DB_NAME' in os.environ:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql_psycopg2',
            'NAME': os.environ['RDS_DB_NAME'],
            'USER': os.environ['RDS_USERNAME'],
            'PASSWORD': os.environ['RDS_PASSWORD'],
            'HOST': os.environ['RDS_HOSTNAME'],
            'PORT': os.environ['RDS_PORT'],
        }
    }
else:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql_psycopg2',
            'NAME': 'django-images',
            'USER': 'django-images',
            'PASSWORD': 'complexpassword123',
            'HOST': 'localhost',
            'PORT': '5432',
        }
    }` 

不要忘记导入 core/settings.py 顶部的os包:

接下来,我们必须告诉 Elastic Beanstalk 在部署新的应用程序版本时运行makemigrationsmigrate。我们可以通过编辑来实现。EB extensions/01 _ django . config文件。将以下内容添加到文件的底部:

`# .ebextensions/01_django.config container_commands: 01_makemigrations: command:  "source /var/app/venv/*/bin/activate && python3 manage.py makemigrations --noinput" leader_only:  true 02_migrate: command:  "source /var/app/venv/*/bin/activate && python3 manage.py migrate --noinput" leader_only:  true` 

现在,每当我们部署新的应用程序版本时,EB 环境都会执行上述命令。我们使用了leader_only,所以只有第一个 EC2 实例执行它们(以防我们的 EB 环境运行多个 EC2 实例)。

弹性 Beanstalk 配置支持两个不同的命令部分,命令容器 _ 命令。它们之间的主要区别在于它们在部署过程中的运行时间:

  1. commands在设置应用程序和 web 服务器以及提取应用程序版本文件之前运行。
  2. container_commands在应用程序和 web 服务器已设置且应用程序版本存档已提取之后,但在应用程序版本部署之前(在文件从暂存文件夹移动到其最终位置之前)运行。

让我们也添加一个命令来创建超级用户。我们可以使用 Django 直观的定制命令框架来添加新命令。在“图像”应用程序中,创建以下文件和文件夹:

`└-- images
    └-- management
        |-- __init__.py
        └-- commands
            |-- __init__.py
            └-- createsu.py` 

create u . py:

`# images/management/commands/createsu.py

from django.contrib.auth.models import User
from django.core.management.base import BaseCommand

class Command(BaseCommand):
    help = 'Creates a superuser.'

    def handle(self, *args, **options):
        if not User.objects.filter(username='admin').exists():
            User.objects.create_superuser(
                username='admin',
                password='complexpassword123'
            )
        print('Superuser has been created.')` 

接下来,将第三个容器命令添加到*。EB extensions/01 _ django . config*:

`# .ebextensions/01_django.config container_commands: 01_makemigrations: command:  "source /var/app/venv/*/bin/activate && python3 manage.py makemigrations --noinput" leader_only:  true 02_migrate: command:  "source /var/app/venv/*/bin/activate && python3 manage.py migrate --noinput" leader_only:  true # ------------------------------------- new ------------------------------------- 03_superuser: command:  "source /var/app/venv/*/bin/activate && python3 manage.py createsu" leader_only:  true # --------------------------------- end of new  ---------------------------------` 

创建createsu命令的另一种方法是 SSH 到一个 EC2 实例中,然后运行 Django 的默认createsuperuser命令。

将更改提交给 git 并部署:

`$ git add .
$ git commit -m "updates for eb"

$ eb deploy` 

等待部署完成。完成后,运行eb open在新的浏览器标签中打开你的应用。您的应用程序现在应该可以工作了。确保您可以上传图像。

文件存储的 S3

查看您的管理仪表板的部署版本。静态文件没有得到正确的服务。此外,我们不希望静态或媒体文件本地存储在 EC2 实例上,因为 EB 应用程序应该是无状态的,这使得将您的应用程序扩展到多个 EC2 实例更加容易。

虽然 AWS 提供了许多持久存储服务,但 T2 S3 T3 可以说是最受欢迎和最容易使用的。

要配置 S3,我们需要:

  1. 创建 S3 存储桶
  2. 为 S3 时段管理创建 IAM 组和用户
  3. 设置弹性豆茎 S3 环境变量
  4. 配置 Django 静态和媒体设置

创建 S3 存储桶

首先,让我们创建一个新的 S3 存储桶。导航到 AWS S3 控制台并点击“创建存储桶”。给这个桶一个惟一的名称,并设置 AWS 区域。对其他一切使用默认配置。按“创建”。

IAM 组和用户

导航到 IAM 控制台。在屏幕左侧,选择“用户组”。创建一个具有“AmazonS3FullAccess”权限的新组:

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

然后,创建一个具有“编程访问”权限的新用户,并将该组分配给该用户:

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

AWS 将为您生成认证凭证。下载提供的*。csv* 文件。在下一步中,我们需要将它们传递给我们的 Elastic Beanstalk 环境。

设置 EB 环境变量

接下来,我们需要设置以下环境变量:

`AWS_ACCESS_KEY_ID  -  your  ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY  -  your  SECRET_ACCESS_KEY AWS_S3_REGION_NAME  -  your  selected  S3  region AWS_STORAGE_BUCKET_NAME  -  your  bucket  name` 

导航到您的弹性豆茎控制台。点击“配置”。然后,在“软件”类别中,单击“编辑”并向下滚动到“环境属性”部分。添加四个变量。

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

添加完所有变量后,点击“应用”。

接下来,为了让 Django 与我们的 S3 桶通信,我们需要安装 django-storagesboto3 包。

将它们添加到 requirements.txt 文件中:

`Django==4.0.2
Pillow==9.0.1
django-tables2==2.4.1
django-crispy-forms==1.14.0
psycopg2-binary==2.9.3
boto3==1.21.3
django-storages==1.12.3` 

接下来,将新安装的 app 添加到 core/settings.py 中的INSTALLED_APPS:

`# core/settings.py

INSTALLED_APPS = [
    # ...
    'storages',
]` 

配置 django-storages 以使用由 Elastic Beanstalk 传递的环境变量:

`if 'AWS_STORAGE_BUCKET_NAME' in os.environ:
    STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
    DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

    AWS_STORAGE_BUCKET_NAME = os.environ['AWS_STORAGE_BUCKET_NAME']
    AWS_S3_REGION_NAME = os.environ['AWS_S3_REGION_NAME']

    AWS_S3_ACCESS_KEY_ID = os.environ['AWS_ACCESS_KEY_ID']
    AWS_S3_SECRET_ACCESS_KEY = os.environ['AWS_SECRET_ACCESS_KEY']` 

最后,我们需要在部署完成后运行collectstatic命令,因此将以下内容添加到 01_django.config 的底部:

`# .ebextensions/01_django.config # ... container_commands: # ... 04_collectstatic: command:  "source /var/app/venv/*/bin/activate && python3 manage.py collectstatic --noinput" leader_only:  true` 

完整的文件现在应该如下所示:

`# .ebextensions/01_django.config option_settings: aws:elasticbeanstalk:application:environment: DJANGO_SETTINGS_MODULE:  "core.settings" PYTHONPATH:  "/var/app/current:$PYTHONPATH" aws:elasticbeanstalk:container:python: WSGIPath:  "core.wsgi:application" container_commands: 01_makemigrations: command:  "source /var/app/venv/*/bin/activate && python3 manage.py makemigrations --noinput" leader_only:  true 02_migrate: command:  "source /var/app/venv/*/bin/activate && python3 manage.py migrate --noinput" leader_only:  true 03_superuser: command:  "source /var/app/venv/*/bin/activate && python3 manage.py createsu" leader_only:  true 04_collectstatic: command:  "source /var/app/venv/*/bin/activate && python3 manage.py collectstatic --noinput" leader_only:  true` 

将更改提交给 git 并部署:

`$ git add .
$ git commit -m "updates for eb"

$ eb deploy` 

确认静态和媒体文件现在存储在 S3 上。

如果您得到一个Signature mismatch错误,您可能想要将以下设置添加到 core/settings.py : AWS_S3_ADDRESSING_STYLE = "virtual"。更多详情,请参见本期 GitHub

要了解更多关于 AWS S3 上静态和媒体文件存储的信息,请看一下亚马逊 S3 上的文章存储 Django 静态和媒体文件。

HTTPS 与证书管理器

教程的这一部分要求您有一个域名。

需要一个便宜的域名来练习?几个域名注册商有特殊优惠。“xyz”域。或者,您可以在 Freenom 创建一个免费域名。如果你没有域名,但仍然想使用 HTTPS,你可以创建并签署一个 X509 证书

要通过 HTTPS 为您的申请提供服务,我们需要:

  1. 请求并验证 SSL/TLS 证书
  2. 把你的域名指向你的 EB CNAME
  3. 修改负载平衡器以服务于 HTTPS
  4. 修改您的应用程序设置

请求并验证 SSL/TLS 证书

导航到 AWS 证书管理器控制台。单击“申请证书”。将证书类型设置为“公共”,然后单击“下一步”。在表单输入中输入您的全限定域名,设置“验证方式”为“DNS 验证”,点击“请求”。

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

然后,您将被重定向到一个页面,在那里您可以看到您的所有证书。您刚刚创建的证书应该具有“待验证”状态。

为了让 AWS 颁发证书,你首先必须证明你是这个域名的所有者。在表中,单击证书以查看“证书详细信息”。注意“CNAME 的名字”和“CNAME 的价值”。要验证域的所有权,您需要在域的 DNS 设置中创建“CNAME 记录”。为此使用“CNAME 名称”和“CNAME 价值”。一旦完成,Amazon 将需要几分钟的时间来获取域更改并颁发证书。状态应该从“等待验证”更改为“已发布”。

将域名指向 EB CNAME

接下来,您需要将您的域(或子域)指向您的 EB 环境 CNAME。回到您的域名的 DNS 设置,添加另一个 CNAME 记录,其值为您的 EB CNAME -例如,django-images-dev.us-west-2.elasticbeanstalk.com

等待几分钟,让您的 DNS 刷新,然后在浏览器中测试您的域名的http://风格。

修改负载平衡器以服务于 HTTPS

回到弹性豆茎控制台,点击“配置”。然后,在“负载平衡器”类别中,单击“编辑”。单击“添加监听程序”并使用以下详细信息创建监听程序:

  1. 端口- 443
  2. 议定书- HTTPS
  3. SSL 证书-选择您刚刚创建的证书

点击“添加”。然后,滚动到页面底部,单击“应用”。环境更新需要几分钟时间。

修改您的应用程序设置

接下来,我们需要对 Django 应用程序进行一些修改。

首先,将您的完全限定域添加到ALLOWED_HOSTS:

`# core/settings.py

ALLOWED_HOSTS = [
    # ...
    'yourdomain.com',
]` 

最后,我们需要将所有流量从 HTTP 重定向到 HTTPS。有多种方法可以做到这一点,但最简单的方法是将 Apache 设置为代理主机。我们可以通过在中的option_settings末尾添加以下内容来编程实现这一点。EB extensions/01 _ django . config:

`# .ebextensions/01_django.config

option_settings:
  # ...
  aws:elasticbeanstalk:environment:proxy:  # new
    ProxyServer: apache                    # new` 

您最终的 01_django.config 文件现在应该是这样的:

`# .ebextensions/01_django.config option_settings: aws:elasticbeanstalk:application:environment: DJANGO_SETTINGS_MODULE:  "core.settings" PYTHONPATH:  "/var/app/current:$PYTHONPATH" aws:elasticbeanstalk:container:python: WSGIPath:  "core.wsgi:application" aws:elasticbeanstalk:environment:proxy: ProxyServer:  apache container_commands: 01_makemigrations: command:  "source /var/app/venv/*/bin/activate && python3 manage.py makemigrations --noinput" leader_only:  true 02_migrate: command:  "source /var/app/venv/*/bin/activate && python3 manage.py migrate --noinput" leader_only:  true 03_superuser: command:  "source /var/app/venv/*/bin/activate && python3 manage.py createsu" leader_only:  true 04_collectstatic: command:  "source /var/app/venv/*/bin/activate && python3 manage.py collectstatic --noinput" leader_only:  true` 

接下来,创建一个”。平台"文件夹中,并添加以下文件和文件夹:

`└-- .platform
    └-- httpd
        └-- conf.d
            └-- ssl_rewrite.conf` 

ssl_rewrite.conf :

`# .platform/httpd/conf.d/ssl_rewrite.conf

RewriteEngine On
<If "-n '%{HTTP:X-Forwarded-Proto}' && %{HTTP:X-Forwarded-Proto} != 'https'">
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
</If>` 

您的项目结构现在应该如下所示:

`|-- .ebextensions
|-- 01_django.config
|-- .elasticbeanstalk
|-- config.yml
|-- .gitignore
|-- .platform
|-- httpd
|-- conf.d
|-- ssl_rewrite.conf
|-- README.md
|-- core
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings.py
|   |-- urls.py
|-- wsgi.py
|-- db.sqlite3
|-- images
|   |-- __init__.py
|   |-- admin.py
|   |-- apps.py
|   |-- forms.py
│   ├── management
│   │   ├── __init__.py
│   │   └── commands
│   │       ├── __init__.py
│   │       └── createsu.py
|   |-- migrations
|   |   |-- 0001_initial.py
|   |-- __init__.py
|   |-- models.py
|   |-- tables.py
|   |-- templates
|   |-- images
|   |-- index.html
|   |-- tests.py
|   |-- urls.py
|-- views.py
|-- manage.py
└-- requirements.txt` 

将更改提交给 git 并部署:

`$ git add .
$ git commit -m "updates for eb"

$ eb deploy` 

现在,在你的浏览器中,你的应用程序的https://风格应该工作了。试试去http://味的。你应该被重定向到https://风味。确保证书也正确加载:

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

环境变量

在生产中,最好将特定于环境的配置存储在环境变量中。使用 Elastic Beanstalk,您可以用两种不同的方式设置自定义环境变量。

通过 EB CLI 的环境变量

让我们把 Django 的SECRET_KEYDEBUG设置变成环境变量。

从跑步开始:

`$ eb setenv DJANGO_SECRET_KEY='<replace me with your own secret key>' \
            DJANGO_DEBUG='1'` 

您可以用一个命令设置多个环境变量,用空格分隔它们。这是推荐的方法,因为它只需要对 EB 环境进行一次更新。

相应地更改 core/settings.py :

`# core/settings.py

SECRET_KEY = os.environ.get(
    'DJANGO_SECRET_KEY',
    '<replace me with your own fallback secret key>'
)

DEBUG = os.environ.get('DJANGO_DEBUG', '1').lower() in ['true', 't', '1']` 

将更改提交给 git 并部署:

`$ git add .
$ git commit -m "updates for eb"

$ eb deploy` 

通过 EB 控制台的环境变量

通过eb open进入弹性豆茎控制台。导航至“配置”>“软件”>“编辑”。然后,向下滚动到“环境属性”。

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

完成后,单击“应用”,您的环境将会更新。

然后,您可以通过os.environ在您的 Python 环境中访问这些变量。

例如:

`VARIABLE_NAME = os.environ['VARIABLE_NAME']` 

调试弹性豆茎

当使用 Elastic Beanstalk 时,如果您不知道如何访问日志文件,那么找出问题所在会非常令人沮丧。在这一节中,我们将会看到这一点。

有两种方法可以访问日志:

  1. 弹性 Beanstalk CLI 或控制台
  2. SSH 到 EC2 实例

从个人经验来看,我已经能够用第一种方法解决所有问题。

弹性 Beanstalk CLI 或控制台

CLI:

该命令将从以下文件中获取最后 100 行:

`/var/log/web.stdout.log /var/log/eb-hooks.log /var/log/nginx/access.log /var/log/nginx/error.log /var/log/eb-engine.log` 

运行eb logs相当于登录 EB 控制台,导航到“日志”。

我建议将日志传送到 CloudWatch 。运行以下命令来启用此功能:

`$ eb logs --cloudwatch-logs enable` 

您通常会在 /var/log/web.stdout.log/var/log/eb-engine.log 中找到 Django 错误。

要了解更多关于弹性 Beanstalk 日志的信息,请查看来自 Amazon EC2 实例的日志。

SSH 到 EC2 实例

要连接到运行 Django 应用程序的 EC2 实例,运行:

第一次会提示您将主机添加到已知主机。答应吧。这样,您现在就可以完全访问 EC2 实例了。请随意试验 Django 管理命令,并检查前一节中提到的一些日志文件。

请记住,Elastic Beanstalk 会自动伸缩和部署新的 EC2 实例。您在这个特定 EC2 实例上所做的更改不会反映在新启动的 EC2 实例上。一旦这个特定的 EC2 实例被替换,您的更改将被清除。

结论

在本教程中,我们介绍了将 Django 应用程序部署到 AWS Elastic Beanstalk 的过程。到目前为止,您应该对弹性豆茎的工作原理有了一个大致的了解。通过回顾本教程开头的目标,快速进行自我检查。

后续步骤:

  1. 你应该考虑创建两个独立的 EB 环境(devproduction)。
  2. 查看用于您的弹性 Beanstalk 环境的自动伸缩组,了解如何配置触发器来自动伸缩您的应用程序。

要删除我们在整个教程中创建的所有 AWS 资源,首先要终止 Elastic Beanstalk 环境:

您需要手动删除 S3 桶、SSL 证书以及 IAM 组和用户。

最后,你可以在 GitHub 上的django-elastic-beanstalkrepo 中找到代码的最终版本。

将 Django 应用程序部署到 Fly.io

原文:https://testdriven.io/blog/django-fly/

在本教程中,我们将看看如何部署一个 Django 应用程序到 Fly.io

目标

学完本教程后,您应该能够:

  1. 解释什么是 Fly.io,它是如何工作的。
  2. 将 Django 应用程序部署到 Fly.io。
  3. 在 Fly.io 上运行一个 PostgreSQL 实例。
  4. 通过 Fly Volumes 设置持久存储。
  5. 将域名链接到您的 web 应用程序。
  6. 获得一个 SSL 证书,让我们加密并在 HTTPS 上提供您的应用程序。

Fly.io 是什么?

Fly.io 是一个流行的平台即服务(PaaS)平台,为 web 应用程序提供托管服务。与许多其他 PaaS 托管提供商不同,他们不是转售 AWSGCP 服务,而是在运行于世界各地的物理专用服务器上托管你的应用。正因为如此,他们能够提供比其他 PaaS 更便宜的主机服务,比如 Heroku。他们的主要关注点是尽可能靠近他们的客户部署应用程序(在撰写本文时,你可以在 24 个地区中挑选)。Fly.io 支持三种构建器:Dockerfile、 Buildpacks ,或者预构建的 Docker 映像。

它们提供了强大的缩放和自动缩放功能

与其他 PaaS 提供商相比,Fly.io 采用不同的方法来管理您的资源。它没有花哨的管理仪表板;相反,所有的工作都是通过他们名为 flyctl 的 CLI 来完成的。

他们的免费计划包括:

  • 多达 3 个共享 cpu-1x 256 MB 虚拟机
  • 3GB 永久卷存储(总计)
  • 160GB 出站数据传输

这应该足够运行一些小应用程序来测试他们的平台了。

为什么要 Fly.io?

  • 小型项目的免费计划
  • 巨大的地区支持
  • 出色的文档和完整的 API 文档
  • 轻松实现水平和垂直缩放
  • 相对便宜

项目设置

在本教程中,我们将部署一个简单的图像托管应用程序,名为 django-images

在学习教程的过程中,通过部署您自己的 Django 应用程序来检查您的理解。

首先,从 GitHub 上的中获取代码:

创建新的虚拟环境并激活它:

`$ python3 -m venv venv && source venv/bin/activate` 

安装需求并迁移数据库:

`(venv)$ pip install -r requirements.txt
(venv)$ python manage.py migrate` 

运行服务器:

`(venv)$ python manage.py runserver` 

打开您最喜欢的网络浏览器,导航到 http://localhost:8000 。使用右边的表格上传图像,确保一切正常。上传图像后,您应该会看到它显示在表格中:

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

安装 Flyctl

要使用 Fly 平台,你首先需要安装 Flyctl ,这是一个命令行界面,允许你做从创建帐户到将应用程序部署到 Fly 的所有事情。

要在 Linux 上安装它,请运行:

`$ curl -L https://fly.io/install.sh | sh` 

对于其他操作系统,请看一下安装指南

安装完成后,将flyctl添加到PATH:

`$ export FLYCTL_INSTALL="/home/$USER/.fly"
$ export PATH="$FLYCTL_INSTALL/bin:$PATH"` 

接下来,使用您的 Fly.io 帐户进行身份验证:

`$ fly auth login

# In case you don't have an account yet:
# fly auth signup` 

该命令将打开您的默认 web 浏览器,并要求您登录。登录后,单击“继续”登录 Fly CLI。

为确保一切正常,请尝试列出应用程序:

`$ fly apps list

NAME         OWNER           STATUS          PLATFORM        LATEST DEPLOY` 

您应该会看到一个空表,因为您还没有任何应用程序。

配置 Django 项目

在教程的这一部分,我们将准备并对接 Django 应用程序,以便部署到 Fly.io。

环境变量

我们不应该在源代码中存储秘密,所以让我们利用环境变量。最简单的方法是使用名为 python-dotenv 的第三方 Python 包。首先将其添加到 requirements.txt :

随意使用不同的包来处理环境变量,如 django-environpython-decouple

然后,在 core/settings.py 的顶部导入并初始化 python-dotenv,如下所示:

`# core/settings.py

from pathlib import Path

from dotenv import load_dotenv

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

load_dotenv(BASE_DIR / '.env')` 

接下来,从环境中加载SECRET_KEYDEBUGALLOWED_HOSTSCSRF_TRUSTED_ORIGINS:

`# core/settings.py

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv('SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv('DEBUG', '0').lower() in ['true', 't', '1']

ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS').split(' ')
CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS').split(' ')` 

不要忘记在文件顶部导入os:

数据库ˌ资料库

要使用 Postgres 代替 SQLite,我们首先需要安装数据库适配器。

将下面一行添加到 requirements.txt 中:

当我们在本教程的后面创建 Postgres 实例时,一个受十二因素应用启发的名为DATABASE_URL的环境变量将被设置并以如下格式传递给我们的 web 应用:

`postgres://USER:PASSWORD@HOST:PORT/NAME` 

为了在 Django 中使用它,我们可以使用一个名为 dj-database-url 的包。这个包将 URL 转换成 Django 数据库参数。

像这样添加到 requirements.txt 中:

接下来,导航到 core/settings.py ,将DATABASES更改如下:

`# core/settings.py

DATABASES = {
    'default': dj_database_url.parse(os.environ.get('DATABASE_URL'), conn_max_age=600),
}` 

不要忘记重要的一点:

格尼科恩

接下来,让我们安装 Gunicorn ,这是一个生产级的 WSGI 服务器,将用于生产,而不是 Django 的开发服务器。

添加到 requirements.txt :

Dockerfile

如简介中所述,有三种方式将应用部署到 Fly.io:

  1. Dockerfile
  2. 构建包
  3. 预建的 Docker 图像

在本教程中,我们将使用第一种方法,因为它是最灵活的,并且给了我们对 web 应用程序最大的控制权。它也很棒,因为它允许我们在未来轻松地切换到另一个托管服务(支持 Docker)。

首先,在项目根目录下创建一个名为 Dockerfile 的新文件,内容如下:

`# pull official base image
FROM  python:3.9.6-alpine

# set work directory
WORKDIR  /usr/src/app

# set environment variables
ENV  PYTHONDONTWRITEBYTECODE 1
ENV  PYTHONUNBUFFERED 1

# create the app directory - and switch to it
RUN  mkdir -p /app
WORKDIR  /app

# install dependencies
COPY  requirements.txt /tmp/requirements.txt
RUN  set -ex && \
    pip install --upgrade pip && \
    pip install -r /tmp/requirements.txt && \
    rm -rf /root/.cache/

# copy project
COPY  . /app/

# expose port 8000
EXPOSE  8000

CMD  ["gunicorn",  "--bind",  ":8000",  "--workers",  "2",  "core.wsgi:application"]` 

如果您正在部署自己的 Django 应用程序,请确保相应地更改CMD

接下来,创建一个*。dockerignore* :

`*.pyc *.pyo *.mo *.db *.css.map *.egg-info *.sql.gz .cache .project .idea .pydevproject .DS_Store .git/ .sass-cache .vagrant/ __pycache__ dist docs env logs Dockerfile` 

这是一个通用的*。Django 的 dockerignore* 模板。如果您想从图像中排除任何其他内容,请确保对其进行更改。

部署应用程序

在本节教程中,我们将启动一个 Postgres 实例,并将我们的 Django 应用程序部署到 Fly.io。

发动

要创建和配置新的应用程序,请运行:

`$ fly launch

Creating app in /dev/django-flyio
Scanning source code
Detected a Dockerfile app
? Choose an app name (leave blank to generate one): django-images
automatically selected personal organization: Nik Tomazic
? Choose a region for deployment: Frankfurt, Germany (fra)
Created app django-images in organization personal
Wrote config file fly.toml
? Would you like to set up a Postgresql database now? Yes
? Select configuration: Development - Single node, 1x shared CPU, 256MB RAM, 1GB disk
Creating postgres cluster in organization personal
Creating app...
Setting secrets on app django-images-db...
Provisioning 1 of 1 machines with image flyio/postgres:14.4
Waiting for machine to start...
Machine 21781350b24d89 is created
==> Monitoring health checks
  Waiting for 21781350b24d89 to become healthy (started, 3/3)
Postgres cluster django-images-db created
Postgres cluster django-images-db is now attached to django-images
? Would you like to set up an Upstash Redis database now? No
? Would you like to deploy now? No
Your app is ready! Deploy with `flyctl deploy`` 

注意事项:

  1. 选择一个应用名称:自定义名称或留空以生成一个随机名称
  2. 选择部署区域:离你最近的区域
  3. 是否要现在设置 Postgresql 数据库:
  4. 数据库配置:开发
  5. 您想现在设置一个 Upstash Redis 数据库吗:
  6. 是否要立即部署:

该命令将在 Fly.io 上创建一个应用程序,启动一个 Postgres 实例,并在项目根目录中创建一个名为 fly.toml 的应用程序配置文件。

确保应用程序已成功创建:

`$ fly apps list

NAME                            OWNER           STATUS          PLATFORM        LATEST DEPLOY
django-images                   personal        pending
django-images-db                personal        deployed        machines
fly-builder-damp-wave-89        personal        deployed        machines` 

你会注意到三个应用程序。第一个是实际的 web 应用程序,然后是 Postgres 实例,最后是一个 Fly builder。Fly builders 用于构建您的 Docker 映像,将它们推送到容器注册表,并部署您的应用程序。

检查你的应用程序的状态:

`$ fly status

App
  Name     = django-images
  Owner    = personal
  Version  = 0
  Status   = pending
  Hostname = django-images.fly.dev
  Platform =

App has not been deployed yet.` 

主机名告诉您 web 应用程序可以访问哪个地址。记下它,因为我们需要在教程的后面将其添加到ALLOWED_HOSTSCSRF_TRUSTED_ORIGINS中。

应用程序配置

让我们稍微修改一下应用配置文件,使其能够很好地与 Django 配合使用。

首先,将端口8080更改为 Django 的首选端口8000:

`# fly.toml app  =  "django-images" kill_signal  =  "SIGINT" kill_timeout  =  5 processes  =  [] [env] PORT  =  "8000"  # new [experimental] allowed_public_ports  =  [] auto_rollback  =  true [[services]] http_checks  =  [] internal_port  =  8000  # changed processes  =  ["app"] protocol  =  "tcp" script_checks  =  [] [services.concurrency] hard_limit  =  25 soft_limit  =  20 type  =  "connections"` 

接下来,为了确保数据库得到迁移,添加一个部署部分,并在新创建的部分中定义一个release_command:

`# fly.toml [deploy] release_command  =  "python manage.py migrate --noinput"` 

在这个版本部署之前,release_command运行在一个临时的 VM 中——使用成功构建的版本。这对于运行数据库迁移等一次性命令非常有用。

如果将来需要运行多个命令,可以在项目文件中创建一个 bash 脚本,然后像这样执行它:

`# fly.toml [deploy] release_command  =  "sh /path/to/your/script"` 

秘密

设置我们在 Django 的 settings.py 中使用的秘密:

`$ fly secrets set DEBUG="1"
$ fly secrets set ALLOWED_HOSTS="localhost 127.0.0.1 [::1] <your_app_hostname>"
$ fly secrets set CSRF_TRUSTED_ORIGINS="https://<your_app_hostname>"
$ fly secrets set SECRET_KEY="[[email protected]](/cdn-cgi/l/email-protection)"` 

确保将<your_app_hostname>替换为您实际的应用程序主机名。例如:

`$ fly secrets set ALLOWED_HOSTS="localhost 127.0.0.1 [::1] django-images.fly.dev"
$ fly secrets set CSRF_TRUSTED_ORIGINS="https://django-images.fly.dev"` 

为了使调试更容易,我们临时启用了调试模式。不要担心,因为我们将在教程的后面更改它。

确保密码设置成功:

`$ fly secrets list

NAME                    DIGEST                  CREATED AT
ALLOWED_HOSTS           06d92bcb15cf7eb1        30s ago
CSRF_TRUSTED_ORIGINS    06d92bcb15cf7eb1        21s ago
DATABASE_URL            e63c286f83782cf3        5m31s ago
DEBUG                   3baf154b33091aa0        45s ago
SECRET_KEY              62ac51c770a436f9        10s ago` 

部署 Fly 应用程序后,每个秘密修改都会触发重新部署。如果您需要一次设置多个密码,并且不希望您的应用程序多次重新部署,您可以在一个命令中连接这些密码,如下所示:

`$ fly secrets set NAME1="VALUE1" NAME2="VALUE2"` 

部署

要将应用程序部署到 Fly 平台,请运行:

`$ fly deploy

==> Verifying app config
--> Verified app config
==> Building image
Remote builder fly-builder-damp-wave-89 ready
==> Creating build context
--> Creating build context done
==> Building image with Docker
--> docker host: 20.10.12 linux x86_64
[+] Building 24.3s (11/11) FINISHED                                                             0.0s
--> Building image done
==> Pushing image to fly
The push refers to repository [registry.fly.io/django-images]
bee487f02b7f: Pushed
deployment-01GH4AGWQEZ607T7F93RB9G4NB: digest: sha256:85309cd5c7fe58f3a59b13d50576d8568525012bc6e665ba7b5cc1df3da16a9e size: 2619
--> Pushing image done
image: registry.fly.io/django-images:deployment-01GH4AGWQEZ607T7F93RB9G4NB
image size: 152 MB
==> Creating release
--> release v2 created
==> Monitoring deployment

 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing]
--> v1 deployed successfully` 

该命令将使用 Fly builder 构建 Docker 映像,将其推送到容器注册表,并使用它来部署您的应用程序。在部署您的应用程序之前,release_command将在一个临时虚拟机上运行。

第一次部署你的应用大约需要五分钟,所以你可以在等待的时候喝杯咖啡。

部署应用程序后,检查其状态:

`$ fly status

App
  Name     = django-images
  Owner    = personal
  Version  = 0
  Status   = running
  Hostname = django-images.fly.dev
  Platform = nomad

Deployment Status
  ID          = 563c84b4-bf10-874c-e0e9-9820cbdd6725
  Version     = v1
  Status      = successful
  Description = Deployment completed successfully
  Instances   = 1 desired, 1 placed, 1 healthy, 0 unhealthy

Instances
ID              PROCESS VERSION REGION  DESIRED STATUS  HEALTH CHECKS           RESTARTS        CREATED
c009e8b0        app     1       fra     run     running 1 total, 1 passing      0               1m8s ago` 

检查日志:

`$ fly logs

[info]Starting init (commit: 81d5330)...
[info]Mounting /dev/vdc at /app/data w/ uid: 0, gid: 0 and chmod 0755
[info]Preparing to run: `gunicorn --bind :8000 --workers 2 core.wsgi:application` as root
[info]2022/11/05 17:09:36 listening on [fdaa:0:c65c:a7b:86:2:bd43:2]:22 (DNS: [fdaa::3]:53)
[info][2022-11-05 17:09:36 +0000] [529] [INFO] Starting gunicorn 20.1.0
[info][2022-11-05 17:09:36 +0000] [529] [INFO] Listening at: http://0.0.0.0:8000 (529)
[info][2022-11-05 17:09:36 +0000] [529] [INFO] Using worker: sync
[info][2022-11-05 17:09:36 +0000] [534] [INFO] Booting worker with pid: 534
[info][2022-11-05 17:09:36 +0000] [535] [INFO] Booting worker with pid: 535` 

一切看起来都很棒。让我们在浏览器中打开应用程序,确保它能够正常工作:

通过上传图像进行测试。

持久存储

Fly.io(以及许多其他类似的服务,如 Heroku)提供了一个短暂的文件系统。这意味着您的数据不是持久的,可能会在应用程序关闭或重新部署时消失。如果你的应用程序需要保留文件,这是非常糟糕的。

为了解决这个问题,Fly.io 为 Fly 应用程序提供了、持久存储。这听起来很棒,但它不是生产的最佳解决方案,因为卷被绑定到一个区域和一个服务器-这限制了您的应用程序的可伸缩性和跨不同区域的分布。

此外,Django 本身并不是为生产中的静态/媒体文件服务的。

我强烈建议你使用 AWS S3 或类似的服务,而不是 Volumes。如果您的应用程序不需要处理媒体文件,您仍然可以使用卷和白化来处理静态文件。

要了解如何使用 Django 设置 AWS S3,请看一下在亚马逊 S3 上存储 Django 静态和媒体文件的

为了本教程的简单性和完整性,我们仍将使用飞卷。

首先,在与您的应用程序相同的区域创建一个宗卷:

`$ fly volumes create <volume_name> --region <region> --size <in_gigabytes>

# For example:
# fly volumes create django_images_data --region fra --size 1

        ID: vol_53q80vdd16xvgzy6
      Name: django_images_data
       App: django-images
    Region: fra
      Zone: d7f9
   Size GB: 1
 Encrypted: true
Created at: 04 Nov 22 13:20 UTC` 

在我的例子中,我在法兰克福地区创建了一个 1 GB 的卷。

接下来,进入 core/settings.py ,修改STATIC_ROOTMEDIA_ROOT如下:

`# core/settings.py

STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'data/staticfiles'

MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'data/mediafiles'` 

我们必须将静态和媒体文件放在一个子文件夹中,因为 Fly volume 只允许我们挂载一个目录。

要将目录挂载到 Fly 卷,请转到您的 fly.toml 并添加以下内容:

`# fly.toml [mounts] source="django_images_data" destination="/app/data"` 
  1. source是您的 Fly 卷的名称
  2. destination是您想要挂载的目录的绝对路径

重新部署你的应用程序:

最后,让我们收集静态文件。

SSH 进入 Fly 服务器,导航到“app”目录,运行collectstatic:

`$ fly ssh console
# cd /app
# python manage.py collectstatic --noinput` 

为了确保文件收集成功,请查看一下 /app/data 文件夹:

`# ls /app/data

lost+found   staticfiles` 

太好了!您可以通过exit退出 SSH。

通过检查管理面板,确保已经成功收集了静态文件:

`http://<your_app_hostname>/admin` 

Django 管理访问

要访问 Django 管理面板,我们需要创建一个超级用户。我们有两个选择:

  1. 使用fly ssh执行命令。
  2. 创建一个 Django 命令来创建一个超级用户,并将其添加到 fly.toml 中。

由于这是一次性任务,我们将使用第一种方法。

首先,使用 Fly CLI SSH 进入服务器:

`$ fly ssh console

Connecting to fdaa:0:e25c:a7b:8a:4:b5c5:2... complete` 

然后,导航到我们的应用程序文件所在的 /app ,运行createsuperuser命令:

`# cd /app
# python manage.py createsuperuser` 

按照提示操作,然后运行exit关闭 SSH 连接。

要确保已成功创建超级用户,请导航至管理控制面板并登录:

`http://<your_app_hostname>/admin` 

或者使用:

然后导航至*/管理*。

添加域

教程的这一部分要求您有一个域名。

需要一个便宜的域名来练习?几个域名注册商有特殊优惠。“xyz”域。或者,您可以在 Freenom 创建一个免费域名。

若要将域添加到您的应用程序,您首先需要获取一个证书:

`$ fly certs add <your_full_domain_name>

# For example:
# fly certs add fly.testdriven.io` 

接下来,进入你的域名注册服务商 DNS 设置,添加一个新的“CNAME 记录”指向你的应用程序的主机名,如下所示:

`+----------+--------------+----------------------------+-----------+ | Type     | Host         | Value                      | TTL       |
+----------+--------------+----------------------------+-----------+ | A Record | <some host> | <your_app_hostname> | Automatic |
+----------+--------------+----------------------------+-----------+` 

示例:

`+----------+--------------+----------------------------+-----------+ | Type     | Host         | Value                      | TTL       |
+----------+--------------+----------------------------+-----------+ | A Record | fly          | django-images.fly.dev      | Automatic |
+----------+--------------+----------------------------+-----------+` 

如果您不想使用子域,您可以遵循相同的步骤,但只需将 DNS 主机更改为@,并相应地配置 Fly.io 证书。

检查域是否已成功添加:

`$ fly certs list

Host Name                 Added                Status
fly.testdriven.io         5 minutes ago        Awaiting configuration` 

检查证书是否已颁发:

`$ fly certs check <your_full_domain_name>

# For example:
# fly certs check fly.testdriven.io

The certificate for fly.testdriven.io has not been issued yet.
Your certificate for fly.testdriven.io is being issued. Status is Awaiting certificates.` 

如果证书尚未颁发,请等待大约十分钟,然后重试:

`$ fly certs check fly.testdriven.io

The certificate for fly.testdriven.io has been issued.
Hostname                  = fly.testdriven.io
DNS Provider              = enom
Certificate Authority     = Let's Encrypt
Issued                    = rsa,ecdsa
Added to App              = 8 minutes ago
Source                    = fly` 

最后,将新的域添加到ALLOWED_HOSTSCSRF_TRUSTED_ORIGINS:

`$ fly secrets set ALLOWED_HOSTS="localhost 127.0.0.1 [::1] <your_app_hostname> <your_full_domain_name>"
$ fly secrets set CSRF_TRUSTED_ORIGINS="https://<your_app_hostname> https://<your_full_domain_name>"

# For example:
# fly secrets set ALLOWED_HOSTS="localhost 127.0.0.1 [::1] django-images.fly.dev fly.testdriven.io"
# fly secrets set CSRF_TRUSTED_ORIGINS="https://django-images.fly.dev https://fly.testdriven.io"` 

当你修改你的密码时,你的应用将重新启动。重新启动后,您的 web 应用程序应该可以在新添加的域中访问。

结论

在本教程中,我们已经成功地将 Django 应用程序部署到 Fly.io。我们已经处理了 PostgreSQL 数据库、通过 Fly Volumes 的持久存储,并添加了域名。现在,您应该对 Fly 平台的工作原理有了一个大致的了解,并且能够部署自己的应用程序。

下一步是什么?

  1. AWS S3 或类似的服务交换 Fly Volumes ,以更好、更安全的方式提供静态/媒体文件。
  2. 设置DEBUG=0禁用调试模式。请记住,当启用调试模式时,应用程序仅提供静态/媒体文件。有关如何在生产中处理静态和媒体文件的更多信息,请参考在 Django 中处理静态和媒体文件。
  3. 看看缩放和自动缩放区域支持

将 Django 应用程序部署到 Google 应用程序引擎

原文:https://testdriven.io/blog/django-gae/

在本教程中,我们将看看如何将一个 Django 应用安全地部署到谷歌应用引擎

目标

学完本教程后,您应该能够:

  1. 解释什么是谷歌应用引擎,它是如何工作的。
  2. 将 Django 应用程序部署到 Google App Engine。
  3. 云 SQL 上运行 Postgres 实例。
  4. 利用秘密管理器处理环境变量和秘密。
  5. 使用云存储为静态和媒体文件设置持久存储。
  6. 将域名链接到您的应用程序,并在 HTTPS 上提供您的应用程序。

什么是谷歌应用引擎?

Google App Engine (GAE)是一个完全托管的无服务器平台,用于大规模开发和托管网络应用。它具有强大的内置自动扩展功能,可以根据需求自动分配更多/更少的资源。GAE 原生支持用 Python、Node.js、Java、Ruby、C#、Go 和 PHP 编写的应用程序。或者,它通过定制运行时或 Dockerfiles 提供对其他语言的支持。

它具有强大的应用程序诊断功能,您可以将它与云监控日志记录相结合,以监控您的应用程序的健康状况和性能。此外,GAE 允许你的应用扩展到零,这意味着如果没有人使用你的服务,你不用支付任何费用。

在撰写本文时,谷歌为新用户提供了300 美元的免费积分来试用他们的平台。积分将在 90 天后到期。

项目设置

在本教程中,我们将部署一个简单的图像托管应用程序,名为 django-images

在学习教程的过程中,通过部署您自己的 Django 应用程序来检查您的理解。

首先,从 GitHub 上的中获取代码:

创建新的虚拟环境并激活它:

`$ python3 -m venv venv && source venv/bin/activate` 

安装需求并迁移数据库:

`(venv)$ pip install -r requirements.txt
(venv)$ python manage.py migrate` 

运行服务器:

`(venv)$ python manage.py runserver` 

打开您最喜欢的网络浏览器,导航到 http://localhost:8000 。使用右边的表格上传图像,确保一切正常。上传图像后,您应该会看到它显示在表格中:

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

安装 Google Cloud CLI

要使用谷歌云平台 (GCP),首先安装谷歌云 CLI (gcloud CLI)。gcloud CLI 允许您创建和管理您的 Google 云资源和服务。

根据您的操作系统和处理器架构,安装过程会有所不同。继续按照官方安装指南为您的操作系统和 CPU 安装。

要验证安装是否成功,请运行:

`$ gcloud version

Google Cloud SDK 415.0.0
bq 2.0.84
core 2023.01.20
gcloud-crc32c 1.0.0
gsutil 5.18` 

配置 Django 项目

在教程的这一部分,我们将配置 Django 项目,以便与 GAE 一起工作。

环境变量

我们不应该在源代码中存储秘密,所以让我们利用环境变量。最简单的方法是使用名为 django-environ 的第三方 Python 包。首先将其添加到 requirements.txt :

我建议您继续使用 django-environ,因为它是专门针对 django 的,并且支持数据库 URL 中的 Unix 套接字路径。

对于 Django 来说,要初始化环境更改,请更新 settings.py 的顶部,如下所示:

`# core/settings.py

import os
import environ

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

env = environ.Env(DEBUG=(bool, False))
env_file = os.path.join(BASE_DIR, '.env')
env.read_env(env_file)` 

接下来,从环境中加载SECRET_KEYDEBUG:

`# core/settings.py

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env('DEBUG')` 

为了设置ALLOWED_HOSTSCSRF_TRUSTED_ORIGINS,我们可以使用来自 GAE 文档的以下代码片段:

`# core/settings.py

APPENGINE_URL = env('APPENGINE_URL', default=None)
if APPENGINE_URL:
    # ensure a scheme is present in the URL before it's processed.
    if not urlparse(APPENGINE_URL).scheme:
        APPENGINE_URL = f'https://{APPENGINE_URL}'

    ALLOWED_HOSTS = [urlparse(APPENGINE_URL).netloc]
    CSRF_TRUSTED_ORIGINS = [APPENGINE_URL]
    SECURE_SSL_REDIRECT = True
else:
    ALLOWED_HOSTS = ['*']` 

这段代码从环境中获取APPENGINE_URL,并自动配置ALLOWED_HOSTSCSRF_TRUSTED_ORIGINS。此外,它使SECURE_SSL_REDIRECT能够执行 HTTPS。

不要忘记在文件顶部添加导入:

`from urllib.parse import urlparse` 

数据库ˌ资料库

要使用 Postgres 代替 SQLite,我们首先需要安装数据库适配器。

将下面一行添加到 requirements.txt 中:

在本教程的后面,我们将创建一个 Postgres 实例,为我们提供形成一个受十二因素应用启发的数据库 URL 所需的细节。DATABASE_URL将采用以下格式:

`postgres://USER:[[email protected]](/cdn-cgi/l/email-protection)//cloudsql/PROJECT_ID:REGION:INSTANCE_NAME/DATABASE_NAME` 

为了将DATABASE_URL用于 Django,我们可以像这样使用 django-environ 的db()方法:

`# core/settings.py

DATABASES = {'default': env.db()}` 

格尼科恩

接下来,让我们安装 Gunicorn ,这是一个生产级的 WSGI 服务器,将用于生产,而不是 Django 的开发服务器。

添加到 requirements.txt :

app.yaml

Google App Engine 的 app.yaml 配置文件用于配置 web 应用程序的运行时环境。 app.yaml 文件包含运行时、URL 处理程序和环境变量等信息。

首先在项目根目录下创建一个名为 app.yaml 的新文件,包含以下内容:

`# app.yaml runtime:  python39 env:  standard entrypoint:  gunicorn -b :$PORT core.wsgi:application handlers: -  url:  /.* script:  auto runtime_config: python_version:  3` 

注意事项:

  1. 我们定义了启动 WSGI 服务器的entrypoint命令。
  2. env 有两个选项:standardflexible。我们选择了 standard,因为它更容易启动和运行,适用于较小的应用程序,并且支持 Python 3.9 开箱即用。
  3. 最后,handlers定义不同的 URL 是如何路由的。我们将在教程的后面定义静态和媒体文件的处理程序。

有关 app.yaml 的更多信息,请查看文档

。gcloudnignore

一个。gcloudnignorefile 允许您在部署应用程序时指定不想上传到 GAE 的文件。它的工作原理类似于*。gitignore* 文件。

继续创建一个*。项目根中的 gcloudnignore*文件包含以下内容:

`# .gcloudignore .gcloudignore # Ignore local .env file .env # If you would like to upload your .git directory, .gitignore file, or files # from your .gitignore file, remove the corresponding line # below: .git .gitignore # Python pycache: __pycache__/ # Ignore collected static and media files mediafiles/ staticfiles/ # Ignore the local DB db.sqlite3 # Ignored by the build system /setup.cfg venv/ # Ignore IDE files .idea/` 

部署应用程序

在本节教程中,我们将把应用程序部署到 Google App Engine。

项目初始化

如果您尚未初始化 gcloud CLI,请继续操作:

CLI 将打开您的浏览器,要求您登录并接受一些权限。

之后,你必须选择你的项目。我建议您创建一个新项目,因为删除一个项目比单独删除所有服务和资源更容易。

对于该地区,选择离您最近的地区。

创建应用程序

要创建 App Engine 应用程序,请转到项目根目录并运行:

`$ gcloud app create

You are creating an app for project [indigo-griffin-376011].
WARNING: Creating an App Engine application for a project is irreversible and the region
cannot be changed.

Please choose the region where you want your App Engine application located:

 ...
 [13] europe-west3  (supports standard and flexible and search_api)
 [14] europe-west6  (supports standard and flexible and search_api)
 [15] northamerica-northeast1 (supports standard and flexible and search_api)
 [16] southamerica-east1 (supports standard and flexible and search_api)
 [17] us-central    (supports standard and flexible and search_api)
 [18] us-east1      (supports standard and flexible and search_api)
 ...
 [24] cancel
Please enter your numeric choice:  13

Creating App Engine application in project [indigo-griffin-376011] and region [europe-west3]....done.
Success! The app is now created. Please use `gcloud app deploy` to deploy your first app.` 

同样,选择离你最近的地区。

数据库ˌ资料库

规定

导航到云 SQL 仪表板并使用以下参数创建一个新的 Postgres 实例:

  • 实例 ID:mydb-实例
  • 密码:输入自定义密码或生成密码
  • 数据库版本: PostgreSQL 14
  • 配置:由你决定
  • 地区:与您的应用相同的地区
  • 区域可用性:由您决定

您可能还需要启用“计算引擎 API”来创建 SQL 实例。

设置数据库需要几分钟时间。同时,通过搜索“云 SQL 管理 API”并点击“启用”,继续启用云 SQL 管理 API 。我们需要启用它来测试数据库连接。

一旦提供了数据库,您应该会被重定向到数据库详细信息。记下“连接名称”:

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

接下来,选择侧边栏上的“Databases”并创建一个新的数据库。

最后,选择侧边栏上的“Users”并创建一个新用户。生成一个密码并记下它。

就是这样。数据库现在已经准备好了!

云 SQL 代理

为了测试数据库连接和迁移数据库,我们将使用云 SQL 身份验证代理。云 SQL 身份验证代理提供对云 SQL 实例的安全访问,无需授权网络或配置 SSL。

首先,验证并获取 API 的凭证:

`$ gcloud auth application-default login` 

接下来,下载云 SQL 身份验证代理并使其可执行:

`$ wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 -O cloud_sql_proxy
$ chmod +x cloud_sql_proxy` 

如果您不在 Linux 上,请遵循安装指南来安装云 SQL 代理。

安装完成后,打开一个新的终端窗口,使用您的连接详细信息启动代理,如下所示:

`$ ./cloud_sql_proxy.exe -instances="PROJECT_ID:REGION:INSTANCE_NAME"=tcp:5432

# Example:
# cloud_sql_proxy.exe -instances="indigo-35:europe-west3:mydb-instance"=tcp:5432

2023/01/30 13:45:22 Listening on 127.0.0.1:5432 for indigo-35:europe-west3:mydb-instance
2023/01/30 13:45:22 Ready for new connections
2023/01/30 13:45:22 Generated RSA key in 110.0168ms` 

现在您可以像在本地机器上运行 Postgres 一样连接到localhost:5432

迁移数据库

因为 GAE 不允许我们在服务器上执行命令,所以我们必须从本地机器上迁移数据库。

如果您还没有安装,请继续安装这些要求:

`(venv)$ pip install -r requirements.txt` 

接下来,创建一个*。项目根目录中的 env* 文件,带有所需的环境变量:

`# .env DEBUG=1 SECRET_KEY=+an@of0zh--q%vypb^9x@vgecoda5o!m!l9sqno)vz^n!euncl DATABASE_URL=postgres://DB_USER:DB_PASS@localhost/DB_NAME # Example `DATABASE_URL`: # DATABASE_URL=postgres://django-images:[[email protected]](/cdn-cgi/l/email-protection)/mydb` 

确保用您的实际凭证替换DB_USERDB_PASSDB_NAME

最后,迁移数据库:

`(venv)$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, images, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  ...
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying images.0001_initial... OK
  Applying sessions.0001_initial... OK` 
创建超级用户

要创建超级用户,请运行:

`(venv)$ python manage.py createsuperuser` 

并按照提示进行操作。

秘密经理

为了安全地管理我们的秘密和环境文件,我们将使用 Secret Manager

导航到秘密管理器仪表板并启用 API,如果你还没有的话。接下来,创建一个名为django_settings的秘密,内容如下:

`DEBUG=1 SECRET_KEY=+an@of0zh--q%vypb^9x@vgecoda5o!m!l9sqno)vz^n!euncl DATABASE_URL=postgres://DB_USER:DB_PASS@//cloudsql/PROJECT_ID:REGION:INSTANCE_NAME/DB_NAME GS_BUCKET_NAME=django-images-bucket # Example `DATABASE_URL`: # postgres://django-images:[[email protected]](/cdn-cgi/l/email-protection)//cloudsql/indigo-35:europe-west3:mydb-instance/mydb` 

确保相应地更改DATABASE_URLPROJECT_ID:REGION:INSTANCE_NAME等于您的数据库连接细节。

你不用担心GS_BUCKET_NAME。这只是我们稍后要创建和使用的一个存储桶的名称。

回到您的项目,将以下内容添加到 requirements.txt :

`google-cloud-secret-manager==2.15.1` 

要从 Secret Manager 加载环境变量,我们可以使用下面的官方代码片段:

`# core/settings.py

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

env = environ.Env(DEBUG=(bool, False))
env_file = os.path.join(BASE_DIR, '.env')

if os.path.isfile(env_file):
    # read a local .env file
    env.read_env(env_file)
elif os.environ.get('GOOGLE_CLOUD_PROJECT', None):
    # pull .env file from Secret Manager
    project_id = os.environ.get('GOOGLE_CLOUD_PROJECT')

    client = secretmanager.SecretManagerServiceClient()
    settings_name = os.environ.get('SETTINGS_NAME', 'django_settings')
    name = f'projects/{project_id}/secrets/{settings_name}/versions/latest'
    payload = client.access_secret_version(name=name).payload.data.decode('UTF-8')

    env.read_env(io.StringIO(payload))
else:
    raise Exception('No local .env or GOOGLE_CLOUD_PROJECT detected. No secrets found.')` 

不要忘记导入文件顶部的iosecretmanager:

`import io
from google.cloud import secretmanager` 

太好了!终于到了部署我们应用的时候了。为此,请运行:

`$ gcloud app deploy

Services to deploy:

descriptor:                  [C:\Users\Nik\PycharmProjects\django-images-new\app.yaml]
source:                      [C:\Users\Nik\PycharmProjects\django-images-new]
target project:              [indigo-griffin-376011]
target service:              [default]
target version:              [20230130t135926]
target url:                  [https://indigo-griffin-376011.ey.r.appspot.com]

Do you want to continue (Y/n)?  y

Beginning deployment of service [default]...
#============================================================#
#= Uploading 21 files to Google Cloud Storage               =#
#============================================================#
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://indigo-griffin-376011.ey.r.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default` 

在浏览器中打开您的 web 应用程序,并测试它是否正常工作:

如果你得到一个502 Bad Gateway错误,你可以导航到日志浏览器来查看你的日志。

如果有一个403 Permission 'secretmanager.versions.access' denied错误,导航到django_settings secret permissions 并确保默认的 App Engine 服务帐户可以访问此机密。参见栈顶溢出的解决方案。

如果您尝试上传图像,您应该会看到以下错误:

`[Errno 30] Read-only file system: '/workspace/mediafiles'` 

这是因为 GAE 文件是只读的。别担心。我们将在下一节中解决它。

持久存储

Google App Engine(以及许多其他类似的服务,如 Heroku)提供了一个短暂的文件系统。这意味着您的数据不是持久的,可能会在应用程序关闭或重新部署时消失。此外,GAE 文件是只读的,这使得您无法将媒体文件直接上传到 GAE。

正因为如此,我们将使用云存储来设置持久存储。

服务帐户

要使用云存储,我们首先需要创建一个专用的服务帐户,该帐户具有足够的权限来读/写和签署云存储中的文件。

导航至服务账户仪表板并点击“创建服务账户”。将其命名为“django-images-bucket ”,并将其他内容保留为默认值。提交表单后,您应该会在表格中看到一个新的服务帐户。记下您的新服务帐户的电子邮件。

接下来,单击您的服务帐户旁边的三个点,然后单击“管理详细信息”:

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

在导航中选择“Keys”和“Create a new key”。将其导出为 JSON。

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

一旦 JSON 密匙被下载,给它一个更易读的名字,比如 gcpCredentials.json ,并把它放在您的项目根目录中。

确保将该文件添加到您的中。gitignore 以防止您的服务帐户凭证意外泄露给版本控制。

水桶

导航到您的云存储桶,并使用以下详细信息创建一个新桶:

  • 名称:姜戈-图像-桶
  • 位置类型:地区
  • 位置:与您的应用程序相同的地区

将其他内容保留为默认值,然后单击“创建”。

如果您收到一条消息说“公共访问将被阻止”,请取消选中“在此桶上实施公共访问阻止”和“确认”。我们必须允许公众访问,因为我们正在部署一个图像托管网站,并希望上传的图像可以被每个人访问。

有关更多信息,请查看公共访问防护

要授予我们的新服务帐户对存储桶的权限,请查看您的存储桶详细信息,然后在导航中选择“权限”。之后,点击“授权访问”:

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

添加新主体:

  • 电子邮件:您的服务帐户电子邮件
  • 角色:云存储>存储管理员

太好了!我们现在有了一个存储桶和一个可以使用该存储桶的服务帐户。

配置 Django

为了利用 Django 的云存储,我们将使用一个名为 django-storages 的第三方包。

在本地安装软件包,并将下面两行添加到 requirements.txt :

`django-storages[google]==1.13.2 google-cloud-storage==2.7.0` 

接下来,配置 django-storages 以使用云存储和您的服务帐户:

`# core/settings.py

GS_CREDENTIALS = service_account.Credentials.from_service_account_file(
    os.path.join(BASE_DIR, 'gcpCredentials.json')
)

DEFAULT_FILE_STORAGE = 'storages.backends.gcloud.GoogleCloudStorage'
STATICFILES_STORAGE = 'storages.backends.gcloud.GoogleCloudStorage'
GS_BUCKET_NAME = env('GS_BUCKET_NAME')` 

不要忘记重要的一点:

`from google.oauth2 import service_account` 

使用 app.yaml 中的处理程序使 App Engine 服务于静态和媒体文件:

`# app.yaml handlers: -  url:  /static  # new static_dir:  staticfiles/  # new -  url:  /media  # new static_dir:  mediafiles/  # new -  url:  /.* script:  auto` 

确保它们被放置在/.*之前。

如果你正在部署自己的应用程序,确保你的STATIC_URLSTATIC_ROOTMEDIA_URLMEDIA_ROOT设置正确(示例)。

收集静态文件到 GAE:

`(venv)$ python manage.py collectstatic

You have requested to collect static files at the destination
location as specified in your settings.

This will overwrite existing files!
Are you sure you want to do this?

Type 'yes' to continue, or 'no' to cancel: yes

141 static files copied.` 

重新部署你的应用程序:

在浏览器中打开应用程序:

导航到/admin并确保静态文件已被收集和加载。

自定义域

要将自定义域链接到您的 web 应用,首先导航到应用引擎仪表板。在边栏中选择“设置”,然后选择“自定义域”。最后,单击“添加自定义域”。

选择您想要使用的域,或者添加并验证新域。如果您添加一个新域,请确保输入裸域名。示例:

`testdriven.io       <-- good
app.testdriven.io   <-- bad` 

单击“继续”,然后输入要映射到你的 GAE 应用的域名和子域名。然后单击“保存映射”。

接下来,转到您的域名注册商的 DNS 设置,添加一个指向ghs.googlehosted.com的新“CNAME 记录”,如下所示:

`+----------+--------------+----------------------------+-----------+ | Type     | Host         | Value                      | TTL       |
+----------+--------------+----------------------------+-----------+ | CNAME    | <some host> | ghs.googlehosted.co        | Automatic |
+----------+--------------+----------------------------+-----------+` 

示例:

`+----------+--------------+----------------------------+-----------+ | Type     | Host         | Value                      | TTL       |
+----------+--------------+----------------------------+-----------+ | CNAME    | app          | ghs.googlehosted.com       | Automatic |
+----------+--------------+----------------------------+-----------+` 

返回到您的自定义域设置,然后单击“完成”。

您已成功添加了自定义域名。等待 DNS 更改传播,等待 Google 发布 SSL 证书。要耐心…有时需要 24 小时。

在您验证您的应用程序可通过 HTTPS 访问后,将以下内容添加到 app.yaml 的末尾:

`# app.yaml env_variables: APPENGINE_URL:  <the domain you mapped> # Example: # env_variables: #   APPENGINE_URL: app.testdriven.io` 

这添加了一个新的环境变量,由 settings.py 用来配置ALLOWED_HOSTSCSRF_TRUSTED_ORIGINS,并强制 SSL 重定向。

最后,再次重新部署您的应用:

结论

在本教程中,我们已经成功地将 Django 应用程序部署到 Google App Engine。我们已经处理了 Postgres 数据库、静态和媒体文件,添加了自定义域名,并启用了 HTTPS。现在,您应该能够将自己的应用程序部署到 App Engine。

django-app-engine repo 中获取最终的源代码。

如果您希望删除我们在整个教程中创建的所有服务和资源,请查看文档中的清理资源。

未来的步骤

  1. 设置秘密管理器中的DEBUG=0禁用调试模式。
  2. 看看如何管理实例以更好地理解 GAE 伸缩。
  3. 了解如何使用日志浏览器

使用 Django、htmx 和 Tailwind CSS 进行快速原型制作

原文:https://testdriven.io/blog/django-htmx-tailwind/

在本教程中,你将学习如何用 htmxTailwind CSS 设置 Django。htmx 和 Tailwind 的目标都是简化现代 web 开发,这样您就可以设计和实现交互性,而不会离开 HTML 的舒适和方便。我们还将看看如何使用 Django Compressor 来捆绑和缩小 Django 应用程序中的静态资产。

htmx

htmx 是一个库,它允许你直接从 HTML 访问现代浏览器特性,如 AJAX、CSS 转换、WebSockets 和服务器发送的事件,而不是使用 JavaScript。它允许您直接在标记中快速构建用户界面。

htmx 扩展了浏览器已经内置的几个特性,比如发出 HTTP 请求和响应事件。例如,您可以使用 HTML 属性在任何 HTML 元素上发送 GET、POST、PUT、PATCH 或 DELETE 请求,而不仅仅是通过aform元素发出 GET 和 POST 请求:

`<button hx-delete="/user/1">Delete</button>` 

您还可以更新页面的某些部分来创建单页应用程序(SPA):

CodePen 上 Michael Herman ( @mjhea0 )的笔 RwoJYyx

CodePen 链接

在浏览器的开发工具中打开网络标签。当点击按钮时,一个 XHR 请求被发送到https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode端点。然后,响应被附加到具有输出的idp元素。

如需更多示例,请查看官方 htmx 文档中的 UI 示例页面。

利弊

优点:

  1. 开发者生产力:你可以不用接触 JavaScript 就能构建现代化的用户界面。关于这方面的更多信息,请查看的一个水疗选择
  2. 组合拳:库本身很小(~10k min.gz’d),无依赖性,可扩展

缺点:

  1. 库成熟度:由于库相当新,文档和示例实现很少。
  2. 传输数据的大小:通常,SPA 框架(如 React 和 Vue)通过在客户机和服务器之间以 JSON 格式来回传递数据来工作。然后,接收到的数据由客户端呈现。另一方面,htmx 从服务器接收呈现的 HTML,并用响应替换目标元素。呈现格式的 HTML 通常比 JSON 响应更大。

顺风 CSS

Tailwind CSS 是一个“实用优先”的 CSS 框架。它不提供预先构建的组件(像 Bootstrap布尔玛这样的框架专门提供这些组件),而是以实用程序类的形式提供构建模块,使人们能够快速、轻松地创建布局和设计。

例如,以下面的 HTML 和 CSS 为例:

`<style> .hello  { height:  5px; width:  10px; background:  gray; border-width:  1px; border-radius:  3px; padding:  5px; } </style>

<div class="hello">Hello World</div>` 

这可以通过顺风实现,如下所示:

`<div class="h-1 w-2 bg-gray-600 border rounded-sm p-1">Hello World</div>` 

查看 CSS 顺风转换器将原始 CSS 转换为顺风中的等效实用程序类。比较结果。

利弊

优点:

  1. 高度可定制:虽然 Tailwind 自带预建类,但是可以使用 tailwind.config.js 文件覆盖它们。
  2. 优化:你可以配置 Tailwind,通过只加载实际使用的类来优化 CSS 输出。
  3. 黑暗模式:轻松实现黑暗模式——比如<div class="bg-white dark:bg-black">

缺点:

  1. 组件 : Tailwind 不提供任何官方预置的组件,比如按钮、卡片、导航条等等。组件必须从头开始创建。有一些社区驱动的组件资源,例如顺风 CSS 组件顺风工具箱。还有一个强大的组件库,尽管是付费的,由 Tailwind 的开发者开发,名为 Tailwind UI
  2. CSS 是内联的:它将内容和设计结合在一起,增加了页面的大小,使 HTML 变得混乱。

Django 压缩机

Django Compressor 是一个扩展,用于管理(压缩/缓存)Django 应用程序中的静态资产。通过它,您可以为以下各项创建简单的资产管道:

  1. SassLESS 编译成 CSS 样式表
  2. 将多个 CSS 和 JavaScript 文件合并并缩小为单个文件
  3. 创建在模板中使用的资产包

至此,让我们来看看如何在 Django 中使用上述工具!

项目设置

首先,为我们的项目创建一个新文件夹,创建并激活一个新的虚拟环境,并安装 Django 和 Django Compressor:

`$ mkdir django-htmx-tailwind && cd django-htmx-tailwind
$ python3.10 -m venv venv
$ source venv/bin/activate
(venv)$

(venv)$ pip install Django==4.0.3 django-compressor==3.1` 

接下来,让我们安装 pytailwindcss 并下载它的二进制文件:

`(venv)$ pip install pytailwindcss==0.1.4
(venv)$ tailwindcss` 

创建一个新的 Django 项目和一个todos应用:

`(venv)$ django-admin startproject config .
(venv)$ python manage.py startapp todos` 

将应用添加到 config/settings.py 中的INSTALLED_APPS列表:

`# config/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todos',  # new
    'compressor',  # new
]` 

在项目的根目录下创建一个“模板”文件夹。然后,像这样更新TEMPLATES设置:

`# config/settings.py

TEMPLATES = [
    {
        ...
        'DIRS': [BASE_DIR / 'templates'], # new
        ...
    },
]` 

让我们为compressorconfig/settings.py 添加一些配置:

`# config/settings.py

COMPRESS_ROOT = BASE_DIR / 'static'

COMPRESS_ENABLED = True

STATICFILES_FINDERS = ('compressor.finders.CompressorFinder',)` 

注意事项:

  1. COMPRESS_ROOT 定义要压缩的文件的读取和写入的绝对位置。
  2. COMPRESS_ENABLED 判断压缩是否会发生的布尔值。默认为DEBUG的相反值。
  3. 安装django.contrib.staticfiles时,必须包含 Django Compressor 的文件查找器。

初始化项目中的顺风 CSS:

该命令在项目的根目录下创建了一个 tailwind.config.js 文件。所有与顺风相关的定制都放在这个文件中。

更新 tailwind.config.js 这样:

`module.exports  =  { content:  [ './templates/**/*.html', ], theme:  { extend:  {}, }, plugins:  [], }` 

记下部分的内容。在这里,您可以配置项目 HTML 模板的路径。顺风 CSS 将扫描你的模板,搜索顺风类名。生成的输出 CSS 文件将只包含模板文件中相关类名的 CSS。这有助于保持生成的 CSS 文件较小,因为它们将只包含实际使用的样式。

接下来,在项目根目录中,创建以下文件和文件夹:

`static
└── src
    └── main.css` 

然后,将以下内容添加到 static/src/main.css 中:

`/* static/src/main.css */ @tailwind  base; @tailwind  components; @tailwind  utilities;` 

这里,我们定义了来自 Tailwind CSS 的所有basecomponentsutilities类。

就是这样。你现在有姜戈压缩机和顺风连线。接下来,我们将看看如何提供一个index.html文件来看看 CSS 的作用。

简单的例子

像这样更新 todos/views.py 文件:

`# todos/views.py

from django.shortcuts import render

def index(request):
    return render(request, 'index.html')` 

将视图添加到 todos/urls.py :

`# todos/urls.py

from django.urls import path

from .views import index

urlpatterns = [
    path('', index, name='index'),
]` 

然后,将todos.urls添加到 config/urls.py :

`# config/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('todos.urls')), # new
]` 

向“模板”添加一个 _base.html 文件:

`<!-- templates/_base.html -->

{% load compress %}
{% load static %}

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Django + HTMX + Tailwind CSS</title>

    {% compress css %}
      <link rel="stylesheet" href="{% static 'src/output.css' %}">
    {% endcompress %}

  </head>
  <body class="bg-blue-100">
    {% block content %}
    {% endblock content %}
  </body>
</html>` 

注意事项:

  1. {% load compress %}导入与 Django Compressor 配合使用所需的所有标签。
  2. {% load static %}将静态文件载入模板。
  3. {% compress css %}静态/src/main.css 文件应用适当的过滤器。

此外,我们通过<body class="bg-blue-100">给 HTML 主体添加了一些颜色。bg-blue-100用于将背景颜色改为浅蓝色。

添加一个index.html文件:

`<!-- templates/index.html -->

{% extends "_base.html" %}

{% block content %}
  <h1>Hello World</h1>
{% endblock content %}` 

现在,在项目的根目录下运行以下命令,扫描模板中的类并生成一个 CSS 文件:

`(venv)$ tailwindcss -i ./static/src/main.css -o ./static/src/output.css --minify` 

应用迁移并运行开发服务器:

`(venv)$ python manage.py migrate
(venv)$ python manage.py runserver` 

在浏览器中导航到 http://localhost:8000 以查看结果。还要注意“static/CACHE/css”文件夹中生成的文件。

在配置了 Tailwind 之后,让我们将 htmx 添加到组合中,并构建一个在您键入时显示结果的 live search。

实时搜索示例

与其从 CDN 中获取 htmx 库,不如下载它并使用 Django Compressor 进行捆绑。

https://unpkg.com/【邮件保护】 /dist/htmx.js 下载库,保存到 static/src/htmx.js

为了让我们有一些数据可以处理,将https://github . com/testdrivenio/django-htmx-tailwind/blob/master/todos/todo . py保存到一个名为 todos/todo.py 的新文件中。

现在,将实现搜索功能的视图添加到 todos/views.py :

`# todos/views.py

from django.shortcuts import render
from django.views.decorators.http import require_http_methods  # new

from .todo import todos  # new

def index(request):
    return render(request, 'index.html', {'todos': []}) # modified

# new
@require_http_methods(['POST'])
def search(request):
    res_todos = []
    search = request.POST['search']
    if len(search) == 0:
        return render(request, 'todo.html', {'todos': []})
    for i in todos:
        if search in i['title']:
            res_todos.append(i)
    return render(request, 'todo.html', {'todos': res_todos})` 

我们添加了一个新的视图search,它搜索 todos 并呈现带有所有结果的todo.html模板。

将新创建的视图添加到 todos/urls.py :

`# todos/urls.py

from django.urls import path

from .views import index, search  # modified

urlpatterns = [
    path('', index, name='index'),
    path('search/', search, name='search'),  # new
]` 

接下来,将新资产添加到 _base.html 文件中:

`<!-- templates/base.html -->

{% load compress %}
{% load static %}

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Django + HTMX + Tailwind CSS</title>

    {% compress css %}
      <link rel="stylesheet" href="{% static 'src/output.css' %}">
    {% endcompress %}

  </head>
  <body class="bg-blue-100">
    {% block content %}
    {% endblock content %}

    <!-- new -->
    {% compress js %}
      <script type="text/javascript" src="{% static 'src/htmx.js' %}"></script>
    {% endcompress %}

    <!-- new -->
    <script> document.body.addEventListener('htmx:configRequest',  (event)  =>  { event.detail.headers['X-CSRFToken']  =  '{{ csrf_token }}'; }) </script>
  </body>
</html>` 

我们使用{% compress js %}标签加载了 htmx 库。js标签,默认为,应用JSMinFilter(反过来,应用 rjsmin )。因此,这将缩小 static/src/htmx.js 并从“static/CACHE”文件夹中提供它。

我们还添加了以下脚本:

`document.body.addEventListener('htmx:configRequest',  (event)  =>  { event.detail.headers['X-CSRFToken']  =  '{{ csrf_token }}'; })` 

该事件侦听器将 CSRF 令牌添加到请求标头中。

接下来,让我们添加基于每个待办事项标题的搜索功能。

更新index.html文件是这样的:

`<!-- templates/index.html -->

{% extends "_base.html" %}

{% block content %}
  <div class="w-small w-2/3 mx-auto py-10 text-gray-600">
    <input
      type="text"
      name="search"
      hx-post="/search/"
      hx-trigger="keyup changed delay:250ms"
      hx-indicator=".htmx-indicator"
      hx-target="#todo-results"
      placeholder="Search"
      class="bg-white h-10 px-5 pr-10 rounded-full text-2xl focus:outline-none"
    >
    <span class="htmx-indicator">Searching...</span>
  </div>
  <table class="border-collapse w-small w-2/3 mx-auto">
    <thead>
      <tr>
        <th class="p-3 font-bold uppercase bg-gray-200 text-gray-600 border border-gray-300 hidden lg:table-cell">#</th>
        <th class="p-3 font-bold uppercase bg-gray-200 text-gray-600 border border-gray-300 hidden lg:table-cell">Title</th>
        <th class="p-3 font-bold uppercase bg-gray-200 text-gray-600 border border-gray-300 hidden lg:table-cell">Completed</th>
      </tr>
    </thead>
    <tbody id="todo-results">
      {% include "todo.html" %}
    </tbody>
  </table>
{% endblock content %}` 

让我们花点时间来看看从 htmx 定义的属性:

`<input
  type="text"
  name="search"
  hx-post="/search/"
  hx-trigger="keyup changed delay:250ms"
  hx-indicator=".htmx-indicator"
  hx-target="#todo-results"
  placeholder="Search"
  class="bg-white h-10 px-5 pr-10 rounded-full text-2xl focus:outline-none"
>` 
  1. 输入向/search端点发送一个 POST 请求。
  2. 该请求通过延迟 250 毫秒的击键事件触发。因此,如果在上一次按键之后 250 毫秒之前输入了新的按键事件,则不会触发请求。
  3. 来自请求的 HTML 响应显示在#todo-results元素中。
  4. 我们还有一个指示器,它是一个加载元素,在发送请求后出现,在响应返回后消失。

添加模板/todo.html 文件:

`<!-- templates/todo.html -->

{% for todo in todos %}
  <tr
    class="bg-white lg:hover:bg-gray-100 flex lg:table-row flex-row lg:flex-row flex-wrap lg:flex-no-wrap mb-10 lg:mb-0">
    <td class="w-full lg:w-auto p-3 text-gray-800 text-center border border-b block lg:table-cell relative lg:static">
      {{todo.id}}
    </td>
    <td class="w-full lg:w-auto p-3 text-gray-800 text-center border border-b block lg:table-cell relative lg:static">
      {{todo.title}}
    </td>
    <td class="w-full lg:w-auto p-3 text-gray-800 text-center border border-b block lg:table-cell relative lg:static">
      {% if todo.completed %}
        <span class="rounded bg-green-400 py-1 px-3 text-xs font-bold">Yes</span>
      {% else %}
        <span class="rounded bg-red-400 py-1 px-3 text-xs font-bold">No</span>
      {% endif %}
    </td>
  </tr>
{% endfor %}` 

该文件呈现了与我们的搜索查询相匹配的待办事项。

生成一个新的 src/output.css 文件:

`(venv)$ tailwindcss -i ./static/src/main.css -o ./static/src/output.css --minify` 

使用python manage.py runserver运行应用程序,并再次导航到 http://localhost:8000 进行测试。

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

结论

在本教程中,我们学习了如何:

  • 设置 Django 压缩机和顺风 CSS
  • 使用 Django、Tailwind CSS 和 htmx 构建一个实时搜索应用程序

htmx 可以在不重新加载页面的情况下呈现元素。最重要的是,您无需编写任何 JavaScript 就可以实现这一点。虽然这减少了客户端所需的工作量,但从服务器发送的数据可能会更多,因为它发送的是渲染的 HTML。

像这样提供部分 HTML 模板在 21 世纪初很流行。htmx 为这种方法提供了一种现代的变形。总的来说,由于 React 和 Vue 等框架的复杂性,提供部分模板再次变得流行起来。您还可以将 WebSockets 添加到组合中,以交付实时更改。著名的 Phoenix LiveView 也使用了同样的方法。你可以在阅读更多关于 HTML over WebSockets 的内容,Web 软件的未来是 HTML-Over Web socketsHTML Over WebSockets

图书馆还年轻,但未来看起来很光明。

Tailwind 是一个强大的 CSS 框架,专注于开发人员的生产力。虽然这个教程没有涉及到,但是 Tailwind 是高度可定制的。查看以下资源了解更多信息:

当使用 Django 时,一定要将 htmx 和 Tailwind 与 Django Compressor 耦合,以简化静态资产管理。

完整的代码可以在 django-htmx-tailwind 资源库中找到。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值