Django 是一个高级开源的 Python Web 框架,可以帮助您快速构建 Python 应用程序。它遵循模型-模板-视图(model–template–views)架构模式,鼓励快速开发和干净、务实的设计。该框架开箱即用,配备了必要的现代应用程序组件,例如用户身份验证、缓存框架, 对象关系映射器, URL 分发器, 模板系统,以及可定制的管理界面。
Gunicorn ‘Green Unicorn’ 是一个适用于 UNIX 系统的 Python WSGI HTTP 服务器。Gunicorn 服务器与各种 Web 框架兼容,性能优异,且占用服务器资源少。 Docker 是一个开源容器平台,已经存在了一段时间,它使应用程序开发变得快速、高效且可预测。
在本教程中,您 将掌握开发和部署可扩展的容器化 Django Web 应用程序的技能。我们将使用一个通过遵循 Django 入门指南创建的 Django Polls 应用程序。在编写本教程时,我们基于 Django 3.2,并由 Python 3.6 或更高版本支持。我们将使用 Docker 将该应用程序部署为容器,并使用 Gunicorn 服务器进行服务。当然,在容器中部署 Django 应用程序之前,您必须对项目代码进行一些修改,以处理诸如记录到标准输出流以及使用环境变量等操作。CSS 和 JavaScript 图像等静态文件可以卸载到对象存储服务中,以便在多容器环境中从一个位置轻松管理这些文件。
我们将向您展示如何根据构建可扩展 Web 应用程序的、定义明确的 twelve-factor 方法论来实现这些修改。完成修改后,您将构建该应用程序的 Docker 镜像,并使用 Docker 部署容器化应用程序。我们建议您按照本教程中概述的步骤进行操作,以全面理解本教程。
前提条件
由于这是一个动手实践教程,我们建议您进行以下设置以帮助您跟上进度:
-
一台 Ubuntu 20.04 服务器。您可以按照此 分步教程来帮助您设置您的 Ubuntu 服务器(在 CloudSigma 上)。
-
确保添加一个 具有 sudo 特权的用户,在我们将用于运行上述教程中概述的命令的两个节点上。
-
在服务器上安装 Docker。您可以按照我们关于 安装和操作 Docker 的教程 的步骤 1、2 和 3 进行操作。记住将上面创建的 sudo 用户添加到 Docker 用户组中。
-
一个兼容的对象存储空间。Django 支持多个存储服务,如 django-storages 文档 中所述。您可以选择您喜欢的一个,并按照文档进行设置。在本教程中,我们将使用 MinIO,这是一个兼容 S3 的云存储服务。
-
一个 SQL 数据库实例。Django 支持几种 SQL 数据库,您可以自由选择。在本教程中,我们将使用 PostgreSQL。PostgreSQL 数据库将不会部署在容器内部。我们将设置一台独立的 Ubuntu 服务器来托管 PostgreSQL 实例,以确保实现我们的多容器设置以及数据持久化。您可以 创建另一个 Ubuntu 20.04 实例,并按照本教程 在 Ubuntu 上设置 PostgreSQL 数据库实例。记住按照步骤 2 和 3 中的说明,在 PostgreSQL 数据库中为您的 sudo 用户添加一个角色。此角色将允许您从托管容器的其他服务器连接到该数据库。
根据这些前提条件,您应该有两个 Ubuntu 服务器实例。一个实例将运行您的 Docker 容器,另一个实例将运行 PostgreSQL 实例。让我们开始吧!
步骤 1:配置 PostgreSQL 数据库实例
在本节中,我们将修改运行 Postgres 实例的 Ubuntu 服务器上的 Postgres 配置。这将允许来自外部 IP 地址的连接。连接后,我们可以创建一个专门用于我们正在部署的 Django Polls 应用程序的数据库和用户角色。
首先,如果您已按照前提条件设置了环境,那么您的 PostgreSQL 数据库中应该已经有一个适用于您的 sudo 用户的角色。接下来,我们需要为此角色设置密码。在运行 PostgreSQL 的服务器上,使用以下命令登录 Postgres 终端:
|
1 |
sudo -u postgres psql |
进入 Postgres 终端后,发出 \password 命令来修改用户的密码。该 \password命令的语法是 \password <username>。在我们的情况下,命令为:
|
1 |
\password cloudsigma |
输入密码并确认。将此密码保存在安全的地方,因为稍后您将使用它从另一个 Ubuntu 服务器进行身份验证。之后,输入 exit 并按回车键退出 Postgres 终端。
如果您在 PostgreSQL 服务器实例上启用了防火墙 (ufw),则需要允许流量通过 Postgres 默认端口 5432。您可以将流量限制为仅源自运行 Docker 容器的另一个 Ubuntu 服务器的特定 IP 地址。执行以下命令以添加 ufw 规则,并在高亮显示处替换您的 IP 地址:
|
1 |
sudo ufw allow from ubuntu_server_ip_address to any port 5432 |
这将确保只有您的服务器可以连接 to PostgreSQL 实例。虽然这允许流量通过防火墙,但您还需要修改 PostgreSQL 配置文件以允许来自远程 IP 地址的连接。默认情况下,配置仅允许来自 localhost 的连接。PostgreSQL 的配置文件位于 /etc/postgresql/12/main 目录。 12,在这种情况下,是我们为本教程安装的 PostgreSQL 版本。您可能安装了不同的版本。因此,您可以切换到目录 /etc/postgresql/ 并列出内容以找到您安装的 PostgreSQL 的版本号。
使用 nano 修改配置文件:
|
1 |
sudo nano /etc/postgresql/12/main/postgresql.conf |
找到下面这一行,取消注释,并将其设置为允许来自所有 IP 的连接:
|
1 |
listen_addresses = '*' |
保存并关闭文件。然后,您还必须编辑 pg_hba.conf 文件,它与 postgresql.conf 在同一个目录下。 pg_hba.conf 允许您定义可以从哪些计算机连接到 PostgreSQL 实例以及身份验证方法。使用 nano 打开文件:
|
1 |
sudo nano /etc/postgresql/12/main/pg_hba.conf |
请阅读此文件中的注释以理解关键字。我们要找的部分是这个:

我们的重点将放在第二行,您希望在取消注释后它看起来像下面这一行:
|
1 |
host all all your_ubuntu_server_ip/24 md5 |
请将高亮显示的部分替换为您的 Ubuntu 服务器 IP 地址,以允许其连接到 PostgreSQL 实例。准备就绪后保存文件。重启 PostgreSQL 数据库以使更改生效:
|
1 |
sudo service postgresql restart |
我们具有指定 IP 地址的另一个 Ubuntu 服务器应该能够连接到 Postgres 实例。
步骤 2:连接到 PostgreSQL 服务器实例并创建数据库和用户
在这一步中,我们将尝试确保运行我们的 Docker 容器的 Ubuntu 实例 能够连接到运行 PostgreSQL 实例 的另一台服务器。登录到安装了 Docker 的 Ubuntu 实例,并安装 postgresql-client 软件包(在 Ubuntu 宿主机内,还不是在容器内)。
通常,首先更新 apt 软件包,然后使用以下命令安装该软件包:
|
1 |
sudo apt update |
|
1 |
sudo apt install postgresql-client |
上面安装的软件包将帮助您为应用程序创建数据库和用户。接下来,我们需要通过向 PostgreSQL 客户端指定连接参数来连接到 PostgreSQL 实例。
连接参数遵循以下语法:
|
1 |
psql -U username -h host -p port -d database --set=sslmode=require |
在此命令中, username 是您添加到 PostgreSQL 数据库的用户/角色。 host 是运行 PostgreSQL 数据库的 Ubuntu 实例的 IP 地址。 port 是 Postgres 监听传入连接的默认端口,即 5432。在 database 的位置,我们将使用名为 postgres 的默认数据库,它是随 PostgreSQL 安装一起提供的。在突出显示的部分中相应地替换您的值,然后按 Enter。出现提示时,输入您设置的密码。这将使您登录到 Postgres 提示符,您可以在其中管理数据库。
您已成功连接到 PostgreSQL 实例。现在,您可以为 Django 投票应用(polls app)创建一个数据库。我们将其命名为 django_polls:
|
1 |
CREATE DATABASE django_polls; |
确保您的语句以分号结尾,以避免运行出错。然后,使用以下命令切换到 django_polls 数据库:
|
1 |
\c django_polls; |
接下来,创建一个专门用于此项目的数据库用户。我们将该用户命名为 django_user:
|
1 |
CREATE USER django_user WITH PASSWORD 'password'; |
为您的用户选择一个安全的密码。完成后,我们需要修改刚刚创建的用户的连接参数。这有助于通过确保在每次建立连接时不会查询和设置正确的值,从而加快数据库操作。
将 Django 期望的默认编码设置为 UTF-8:
|
1 |
ALTER ROLE django_user SET client_encoding TO 'utf8'; |
接下来,将默认事务隔离方案设置为“ read committed”,这将阻止读取未提交的事务:
|
1 |
ALTER ROLE django_user SET default_transaction_isolation TO 'read committed'; |
设置您的时区。为了保持教程的通用性,我们将使用 UTC:
|
1 |
ALTER ROLE django_user SET timezone TO 'UTC'; |
最后,将数据库的管理权限授予新用户:
|
1 |
GRANT ALL PRIVILEGES ON DATABASE django_polls TO django_user; |
准备就绪后,退出 PostgreSQL 提示符:
|
1 |
\q |
这一步就到这里。一旦您正确配置了 Django 应用,它应该就能够管理您的数据库了。
步骤 3:从 Git 仓库拉取应用并定义依赖项
在此步骤中,我们将克隆 Django-polls 应用仓库。此仓库包含 Django 的 “编写您的第一个 Django 应用”教程.
登录运行 Docker 的 Ubuntu 服务器,创建一个名为 django_project 的目录并进入其中:
|
1 2 |
mkdir django_project cd django_project |
然后,使用以下命令将仓库克隆到该目录中:
|
1 |
git clone https://github.com/jaymoh/django-polls.git |
进入该目录并列出内容:
|
1 |
cd django-polls |
列出目录的内容:
|
1 |
ls |

请注意以下项:
-
manage.py:此文件是 Django 提供的用于管理应用的命令行工具的入口。
-
mysite:包含 Django 项目范围和代码设置的目录。
-
polls:包含 polls 应用程序代码的目录。
-
templates:包含管理页面的自定义模板文件。
要详细了解我们实际是如何创建该项目的,请参阅官方文档中的 Writing your first Django app。在 django-polls 目录中,我们希望在一个文本文件中定义 Python 依赖项。我们将其命名为 requirements.txt。使用您喜欢的编辑器打开该文件:
|
1 |
nano requirements.txt |
将以下行粘贴到该文件中以声明依赖项:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Django==3.2.9 gunicorn==20.1.0 docutils==0.18.1 sqlparse==0.4.2 jmespath==0.10.0 psycopg2==2.9.2 python-dateutil==2.8.2 pytz==2021.3 six==1.16.0 urllib3==1.26.7 django-storages==1.12.2 minio==7.1.6 django-minio-backend==3.3.2 django-dotenv==1.4.2 boto3==1.21.38 |
在此文件中,我们定义了在构建应用程序时应安装的 Python 依赖项及其确切版本。其中一些包括 Django, django-storages,用于与对象存储桶进行交互, psycopg2 适配器(用于 PostgreSQL), gunicorn WSGI 服务器以及其他附加依赖项。完成后保存并关闭文件。
第 4 步:为 Django 应用程序配置环境变量
“twelve-factor app” 方法论建议您应该从应用程序的代码库中提取硬编码的配置。这样做,您就可以在不修改代码库的情况下,通过修改环境变量来自由地在运行时更改应用程序的行为。Docker 支持这种设置,因此我们将修改配置文件以使用环境变量。Kubernetes 也支持这种配置设置。我们将在 CloudSigma blog.
该 settings.py 是 Django 项目的主要配置文件。它是一个使用原生数据结构来配置应用程序的 Python 模块。对于我们的应用程序,该文件位于 django-polls/mysite/settings.py。它的大多数值都是硬编码的。如果您更改应用程序的行为,这将需要您修改代码库中的配置文件。我们想要改变这一点。幸运的是,Python 提供了 getenv 函数,位于 os 模块中。我们可以使用它来配置 Django,使其从本地环境变量中读取配置参数。
让我们继续修改 django-polls/mysite/settings.py 文件,以替换变量的硬编码值。我们可能希望在运行时通过调用 os.getenv 来进行更新。此函数读取在提供的环境变量名称中设置的值。或者,您可以提供第二个参数,即在未设置环境变量时将使用的默认值。
这里有一个例子:
|
1 |
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY') |
在上面的代码行中,我们告诉 Django 从环境变量中检索密钥。我们没有提供备用值,因为我们将从外部提供该密钥。如果它不存在,应用程序应该无法启动。在外部提供密钥的同时,我们还希望确保我们所有容器化的应用程序副本在各个服务器上都使用相同的密钥。这可以避免由于应用程序的各个副本使用不同的密钥而引起的潜在问题。
这是另一个带有默认选项的例子:
|
1 |
DEBUG = os.getenv('DEBUG', False) |
在这一行中,我们定义了一个应该被读取的环境变量 DEBUG。但是,如果未设置它,我们提供了第二个参数,该参数将传递给 DEBUG 设置变量。 DEBUG 被设置为 False,以确保在应用程序出现问题时,敏感信息不会传递到前端。但是,如果我们处于开发模式,我们希望将其设置为 True,以便我们能够看到错误信息,从而更容易地修复错误。
现在您已经了解了环境变量的重要性,请在编辑器中打开 django_project/django-polls/settings.py。首先,导入 os 模块,方法是在 settings.py 文件的顶部添加此行:
|
1 |
import os |
然后,找到这些变量并按如下方式更新它们:
|
1 2 3 |
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY') DEBUG = os.getenv('DEBUG', False) ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', '127.0.0.1').split(',') |
在 ALLOWED_HOSTS 设置中,我们指定它应该从 DJANGO_ALLOWED_HOSTS 环境变量中获取值,并将其拆分为一个 Python 列表,使用逗号( ,)作为分隔符。如果该变量缺失, ALLOWED_HOSTS 将被设置为 127.0.0.1.
接下来,滚动浏览文件并找到 DATABASES 部分,将其配置为也从环境变量中读取:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.{}'.format( os.getenv('DB_ENGINE', 'sqlite3') ), 'NAME': os.getenv('DB_DATABASE', 'django_polls'), 'USER': os.getenv('DB_USERNAME', 'your_db_username'), 'PASSWORD': os.getenv('DB_PASSWORD', 'your_secure_default_password'), 'HOST': os.getenv('DB_HOST', '127.0.0.1'), 'PORT': os.getenv('DB_PORT', 5432), 'OPTIONS': json.loads( os.getenv('DB_OPTIONS', '{}') ), } } |
请注意,我们添加了 json.loads 模块。您还应该在 settings.py 文件的顶部添加该模块的导入:
|
1 |
import json |
这个 json.loads 函数反序列化传入 DATABASES['default']['OPTIONS'] 的 JSON 对象,该对象来自 DB_OPTIONS 环境变量。指定此选项允许我们传入任意数据结构来定义数据库配置。数据库引擎包含一组适用于它的有效选项。JSON 选项使我们能够灵活地对 JSON 对象进行编码,其中包含我们当时使用的数据库引擎的适当参数。
The DATABASES['default']['NAME'] 指定了我们设置的关系数据库管理系统中的数据库名称。在使用 SQLite 数据库的情况下,您应该指定数据库文件的路径。
请注意,Python 提供了几种方法来读取外部环境变量。我们只使用了其中一种。您可以自由研究并使用其他方法。在这一步中,您学习了如何使用外部环境变量。这使您能够灵活地更改变量并改变在容器中运行的应用程序的行为。在下一步中,您将学习如何使用对象存储服务。
第 5 步:使用外部对象存储服务
将应用程序容器化的一个主要优势是使其具有可移植性,以便在流量增加时轻松部署应用程序的多个副本。因此,为扩展提供了空间。然而,这带来了在各个容器之间维护静态文件和资产版本的问题。得益于云技术的进步,您可以将这些共享的静态元素卸载到外部存储中。然后,您可以通过网络使所有正在运行的容器都可以访问这些文件。您无需尝试在各个运行的容器之间同步文件,而是有一个中心位置来管理它们。
我们上面试图解释的概念是使用云对象存储服务,或简单存储服务(S3)。Django 有一个名为 django-storages,它允许您使用远程存储后端。 Django-storages 适用于大多数兼容 S3 的对象存储服务,例如 FTP、SFTP、Amazon 的 AWS S3、Google Cloud Storage、Dropbox 和 Azure Storage 等。在本教程中,我们将使用 MinIO。您可以随意使用任何其他 兼容 S3 的对象存储服务. MinIOMinIO 提供高性能、兼容 S3 的对象存储。借助 MinIO,您可以在任何云上构建兼容 S3 的数据基础设施。
我们将向您展示如何在 CloudSigma 平台上设置 MinIO 存储服务。请按照以下步骤操作:
-
首先 在 CloudSigma 上创建账户。 如果您在创建 MinIO 存储时遇到任何问题,请联系 CloudSigma’s 免费 24/7 在线客服支持,他们将为您提供帮助。
-
添加您的账单信息。
-
接下来,从这里申请您的公开可访问存储桶: https://blog.cloudsigma.com/xxxx。您需要联系在线客服支持以获取您的账户访问凭据。
-
一旦创建了 MinIO 对象存储环境,系统将为您提供访问凭据和其他访问说明。凭据应包括您的 MINI_ACCESS_KEY, MINIO_SECRET_KEY,以及 MINIO_URL。您将在下面的说明中使用这些密钥。
让我们对我们在上一步中修改的 mysite/settings.py 文件进行更多更改。在文件中,将 storages 应用添加到 Django 的 INSTALLED_APPS:

该 storages 应用是通过 django-storages 安装的,如 requirements.txt 中所定义。滚动到文件底部,并将 STATIC_URL 变量替换为以下代码片段:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 静态文件 (CSS, JavaScript, 图像) # https://docs.djangoproject.com/en/3.2/howto/static-files/ DEFAULT_FILE_STORAGE = os.getenv('STATIC_DEFAULT_FILE_STORAGE', 'storages.backends.s3boto3.S3Boto3Storage') AWS_S3_ENDPOINT_URL = os.getenv('MINIO_URL') AWS_ACCESS_KEY_ID = os.getenv('MINIO_ACCESS_KEY') AWS_SECRET_ACCESS_KEY = os.getenv('MINIO_SECRET_KEY') AWS_STORAGE_BUCKET_NAME = os.getenv('STATIC_MINIO_BUCKET_NAME') AWS_S3_OBJECT_PARAMETERS = { 'CacheControl': 'max-age=86400', } AWS_LOCATION = 'static' AWS_DEFAULT_ACL = 'public-read' STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' STATIC_URL = '{}/{}/'.format(AWS_S3_ENDPOINT_URL, AWS_LOCATION) STATIC_ROOT = "static/" |
请注意,某些配置变量是硬编码的:
-
STATICFILES_STORAGE:定义 Django 将用于处理静态文件的存储后端。在我们的指南中,我们使用的是 MinIO 存储,但您可以使用任何 S3 兼容后端,如 Django Storages 文档中所述.
-
AWS_S3_OBJECT_PARAMETERS:定义缓存控制标头。
-
AWS_LOCATION:我们使用它在存储桶中设置一个目录,所有静态文件都将存储在该目录中。您可以自由选择其他名称。
-
AWS_DEFAULT_ACL:设置静态文件的访问控制列表 (ACL)。将该值设置为 ‘ public-Read’ 将使所有公开用户都可以访问这些文件。
-
STATIC_URL:Django 使用此变量中设置的基础 URL 来生成静态文件的 URL。在这种情况下,基础 URL 是通过结合端点 URL 和静态文件子目录派生而来的。
-
STATIC_ROOT:定义在将静态文件复制到远程对象存储之前,在本地收集它们的位置。
我们还定义了一些外部环境变量,以保持灵活性和可移植性:
-
AWS_STORAGE_BUCKET_NAME:定义 Django 将静态资源上传到的存储桶的名称。
-
AWS_S3_ENDPOINT_URL:定义用于访问对象存储服务的端点 URL。这将是映射到托管 MinIO 服务的服务器的 URL。
编辑完成后保存并关闭文件。
一旦您配置好了这些设置并安装了声明的 Python 依赖项,您就可以随时运行 Django 命令 manage.py collectstatic 来收集您项目的静态文件并将其上传到远程对象存储后端:
|
1 |
python manage.py collectstatic |
但是,我们还没有设置 env 配置文件,因此它可能会失败。
运行该命令时,根据文件大小和您的网速,将静态资源复制到 MinIO 云存储需要一些时间。
这一步就到这里。让我们看看如何将 Django 日志推送到 Docker 引擎,以便您可以在下一步中使用 docker logs 命令来查看它们。
第 6 步:在 Django 应用中设置日志记录
在调试模式下,当 DEBUG 选项设置为 True 时,Django 会将信息记录到标准输出和标准错误。日志信息通常会显示在您启动开发 HTTP 服务器的终端上。
在生产环境中,您可能会使用不同的 HTTP 服务器,并且 DEBUG 选项设置为 False。在这种情况下,Django 将使用不同的日志记录方法。Django 会将优先级为 ERROR 或 CRITICAL 的日志发送到您定义的管理员电子邮件帐户。这在许多情况下都非常有效。
在容器化和 Kubernetes 设置中,强烈建议记录到标准输出和标准错误。日志消息会收集在节点文件系统的单个目录中,并且可以使用 kubectl 和 docker 命令轻松访问。通过在节点文件系统上建立集中式日志记录点,运维团队可以轻松地在每个节点上运行进程来监视和转发日志。因此,我们必须配置我们的应用程序将日志写入此标准设置。
您会很高兴地了解到,Django 利用了 Python 标准库中高度可定制的 logging 模块。这允许您定义一个传递给 logging.config.dictConfig 的字典,以定义所需的输出和格式。这里有一篇关于 Django Logging, The Right Way 的好文章,可以帮助您掌握 Django 日志记录的技术。
在编辑器中打开 django-polls/mysite/settings.py 文件。在文件顶部添加 Python logging.config 库的导入:
|
1 |
import logging.config |
到目前为止,加上我们添加的所有导入,您在 settings.py 中的导入部分应该像这样:

The logging.config 库通过 dictConfig 函数接收一个新日志配置的字典,以覆盖 Django 的默认日志记录行为。
滚动到文件底部并添加以下日志配置代码片段:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# 日志配置 # 禁用之前的配置 LOGGING_CONFIG = None # 从环境变量获取日志级别 LOGLEVEL = os.getenv('DJANGO_LOGLEVEL', 'info').upper() logging.config.dictConfig({ 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'console': { 'format': '%(asctime)s %(levelname)s [%(name)s:%(lineno)s] %(module)s %(process)d %(thread)d %(message)s', }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'formatter': 'console', }, }, 'loggers': { '': { 'level': LOGLEVEL, 'handlers': ['console',], }, }, }) |
LOGGING_CONFIG 被设置为 None,以禁用/清除 Django 定义的默认日志配置。 LOGLEVEL 是由 DJANGO_LOGLEVEL 环境变量设置的。但是,如果它不存在,我们希望将其设置为 ‘ info’.
我们在顶部导入的 logging.config 模块提供了一个函数 dictConfig,该函数用于设置新的配置字典。该字典使用 formatters 键定义文本格式。输出是通过 handlers 键设置的,最后, loggers 键定义了哪些消息应该发送到哪个处理器。
一旦定义了这些设置,Docker 将通过 docker logs 命令公开日志。同样,在我们将为 Kubernetes 制作的另一个教程中,您可以使用 kubectl logs 命令查看日志。现在让我们在下一步中开始容器化过程。
步骤 7:定义应用程序 Dockerfile
在此步骤中,我们定义用于启动容器镜像的配置,该镜像将运行由 Gunicorn WSGI 服务器提供服务的 Django 应用。我们将定义用于构建容器镜像的运行时环境,安装应用程序及其依赖项,并进行一些最终配置。
-
Django 应用的父镜像
在处理容器化部署时,决定作为容器基础的基础镜像是您要做的第一个决定。当然,您可以选择从 SCRATCH(即空文件系统)构建容器镜像,或者基于现有的容器镜像。由于我们不想重复造轮子,我们将从基础镜像构建我们的镜像。有许多开源容器镜像可从 Docker 的官方容器镜像仓库 获取。除非您是从头开始构建镜像,否则强烈建议您使用 Docker 官方 Hub 中的镜像。这是因为 Docker 会验证这些镜像是否遵循最佳实践,并确保定期更新和安全补丁到位。
由于 Django 是一个 Python 框架,我们将利用具有标准 Python 环境的镜像,该环境已经安装了我们需要的工具和库。从 Docker Hub 上的 Python 镜像 官方页面,您可以找到适用于各种 Python 版本的基于 Python 的镜像。
从我们的各种 基于 Docker 的教程 中,您会注意到我们使用的是基于 Alpine Linux 的镜像。Alpine Linux 为运行容器化应用程序提供了一个健壮但精简的操作系统环境。虽然它的文件系统很小,但它’是可扩展的,并带有一个完整的包管理系统,可以添加各种功能。
在 Docker Hub 上选择基础镜像时,您可能会注意到每个镜像都有多个可用的标签。对于 Python,我们有 3-alpine,它指向最新 Alpine 版本的最新 Python 3 版本镜像。这意味着,如果您的项目使用的是较旧的镜像版本,那么当 Docker 镜像的维护者进行更新时,它可能会崩溃。为了避免将来出现这种情况,始终建议为您要使用的镜像选择最具体的标签。
在本教程中,我们将使用 3.8.12-alpine3.15 镜像作为我们 Django 应用程序的基础镜像。这个特定的标签将在 Dockerfile 中使用 FROM 指令进行指定。Dockerfile 将位于主项目目录中: django_project.
首先,从 Django-polls 目录导航回 django_project 目录:
|
1 |
cd .. |
进入该目录后,使用您最喜欢的编辑器打开一个名为 Dockerfile :
|
1 |
nano Dockerfile |
接下来,粘贴以下行以设置镜像的基础镜像:
|
1 |
FROM python:3.8.12-alpine3.15 |
The FROM 关键字定义了自定义 Docker 镜像的起点。定义好这一点后,我们可以继续添加指令来设置应用程序。这些指令将安装必要的依赖项、复制应用程序文件并设置运行环境。
在 Dockerfile 中添加以下代码片段:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
ADD django-polls/requirements.txt /app/requirements.txt RUN set -ex \ && apk add --no-cache --virtual .build-deps postgresql-dev build-base \ && python -m venv /env \ && /env/bin/pip install --upgrade pip \ && /env/bin/pip install --no-cache-dir -r /app/requirements.txt \ && runDeps="$(scanelf --needed --nobanner --recursive /env \ | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \ | sort -u \ | xargs -r apk info --installed \ | sort -u)" \ && apk add --virtual rundeps $runDeps \ && apk del .build-deps ADD django-polls /app WORKDIR /app ENV VIRTUAL_ENV /env ENV PATH /env/bin:$PATH EXPOSE 8000 |
在此代码片段中,我们告诉 Docker 将 requirements.txt 文件复制到 /app/requirements.txt,以确保应用程序的依赖项在镜像的文件系统中可用。这些依赖项包括运行应用程序所需的所有 Python 包。首先复制依赖项,以便 Docker 可以缓存镜像层。这是因为 Docker 会缓存 Dockerfile 中的每一步。镜像的首次构建通常需要更长时间。Docker 将下载依赖项,然后将其缓存。如果 requirements.txt 文件没有更改,Docker 将从缓存中进行构建,从而使后续构建速度更快。
下一步包含 RUN 指令,该指令执行一系列 Linux 命令,并使用 Linux && 运算符进行链式连接。这些命令执行以下操作:
-
使用 Alpine 的 apk 包管理器工具来安装 PostgreSQL 开发文件和基本构建依赖项。
-
创建一个 Python 虚拟环境。
-
安装在 requirements.txt 文件中定义的 Python 依赖项,使用 pip.
-
通过分析已安装 Python 包的依赖要求来编译必要的运行时包。
-
删除任何不再需要的构建依赖项。
在 RUN 步骤中链式连接命令的原因是为了减少镜像层。每当 Docker 遇到 ADD, COPY 或 RUN Dockerfile 中的指令。在适用时压缩命令将最大限度地减少创建的镜像层数。
添加到镜像层中的项无法在后续层中删除。在进入下一条指令之前,您必须声明删除不需要的项的指令。这对于减小镜像大小是必要的。您应该注意到我们添加了 apk del 命令在 RUN 命令的末尾。这样做是为了在运行构建应用包之后删除构建依赖项。
接下来,我们有另一个 ADD 指令,我们用它来将应用程序代码复制到 /app 目录。然后,我们将使用 WORKDIR 指令将镜像的工作目录设置为 /app 目录,该目录现在包含应用程序的代码。
接下来,我们有 ENV 指令,我们用它来设置两个环境变量,镜像将使这些变量对运行中的容器可用。首先,我们将 VIRTUAL_ENV 变量设置为 /env。其次,我们将 PATH 变量设置为包含 /env/bin 目录。在这两行中,我们正在加载 /env/bin/activate 脚本,这就是我们在 Linux 环境中激活虚拟环境的方式。您可以阅读更多关于在其他操作系统上使用 Python 虚拟环境 的内容。最后一条指令是 EXPOSE 命令,它设置了端口 8000,容器将在运行时监听该端口。
到目前为止,除了启动容器时运行的默认命令外,您的 Dockerfile 几乎已经完成了。让我们在下一节中定义它。
-
理解默认的 Docker 镜像命令
启动 Docker 容器时,您可以提供要执行的命令。但是,如果您不提供命令,Docker 镜像的默认命令将决定容器启动时会发生什么。我们单独或结合使用 ENTRYPOINT 或 CMD 指令,在 Dockerfile 中定义默认命令。
如果您选择同时定义 ENTRYPOINT 和 CMD,在 ENTRYPOINT 指令中,您定义了将由容器运行的可执行文件。在 CMD 指令中,定义可执行命令的默认参数列表。在启动容器时,您可以通过在命令行中追加替代参数来覆盖默认参数列表,格式如下:
|
1 |
docker run <image> <arguments> |
这种格式可以防止开发人员轻易覆盖 ENTRYPOINT 命令。定义 ENTRYPOINT 命令是为了调用一个脚本,该脚本将设置环境并根据提供的参数列表执行不同的操作。
您可以单独使用 ENTRYPOINT 指令来配置容器的可执行文件。但是,这种格式不允许定义默认参数列表。当您使用 docker run 命令运行容器时,可以提供参数。
如果您选择仅使用 CMD ,Docker 会将其解释为默认命令和参数列表,您可以在运行时覆盖它。您可以在 官方 Dockerfile 参考文档.
让我们看看如何将您学到的关于默认命令的知识应用到我们的容器示例中。默认情况下,我们希望使用 gunicorn 服务器来运行应用程序。虽然传递给 gunicorn 服务器的参数列表不需要在运行时进行配置,但我们希望能够灵活地运行其他命令,以用于调试或管理配置(初始化数据库、收集静态资源等)等目的。如您所见,使用 CMD 来定义默认命令最符合我们的利益,这将允许我们在必要时随时覆盖它。
以下是可用于定义 CMD 命令的一些语法:
- CMD ["command", "argument 1", "argument 2", . . . ,"argument n"]: exec 格式(推荐格式),接受一个命令和一个参数列表。它直接执行命令,不进行 shell 处理。
- CMD command "argument 1" "argument 2" . . . "参数 n": shell 格式定义了一个命令和参数列表。它将命令列表传递给 shell 进行处理。如果您想在命令中替换环境变量,您可能会发现这很有用,但是,它并非完全可预测。
- CMD ["参数 1", "参数 2", . . . ,"参数 n"]: 参数列表格式,它仅定义默认参数列表,并与 ENTRYPOINT 指令。
我们将使用 exec 格式来定义我们在 Dockerfile 中的最终指令。在您的 Dockerfile:
|
1 |
CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "mysite.wsgi:application"] |
您现在可以保存并关闭 Dockerfile.
当您使用此镜像启动容器时,它们将执行 gunicorn 绑定到本地主机端口 8000,带有 3 个工作进程,并调用 application 函数,该函数位于 wsgi.py 文件中,该文件位于 mysite 目录。您可以选择提供不同的命令以在运行时覆盖默认命令,并执行不同的进程而不是 gunicorn。您可能想了解更多关于 Gunicorn 工作进程.
您的 Dockerfile 现在已准备就绪,您可以使用 docker build 来构建应用镜像。您可以使用 docker run 在您的本地开发机器上启动容器。
-
构建 Docker 镜像
该 docker build 命令默认会在当前目录中寻找 Dockerfile 以查找其构建指令。它还会将构建“上下文”发送给 Docker 守护进程。构建上下文 是一组在构建过程中应该可用的文件。默认情况下,您运行 docker build 命令的当前目录被设置为构建上下文。
在包含 Dockerfile 的相同目录下,运行命令 docker build。使用 -t 标志提供镜像和标签,并使用命令末尾的 ( .) 点号将当前目录设置为构建上下文:
|
1 |
docker build -t django-polls:v1 . |
在此命令中,我们将镜像命名为 django-polls ,标签命名为 v1。注意命令末尾的点号,我们用它来表示当前目录作为构建上下文。
当 docker build 完成时,您应该会看到类似于以下内容的输出:

您的 Docker 镜像现在已准备就绪。如果我们没有将某些配置偏移到外部环境变量中,您可以使用 docker run 命令轻松运行您的容器。然而,由于我们尚未配置在 settings.py 文件中设置的外部环境变量,运行将会失败。让我们在下一步中完成它。
第 8 步:设置运行环境并测试应用
我们即将结束本教程。在这一步中,我们将在 env 文件中配置环境变量。有了 env 文件变量后,我们可以创建数据库模式,生成静态文件并将其上传到外部对象存储服务,最后测试应用。
Docker 提供了几种方法,您可以用来 提供环境变量 给容器。在我们的例子中,我们希望通过文件提供环境变量列表。因此,我们将使用 --env-file 方法。
使用您首选的编辑器,在 env 目录中创建一个名为 django_project 的文件:
|
1 |
nano env |
粘贴以下变量列表:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
DJANGO_SECRET_KEY=your_secret_key DEBUG= DJANGO_LOGLEVEL=info DJANGO_ALLOWED_HOSTS=your_server_IP_address DB_ENGINE=postgresql_psycopg2 DB_DATABASE=polls_db DB_USERNAME=hackins DB_PASSWORD=your_database_password DB_HOST=your_database_host DB_PORT=your_database_port STATIC_DEFAULT_FILE_STORAGE=storages.backends.s3boto3.S3Boto3Storage STATIC_MINIO_BUCKET_NAME=test-bucket MINIO_ACCESS_KEY=your_minio_access_key MINIO_SECRET_KEY=your_minio_secret_key MINIO_URL=your_minio_url:your_minio_port |
列表中的变量是您在前面步骤中定义的变量:
-
DJANGO_SECRET_KEY:生成一个唯一且不可预测的值,如中所述 Django 文档。您可以使用此命令生成一个随机字符串并将其设置为该变量:
|
1 |
python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())' |
-
DEBUG:我们已将此值设置为 True,但对于生产部署,请记住将其设置为 False,只需将其留空即可。
-
DJANGO_LOGLEVEL:我们已将其设置为 info,您可以根据需要将其调整为所需的级别。
-
DJANGO_ALLOWED_HOSTS:将此值设置为运行 Docker 容器的 Ubuntu 服务器的 IP 地址。或者,将其设置为 *,如果在开发模式下,通配符将匹配所有主机。
-
DB_DATABASE:如果您使用了不同的数据库名称,请在此处进行相应设置。
-
DB_USERNAME:将此设置为您为数据库选择的用户名。
-
DB_PASSWORD:将此设置为您为数据库选择的密码。
-
DB_HOST:将此设置为运行数据库实例的主机,正如您在第一步.
-
DB_PORT:将此设置为数据库的端口。
-
STATIC_MINIO_BUCKET_NAME:将此设置为您在 MinIO 云存储帐户中创建的存储桶名称。
编辑完成后保存并关闭文件。
环境配置现已就绪。我们需要运行容器并传入参数以覆盖默认的 CMD 命令,并使用 manage.py makemigrations 和 manage.py migrate 命令来创建数据库模式。
以下是命令:
|
1 |
docker run --env-file env django-polls:v1 sh -c "python manage.py makemigrations && python manage.py migrate" |
在此命令中,我们正在运行 django-polls:v1 容器镜像,使用 –env-file 标志来传入环境变量文件。我们还使用 sh -c "python manage.py makemigrations && python manage.py migrate" 运行此命令启动容器时,它将创建应用程序代码中定义的数据库模式。
如果成功,您应该会看到类似于以下的输出:

输出表明数据库模式已成功创建。
下一步是为 Django 应用创建管理员用户。我们将启动容器并使用以下命令在其中启动交互式 shell:
|
1 |
docker run -i -t --env-file env django-polls:v1 sh |
该命令会启动容器并显示一个 shell 提示符,您可以使用它与 Python shell 进行交互。让我们创建一个用户:
|
1 |
python manage.py createsuperuser |
按照提示提供用户名、电子邮件地址、密码、重新输入密码,然后按回车键创建用户。通过按下 CTRL+D.
接下来,我们需要再次运行容器,使用 collectstatic Django 命令覆盖默认命令,以生成应用的静态文件并将其上传到您的 MinIO 云存储服务:
|
1 |
docker run --env-file env django-polls:v1 sh -c "python manage.py collectstatic --noinput" |
完成后,您应该会看到类似于以下的输出,表明您的容器已成功连接到 MinIO 存储服务并上传了静态文件:
![]()
我们的存储桶现在看起来像这样,其中包含 Django 创建的目录:

最后,我们现在可以使用以下命令运行该应用:
|
1 |
docker run --env-file env -p 80:8000 django-polls:v1 |
以下是输出:

当您执行上述命令时,它会运行镜像中的默认 CMD 命令并公开端口 8000(按定义)。现在,Ubuntu 上的端口 80 被映射到 8000 端口,属于 django-polls:v1 容器。
我们现在可以在浏览器中测试该应用程序。在浏览器中转到您服务器的公网 IP 地址: http://your_server_public_ip.
预计会看到 404 Page Not Found 错误,因为根据 Django 教程,我们没有为 / 路径定义路由:

我们已将 DEBUG 变量设置为 True,这就是为什么我们能看到这个包含大量关键信息的错误页面。让我们取消设置 DEBUG 变量。首先,您需要使用 CTRL+C 停止正在运行的容器。然后,打开 env 文件:
|
1 |
nano env |
接下来,找到 DEBUG 变量并取消设置,或者将其留空。我们将其留空是因为 getenv 函数会将 False 解释为字符串,从而返回 true:
|
1 |
DEBUG= |
保存文件并使用以下命令再次运行容器:
|
1 |
docker run --env-file env -p 80:8000 django-polls:v1 |
如果您在浏览器中访问此 http://your_server_public_ip ,您应该会看到默认的 404 页面:

您已经了解了如何在不修改源代码的情况下,使用环境变量来操纵 Django 应用的运行时行为。
导航至 http://your_server_public_ip/polls 以查看投票(Polls)主页:

由于我们刚刚部署了该应用,因此目前没有任何投票。
导航到管理界面: http://your_server_public_ip/admin 以查看管理员身份验证窗口:

提供您使用 createsuperuser 命令设置的凭据进行登录。您现在应该已进入管理页面界面:

请注意,所有静态文件都由我们设置的外部存储服务提供。您可以在浏览器窗口中 右键单击 并选择 查看网页源代码:

您可以添加一些问题和选项,并测试应用的整体性能:

返回投票首页 http://your_server_public_ip/polls 并尝试对问题进行投票:

在测试并确认一切按预期工作后,您可以销毁(停止)该容器。
结论
您已成功配置了 Django Web 应用,使其在基于容器的环境中良好运行。这包括使应用适应外部环境变量、设置应用使用云存储服务来存储静态文件,以及为容器镜像创建 Dockerfile。您可以在 django-polls-docker 分支上查看我们为将应用 Docker 化所做的更改,该分支位于 django-polls GitHub 仓库中。
从这里开始,可能性仅受您的想象力限制。您可以设置 Nginx 反向代理,使其介于客户端和 Gunicorn 服务器之间。您还可以添加 Certbot 以获取 TLS 证书来保护您的 Nginx 服务器。我们建议添加 HTTP 代理以缓冲慢速客户端,并保护您的 Gunicorn 服务器免受拒绝服务攻击。
虽然我们在 Dockerfile 的启动命令中定义了 3 个 worker,但您可以根据服务器上的可用资源设置您首选的数量。您可以在 官方 Gunicorn 设计文档 上找到更多信息。如果您愿意,可以将构建的 Docker 镜像推送到 Docker Hub,并尝试将其部署到安装了 Docker 的多个环境中。如果您想了解更多信息,请继续关注我们的 教程博客 ,因为我们将制作后续教程,介绍如何使用 Nginx 和 Let's Encrypt 来确保 Django 应用的安全。
最后,这里有更多可以帮助您利用 Docker 的资源:
- 如何在 Ubuntu 20.04 上使用 GitLab 自托管实例托管 Docker 镜像仓库并构建 Docker 镜像
- 在 Ubuntu 20.04 上使用 Docker 数据卷
- 在 Ubuntu 20.04 上使用 Docker 构建和部署 Flask 应用程序
- 如何在 Ubuntu 20.04 上使用 Docker 容器部署 WordPress
祝您计算愉快!
评论
暂无评论。发表第一条评论吧。