Django 是最受欢迎的基于 Python 的 web 框架之一,也非常适合新手入门。虽然 Django 为我们提供了一个用于测试的轻量级 server,但这个 server 不能用于实际生产环境的部署。最早的 Django 的部署方法推荐的是 Apache+mod_wsgi。演化到现在,django 的部署方法也变得越来越弹性、有效,也更加的复杂了。在下面的教程中我们需要使用下面的这些工具:

  • Nginx: Web 服务器
  • Gunicorn: WSGI 服务器
  • virtualenv: Python 虚拟环境工具
  • supervisor: 进程监管工具
  • PostgreSQL: 数据库(也可以使用 MariaDB)

1 前期准备

你需要一个你拥有 root 权限的 server。下面的教程是基于 Debian 7,所有相同的步骤对于 Ubuntu 和其他的基于 Debian 的发行版都是适用的。

1.1 更新系统

首先确保系统处于最新的状态

1
2
$ sudo aptitude update
$ sudo aptitude upgrade

如果是 Ubuntu 的话,使用 apt

1
2
$ sudo apt update
$ sudo apt upgrade

1.2 创建一个运行账户

为了避免万一 web 应用被攻击以后带来不受控制的后果,我们一般会单独为 web 应用创建一个权限受限的用户来运行这个 web 应用。例如我们这里为我们的 django 应用创建一个名为hello的用户,并将其归入webapps这个组。

1
2
$ sudo groupadd --system webapps
$ sudo useradd --system --gid webapps --shell /bin/bash --home /webapps/hello_django hello

1.3 安装数据库(PostgreSQL)

为基于 Debian 的系统安装 PostgreSQL,你只需要运行下面这个命令:

1
$ sudo aptitude install postgresql postgresql-contrib

完成安装以后为我们的 django 应用创建一个用户和一个数据库

1
2
3
4
5
6
7
8
9
10
$ sudo su - postgres 
postgres@django:~$ createuser --interactive -P
Enter name of role to add: hello_django
Enter password for new role:
Enter it again: Shall the new role be a superuser? (y/n) n
Shall the new role be allowed to create databases? (y/n) n
Shall the new role be allowed to create more new roles? (y/n) n postgres@django:~$

postgres@django:~$ createdb --owner hello_django hello
postgres@django:~$ logout $

当然你也可以选择其他数据库,例如 MariaDB。简单应用也可以使用 SQLite 数据库。

2 Python 运行环境(virtualenv)

Virtualenv 是一个 Python 虚拟环境管理的工具,所谓虚拟环境就是讲你 Web 应用所需要的 python 环境从系统的 python 环境中独立出来,这使得你可以在不同的应用中使用不同版本的第三方库。

用下面的命令来在 Debian 上安装 Virtualenv

1
$ sudo aptitude install python-virtualenv

2.1 准备虚拟环境

这里我将 django 应用放在了/webapps这个路径下面,如果你偏好/var/www, srv或者其他的路径也可以。首先在这个目录下面创建/webapps/hello_django/文件夹来存储应用,并将这个文件夹的 owner 设置为上面我们创建的运行账户hello

1
2
$ sudo mkdir -p /webapps/hello_django/
$ sudo chown hello /webapps/hello_django/

切换到hello用户并创建虚拟环境

1
2
3
4
5
6
7
8
9
$ sudo su - hello
hello@django:~$ cd /webapps/hello_django/
hello@django:~$ virtualenv .

New python executable in hello_django/bin/python
Installing distribute..............done.
Installing pip.....................done.
hello@django:~$ source bin/activate
(hello_django)hello@django:~$

要精确控制创建虚拟环境的版本,可是通过 -p 传递控制参数

1
virtualenv -p python3 env

这时虚拟环境中创建的是 Python 版本就是当前命令行环境下 python3 命令对应的 Python 版本。事实上你可以通过输入具体的 Python 命令绝对路径来指定以任意的 Python 安装版本创建虚拟环境。

现在虚拟环境就被激活了,我们可以将 django 安装到这个虚拟环境里面

1
2
3
4
5
6
Downloading/unpacking django 
(...)
Installing collected packages: django
(...)
Successfully installed django
Cleaning up...

完成 django 的安装以后我们来创建一个空的 django 项目

1
(hello_django)hello@django:~$ django-admin.py startproject hello

你可以通过启动测试服务器来测试是否一切正常

1
2
3
4
5
6
7
8
(hello_django)hello@django:~$ cd hello 
(hello_django)hello@django:~$ python manage.py runserver example.com:8000
Validating models...

0 errors found
June 09, 2013 - 06:12:00 Django version 1.5.1, using settings 'hello.settings'
Development server is running at http://example.com:8000/
Quit the server with CONTROL-C.

现在你可以通过http://example.com:8000来访问了。

在部署已有的 Django 项目时,我们可以通过

1
pip freeze > requirements.txt

输出项目的版本依赖,然后将 requirements.txt 文件上传到服务器上,在虚拟环境中运行

1
pip install -r requirments.txt

来安装所有依赖库。

2.2 配置数据库

Django 默认的新工程使用的是 SQLite3 作为数据库的,这个数据库的性能不足以支持生产环境下的数据库应用。我们这里采用 PostgreSQL 来做为我们的 Django 项目的数据库。为了让 Django 能够使用 PostgreSQL,我们需要将psycopg2安装到虚拟环境。首先安装这个包的依赖项

1
libpq-dev

然后通过 pip 来安装的psycopg2

1
(hello_django)hello@django:~$ pip install psycopg2

现在可以将 Django 的数据库设置修改为:

1
2
3
4
5
6
7
8
9
10
DATABASES = { 
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'hello',
'USER': 'hello_django',
'PASSWORD': 'Your password',
'HOST': 'localhost',
'PORT': '', # Set to empty string for default.
}
}

然后向向 Postgres 应用你的 Django 设置

1
(hello_django)hello@django:~$ python manage.py migrate

3 Gunicorn

在实际生产环境中我们不会使用 Django 的单线程开发服务器。这里我们使用Gunicorn. Gunicorn ('Green Unicorn') 是一个 UNIX 下的纯 Python WSGI 服务器。它没有其它依赖,容易安装和使用。

通过 pip 来安装 Gunicorn

1
(hello_django)hello@django:~$ pip install gunicorn 

安装好以后你可以尝试用 Gunicorn 来运行 Django 了

1
(hello_django)hello@django:~$ gunicorn hello.wsgi:application --bind example.com:8001

上面的命令是一个简单的测试,为了真正在生产环境下使用 Gunicorn,我们还需要增加一些配置。我们把这些配置文件写成一个 bash 脚本,保存为 bin/gunicorn_start

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
29
30
31
32
#!/bin/bash
 
NAME="hello_app" # Name of the application
DJANGODIR=/webapps/hello_django/hello # Django project directory
SOCKFILE=/webapps/hello_django/run/gunicorn.sock # we will communicte using this unix socket
USER=hello # the user to run as
GROUP=webapps # the group to run as
NUM_WORKERS=3 # how many worker processes should Gunicorn spawn
DJANGO_SETTINGS_MODULE=hello.settings # which settings file should Django use
DJANGO_WSGI_MODULE=hello.wsgi # WSGI module name
 
echo "Starting $NAME as `whoami`"
 
# Activate the virtual environment
cd $DJANGODIR
source ../bin/activate
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH
 
# Create the run directory if it doesn't exist
RUNDIR=$(dirname $SOCKFILE)
test -d $RUNDIR || mkdir -p $RUNDIR
 
# Start your Django Unicorn
# Programs meant to be run under supervisor should not daemonize themselves (do not use --daemon)
exec ../bin/gunicorn ${DJANGO_WSGI_MODULE}:application \
--name $NAME \
--workers $NUM_WORKERS \
--user=$USER --group=$GROUP \
--bind=unix:$SOCKFILE \
--log-level=debug \
--log-file=-

将这个文件改成可执行模式

1
$ sudo chmod u+x bin/gunicorn_start

下面你可以尝试运行这个脚本了

1
$ sudo su - hello hello@django:~$ bin/gunicorn_start Starting hello_app as hello 2013-06-09 14:21:45 [10724] [INFO] Starting gunicorn 18.0 2013-06-09 14:21:45 [10724] [DEBUG] Arbiter booted 2013-06-09 14:21:45 [10724] [INFO] Listening at: unix:/webapps/hello_django/run/gunicorn.sock (10724) 2013-06-09 14:21:45 [10724] [INFO] Using worker: sync 2013-06-09 14:21:45 [10735] [INFO] Booting worker with pid: 10735 2013-06-09 14:21:45 [10736] [INFO] Booting worker with pid: 10736 2013-06-09 14:21:45 [10737] [INFO] Booting worker with pid: 10737 ^C (CONTROL-C to kill Gunicorn) 2013-06-09 14:21:48 [10736] [INFO] Worker exiting (pid: 10736) 2013-06-09 14:21:48 [10735] [INFO] Worker exiting (pid: 10735) 2013-06-09 14:21:48 [10724] [INFO] Handling signal: int 2013-06-09 14:21:48 [10737] [INFO] Worker exiting (pid: 10737) 2013-06-09 14:21:48 [10724] [INFO] Shutting down: Master $ exit

注意,如果你在上面的过程中设置了自定义的参数的话,需要将gunicorn_start脚本中对应的参数改过来。其中,worker 的数量推荐设置为 2 * CPUs + 1,这样的话,在任何时候都有一半的 worker 在做 IO。

4 Supervisor

Superviosr 是一个进程监管的工具。简而言之,Superviosr 可以保证你的程序在服务器开机时自动启动以及程序意外终止时重新启动。我们使用 Supervisor 来监管 Gunicorn 进程。

通过下面的命令即可安装:

1
sudo aptitude install supervisor

Superviosr 通过配置文件来设置被监管的程序。一般配置文件都放置在/etc/supervisor/conf.d路径下面。此处我们创建一个名为hello.conf的配置文件,内容如下:

1
2
3
4
5
6
[program:hello]
command = /webapps/hello_django/bin/gunicorn_start ; Command to start app
user = hello ; User to run as
stdout_logfile = /webapps/hello_django/logs/gunicorn_supervisor.log ; Where to write log messages
redirect_stderr = true ; Save stderr in the same log
environment=LANG=en_US.UTF-8,LC_ALL=en_US.UTF-8 ; Set UTF-8 as default encoding

你可以参考其他设置,不过上面的设置一般情况下应该足够了。

日志文件需要我们手动创建一下:

1
hello@django:~$ mkdir -p /webapps/hello_django/logs/ hello@django:~$ touch /webapps/hello_django/logs/gunicorn_supervisor.log

设置好上面的文件以后,我们可以通过supervisorctl工具来启用这些设置了:

1
2
3
4
$ sudo supervisorctl reread 
hello: available
$ sudo supervisorctl update
hello: added process group

现在你可以 start,stop 或者 restart 你的进程了

1
2
3
4
5
6
7
8
$ sudo supervisorctl status hello hello RUNNING pid 18020, uptime 0:00:50 
$ sudo supervisorctl stop hello
hello: stopped
$ sudo supervisorctl start hello
hello: started
$ sudo supervisorctl restart hello
hello: stopped
hello: started

5 Nginx

安装 Nginx 同样非常简单:

1
2
$ sudo aptitude install nginx 
$ sudo service nginx start

此时你访问 server(http://example.com)就应该可以看见 Nginx 的欢迎页面了("Welcome to nginx")

5.1 为 Django 创建一个虚拟 server 配置

每个 Nginx 的虚拟 server 都由/etc/nginx/sites-available路径下的一个配置文件来表示。而将其链接到的/etc/nginx/sites-enabled路径下则可以启用对应的站点。

为我们的 Django 应用创建一个配置文件/etc/nginx/sites-available/hello. 文件内容如下:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
upstream hello_app_server {
# fail_timeout=0 means we always retry an upstream even if it failed
# to return a good HTTP response (in case the Unicorn master nukes a
# single worker for timing out).
 
server unix:/webapps/hello_django/run/gunicorn.sock fail_timeout=0;
}
 
server {
 
listen 80;
server_name example.com;
 
client_max_body_size 4G;
 
access_log /webapps/hello_django/logs/nginx-access.log;
error_log /webapps/hello_django/logs/nginx-error.log;

location /static/ {
alias /webapps/hello_django/static/;
}

location /media/ {
alias /webapps/hello_django/media/;
}
 
location / {
# an HTTP header important enough to have its own Wikipedia entry:
# http://en.wikipedia.org/wiki/X-Forwarded-For
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 
# enable this if and only if you use HTTPS, this helps Rack
# set the proper protocol for doing redirects:
# proxy_set_header X-Forwarded-Proto https;
 
# pass the Host: header from the client right along so redirects
# can be set properly within the Rack application
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;
 
# set "proxy_buffering off" *only* for Rainbows! when doing
# Comet/long-poll stuff. It's also safe to set if you're
# using only serving fast clients with Unicorn + nginx.
# Otherwise you _want_ nginx to buffer responses to slow
# clients, really.
# proxy_buffering off;
 
# Try to serve static files from nginx, no point in making an
# *application* server like Unicorn/Rainbows! serve static files.
if (!-f $request_filename) {
proxy_pass http://hello_app_server;
break;
}
}
 
# Error pages
error_page 500 502 503 504 /500.html;
location = /500.html {
root /webapps/hello_django/static/;
}
}

将这个文件链接到site-enabled文件夹下:

1
$ sudo ln -s /etc/nginx/sites-available/hello /etc/nginx/sites-enabled/hello

然后重启 nginx

1
$ sudo service nginx restart

现在你再访问http://example.com看到的就应该不是 nginx 的欢迎页面,而是 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
29
30
/webapps/hello_django/ 
├── bin <= Directory created by virtualenv
│ ├── activate <= Environment activation script
│ ├── django-admin.py
│ ├── gunicorn
│ ├── gunicorn_django
│ ├── gunicorn_start <= Script to start application with Gunicorn
│ └── python
├── hello <= Django project directory, add this to PYTHONPATH
│ ├── manage.py
│ ├── project_application_1
│ ├── project_application_2
│ └── hello <= Project settings directory
│ ├── __init__.py
│ ├── settings.py <= hello.settings - settings module Gunicorn will use
│ ├── urls.py
│ └── wsgi.py <= hello.wsgi - WSGI module Gunicorn will use
├── include
│ └── python2.7 -> /usr/include/python2.7
├── lib
│ └── python2.7
├── lib64 -> /webapps/hello_django/lib
├── logs <= Application logs directory
│ ├── gunicorn_supervisor.log
│ ├── nginx-access.log
│ └── nginx-error.log
├── media <= User uploaded files folder
├── run
│ └── gunicorn.sock
└── static <= Collect and serve static files from here