在 Linux 上部署 Django 应用,nginx+gunicorn+supervisor

如果是新服务器先要进行基本的配置

部署前应用配置

开发环境与生产环境中的应用一般会有两套不同的配置。比如 DEBUG 选项,数据库配置等。

对于 setting.py 文件, 在生产环境中我们要设置 DEBUG, SECRET_KEY 以及数据库密码等私密的设置。为了项目的安全这些设置应该最好从环境变量加载,或从仅供服务的文件中读取。

为了更好的统一线上和线下两个环境, 我们使用 python-dotenv 工具. 有了这个工具, 我们可以将配置信息写在 .env 文件中,然后使用下面的代码加载配置信息到环境变量中:

# settings.py
from dotenv import load_dotenv
load_dotenv()

具体配置方法:

  1. 进入虚拟环境, 安装 python-dotenv
pip install python-dotenv
  1. 在 Django 项目根目录处建立 .env 文件(与manage.py 同目录), 并添加配置
DJANGO_DEBUG=Ture
  1. 修改 setting.py 文件,将一些关键配置改成从环境变量中读取, 没有就设为默认值
import os
from dotenv import load_dotenv
load_dotenv()

SECRET_KEY = os.environ.get('SECRET_KEY', 'mzi&f@)*4985)m19oj+x-hg4$hi!478v)+dz)bh+mgiz!zjg9n')
DEBUG = os.environ.get('DJANGO_DEBUG', False)
# 当 DEBUG=Flase 时, 设置的能够访问的主机
ALLOWED_HOSTS = ['127.0.0.1', 'localhost', '.xxmblog.top', '<服务器的IP地址>']
# 数据库设置, 便于在生产环境中直接改为 MySQL 数据库
DATABASES = {
    'default': {
        'ENGINE': os.environ.get('DATABASE_ENGINE') or 'django.db.backends.sqlite3',
        'NAME': os.environ.get('DATABASE_NAME') or os.path.join(BASE_DIR, 'db.sqlite3'),
        'USER': os.environ.get('DATABASE_USER'),     # 用户名,可以自己创建用户
        'PASSWORD': os.environ.get('DATABASE_PASSWORD'),  # 密码
        'HOST': os.environ.get('DATABASE_HOST'),  # mysql服务所在的主机ip
        'PORT': os.environ.get('DATABASE_PORT'),         # mysql服务端口
    }
}
# ...
STATIC_URL = '/static/'
# 增加下面的, 便于在生产环境中收集静态文件
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

接下来,关键的是,.env 保存一些私密的配置,不要添加到git中。

开发环境的 .env:

DJANGO_DEBUG 为非空字符串时为 Ture, 不设置默认为 Flase
# 其他一些数据库配置

生产环境中的 .env 设置后面单独说明。

当有其他生产环境和开发环境不同的配置时,都让Django从环境变量中读取,我们就写在.env 文件中。这样部署的时候就不用修改源代码了。

其他配置:

  • 静态文件收集
    为了使 Nginx 方便的处理静态文件的请求,生产环境中需要将全部静态文件收集到一个统一的目录下。
    先添加如下配置:
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

STATIC_ROOT 指明了静态文件的收集目录,到生产环境中有用

  • 导出依赖库
    进入虚拟环境, 执行下列命令
pip freeze > requirements.txt

会生成一个 requirements.txt 文件, 里面包含了虚拟环境中安装的所有依赖。
到了服务器上,只需要执行

pip install -r requirements.txt

就能安装里面列的所有库了。

上传到 Github

注意到
.env文件不能上传,服务器应该自己建一个
db.sqlite3 不上传, 因为这是本地的测试数据库, 到了生产环境, 应该用其他的数据库了
venv 文件夹不上传

在服务器上安装应用

在自己的家目录下(如 home/xxm/) 创建文件夹

➜  ~ mkdir sites
➜  ~ mkdir sites/www.xxmblog.com

所有的网站放在 sites 目录中,网站的项目以域名区分, 进入 www.xxmblog.com 目录。
通过 git clone 将应用下载到该目录中
然后进入项目目录并创建虚拟环境:

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

此时的项目结构:

home/xxm/sites/www.xxmblog.top/blog-django/
                                    blogproject/
                                    venv/
                                    .gitignore
                                    requirements.txt

安装项目依赖

先安装必要的包

(venv) $ pip install -r requirements.txt

除了requirements.txt中的包之外,还要安装生产部署需要的两个包。gunicorn软件包是Python应用程序的生产Web服务器。 mysqlclient 软件包包含MySQL驱动程序

(venv) $ pip install gunicorn mysqlclient

如果安装 mysqlclient 出现错误, 运行下面的命令。

sudo apt-get install python-dev python3-dev
sudo apt-get install libmysqlclient-dev
pip install pymysql
pip install mysqlclient

配置 MySQL

MySQL 的安装不做说明, 具体讲述如何让 Django 使用 MySQL 数据库。

下列命令创建名为 blog-django 的新数据库,并创建一个具有完全访问权限的同名用户:

mysql> create database blogdjango character set utf8 collate utf8_bin;
mysql> create user 'blogdjango'@'localhost' identified by '';
mysql> grant all privileges on blogdjango.* to 'blogdjango'@'localhost';
mysql> flush privileges;
mysql> quit;

需要用你选择的密码来替换, 这将是 blogdjango 数据库用户的密码,所以不要使用你已为root用户选择的密码

接下来就是修改 setting.py 文件,让 Django 使用刚刚创建的数据库。但是由于我们前面使用了 dotenv,seeting.py 的一些配置已经从环境变量中读取, 所以我们只需配置 .env 文件即可

配置 .env

创建一个 .env 文件, 与 manage.py 同目录。

在里面输入:

SECRET_KEY=<随机字符串>
DATABASE_ENGINE=django.db.backends.mysql
DATABASE_NAME=blogdjango
DATABASE_USER=blogdjango
DATABASE_PASSWORD=<刚刚创建用的密码>
DATABASE_HOST=localhost
DATABASE_PORT=3306

SECRET_KEY 使用一个随机字符串
DJANGO_DEBUG 不设置时,DEBUG为FALSE
其他为MySQL的数据库配置

收集静态文件

虚拟环境下继续运行 python manage.py collectstatic 命令收集静态文件到 static 目录下:

数据库迁移

虚拟环境下继续运行 python manage.py migrate 命令创建数据库文件:

python manage.py makemigrations
python manage.py migrate

创建超级用户

虚拟环境下继续运行 python manage.py createsuperuser 命令创建一个超级用户,方便我们进入 Django 管理后台

尝试运行一下应用

python manage.py runserver

如果上面的命令没有出错的话,一般就没有问题了。

设置 Gunicorn 和 Supervisor

当使用 python manage.py runserver 运行服务器时, 我们使用的是 Django 自带的Web服务器。这个服务器在开发过程中很有用,但并不适用于生产环境,所以我们改用 Gunicorn, 它是一个纯粹的Python Web服务器, 并且支持高并发。

尝试在 gunicorn 下启动 blogproject:

gunicorn -b localhost:8000 -w 4 blogproject.wsgi:application

-b 告诉 gunicorn 在哪里监听请求,我在8000端口上监听了内部网络接口。在没有外部访问的情况下,运行 Python Web 服务器很快。当有外部请求的时候,我们就需要一个更快的Web服务器,比如 nginx,它可以优化客户端的所有静态文件的请求,并将任何请求转发到内部服务器。后面我们会设置 nginx。
-w 配置gunicorn将运行多少worker
blogproject.wsgi:application 告诉gunicorn如何加载应用程序实例. 冒号前的名称是包含应用程序的模块,冒号后面的名称是此应用程序的名称。

运行了上面的命令后,我们看到:

(venv) ➜  blogproject git:(master) ✗ gunicorn -b localhost:8000 -w 4 blogproject.wsgi:application
[2019-08-16 11:18:50 +0800] [5674] [INFO] Starting gunicorn 19.9.0
[2019-08-16 11:18:50 +0800] [5674] [INFO] Listening at: http://127.0.0.1:8000 (5674)
[2019-08-16 11:18:50 +0800] [5674] [INFO] Using worker: sync
[2019-08-16 11:18:50 +0800] [5677] [INFO] Booting worker with pid: 5677
[2019-08-16 11:18:50 +0800] [5678] [INFO] Booting worker with pid: 5678
[2019-08-16 11:18:50 +0800] [5680] [INFO] Booting worker with pid: 5680
[2019-08-16 11:18:50 +0800] [5679] [INFO] Booting worker with pid: 5679

gunicorn 监听 http://127.0.0.1:8000, 并启动了4个worker.
[Ctrl+C] 退出进程。

可以看到,目前我们想要启动服务器,每次都要输入一遍上面的命令,如果进程崩溃了,还要手动输入上面的命令让服务器重新启动。这真的很傻,所以我们使用一个 supervisor 的应用。

supervisor 的功能:

  • 可以让这个进程后台运行,并持续监控
  • 如果服务器崩溃退出,这个进程可以重新自动启动
  • 而且如果机器重新启动,服务器在启动时自动运行,而无需人工登录和启动

安装:

sudo apt-get install supervisor

supervisor 使用配置文件来告诉它要监视什么程序以及如何在必要时重新启动它们

配置文件必须存储在 /etc/supervisor/conf.d

对于我们这个 blogproject 应用,我们创建一个 blogproject.conf 配置文件:
/etc/supervisor/conf.d/blogproject.conf:Supervisor配置。

[program:blogproject]
command=/home/xxm/sites/www.xxmblog.com/blog-django/venv/bin/gunicorn -b localhost:8000 -w 4 blogproject.wsgi:application
directory=/home/xxm/sites/www.xxmblog.com/blog-django/blogproject
user=xxm
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true


commanddirectoryuser设置告诉supervisor如何运行应用程序。 如果计算机启动或崩溃,autostartautorestart设置会使microblog自动重新启动。 stopasgroupkillasgroup选项确保当supervisor需要停止应用程序来重新启动它时,它仍然会调度成顶级gunicorn进程的子进程。

编写此配置文件后,必须重载supervisor服务的配置才能导入它:

$ sudo supervisorctl reload

像这样,这个gunicorn web服务器就已经启动和运行,并处于监控之中!

查看状态:

sudo supervisorctl status

如果出现下面的内容,可能是配置文件出错了

blogproject                      FATAL     Exited too quickly (process log may have details)

如果是下面表示正在运行:

blogproject                      RUNNING   pid 5865, uptime 0:00:03

其他命令

sudo supervisorctl stop xxx # 关闭 xxx
sudo supervisorctl restart xxx # 重启 xxx

配置 Nginx

由 gunicorn 启动的 blogproject 应用服务器现在运行在本地端口8000. 我们想要让服务器能够被外部世界访问。

首先打开两个端口 80 和 443 用来处理应用程序的 Web 通讯。如果是阿里云服务器需要在控制台上配置安全组。

我们直接使用 https, 配置端口80将所有流量转发到将要加密的端口443.

首先创建一个 SSL 证书, 这个证书只能用来测试,当用户访问的时候, Web 浏览器会警告用户证书不是由可信证书颁发机构颁发的。

mkdir certs
openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \
  -keyout certs/key.pem -out certs/cert.pem

生成的时候填写的信息,将包含在 SSL 证书中, 用户在浏览器访问的时候可以查看。

命令的结果将生成名为key.pem和cert.pem的两个文件。

为了使用 nginx, 为我们就要编写一个配置文件,这个文件位于 /etc/nginx/sites-enabled 中,
通常刚安装完 Ngnix,在这个位置有一个默认的测试站点, 我们将其删除:

$ sudo rm /etc/nginx/sites-enabled/default

然后创建 blogproject 配置文件:
/etc/nginx/sites-enabled/blogproject:Nginx配置。

server {
    # 监听 80 端口
    listen 80;
    server_name _;
    location / {
        # redirect any requests to the same URL but on https
        return 301 https://$host$request_uri;
    }
}
server {
    # 监听 443 端口
    listen 443 ssl;
    # 服务器域名
    server_name _;

    # 刚刚生成的自签名ssl证书的位置
    ssl_certificate /home/xxm/sites/www.xxmblog.com/blog-django/certs/cert.pem;
    ssl_certificate_key /home/xxm/sites/www.xxmblog.com/blog-django/certs/key.pem;

    # 将访问和错误日志写入 /var/log
    access_log /var/log/microblog_access.log;
    error_log /var/log/microblog_error.log;

    location / {
        # forward application requests to the gunicorn server
        proxy_pass http://localhost:8000;
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # 所有URL 带有 /static 的请求均由 Nginx 处理,alias 指明了静态文件的存放目录。
    location /static {
        alias /home/xxm/sites/www.xxmblog.com/blog-django/blogproject/static;
        expires 30d;
    }
}

添加此文件后,你需要告诉nginx重新加载配置以激活它:

$ sudo service nginx reload

浏览器输入域名或者服务器的IP地址,应该可以看到访问成功了。

应用更新

当应用更新的时候,往往需要重复下列的过程:

  1. 从 github 中拉取应用
git pull
  1. 可能需要修改 .env 文件, 增加环境变量
  2. 进入虚拟环境, 安装额外的依赖
pip install -r requirements.txt
  1. 收集静态文件
python manage.py collectstatic
  1. 数据库迁移
python manage.py makemigrations
python manage.py migrate
  1. 重启进程
sudo supervisorctl restart blogproject
  1. 重启nginx
sudo service nginx reload

你可能感兴趣的:(Django)