如何使用 Docker、Nginx 和 Let's Encrypt 扩展 Django 应用程序并确保其安全

介绍

在基于云的环境中,有多种方式来扩展和保护 Django 应用程序。 通过 scaling 水平 和运行多个副本的应用程序,您可以构建一个更易于故障和高度可用的系统,同时增加其 throughput 以便请求可以同时处理。 一个方法来横向扩展 Django 应用程序是提供额外的 _app 服务器来运行您的 Django 应用程序和其 WSGI HTTP 服务器(如 GunicornuWSGI)。 为了在这个应用程序服务器组中路由和分发收入请求,您可以使用 负载平衡器和 [反向代理](

运行您的 Django 应用程序和 Nginx 代理程序在 Docker 容器内,确保这些组件以相同的方式行为,无论部署到哪个环境。

在本教程中,您将通过提供两个应用程序服务器,每个应用程序将运行Django和Gunicorn应用程序容器的副本,以横向扩展集装箱化的Django和Gunicorn Polls应用程序。

您还将通过提供和配置第三方代理服务器来启用 HTTPS,该服务器将运行 Nginx 反向代理容器和 Certbot客户端容器。Certbot 将从 Let's Encrypt 证书授予 Nginx 的 TLS 证书。这将确保您的网站从 SSL Labs获得高安全级别。这个代理服务器将收到您的应用程序的所有外部请求,并坐在两个 upstream Django 应用服务器前面。

前提条件

要遵循本教程,您将需要:

  • 三台Ubuntu 18.04服务器:
  • 有两台服务器将是应用程序服务器,用于运行你的Django和Gunicornapp. (_) ) - 一个服务器将是一个** proxy** 服务器,用于运行Nginx和 Certbot.
  • 所有服务器都应有一个拥有"sudo"权限的非根用户,并有一个活动防火墙. 关于这些设置的指导,请参见此初始服务器设置指导.

步骤 1 – 配置第一个 Django 应用服务器

首先,我们将Django应用程序存储库克隆到第一个应用程序服务器,然后,我们将配置和构建应用程序Docker图像,并通过运行Django容器来测试应用程序。

<$>[注] 注: 如果您正在继续从 如何使用 Docker 构建 Django 和 Gunicorn 应用程序,您已经完成了步骤 1 并可以跳到 步骤 2)来配置 第二 应用程序服务器。

首先,您要登录两个 Django 应用程序服务器中的第一个,然后使用git来克隆 Django Tutorial Polls App(GitHub 存储库)的polls-docker分支(https://github.com/do-community/django-polls)。这个复制程序包含 Django 文档的 [样本 Polls 应用程序]的代码(https://docs.djangoproject.com/en/3.0/intro/)。

1[environment second]
2git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

导航到‘django-polls’目录:

1[environment second]
2cd django-polls

该目录包含了Django应用程序Python代码,一个Dockerfile,Docker将用来构建容器图像,以及一个env文件,其中包含一份将环境变量列入容器运行环境的列表。

1[environment second]
2cat Dockerfile
 1[secondary_label Output]
 2[environment second]
 3FROM python:3.7.4-alpine3.10
 4
 5ADD django-polls/requirements.txt /app/requirements.txt
 6
 7RUN set -ex \
 8    && apk add --no-cache --virtual .build-deps postgresql-dev build-base \
 9    && python -m venv /env \
10    && /env/bin/pip install --upgrade pip \
11    && /env/bin/pip install --no-cache-dir -r /app/requirements.txt \
12    && runDeps="$(scanelf --needed --nobanner --recursive /env \
13        | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
14        | sort -u \
15        | xargs -r apk info --installed \
16        | sort -u)" \
17    && apk add --virtual rundeps $runDeps \
18    && apk del .build-deps
19
20ADD django-polls /app
21WORKDIR /app
22
23ENV VIRTUAL_ENV /env
24ENV PATH /env/bin:$PATH
25
26EXPOSE 8000
27
28CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "mysite.wsgi"]

此 Dockerfile 使用官方 Python 3.7.4 Docker 图像作为基础,并安装 Django 和 Gunicorn 的 Python 包要求,如django-polls/requirements.txt文件中所定义。它然后删除一些不必要的构建文件,将应用程序代码复制到图像中,并设置执行PATH

有关本 Dockerfile 中的每一个步骤的更多信息,请参阅 如何使用 Docker 构建 Django 和 Gunicorn 应用程序的第 6 步。

现在,使用docker build来构建图像:

1[environment second]
2docker build -t polls .

我们使用t旗号命名图像调查,并在当前目录中作为 build context 传输,这是构建图像时要参考的文件集。

在 Docker 构建和标记图像后,使用docker 图像列出可用的图像:

1[environment second]
2docker images

你应该看到列出的民意调查图像:

1[environment second]
2[secondary_label Output]
3REPOSITORY TAG IMAGE ID CREATED SIZE
4polls latest 80ec4f33aae1 2 weeks ago 197MB
5python 3.7.4-alpine3.10 f309434dea3a 8 months ago 98.7MB

在运行Django容器之前,我们需要使用当前目录中存在的env文件来配置其运行环境,该文件将传入用于运行容器的docker run命令,Docker将将配置的环境变量注入容器的运行环境。

使用nano或您最喜欢的编辑器打开env文件:

1[environment second]
2nano env

我们将这样配置文件,您将需要添加一些额外的值,如下所述。

 1[environment second]
 2[label django-polls/env]
 3DJANGO_SECRET_KEY=
 4DEBUG=True
 5DJANGO_ALLOWED_HOSTS=
 6DATABASE_ENGINE=postgresql_psycopg2
 7DATABASE_NAME=polls
 8DATABASE_USERNAME=
 9DATABASE_PASSWORD=
10DATABASE_HOST=
11DATABASE_PORT=
12STATIC_ACCESS_KEY_ID=
13STATIC_SECRET_KEY=
14STATIC_BUCKET_NAME=
15STATIC_ENDPOINT_URL=
16DJANGO_LOGLEVEL=info

填写以下键的缺失值:

  • `DJANGO-SECRET-KEY':将此设定为一个独特的、无法预测的价值,详见Django docs。 生成此密钥的一个方法是在 [可缩放 Django App (https://andsky.com/tech/tutorials/how-to-set-up-a-scalable-django-app-with-digitalocean-managed-databases-and-spaces#step-5-%E2%80%94-adjusting-the-app-settings) 的 [调整 App 设置] (https://andsky.com/tech/tutorials/how-to-set-up-a-scalable-django-app-with-digitalocean-managed-databases-and-spaces#step-5-%E2%80%94-adjusting-the-app-settings] 教程中提供。
  • DJANGO-ALLULED-HOSTS': 这个变量可以保护应用程序,防止HTTP主机头攻击. 为测试目的, 将此设定为QQ, 一个匹配所有主机的通配符 。 在制作中,您应该将此设定为 your_domain.com`。 欲了解更多关于Django设置的情况,请参考Django文档中的核心设置
  • 美利坚合众国: 设定为在先决条件步骤中创建的 PostgreSQL 数据库用户 。 (_) ( )* DATABASE-NAME':将其设定为polls'或先决条件步骤中创建的PostgreSQL数据库的名称。 (_ ( )* DATABASE_PASWORD': 设定为在先决条件步骤中创建的 PostgreSQL 用户密码 。 () ( )* DATABASE_HOST': 将此设定为数据库的主机名。 (_) ( )* DATABASE PORT` : 将此设定到您的数据库端口。
  • 统计: 设定为您的 S3 桶或 Space 访问密钥 。 ( _) ( )* `STATIC_SECRET_KEY': 将此设定在您的 S3 桶或 Space 访问密钥上 。
  • STIC_BUKET_NAME': 设定为您的 S3 桶或空格名 。 (_) ( )* STATIC-ENDPOINT-URL': 设定为合适的 S3 桶或空间端点 URL, 例如 https://space-name.nyc3. digital Oceanspaces.com' 如果您的空间位于 nyc3` 区域。 (_) (英语)

完成编辑后,保存并关闭文件。

现在我们将使用docker run来取代Dockerfile中设置的CMD,并使用manage.py makemigrationsmanage.py migrate命令创建数据库方案:

1[environment second]
2docker run --env-file env polls sh -c "python manage.py makemigrations && python manage.py migrate"

我们运行polls:latest容器图像,传入我们刚刚修改的环境变量文件,并用sh -c``python manage.py makemigrations && python manage.py迁移来代替Dockerfile命令,这将创建应用程序代码定义的数据库方案。

 1[environment second]
 2[secondary_label Output]
 3No changes detected
 4Operations to perform:
 5  Apply all migrations: admin, auth, contenttypes, polls, sessions
 6Running migrations:
 7  Applying contenttypes.0001_initial... OK
 8  Applying auth.0001_initial... OK
 9  Applying admin.0001_initial... OK
10  Applying admin.0002_logentry_remove_auto_add... OK
11  Applying admin.0003_logentry_add_action_flag_choices... OK
12  Applying contenttypes.0002_remove_content_type_name... OK
13  Applying auth.0002_alter_permission_name_max_length... OK
14  Applying auth.0003_alter_user_email_max_length... OK
15  Applying auth.0004_alter_user_username_opts... OK
16  Applying auth.0005_alter_user_last_login_null... OK
17  Applying auth.0006_require_contenttypes_0002... OK
18  Applying auth.0007_alter_validators_add_error_messages... OK
19  Applying auth.0008_alter_user_username_max_length... OK
20  Applying auth.0009_alter_user_last_name_max_length... OK
21  Applying auth.0010_alter_group_name_max_length... OK
22  Applying auth.0011_update_proxy_permissions... OK
23  Applying polls.0001_initial... OK
24  Applying sessions.0001_initial... OK

这表明数据库方案已成功创建。

如果您在随后的时间运行迁移,Django 将执行禁令,除非数据库架构发生了变化。

接下来,我们将运行应用程序容器的另一个实例,并使用其内部的交互式壳创建 Django 项目的管理用户。

1[environment second]
2docker run -i -t --env-file env polls sh

这将为您提供运行容器内部的壳提示,您可以使用它来创建Django用户:

1[environment second]
2python manage.py createsuperuser

输入您的用户的用户名、电子邮件地址和密码,然后创建用户后,点击CTRL+D,离开容器并杀死它。

最后,我们将生成应用程序的静态文件,并使用collectstatic将其上传到DigitalOcean Space。

1[environment second]
2docker run --env-file env polls sh -c "python manage.py collectstatic --noinput"

这些文件被生成和上传后,您将收到以下输出。

1[environment second]
2[secondary_label Output]
3121 static files copied.

现在我们可以运行应用程序:

1[environment second]
2docker run --env-file env -p 80:8000 polls
1[environment second]
2[secondary_label Output]
3[2019-10-17 21:23:36 +0000] [1] [INFO] Starting gunicorn 19.9.0
4[2019-10-17 21:23:36 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
5[2019-10-17 21:23:36 +0000] [1] [INFO] Using worker: sync
6[2019-10-17 21:23:36 +0000] [7] [INFO] Booting worker with pid: 7
7[2019-10-17 21:23:36 +0000] [8] [INFO] Booting worker with pid: 8
8[2019-10-17 21:23:36 +0000] [9] [INFO] Booting worker with pid: 9

在这里,我们运行在Dockerfile中定义的默认命令gunicorn --bind :8000 -workers 3 mysite.wsgi:application,并曝光容器端口8000,以便Ubuntu服务器上的端口80被映射到8000的容器。

您现在应该能够通过键入URL栏中的http://APP_SERVER_1_IP来导航到调查应用程序,因为没有定义的路径为/路径,您可能会收到一个404页未找到错误,这是预期的。

<$>[警告] 警告: 使用 UFW 防火墙与 Docker 时,Docker 会绕过任何已配置的 UFW 防火墙规则,如本 GitHub 问题中文档所示。这解释了为什么您可以访问服务器的端口 80,即使您在任何先决步骤中都没有明确创建 UFW 访问规则。

点击http://APP_SERVER_1_IP/polls查看调查应用程序界面:

Polls Apps Interface

要查看管理界面,请访问http://APP_SERVER_1_IP/admin。您应该看到调查应用程序管理员身份验证窗口:

Polls Admin Auth Page

输入您用createsuperuser命令创建的管理用户名和密码。

验证后,您可以访问 Polls 应用程序的管理界面:

Polls Admin Main Interface

请注意,对于adminpolls应用程序的静态资产正在直接从对象存储中交付,请参阅 Testing Spaces Static File Delivery来确认此情况。

当你完成探索时,在运行 Docker 容器的终端窗口中按CTRL + C来杀死容器。

现在你已经确认应用容器按照预期运行,你可以运行它在 detached 模式,这将在背景中运行,并允许你退出你的 SSH 会话:

1[environment second]
2docker run -d --rm --name polls --env-file env -p 80:8000 polls

-d旗指示Docker在分离模式下运行容器,-rm旗在容器离开后清理容器的文件系统,我们将容器命名为调查

登出第一个 Django 应用服务器,然后导航到http://APP_SERVER_1_IP/polls,以确认容器按照预期运行。

现在你的第一个Django应用服务器已启动并运行,你可以设置你的第二个Django应用服务器。

步骤 2 — 配置第二个 Django 应用服务器

由于许多设置此服务器的命令将与前一步中的命令相同,因此它们将在这里以简短的形式呈现。

首先,您可以登录到第二个Django应用程序服务器。

克隆django-polls GitHub 存储库的polls-docker分支:

1[environment third]
2git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

导航到‘django-polls’目录:

1[environment third]
2cd django-polls

使用docker build构建图像:

1[environment third]
2docker build -t polls .

使用nano或您最喜欢的编辑器打开env文件:

1[environment third]
2nano env
 1[environment third]
 2[label django-polls/env]
 3DJANGO_SECRET_KEY=
 4DEBUG=True
 5DJANGO_ALLOWED_HOSTS=
 6DATABASE_ENGINE=postgresql_psycopg2
 7DATABASE_NAME=polls
 8DATABASE_USERNAME=
 9DATABASE_PASSWORD=
10DATABASE_HOST=
11DATABASE_PORT=
12STATIC_ACCESS_KEY_ID=
13STATIC_SECRET_KEY=
14STATIC_BUCKET_NAME=
15STATIC_ENDPOINT_URL=
16DJANGO_LOGLEVEL=info

填写缺少的值,如在 步骤 1。当你完成编辑后,保存并关闭文件。

最后,在分离模式下运行应用程序容器:

1[environment third]
2docker run -d --rm --name polls --env-file env -p 80:8000 polls

点击http://APP_SERVER_2_IP/polls确认容器按预期运行,您可以安全地退出第二个应用服务器,而无需终止运行容器。

随着 Django 应用程序容器的启动和运行,您可以继续配置 Nginx 反向代理容器。

步骤 3 – 配置 Nginx Docker 容器

Nginx是一个多功能的网页服务器,提供了一些功能,包括 反向代理, 负载平衡,和 缓存。在本教程中,我们已将Django的静态资产下载到对象存储中,所以我们不会使用Nginx的缓存功能。然而,我们将使用Nginx作为向我们两个后端Django应用程序服务器的反向代理,并在它们之间分发接收请求。此外,Nginx将执行TLS终止(https://en.wikipedia.org/wiki/TLS_termination_proxy)和重定向使用Certbot提供的TLS证书。这意味着它将迫使客户使用HTTPS,将接收的HTTP请求重定向到端口443。

在本教程中,我们决定将 Nginx 容器从后端服务器中分开。根据您的使用情况,您可以选择在 Django 应用服务器中的一个上运行 Nginx 容器,在本地和其他 Django 服务器上进行代理请求。另一个可能的架构是运行两个 Nginx 容器,在每个后端服务器上运行一个,前面有一个云 负载平衡器 每个架构都具有不同的安全性和性能优势,您应该 负载测试您的系统发现瓶颈。本教程中描述的灵活架构允许您扩展后端 Django 应用层以及接口 Nginx 层。一旦单个 Nginx 容器变成瓶颈,您可以扩展到多个 Nginx,并

随着 Django 应用服务器的启动和运行,我们可以开始设置 Nginx 代理服务器. 登录您的代理服务器并创建一个名为 conf 的目录:

1[environment fourth]
2mkdir conf

使用nano或您最喜欢的编辑器创建名为nginx.conf的配置文件:

1[environment fourth]
2nano conf/nginx.conf

使用以下 Nginx 配置:

 1[environment fourth]
 2[label conf/nginx.conf]
 3
 4upstream django {
 5    server APP_SERVER_1_IP;
 6    server APP_SERVER_2_IP;
 7}
 8
 9server {
10    listen 80 default_server;
11    return 444;
12}
13
14server {
15    listen 80;
16    listen [::]:80;
17    server_name your_domain.com;
18    return 301 https://$server_name$request_uri;
19}
20
21server {
22    listen 443 ssl http2;
23    listen [::]:443 ssl http2;
24    server_name your_domain.com;
25
26    # SSL
27    ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
28    ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
29
30    ssl_session_cache shared:le_nginx_SSL:10m;
31    ssl_session_timeout 1440m;
32    ssl_session_tickets off;
33
34    ssl_protocols TLSv1.2 TLSv1.3;
35    ssl_prefer_server_ciphers off;
36
37    ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
38
39    client_max_body_size 4G;
40    keepalive_timeout 5;
41
42        location / {
43          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
44          proxy_set_header X-Forwarded-Proto $scheme;
45          proxy_set_header Host $http_host;
46          proxy_redirect off;
47          proxy_pass http://django;
48        }
49
50    location ^~ /.well-known/acme-challenge/ {
51    	root /var/www/html;
52    }
53
54}

这些上游服务器位置块将 Nginx 配置为将 HTTP 请求重定向到 HTTPS,并在第 1 步和 2 步中配置的两台 Django 应用服务器上平衡其负荷。

此配置是由 Gunicorn, CerbotNginx提供的样本配置文件组合的,旨在作为一个最小的 Nginx 配置来实现该架构并运行。调整此 Nginx 配置超出了本文的范围,但您可以使用像 NGINXConfig这样的工具来为您的架构生成性能和安全的 Nginx 配置文件。

上游块定义了用于使用proxy_pass指令的代理请求的服务器组:

1[environment fourth]
2[label conf/nginx.conf]
3upstream django {
4    server APP_SERVER_1_IP;
5    server APP_SERVER_2_IP;
6}
7. . .

在此块中,我们将上游名称称为django,并包括 Django 应用程序服务器的 IP 地址。如果应用程序服务器在 DigitalOcean 上运行,并且已启用 VPC 网络,请在此处使用他们的私人 IP 地址。

第一个服务器块捕捉了不匹配您的域的请求,并终止了连接,例如,直接HTTP请求到您的服务器的IP地址将由此块处理:

1[environment fourth]
2[label conf/nginx.conf]
3. . .
4server {
5    listen 80 default_server;
6    return 444;
7}
8. . .

下一个服务器块将HTTP请求重定向到您的域名到HTTPS使用HTTP 301重定向(https://en.wikipedia.org/wiki/HTTP_301)。

 1[environment fourth]
 2[label conf/nginx.conf]
 3. . .
 4server {
 5    listen 80;
 6    listen [::]:80;
 7    server_name your_domain.com;
 8    return 301 https://$server_name$request_uri;
 9}
10. . .

这两个指令定义了通往 TLS 证书和秘密密钥的路径,这些路径将通过 Certbot 提供并在下一步安装到 Nginx 容器中。

1[environment fourth]
2[label conf/nginx.conf]
3. . .
4ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
5ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
6. . .

这些参数是 Certbot 推荐的 SSL 安全默认值。 有关这些参数的更多信息,请参阅 Nginx 文档中的 模块 ngx_http_ssl_module

 1[environment fourth]
 2[label conf/nginx.conf]
 3. . .
 4    ssl_session_cache shared:le_nginx_SSL:10m;
 5    ssl_session_timeout 1440m;
 6    ssl_session_tickets off;
 7
 8    ssl_protocols TLSv1.2 TLSv1.3;
 9    ssl_prefer_server_ciphers off;
10
11    ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
12. . .

Gunicorn 的 样本 Nginx 配置的这两个指令设置了客户端请求体的最大允许大小,并分配了与客户端保持连接的时间。

1[environment fourth]
2[label conf/nginx.conf]
3. . .
4client_max_body_size 4G;
5keepalive_timeout 5;
6. . .

第一个位置块指示 Nginx 通过 HTTP 向上游 django 服务器发送代理请求,还保留了客户端 HTTP 标题,这些标题捕捉了源 IP 地址、用于连接的协议和目标主机:

 1[environment fourth]
 2[label conf/nginx.conf]
 3. . .
 4location / {
 5    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 6    proxy_set_header X-Forwarded-Proto $scheme;
 7    proxy_set_header Host $http_host;
 8    proxy_redirect off;
 9    proxy_pass http://django;
10}
11. . .

有关这些指令的更多信息,请参阅 部署 Gunicorn模块 ngx_http_proxy_module 从 Nginx 文档。

最终的位置块捕获了对/已知/acme-challenge/路径的请求,Certbot 用于 HTTP-01 挑战,以便使用 Let’s Encrypt 验证您的域名,并提供或更新 TLS 证书。

1[environment fourth]
2[label conf/nginx.conf]
3. . .
4location ^~ /.well-known/acme-challenge/ {
5    	root /var/www/html;
6}

完成编辑后,保存并关闭文件。

您现在可以使用此配置文件运行 Nginx Docker 容器. 在本教程中,我们将使用由 Nginx 维护的 官方 Docker 图像nginx:1.19.0 图像,版本 1.19.0

当我们第一次运行容器时, Nginx 会发出错误并失败,因为我们还没有提供配置文件中定义的证书。

1[environment fourth]
2docker run --rm --name nginx -p 80:80 -p 443:443 \
3    -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \
4    -v /var/www/html:/var/www/html \
5    nginx:1.19.0

在这里,我们将容器命名为nginx,并将主机端口80443绘制到相应的容器端口中。v旗将配置文件安装在/etc/nginx/conf.d/nginx.conf的 Nginx 容器中,而 Nginx 图像已预配置为加载。它安装在ro只读模式中,因此容器无法修改文件。根网页目录/var/www/html也安装在容器中。最后,nginx:1.19.0指示 Docker 将nginx:1.19.0`从 Dockerhub 图像中拉动并运行。

Docker 会拖动并运行图像,然后 Nginx 会发出错误,当它找不到配置的 TLS 证书和秘密密密钥时。

步骤 4 – 配置 Certbot 和 Let’s Encrypt 证书更新

Certbot是由 Electronic Frontier Foundation开发的 Let's Encrypt 客户端,它提供免费的来自 Let's Encrypt的证书授权的 TLS 证书,允许浏览器验证您的 Web 服务器的身份。鉴于我们在我们的 Nginx 代理服务器上安装了 Docker,我们将使用 Certbot Docker 图像提供和更新 TLS 证书。

首先,请确保您有一个 DNS 记录A,并将其映射到代理服务器的公共 IP 地址,然后在代理服务器上使用certbot Docker 图像提供证书的阶段版本:

1[environment fourth]
2docker run -it --rm -p 80:80 --name certbot \
3         -v "/etc/letsencrypt:/etc/letsencrypt" \
4         -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
5         certbot/certbot certonly --standalone --staging -d your_domain.com

此命令在交互模式下运行certbot Docker 图像,并将主机上的端口80提前到容器端口80。它创建并将两个主机目录安装到容器中:/etc/letsencrypt//var/lib/letsencrypt/

当被提示时,输入您的电子邮件地址并同意服务条款. 如果域验证成功,您应该看到以下输出:

 1[environment fourth]
 2[secondary_label Output]
 3Obtaining a new certificate
 4Performing the following challenges:
 5http-01 challenge for stubb.dev
 6Waiting for verification...
 7Cleaning up challenges
 8
 9IMPORTANT NOTES:
10 - Congratulations! Your certificate and chain have been saved at:
11   /etc/letsencrypt/live/your_domain.com/fullchain.pem
12   Your key file has been saved at:
13   /etc/letsencrypt/live/your_domain.com/privkey.pem
14   Your cert will expire on 2020-09-15. To obtain a new or tweaked
15   version of this certificate in the future, simply run certbot
16   again. To non-interactively renew *all* of your certificates, run
17   "certbot renew"
18 - Your account credentials have been saved in your Certbot
19   configuration directory at /etc/letsencrypt. You should make a
20   secure backup of this folder now. This configuration directory will
21   also contain certificates and private keys obtained by Certbot so
22   making regular backups of this folder is ideal.

您可以使用检查证书:

1[environment fourth]
2sudo cat /etc/letsencrypt/live/your_domain.com/fullchain.pem

有了提供的 TLS 证书,我们可以测试上一步组装的 Nginx 配置:

1[environment fourth]
2docker run --rm --name nginx -p 80:80 -p 443:443 \
3    -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \
4    -v /etc/letsencrypt:/etc/letsencrypt \
5    -v /var/lib/letsencrypt:/var/lib/letsencrypt \
6    -v /var/www/html:/var/www/html \
7    nginx:1.19.0

这是在 步骤 3中运行的相同命令,加上最近创建的 Let's Encrypt 目录。

一旦 Nginx 启动并运行,请导航到 http://your_domain.com. 您可能会在浏览器中收到一个警告,即证书授权权是无效的。 这是我们预期的,因为我们已经提供阶段性证书,而不是生产 Let's Encrypt 证书。 检查您的浏览器的 URL 栏以确认您的 HTTP 请求被重定向到 HTTPS。

点击您的终端中的CTRL+C,退出 Nginx,然后再次运行certbot客户端,这一次错过了--staging旗帜:

1[environment fourth]
2docker run -it --rm -p 80:80 --name certbot \
3         -v "/etc/letsencrypt:/etc/letsencrypt" \
4         -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
5         certbot/certbot certonly --standalone -d your_domain.com

当被要求保留现有证书或更新并更换它时,点击2来更新它,然后点击ENTER来确认您的选择。

有了生产 TLS 证书,重新运行 Nginx 服务器:

1[environment fourth]
2docker run --rm --name nginx -p 80:80 -p 443:443 \
3    -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \
4    -v /etc/letsencrypt:/etc/letsencrypt \
5    -v /var/lib/letsencrypt:/var/lib/letsencrypt \
6    -v /var/www/html:/var/www/html \
7    nginx:1.19.0

在您的浏览器中,导航到 http://your_domain.com. 在 URL 栏中,确认 HTTP 请求已被重定向到 HTTPS. 鉴于 Polls 应用程序没有默认路线配置,您应该看到 Django Page not found 错误。 导航到 https://your_domain.com/polls,您将看到标准 Polls 应用程序界面:

Polls Apps Interface

此时,您已使用 Certbot Docker 客户端提供生产 TLS 证书,并对两个 Django 应用服务器进行反向代理和负载平衡外部请求。

让我们加密证书每90天都到期。为了确保您的证书仍然有效,您应该在计划到期前定期更新它。随着 Nginx 运行,您应该使用 Certbot 客户端在webroot模式而不是standalone模式。这意味着 Certbot 将通过在/var/www/html/.well-known/acme-challenge/目录中创建一个文件来进行验证,然后将使用 Nginx 配置中的位置规则捕捉到该路径的 Let’s Encrypt 验证请求。

有多种方法可以自动化此过程,并且自动更新 TLS 证书超出本教程的范围。 对于使用cron编程工具的类似过程,请参阅 如何使用 Nginx、Let's Encrypt 和 Docker Compose 保护 Containerized Node.js 应用程序的步骤 6

在您的终端中,点击CTRL+C来杀死 Nginx 容器,并在-d 标志上重新运行它:

1[environment fourth]
2docker run --rm --name nginx -d -p 80:80 -p 443:443 \
3    -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \
4    -v /etc/letsencrypt:/etc/letsencrypt \
5    -v /var/lib/letsencrypt:/var/lib/letsencrypt \
6  -v /var/www/html:/var/www/html \
7    nginx:1.19.0

如果 Nginx 在背景中运行,请使用以下命令执行证书更新程序的干运行:

1[environment fourth]
2docker run -it --rm --name certbot \
3    -v "/etc/letsencrypt:/etc/letsencrypt" \
4  -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
5  -v "/var/www/html:/var/www/html" \
6  certbot/certbot renew --webroot -w /var/www/html --dry-run

我们使用--webroot插件,指定 Web 根路径,并使用--dry-run旗帜来验证一切正常工作,而无需实际执行证书更新。

如果更新模拟成功,您应该看到以下输出:

 1[environment fourth]
 2[secondary_label Output]
 3Cert not due for renewal, but simulating renewal for dry run
 4Plugins selected: Authenticator webroot, Installer None
 5Renewing an existing certificate
 6Performing the following challenges:
 7http-01 challenge for your_domain.com
 8Using the webroot path /var/www/html for all unmatched domains.
 9Waiting for verification...
10Cleaning up challenges
11
12- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
13new certificate deployed without reload, fullchain is
14/etc/letsencrypt/live/your_domain.com/fullchain.pem
15- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
16
17- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
18** DRY RUN: simulating 'certbot renew' close to cert expiry
19**          (The test certificates below have not been saved.)
20
21Congratulations, all renewals succeeded. The following certs have been renewed:
22  /etc/letsencrypt/live/your_domain.com/fullchain.pem (success)
23** DRY RUN: simulating 'certbot renew' close to cert expiry
24**          (The test certificates above have not been saved.)
25- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

在生产设置中,在更新证书后,您应该重新加载 Nginx,以便更改生效。

1[environment fourth]
2docker kill -s HUP nginx

此命令将向nginx Docker 容器内运行的 Nginx 进程发送一个 HUP Unix 信号.接收此信号后, Nginx 将重新加载其配置和更新证书。

有了 HTTPS 启用并启用了该架构的所有组件,最后一步是通过阻止对两个后端应用服务器的外部访问来锁定设置;所有 HTTP 请求都应该通过 Nginx 代理程序流动。

步骤 5 – 防止外部访问 Django App 服务器

在本教程中描述的架构中,SSL终止发生在 Nginx 代理程序中,这意味着 Nginx 会解密 SSL 连接,并且数据包被非加密地向 Django 应用程序服务器传输。对于许多使用情况,这种安全级别是足够的。对于涉及财务或健康数据的应用程序,您可能想要实施端到端加密。您可以通过负载平衡器传输加密包,并在应用程序服务器上解密,或者在代理程序上重新加密,再一次在 Django 应用程序服务器上解密。这些技术超出本文的范围,但要了解更多信息,请参阅 端到端加密

Nginx 代理服务器作为外部流量和内部网络之间的通道。理论上,任何外部客户端都不能直接访问内部应用程序服务器,所有请求都应该通过 Nginx 服务器流动。在 步骤 1的注释中简要描述了 Docker 的 开放问题 其中 Docker 默认地绕过ufw防火墙设置,并在外部打开端口,这可能不安全。 为了解决这个安全问题,建议在与 Docker 支持的服务器一起工作时使用 cloud firewalls 了解更多有关创建 Cloud Firewalls 与 DigitalOcean 的信息,请参阅 How to Create Firewalls 您也可以直接操纵`iptables

在此步骤中,我们将更改 UFW 的配置,以阻止对 Docker 打开的主机端口的外部访问。在应用服务器上运行 Django 时,我们将 -p 80:8000 标志转移到 docker,该标志将主机上的 80端口转移到集装箱端口8000。这也为外部客户端打开了 80端口,您可以通过访问http://your_app_server_1_IP` 来验证。 为了防止直接访问,我们将使用 ufw-docker GitHub 存储库中描述的方法修改 UFW 的配置。

开始登录到第一个 Django 应用程序服务器,然后打开具有超级用户权限的 /etc/ufw/after.rules' 文件,使用 nano' 或您最喜欢的编辑器:

1[environment second]
2sudo nano /etc/ufw/after.rules

当被提示时,输入您的密码,然后点击ENTER来确认。

你应该看到以下UFW规则:

 1[environment second]
 2[label /etc/ufw/after.rules]
 3#
 4# rules.input-after
 5#
 6# Rules that should be run after the ufw command line added rules. Custom
 7# rules should be added to one of these chains:
 8#   ufw-after-input
 9#   ufw-after-output
10#   ufw-after-forward
11#
12
13# Don't delete these required lines, otherwise there will be errors
14*filter
15:ufw-after-input - [0:0]
16:ufw-after-output - [0:0]
17:ufw-after-forward - [0:0]
18# End required lines
19
20# don't log noisy services by default
21-A ufw-after-input -p udp --dport 137 -j ufw-skip-to-policy-input
22-A ufw-after-input -p udp --dport 138 -j ufw-skip-to-policy-input
23-A ufw-after-input -p tcp --dport 139 -j ufw-skip-to-policy-input
24-A ufw-after-input -p tcp --dport 445 -j ufw-skip-to-policy-input
25-A ufw-after-input -p udp --dport 67 -j ufw-skip-to-policy-input
26-A ufw-after-input -p udp --dport 68 -j ufw-skip-to-policy-input
27
28# don't log noisy broadcast
29-A ufw-after-input -m addrtype --dst-type BROADCAST -j ufw-skip-to-policy-input
30
31# don't delete the 'COMMIT' line or these rules won't be processed
32COMMIT

滚动到底部,并粘贴到以下 UFW 配置规则块:

 1[environment second]
 2[label /etc/ufw/after.rules]
 3. . .
 4
 5# BEGIN UFW AND DOCKER
 6*filter
 7:ufw-user-forward - [0:0]
 8:DOCKER-USER - [0:0]
 9-A DOCKER-USER -j RETURN -s 10.0.0.0/8
10-A DOCKER-USER -j RETURN -s 172.16.0.0/12
11-A DOCKER-USER -j RETURN -s 192.168.0.0/16
12
13-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
14
15-A DOCKER-USER -j ufw-user-forward
16
17-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
18-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
19-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
20-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
21-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
22-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
23
24-A DOCKER-USER -j RETURN
25COMMIT
26# END UFW AND DOCKER

这些规则限制了公共访问由Docker打开的端口,并允许从10.0.0.0/8,172.16.0.0/12192.168.0.0/16私有IP范围访问。如果您正在使用VPC与DigitalOcean,那么VPC网络中的Droplets将通过私有网络接口访问开放端口,但外部客户端不会。 有关VPC的更多信息,请参阅 VPC官方文档

如果您不使用 VPC 与 DigitalOcean,并且在 Nginx 配置的上游块中输入了应用程序服务器的公共 IP 地址,则需要明确修改 UFW 防火墙以允许来自 Nginx 服务器的流量通过 Django 应用程序服务器的端口 80。

完成编辑后,保存并关闭文件。

重新启动ufw,以获取新的配置:

1[environment second]
2sudo systemctl restart ufw

在您的 Web 浏览器中导航到http://APP_SERVER_1_IP,以确认您不再可以通过端口80访问应用程序服务器。

在第二个 Django 应用程序服务器上重复此过程。

登出第一個應用程式伺服器或開啟另一個終端窗口,然後登入第二個 Django 應用程式伺服器,然後使用「nano」或您最喜愛的編輯器開啟「/etc/ufw/after.rules」檔案:

1[environment third]
2sudo nano /etc/ufw/after.rules

当被提示时,输入您的密码,然后点击ENTER来确认。

滚动到底部,并粘贴到以下 UFW 配置规则块:

 1[environment third]
 2[label /etc/ufw/after.rules]
 3. . .
 4
 5# BEGIN UFW AND DOCKER
 6*filter
 7:ufw-user-forward - [0:0]
 8:DOCKER-USER - [0:0]
 9-A DOCKER-USER -j RETURN -s 10.0.0.0/8
10-A DOCKER-USER -j RETURN -s 172.16.0.0/12
11-A DOCKER-USER -j RETURN -s 192.168.0.0/16
12
13-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
14
15-A DOCKER-USER -j ufw-user-forward
16
17-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
18-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
19-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
20-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
21-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
22-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
23
24-A DOCKER-USER -j RETURN
25COMMIT
26# END UFW AND DOCKER

完成编辑后,保存并关闭文件。

重新启动ufw,以获取新的配置:

1[environment third]
2sudo systemctl restart ufw

在您的 Web 浏览器中导航到http://APP_SERVER_2_IP,以确认您不再可以通过端口80访问应用程序服务器。

最后,导航到https://your_domain_here/polls,以确认 Nginx 代理仍然可以访问 Django 上流服务器。

结论

在本教程中,您使用 Docker 容器设置了可扩展的 Django Polls 应用程序. 随着流量增长和系统负载增加,您可以单独扩展每个层: Nginx 代理层, Django 后端应用层和 PostgreSQL 数据库层。

在构建分布式系统时,通常会遇到多个设计决策,几个架构可能会满足您的使用情况,本教程中描述的架构旨在作为设计Django和Docker可扩展应用程序的灵活蓝图。

您可能希望在遇到错误时控制容器的行为,或者在系统启动时自动运行容器。 要做到这一点,您可以使用一个流程管理器,例如 Systemd或实施重新启动策略。

当使用运行同一 Docker 图像的多个主机进行规模工作时,使用配置管理工具(如 AnsibleChef等操作步骤可以更有效地自动化。

除了在每个主机上构建相同的图像之外,您还可以通过像 Docker Hub这样的图像注册表来简化部署,该注册表可以集中构建、存储和向多个服务器分发 Docker 图像。

Published At
Categories with 技术
comments powered by Disqus