在 Ubuntu 20.04 上使用 Django 和 Nginx、PostgreSQL 和 Gunicorn

介绍

姜戈 是一个流行的开源 Python Web 框架。 本指南解释了如何使用免费的 Let’s Encrypt TLS 证书在 Ubuntu 20.04 LTS 上使用 Nginx、PostgreSQL 和 Gunicorn 部署安全的 Django 项目。

先决条件

在 Vultr 部署一个新的 Ubuntu 20.04 服务器。 按照 Vultr 的最佳实践指南创建 sudo 用户并更新 Ubuntu 服务器。 (可选)配置 Ubuntu 防火墙并打开端口 80、443 和 22。

确保更换 django.example.com 在这些示例中,使用您服务器的完全限定域名或 IP 地址。

1.安装PostgreSQL

通过 SSH 以非 root sudo 用户身份登录服务器。

从官方 Ubuntu 20.04 存储库安装 PostgreSQL 12。

$ sudo apt -y install postgresql postgresql-contrib

切换到 postgres 用户,这是 PostgreSQL 在安装期间创建的。

$ sudo su - postgres

登录到 PostgreSQL。

$ psql

创建一个名为的新角色 dbuser 用于您的 Django 项目。

postgres=# CREATE ROLE dbuser WITH LOGIN;

设置一个强密码 dbuser 角色。

postgres=# password dbuser

优化 Django 的数据库连接参数。

postgres=# ALTER ROLE dbuser SET client_encoding TO 'utf8';
postgres=# ALTER ROLE dbuser SET default_transaction_isolation TO 'read committed';
postgres=# ALTER ROLE dbuser SET timezone TO 'UTC';

创建一个新的 dbname 数据库。

postgres=# CREATE DATABASE dbname;

授予所有权限 dbname 数据库到 dbuser 角色。

postgres=# GRANT ALL PRIVILEGES ON DATABASE dbname TO dbuser;

退出 PostgreSQL 命令行:

postgres=# q

退出 postgres 帐户并返回给您的 sudo 用户以执行剩余步骤。

$ exit

2. 安装 Django 和 Gunicorn

安装 Django 依赖项。

$ sudo apt -y install build-essential python3-venv python3-dev libpq-dev

创建一个名为的专用用户 django 管理项目的源代码。

$ sudo adduser django

每次更改源代码时都切换到此用户。

$ sudo su django

将工作目录更改为主目录。

$ cd ~

创建一个名为的目录 project_root 用于存储项目源代码。

$ mkdir project_root

创建一个名为的虚拟环境 .venv 里面 project_root 隔离 Django 及其依赖项。

$ python3 -m venv project_root/.venv

激活虚拟环境。

$ source project_root/.venv/bin/activate

安装 Django 点子,Python 的软件包安装程序。

$ pip install Django

安装 psycopg2,一种流行的 Python PostgreSQL 适配器,以便您的 Python 代码可以连接到数据库。 您必须安装 wheel 包之前 psycopg2.

$ pip install wheel 
$ pip install psycopg2

将 Gunicorn 安装到虚拟环境中。

$ pip install gunicorn

3. 创建和配置项目

将项目的源代码上传到 project_root 目录。 确保 manage.py 文件是的直接孩子 project_root. 出于说明目的,本指南创建了一个名为的示例项目 example 而不是上传现有的。

$ django-admin startproject example project_root

的内容 project_root 应该是这样的。

$ ls -a project_root
.  ..  example  manage.py  .venv

将工作目录更改为 project_root 目录。

$ cd project_root

在文本编辑器中打开项目设置文件。

$ nano example/settings.py

设置文件是一个带有模块级变量的 Python 模块。 找出 DATABASES 变量并使用第 1 节中创建的凭据更改其值。确保它看起来像这样:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'dbname',
        'USER': 'dbuser',
        'PASSWORD': 'dbpassword',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}

要在生产环境中有效地提供静态文件,请找到 INSTALLED_APPS 列出并确保 'django.contrib.staticfiles' 是它的项目之一。 然后找到 STATIC_URL = '/static/' 行并添加 /home/django/project_root/static 在它下面。 这是存储项目静态文件的目录。

STATIC_ROOT = '/home/django/project_root/static/'

为安全起见,找到以下变量并更改它们的值,如图所示。

DEBUG = False

ALLOWED_HOSTS = ['django.example.com']

保存并关闭设置文件。

Django 使用 SECRET_KEY 变量到 提供加密签名. 生成唯一值 get_random_secret_key 来自 Django 的函数 utils 模块。

$ python manage.py shell -c 'from django.core.management import utils; print(utils.get_random_secret_key())'

结果应该是随机字符,类似于:

e-m^lc!--w3$-9qv^54*=qpe=4gko([email protected]@[email protected]

将字符串复制到剪贴板并重新打开设置文件。

$ nano example/settings.py

找出 SECRET_KEY 变量并粘贴您的随机字符串。 确保用单引号将值括起来,如图所示。

SECRET_KEY = 'e-m^lc!--w3$-9qv^54*=qpe=4gko([email protected]@[email protected]'

保存并关闭文件。

检查数据库设置。

$ python manage.py check --database default

如果设置正确,结果应如下所示:

System check identified no issues (0 silenced).

根据您的项目模型创建迁移。

$ python manage.py makemigrations

运行迁移并在数据库中创建表。

$ python manage.py migrate

创建您在上面配置的静态目录。

$ mkdir /home/django/project_root/static

将所有静态文件复制到静态目录中。 类型 yes 提示时。

$ python manage.py collectstatic

为项目创建一个管理用户。

$ python manage.py createsuperuser

为管理用户输入所需的凭据。

退出虚拟环境。

$ deactivate

切换回 sudo 用户以继续下一步。

$ exit

4. 配置 Gunicorn

systemd 服务在操作系统启动时启动 Gunicorn 作为后台服务。 要创建此服务:

创建一个名为的新文件 gunicorn-example.service 在里面 /etc/systemd/system 目录。

$ sudo nano /etc/systemd/system/gunicorn-example.service

将以下内容粘贴到文件中。

[Unit]
Description=Gunicorn for the Django example project
After=network.target

[Service]
Type=notify

# the specific user that our service will run as
User=django
Group=django

RuntimeDirectory=gunicorn_example
WorkingDirectory=/home/django/project_root
ExecStart=/home/django/project_root/.venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 example.wsgi
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true

[Install]
WantedBy=multi-user.target

此服务将运行 Gunicorn 3 工作人员并侦听 IP 地址 127.0.0.1 港口 8000. 您可以根据需要自定义这些值。 Gunicorn 将执行项目源代码下 django 用户。 这 example 出现在 example.wsgigunicorn_example arguments 是您的项目的名称。

保存服务文件并退出。

重新加载 systemd 守护进程。

$ sudo systemctl daemon-reload

启用该服务,使其在启动时运行。

$ sudo systemctl enable --now gunicorn-example.service

5. 安装和配置 Nginx

安装 Nginx。

$ sudo apt -y install nginx

为您的项目创建一个新的配置文件。

$ sudo nano /etc/nginx/sites-available/example-http.conf

将以下内容粘贴到文件中。

server {
listen 80;
listen [::]:80;

server_name django.example.com;

# Process static file requests
location /static/ {
    root /home/django/project_root;

    # Set expiration of assets to MAX for caching
    expires max;
}

# Deny accesses to the virtual environment directory
location /.venv {
    return 444;
}

# Pass regular requests to Gunicorn
location / {
    # set the correct HTTP headers for Gunicorn
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;

    # we don't want nginx trying to do something clever with
    # redirects, we set the Host: header above already.
    proxy_redirect off;

    # turn off the proxy buffering to handle streaming request/responses
    # or other fancy features like Comet, Long polling, or Web sockets.
    proxy_buffering off;

    proxy_pass http://127.0.0.1:8000;
}
}

保存配置文件并退出。

启用新配置。

$ sudo ln -s /etc/nginx/sites-available/example-http.conf /etc/nginx/sites-enabled/example-http.conf

添加 www-data 用户到 django group 以便 Nginx 进程可以访问项目源代码目录。

$ sudo usermod -aG django www-data

检查新配置。

$ sudo nginx -t

重新加载 Nginx 服务以使更改生效。

$ sudo systemctl reload nginx.service

6.(可选)使用 Let’s Encrypt 证书配置 HTTPS

如果您拥有有效的域名,则可以免费为您的 Django 项目设置 HTTPS。 您可以使用 Let’s Encrypt 的 Certbot 程序获得免费的 TLS 证书。

按照我们的指南使用 Snap 安装 Certbot。

重命名 HTTP 配置文件,使其成为 HTTPS 配置文件的模板。

$ sudo mv /etc/nginx/sites-available/example-http.conf /etc/nginx/sites-available/example-https.conf

创建一个新的配置文件来处理 HTTP 请求。

$ sudo nano /etc/nginx/sites-available/example-http.conf

将以下内容粘贴到您的文件中。

server {
listen 80;
listen [::]:80;

server_name django.example.com;

root /home/django/project_root;

location / {
    return 301 https://$server_name$request_uri;
}

location /.well-known/acme-challenge/ {}
}

此配置使 Nginx 将所有 HTTP 请求(来自 Let’s Encrypt 的请求除外)重定向到相应的 HTTPS 请求。

保存配置文件并退出。

检查新配置。

$ sudo nginx -t

重新加载 Nginx 服务以使更改生效。

$ sudo systemctl reload nginx.service

获取 Let’s Encrypt 证书。

$ sudo certbot certonly --webroot -w /home/django/project_root -d django.example.com -m [email protected] --agree-tos

您可能需要回答有关与电子前沿基金会共享电子邮件的问题。 完成后,Certbot 会将与证书相关的所有文件放在 /etc/letsencrypt/archive/django.example.com 目录并在目录中创建相应的符号链接 /etc/letsencrypt/live/django.example.com 目录,方便您使用。 这些符号链接是:

$ sudo ls /etc/letsencrypt/live/django.example.com
cert.pem  chain.pem  fullchain.pem  privkey.pem  README

您将在下一步中使用这些符号链接来安装证书。

使用 Nginx 安装证书

为 DHE 密码生成带有 DH 参数的文件。 这个过程可能需要一段时间。

$ sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048

2048 是推荐的 DH 参数大小。

更新 HTTPS 配置文件。

$ sudo nano /etc/nginx/sites-available/example-https.conf

找到以下几行。

listen 80;
listen [::]:80;

用以下几行替换它们。

listen 443 ssl http2;
listen [::]:443 ssl http2;

ssl_certificate /etc/letsencrypt/live/django.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/django.example.com/privkey.pem;

ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;  # about 40000 sessions

# DH parameters file
ssl_dhparam /etc/nginx/dhparam.pem;

# intermediate configuration
ssl_protocols TLSv1.2;
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;
ssl_prefer_server_ciphers off;

# HSTS (ngx_http_headers_module is required) (63072000 seconds)
#
# Uncomment the following line only if your website fully supports HTTPS
# and you have no intention of going back to HTTP, otherwise, it will
# break your site.
#
# add_header Strict-Transport-Security "max-age=63072000" always;

# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;

# verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate /etc/letsencrypt/live/django.example.com/chain.pem;

# Use Cloudflare DNS resolver
resolver 1.1.1.1;

保存配置文件并退出。

启用新配置。

$ sudo ln -s /etc/nginx/sites-available/example-https.conf /etc/nginx/sites-enabled/example-https.conf

检查新配置。

$ sudo nginx -t

重新加载 Nginx 服务以使更改生效。

$ sudo systemctl reload nginx.service

自动续订

Let’s Encrypt 证书的有效期为 90 天,因此您必须至少每三个月更新一次 TLS 证书。 Certbot 安装自动创建了一个 systemd 计时器单元来自动执行此任务。

验证计时器是否处于活动状态。

$ sudo systemctl list-timers | grep 'certbot|ACTIVATES'

更新证书后,Certbot 不会自动重新加载 Nginx,因此 Nginx 仍然使用旧证书。 相反,您必须在 /etc/letsencrypt/renewal-hooks/deploy 重新加载 Nginx 的目录。

在文本编辑器中创建文件。

$ sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

将以下内容粘贴到您的文件中。

#!/bin/bash

/usr/bin/systemctl reload nginx.service

保存并退出文件。

使脚本可执行。

$ sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

通过试运行测试更新过程。

$ sudo certbot renew --dry-run

这篇 Vultr 文章更详细地解释了上述所有步骤。 这种 TLS 设置给你一个 A SSL 实验室测试.

7. 验证设置

重新启动服务器。

$ sudo reboot

等待系统启动,然后打开 http://django.example.com/admin 浏览器中的链接。

Django 管理 屏幕出现一个登录表单。

使用在步骤 3 中创建的管理用户的用户名和密码登录。

您现在在 Ubuntu 20.04 服务器上有一个可用的 Django 站点。

如果您按照本教程创建示例项目而不是上传现有项目,您将获得一个 未找到 访问主页时的错误消息 http://django.example.com/. 这是完全正常的。 因为,默认情况下,Django 不会在生产环境中为示例项目自动生成主页内容(带有 DEBUG = False 环境)。

参考

Django 部署清单
部署静态文件
Gunicorn 文档

注:本教程在Vultr VPS上测试通过,如需部署请前往Vultr.com