Django部署到线上

文章目录
  1. 1. 前言
  2. 2. 环境准备
    1. 2.1. 服务器
    2. 2.2. python
    3. 2.3. uwsgi
    4. 2.4. nginx和mysql
  3. 3. 项目部署
    1. 3.1. 代码准备
    2. 3.2. 数据库准备
  4. 4. 启动项目
    1. 4.1. 数据库问题
    2. 4.2. url问题
    3. 4.3. 查看效果
  5. 5. nginx配置
  6. 6. uwsgi
    1. 6.1. 一般启动
    2. 6.2. 高级启动
  7. 7. supervisor
    1. 7.1. 安装supervisor
    2. 7.2. 安装pyenv
    3. 7.3. 安装python2.7环境
    4. 7.4. 安装虚拟环境
    5. 7.5. 守护uwsgi
  8. 8. nginx+uwsgi
  9. 9. 小结
  10. 10. 书签

前言

《Django开发简单Blog系统》系列中,我们已经完成了一个迷你Web项目。那么,怎么把这个项目发布到线上呢?怎样给它一个域名呢?

思路:nginx + uwsgi

环境准备

服务器

阿里云服务器,centos7系统。

python

升级python到3.6.1,统一线上和本地python环境。

1、下载python3.6.1源码
wget https://www.python.org/ftp/python/3.6.1/Python-3.6.1.tar.xz

2、解压源码

1
2
xz -d Python-3.6.1.tar.xz
tar -xvf Python-3.6.1.tar

3、编译源码

1
2
3
4
mkdir /usr/local/python3
cd Python-3.6.1
./configure --prefix=/usr/local/python3 --enable-optimizations
make && make install

如果编译失败,需要先更新编译环境:

1
2
3
4
5
gcc -v 
g++ -v

yum install gcc
yum install gcc-c++

注:我的环境版本为 gcc version 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC) 。

4、替换python

1
2
3
4
5
cd /usr/bin
mv python python.bak
ln -s /usr/local/python3/bin/python3.6 /usr/bin/python
ll python*
python -V

5、解决遗留问题
所有python相关的应用,如果使用/usr/bin/python开头的脚本,替换为/usr/bin/python2.7。比如:

1
2
vim /usr/bin/yum
vim /usr/libexec/urlgrabber-ext-down

uwsgi

pip install uwsgi

编写测试:

1
2
3
4
# test.py
def application(env, start_response):
start_response('200 OK', [('Content-Type','text/html')])
return [b"Hello World"]

启动测试:
uwsgi --http :8001 --wsgi-file test.py

报错:uwsgi: command not found,看来我们需要把python3/bin加入到path。
vim /etc/profile,在文件最底部找到PATH,添加:

1
:/usr/local/python3/bin

使配置生效:source /etc/profile

访问 http://ip:8001 ,即可看到Hello World 。

nginx和mysql

参考《在CentOS7上配置PHP运行环境》,安装好了nginx和mysql。

项目部署

代码准备

1、克隆项目到服务器
git clone https://github.com/voidking/djsite.git

2、安装django
pip install django

3、安装pymysql
pip install pymysql

数据库准备

1、创建数据库

1
2
3
# mysql -uroot -p
mysql> create database `djsite` default character set utf8 collate utf8_general_ci;
mysql> exit;

2、修改djsite/djsite/settings.py中的数据库配置
vim djsite/djsite/settings.py

3、创建表结构

1
2
python manage.py makemigrations
python manage.py migrate

报错:

1
django.db.utils.InternalError: (1665, 'Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging. InnoDB is limited to row-logging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED.')

修改mysql的binlog格式为混合模式:

1
2
# mysql -uroot -p
mysql> set global binlog_format=mixed;

删除数据库djsite中的所有表,然后再次执行:

1
python manage.py migrate

启动项目

数据库问题

1
2
cd djsite
python manage.py runserver

报错:

1
2
3
File "/usr/local/python3/lib/python3.6/site-packages/django/db/backends/mysql/base.py", line 36, in <module>
raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)
django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.3 or newer is required; you have 0.7.11.None

解决办法:

1
vim /usr/local/python3/lib/python3.6/site-packages/django/db/backends/mysql/base.py

进入vim命令模式,输入/version,按N查找下一个,找到:

1
2
if version < (1, 3, 3):
raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)

注释掉它,问题解决。

url问题

1
2
cd djsite
python manage.py runserver

再次报错:

1
2
3
4
5
File "/root/djsite/djsite/urls.py", line 21, in <module>
url(r'^blog/', include('blog.urls', namespace='blog')),
File "/usr/local/python3/lib/python3.6/site-packages/django/urls/conf.py", line 39, in include
'Specifying a namespace in include() without providing an app_name '
django.core.exceptions.ImproperlyConfigured: Specifying a namespace in include() without providing an app_name is not supported. Set the app_name attribute in the included module, or pass a 2-tuple containing the list of patterns and app_name instead.

解决办法:

1
vim /usr/local/python3/lib/python3.6/site-packages/django/urls/conf.py

找到:

1
2
3
4
5
6
7
if namespace and not app_name:
raise ImproperlyConfigured(
'Specifying a namespace in include() without providing an app_name '
'is not supported. Set the app_name attribute in the included '
'module, or pass a 2-tuple containing the list of patterns and '
'app_name instead.',
)

注释掉它,问题解决。

查看效果

1
2
cd djsite
python manage.py runserver

启动成功,在服务器上测试访问:
curl localhost:8000/blog/index

使用浏览器查看 http://ip:8000/blog/index ,却无法访问。这是因为在settings.py中,ALLOWED_HOSTS的配置为:

1
ALLOWED_HOSTS = []

官方文档说:

When DEBUG is True and ALLOWED_HOSTS is empty, the host is validated against [‘localhost’, ‘127.0.0.1’, ‘[::1]’].

修改ALLOWED_HOSTS的配置为:

1
ALLOWED_HOSTS = ['*']

然后启动命令改为:python manage.py runserver 0.0.0.0:8000,此时即可在浏览器看到部署好的项目。

如果还是不能访问,尝试先关闭防火墙:systemctl stop firewalld

nginx配置

1、首先,在万网上配置域名解析,添加A记录,解析到阿里云服务器IP。假设解析好的域名为django.voidking.com。

2、在nginx的vhost中,添加django.voidking.com.conf,内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
listen 80;
server_name django.voidking.com;
charset utf-8;
location /{
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 1024m;
client_body_buffer_size 128k;
client_body_temp_path data/client_body_temp;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
proxy_temp_path data/proxy_temp;

proxy_pass http://127.0.0.1:8000;
}
}

3、重启nginx,./nginx -s reload

4、测试访问
服务器:curl django.voidking.com/blog/index
本地浏览器:http://django.voidking.com/blog/index

至此,django项目已经部署成功,没有用到uwsgi。如果给django添加守护进程,那么我们的部署就接近完美了。那么,uwsgi又能干什么呢,我们继续研究。

uwsgi

一般启动

1、编写wsgi.py文件
编写django_wsgi.py文件,将其放在与文件manage.py同一个目录下。

1
2
3
4
5
6
7
8
9
#!/usr/bin/env python
# coding: utf-8

import os,django
from django.core.handlers.wsgi import WSGIHandler

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djsite.settings")
django.setup()
application = WSGIHandler()

2、启动项目
uwsgi --http :8000 --chdir ~/djsite/ --module django_wsgi

3、查看启动结果
lsof -i :8000ps aux | grep uwsgi

4、测试访问
http://ip:8000/blog/index
此时,页面是没有样式的,也就是说静态资源加载失败。

5、配置静态资源
uwsgi --http :8000 --chdir ~/djsite/ --module django_wsgi --static-map=/static=static
此时,页面样式就正常了。

高级启动

1、新建uwsgi.ini,与manage.py在同一级目录。

1
2
3
4
5
[uwsgi]
http = :8000
chdir = /root/djsite/
wsgi-file = django_wsgi.py
static-map = '/static=static'

2、启动uwsgi
uwsgi uwsgi.ini

3、测试访问
http://ip:8000/blog/index

supervisor

关闭shell后,uwsgi服务就很快关闭了。为了让它后台运行,需要让它变成守护进程。

安装supervisor

pip install supervisor

报错,因为supervisor不支持python3:
Supervisor requires Python 2.4 or later but does not work on any version of Python 3. You are using version 3.6.1 (default, Dec 6 2017, 12:03:59)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]. Please install using a supported version.
Command “python setup.py egg_info” failed with error code 1 in /tmp/pip-build-y9wv4fmm/supervisor/

安装pyenv

为了使用supervisor,我们需要python2.7的环境。而多版本python的管理,推荐使用pyenv。

1、安装pyenv套装
curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash
内容除了包含 pyenv 以外,还包含如下插件:

  • pyenv-doctor
  • pyenv-installer
  • pyenv-update
  • pyenv-virtualenv
  • pyenv-which-ext

2、路径添加
vim ~/.bash_profile,添加:

1
2
3
export PATH="/root/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

3、使配置立即生效
source ~/.bash_profile

4、查看安装情况
pyenv -v

5、常用命令

  • 查看可安装的python版本列表:pyenv install -l
  • 安装指定版本的python:pyenv install 2.7.13
  • 查看已安装的python:pyenv versions
  • 查看当前设为默认的python版本:pyenv version

安装python2.7环境

1、配置pyenv下载源为本地目录(可选操作,不做的话下载速度会很慢)

1
2
3
4
5
6
7
8
9
10
11
12
mkdir /root/python/ && cd /root/python/

# 设置变量
export PYTHON_BUILD_CACHE_PATH=/root/python

# 设置变量
export PYTHON_BUILD_MIRROR_URL=/root/python

# 查看变量设置
env | grep PYTHON_BUILD_MIRROR_URL

wget https://www.python.org/ftp/python/2.7.13/Python-2.7.13.tar.xz

2、安装python2.7.13
pyenv install 2.7.13

3、改变全局版本
pyenv global 2.7.13python -V

附:改变回原版本
pyenv global systempython -V

4、刷新数据库
python rehash

安装虚拟环境

1、新建supervisor虚拟环境
pyenv virtualenv 2.7.13 supervisor

2、激活虚拟环境
source /root/.pyenv/versions/2.7.13/envs/supervisor/bin/activate supervisor
或者source activate supervisor

3、安装supervisor
yum install supervisor
pip install supervisor

4、生成配置文件

1
2
mkdir -p /etc/supervisor/
echo_supervisord_conf > /etc/supervisord.conf

5、修改配置文件
vim /etc/supervisord.conf,添加:

1
2
[include]
files = /etc/supervisor/*.conf

6、运行
/root/.pyenv/versions/2.7.13/envs/supervisor/bin/supervisord -c /etc/supervisord.conf

7、编辑supervisord.service
vi /usr/lib/systemd/system/supervisord.service,修改为:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=Process Monitoring and Control Daemon
After=rc-local.service nss-user-lookup.target

[Service]
Type=forking
ExecStart=/root/.pyenv/versions/2.7.13/envs/supervisor/bin/supervisord -c /etc/supervisord.conf
ExecReload=/root/.pyenv/versions/2.7.13/envs/supervisor/bin/supervisorctl reload
ExecStop=/root/.pyenv/versions/2.7.13/envs/supervisor/bin/supervisorctl shutdown

[Install]
WantedBy=multi-user.target

8、重启supervisor

1
2
3
ps aux | grep supervisord
systemctl stop supervisord
systemctl start supervisord

9、开机启动
systemctl enable supervisord

守护uwsgi

1、在/etc/supervisor中新建djsite.conf文件:

1
2
3
4
5
6
7
[program:djsite]
command=/usr/local/python3/bin/uwsgi --http :8000 --chdir /root/djsite/ --module django_wsgi --static-map=/static=static
directory=/root/djsite/
startsecs=0
stopwaitsecs=0
autostart=true
autorestart=true

2、重启supervisor

1
2
systemctl stop supervisord
systemctl start supervisord

附:重启djsite命令

1
supervisorctl -c /etc/supervisord.conf restart djsite

3、测试访问
http://ip:8000/blog/index
页面显示正常,至此守护进程配置成功。

4、退出supervisor环境
source deactivate,守护进程并没有受到影响。

nginx+uwsgi

以上,我们的djsite项目已经通过uwsgi方式启动起来,并且可以保持后台运行。nginx配置不改变的情况下,我们可以正常访问 http://django.voidking.com/blog/index 。此时,nginx作为反向代理,和uwsgi间通过http交互。

接下来,就配置下nginx和uwsgi通过socket结合的方式。原理:用户发送http请求到nginx,nginx通过socket把请求交给uwsgi,uwsgi拿到django的处理结果,通过socket返还给nginx,nginx通过http返回结果给用户。

1、因为nginx和uwsgi通过socket方式交互,我们需要修改uwsgi.ini的配置为:

1
2
3
4
5
6
7
8
9
[uwsgi]
socket = :8000
chdir = /root/djsite/
wsgi-file = django_wsgi.py
static-map = '/static=static'
master = true
processes = 2
enable-threads = true
# daemonize = /root/djsite/uwsgi.log

2、/etc/supervisor/djsite.conf,修改为

1
2
3
4
5
6
[program:djsite]command=/usr/local/python3/bin/uwsgi uwsgi.ini
directory=/root/djsite/
startsecs=0
stopwaitsecs=0
autostart=true
autorestart=true

3、重启supervisor
systemctl stop supervisord
systemctl start supervisord

4、修改nginx配置djsite.voidking.com.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 80;
server_name djsite.voidking.com;
charset utf-8;

location / {
uwsgi_pass 127.0.0.1:8000;
include uwsgi_params;
}

location /static {
alias /root/djsite/static;
}
}

5、重启nginx
./nginx -s reload

6、测试访问
此时,访问 http://ip:8000/blog/index 失败,访问 http://django.voidking.com/blog/index 正常。因为8000端口不再提供http服务,而是一个和nginx连接的socket。

7、static
请问,此时的静态资源,是通过uwsgi获取的?还是通过nginx直接获取的?做一个测试即可,修改uwsgi为:

1
2
3
4
5
6
7
8
9
[uwsgi]
socket = :8000
chdir = /root/djsite/
wsgi-file = django_wsgi.py
# static-map = '/static=static'
master = true
processes = 2
enable-threads = true
# daemonize = /root/djsite/uwsgi.log

此时,uwsgi不再提供静态资源。重启supervisor,页面样式正常,可见,静态资源是通过nginx获取的。之所以可以获取到,是因为我们之前在djsite/settings.py中配置了:

1
2
3
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)

小结

至此,django部署完毕,我们实现了三种部署方法:

  • nginx + django(http方式)
  • nginx + uwsgi(http方式)
  • nginx + uwsgi(socket方式)

在此过程中,解决了一些奇怪的bug,学习了升级python的方法,学习了使用pyenv安装多版本python的方法(类似的还有anaconda),学习了给django或者uwsgi添加守护进程的方法,收获颇丰。

书签

Python Web部署方式总结

Python网络框架——Web服务器

Django在生产环境中的部署

Django 部署(Nginx)

使用Supervisor管理SpiderKeeper和Scrapyd

使用uWSGI提供静态文件 (更新至1.9)