yield-bytes

沉淀、分享与无限进步

基于Centos+uWSGI+Nginx部署Django项目(详细版)

  部署背景:之前开发了一个本地的个人blog项目,还未部署云服务器,有些功能还未完善,先尝试在本地部署。本篇内容则是基于centos+uwsgi+nginx来部署项目,实现高性能web服务,其中还给出uwsgi和nginx分别部署在不同服务器上步骤,这一点,很多文章并未给出相关探讨。

1、相关服务安装配置

安装nginx。配置官方镜像源,这里baseurl里面有$basearch变量,用来检索yum命令安装所需要的包。

1
2
3
4
5
6
7
8
$ vi /etc/yum.repos.d/nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=0
enabled=1
$ yum -y install nginx
$ systemctl enable nginx

安装python3.6,采用python虚拟环境部署项目,跟系统不冲突

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ yum -y install python36 python36-devel
# 配置并载入 Python3 虚拟环境
$ cd /opt
# 在这里不需要将centos默认python2.7版本配置为默认python3.6
# 直接使用python3.6作为启动命令即可,可避免冲突
$ python3.6 -m venv py36 # py3 为虚拟环境名称, 可自定义
# 退出虚拟环境可以使用 deactivate 命令
$ source /opt/py36/bin/activate
(py36) [root@nn py36]# ls
bin include lib lib64 pyvenv.cfg
# 载入环境后默认以下所有命令均在该虚拟环境中运行
(py3) [root@localhost py3]
# 安装 Python 库依赖
$ pip install --upgrade pip setuptools
$ pip install -r requirements.txt

安装uwsgi

1
2
3
4
5
6
# uwsgi需要使用gcc环境编译否则无法安装成功
yum install gcc -y

# 激活py36环境
(py36) [root@nn mywebapp]# pip install uwsgi
Successfully installed uwsgi-2.0.18

数据库安装和配置可以参考本人这篇文章

以上后台技术栈的相关版本总揽如下:

1
2
3
4
5
6
7
Centos 7.5
MariaDB 10.3.17
Python 3.6
Django1.11
uWSGI 2.0.18
Nginx 1.12.2
Redis 5.0.4

这些服务都是部署同一台服务器上,适合单台的个人云服务器,毕竟一年几百元,服务器配置有限,而对于本地部署,可以通过多台linux虚拟机分别部署不同服务,并做HA,这一过程相信也会积累不少知识和经验,学有余力的同学一定要试试。

2、配置uwsgi启动django项目

2.1 uwsgi 命令行启动项目

查看项目,对项目路径必须清楚那些文件在哪里路径下,否则使用uwsgi启动设置参数,容易出错,以本项目为例,项目根目录路径为:/opt/mywebapp,项目根目录内容:

1
2
3
4
5
(py36) [root@nn mywebapp]# pwd
/opt/mywebapp
(py36) [root@nn mywebapp]# ls
account blog db.sqlite3 __init__.py media script templates
article course image manage.py mysite static

==其中非常关键的wsgi入口==,在mysite目录下,也就是django项目总settings.py所在的目录,mysite目录下的wsgi.py,将在之后的uwsgi启动中使用

1
2
(py36) [root@nn mysite]# ls
__init__.py __pycache__ settings.py urls.py wsgi.py

wsgi.py代码逻辑:

1
2
3
4
5
6
7
8
9
10
"""
WSGI config for mysite project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
application = get_wsgi_application()

确认项目使用manage.py启动能正常运行
python manage.py runserver 0.0.0.0:9000

使用uwsgi启动项目并测试是否成功运行django项目,通过连接mysite/wsgi.py实现web server和application通信

这里有两种启动方式:

==A、不带静态文件启动,静态文件将无法加载,页面不正常显示==

—chdir /opt/mywebapp/ django项目根路径
wsgi-file mysite/wsgi.py django项目settings.py所在目录下的 wsgi.py文件

1
uwsgi --http 0.0.0.0:9000 --chdir /opt/mywebapp/ --wsgi-file mysite/wsgi.py --master --processes 4 --threads 2

==B、带静态文件启动,也就是网页打开后,页面能正常显示==

—chdir /opt/mywebapp/ django项目根路径
wsgi-file mysite/wsgi.py django项目settings.py所在目录下的 wsgi.py文件
—static-map=/static=static django项目web页面静态文件,所在根目录的’static’目录
—static-map=/static=media django项目内容静态文件,所在根目录的’media’目录

==注意:这里仅是指static、media目录,根目录下还有其他blog,account,templates目录等,可能也有人会问,是不是都需要把这些目录都要一一加入—static-map里面,答案是不需要,因为这些都不是django application对应的“static”目录(已在settins设定,并可以让其他views索引到static目录),如果使用-static-map=/static=templates,uwsgi将无法找到相关静态文件==

1
uwsgi --http 0.0.0.0:9000 --chdir /opt/mywebapp/ --wsgi-file mysite/wsgi.py --static-map=/static=static --master --processes 4 --threads 2

==注意==,对于这种启动方式,动、静态资源都可以访问

2.2 使用uwsgi.ini配置文件启动项目

以上两种启动直接在命令加入环境参数,比较繁琐,可通过在配置文件中放置这些环境参数,方便启动,在manage.py 同目录下(项目根目录),新建一个目录uwsgi_conf用来放置uwsgi.ini配置文件,目录路径:/opt/mywebapp/uwsgi_conf

1
2
3
(py36) [root@nn mywebapp]# ls
account blog db.sqlite3 __init__.py media uwsgi_conf templates
article course image manage.py mysite static

在uwsgi_conf目录下新建uwsgi.ini文件,配置如下

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
# uwsig使用配置文件启动
[uwsgi]
# 项目所在的根目录
chdir=/opt/mywebapp/
# 指定项目的application,区别于启动命令--wsgi-filemysite/wsgi.py
module=mysite.wsgi:application
#the local unix socket file than commnuincate to Nginx
# 指定sock的文件路径,这个sock文件会在nginx的uwsgi_pass配置,用来nginx与uwsgi通信
# 支持ip+port模式以及socket file模式
#socket=%(chdir)/uwsgi_conf/uwsgi.sock
socket=127.0.0.1:9001
# 进程个数
processes = 8
# 每个进程worker数
workers=5
procname-prefix-spaced=mywebapp # uwsgi的进程名称前缀
py-autoreload=1 # py文件修改,自动加载

# 指定IP端口,web访问入口
http=0.0.0.0:9000

# 指定多个静态文件:static目录和media目录,也可以不用指定该静态文件,在nginx中配置静态文件目录
# uwsgi有自己的配置语法,详细可参考官网,无需写绝对路径,可以用循环、判断等高级配置语法
for =static media
static-map=/static=%(chdir)/%(_)
endfor =

# 启动uwsgi的用户名和用户组
uid=root
gid=root

# 启用主进程
master=true
# 自动移除unix Socket和pid文件当服务停止的时候
vacuum=true

# 序列化接受的内容,如果可能的话
thunder-lock=true
# 启用线程
enable-threads=true
# 设置一个超时,用于中断那些超过服务器请求上限的额外请求
harakiri=30
# 设置缓冲
post-buffering=4096

# 设置日志目录
daemonize=%(chdir)/uwsgi_conf/uwsgi.log
# uWSGI进程号存放
pidfile=%(chdir)/uwsgi_conf/uwsgi.pid
#monitor uwsgi status 通过该端口可以监控 uwsgi 的负载情况
# 支持ip+port模式以及socket file模式
# stats=%(chdir)/uwsgi_conf/uwsgi.status
stats = 127.0.0.1:9001

之所以要新建一个uwsgi_conf目录,是为了集中放置uWSGI配置以及日志、进程等文件,方便管理,配置语法可参考官方配置文档说明

基于配置文件uwsgi启动django项目

1
2
3
4
5
6
7
8
9
10
11
12
(py36) [root@nn uwsgi_conf]# uwsgi --ini uwsgi.ini 
# 启动后打印的信息,可以看到static静态文件和media媒体资源目录被uWSGI索引
[uWSGI] getting INI configuration from uwsgi.ini
[uwsgi-static] added mapping for /static => /opt/mywebapp/static
[uwsgi-static] added mapping for /static => /opt/mywebapp/media

# 运行后,自动参数日志、进程,建议自行查看日志文件内容,了解更多uwsgi
(py36) [root@nn uwsgi_conf]# ls
uwsgi.ini uwsgi.log uwsgi.pid

# 停止uwsgi服务
(py36) [root@nn uwsgi_conf]# uwsgi --stop uwsgi.pid

以上说明使用uWSGI配置启动django项目成功运行,因uWSGI的配置文件里已加入静态文件static-map,因此在不需要nginx的配置下,也可以支撑服务(只是性能未到完美级别),此部分的流程如下图所示:
在这里插入图片描述

下面将使用nginx配置静态文件请求,uwsgi只负责动态请求部分的请求,各司其职,以进一步压榨服务性能。如果确定使用nginx代理django项目静态文件服务,那么配置之前,先把uwsgi.ini里面的—static-map=/static部分注释掉。

3、使用nginx启动uwsgi

3.1 配置nginx

在/etc/nginx/conf.d/,新建一个myblog.conf文件,配置为:

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
upstream blog_app {
# nginx通过socket在环回接口地址的9001端口与本地的uWSGI进程通信
# 支持ip:port模式以及socket file模式
#server unix:/opt/mywebapp/uwsgi_conf/uwsgi.sock;
server 127.0.0.1:9001;
}

server {

listen 9090;
server_name 192.168.100.5;

access_log /var/log/nginx/access.log;
charset utf-8;

gzip_types text/plain application/x-javascript text/css text/javascript application/x-httpd-php application/json text/json image/jpeg image/gif image/png application/octet-stream;
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;

location / {
# nginx转发动态请求到uWSGI
include uwsgi_params;
uwsgi_connect_timeout 20
uwsgi_pass blog_app;
}

# 如果写成/static/,nginx无法找到项目静态文件路径
location /static {
alias /opt/mywebapp/static;
}

# 如果写成/media/,nginx无法找到项目媒体文件路径
location /media {
alias /opt/mywebapp/media;
}


}

重启nginx服务,server nginx restart,只要在第2部分uWSGI与django application正常连接,那么到这部分,nginx是能够正常代理uWSGI服务的,如有问题,请认真检查nignx的在/etc/nginx/conf.d/myblog.conf配置文件。
==这里要解释为何配置可以放在/etc/nginx/conf.d/目录下,网上不是有很多教程是在/etc/nginx/nginx.conf默认配置文件改动吗?==
其实nginx配置文件很灵活,可以从其他模块include根配置文件里面,查看主配置nginx.conf内容里面的http 模块,它可以是可以把/etc/nginx/conf.d/目录下所有的配置文件内容包含到主配置文件里面,注意如果使用这种方式,请把主配置文件server模块注释,其实就是关闭多余其他服务端口而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 要确保nginx用户对django项目根目录下静态文件具有读权限,否则会出现403 Forbidden
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}

http {
......省略
# Load modular configuration files from the /etc/nginx/conf.d directory.
include /etc/nginx/conf.d/*.conf;
......省略
#server {
......省略
# }
}


3.2 关于myblog.conf路径放置

理解了nginx的include配置文件方式,那么我们可以不需要在/etc/nginx/conf.d/目录下创建myblog.conf,直接在django项目的根目录下mywebapp/,新建一个nginx_conf目录,在这里放置myblog.conf,

1
2
3
4
[root@nn nginx_conf]# pwd
/opt/mywebapp/nginx_conf
[root@nn nginx_conf]# ls
myblog.conf

然后把该配置路径/opt/mywebapp/nginx_conf/myblog.conf 加入到nginx默认的主配置文件里面
1
2
3
4
5
6
7
8
9
10
http {
......省略
# Load modular configuration files from the /etc/nginx/conf.d directory.
# 不再是 include /etc/nginx/conf.d/*.conf;
include /opt/mywebapp/nginx_conf/myblog.conf;
......省略
#server {
......省略
# }
}

server nginx restart重启服务,nginx首先解析主配置文件/etc/nginx/nginx.conf,发现主配置里面,include了在其他位置的配置文件,于是nginx找到myblog.conf并加载,接着完成一些列其他逻辑。以上两种nginx配置都可以连接uWSGI服务,至于选哪种方式,看个人需求或项目文件管理习惯。

3.3 nginx+uWSGI+django启动后,访问media目录的图片、文件、视频出现403 forbidden提示

这个问题,在csdn绝大部分nginx+uWSGI+django部署文章都没提及如何处理,因为大部分文章没有测试到这部分内容,只是测试nginx可以正常获取static目录下的js、css、html文件,页面显示正常即结束,但如果项目的代码中,例如有视频课程这么一个功能,上传视频是放在media目录下的course目录里,那么当网页访问该视频时,就会提示该资源403 forbidden状态。
==原因:nginx worker对media整个目录没有读权限==
nginx默认使用名字为“nginx”用户,这一点可在其主配置文件找到,也可以通过进程详情看到

1
2
3
4
[root@nn mywebapp]# ps -ef|grep nginx
root 28759 1 0 02:01 ? 00:00:00 nginx: master process /usr/sbin/nginx
nginx 28760 28759 0 02:01 ? 00:00:00 nginx: worker process
root 28784 28382 0 02:26 pts/0 00:00:00 grep --color=auto nginx

再看mywebapp/media权限,指向root用户
1
2
3
4
-rwxr-xr-x.  1 root  root     804 ** manage.py
drwxr-xr-x. 4 root root 49 ** media
drwxr-xr-x. 3 root root 93 ** mysite
drwxr-xr-x. 9 root root 113 ** static

故需要将相关目录的读权限给到nginx用户,对django项目根目录下的static、media两个目录赋权给nginx user
1
2
3
4
[root@nn mywebapp]# chown -R nginx:nginx /opt/mywebapp/static/
[root@nn mywebapp]# chmod -R ug+r /opt/mywebapp/static/
[root@nn mywebapp]# chown -R nginx:nginx /opt/mywebapp/media/
[root@nn mywebapp]# chmod -R ug+r /opt/mywebapp/media/

聪明的同学可能此时会立刻联想到:既然nginx访问django项目静态文件要赋权,那么前面第2部分的uWSGI进程也是否需要赋权呢?答案:需要看uwsgi.ini配置了什么用户。
1
2
uid=root
gid=root

这里配置了root用户,而且对于django整个项目文件的rwx权限,root用户本已具备,因此不需要再赋权,除非uwsgi.ini配置了一个非root用户,例如blog_user用户,那么就需要重启赋权,目录是整个django项目,例如以下可行的赋权:
1
2
[root@nn mywebapp]# chown -R blog_user:blog_user /opt/mywebapp
[root@nn mywebapp]# chmod -R ug+r /opt/mywebapp

==第3部分的请求流程可以表示为:==
在这里插入图片描述

4、关于nginx配置django的静态文件讨论

关于静态文件的配置,其过程有些地方非常容易引起混淆,在这里一一指出。
首先,在本文中,nginx服务和uWSGI服务部署在同一台服务器,因此在nginx配置中,location的静态文件因为的是本地django项目里面的静态文件,个人把这种配置过程nginx代理本地静态文件配置,另外一种django项目里面的静态文件放置在远程服务器上,由远程的nginx来代理,这种称为nginx代理远程静态文件配置。
==为什么要做这样的部署测试?
大部分文章都是基于同一台服务器进行nginx服务和uWSGI服务部署,很少有讨论在不同服务器上部署,事实上,如果生产环境比较严格,nginx服务器本身要做冗余和负载均衡,uWSGI服务器也是要做冗余和负载均衡,数据库MariaDB本身主-主模式。==

4.1 nginx代理本地静态文件配置

在第三部分的uWSGI的配置文件中,socket配置为loopback地址,说明uWSGI进程与nginx进行本地通信,nginx代理本地静态文件。在第3部分可测试过程中,除了http://192.168.100.5:9090/admin无法正常显示页面(所有关于admin管理页面后台的静态文件nginx无法找到),其他子路径例如http://192.168.100.5:9090/blog,http://192.168.100.5:9090/article,都可以正常显示页面,这种情况如何处理?
从nginx配置的项目静态文件路径为:

1
2
3
4
# 如果写成/static/,nginx无法找到项目静态文件路径,注意避免配置语法出错
location /static {
alias /opt/mywebapp/static;
}

查看其目录文件,发现并没有django项目admin的后台所需的静态文件

1
2
3
4
5
6
[root@nn mywebapp]# ls
account blog image media
all_static course __init__.py mysite templates
article db.sqlite3 manage.py static uwsgi_conf
[root@nn mywebapp]# ls static
css editor fonts images ImgCrop js

通过python manage.py collectstatic将admin后台包含所有的静态文件都拷贝到mywebapp根目录下,在执行命令之前,需要在settings.py设置一个放置整个mywebapp项目静态文件目录

  • 原静态文件目录的设置:
1
2
3
4
5
6

STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)

  • 加入admin静态文件的设置
1
2
3
4
5
6
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)

STATIC_ROOT='all_static'

==注意:若STATIC_ROOT=’static’,collect无法拷贝admin静态文件到项目中,会有提示==

1
# django.core.exceptions.ImproperlyConfigured: The STATICFILES_DIRS setting should not contain the STATIC_ROOT setting

==能否在settings.py不指定STATIC_ROOT,利用已指定的mywebapp/static目录放置collect static?不行==,看以下提示

1
# django.core.exceptions.ImproperlyConfigured: You're using the staticfiles app without having set the STATIC_ROOT setting to a filesystem path.

当正确拷贝所有静态文件到mywebapp/all_static目录后,查看其内容:

1
2
(py36) [root@nn mywebapp]# ls all_static/
admin css editor fonts images ImgCrop js

原来 python manage.py collectstatic 把项目下的static目录静态文件和django自带的后台admin静态文件打包一起放在/mywebapp/all_static目录里。
就个人对项目目录管理习惯而言,把all_static/下的admin目录整个拷贝到static目录下,并删除all_static目录,settings的静态文件路径注释掉STATIC_ROOT='all_static',==这样既可保持整个django项目仅有一个static目录,而且该目录已经包含项目所需的所有静态文件(js、css、html等)==,注意media目录路径不做改变,还是位于根项目路径下。

4.2 远程静态目录配置

4.2.1 拷贝静态文件到远程nginx服务器

如果已经理解了nginx代理本地静态文件的配置,其实远程方式其实也简单,本文的远程nginx服务器地址为192.168.100.6,在创建与uWSGI相同的application目录(可以使用不同路径,这里是为方便管理):/opt/mywebapp/,该目录只放置static和media目录,文件可以手工同步过来,注意要赋权给nginx用户

1
2
3
4
5
6
7
8
9
10
11
[root@dn1 mywebapp]# ls -al
total 0
drwxr-xr-x. 4 root root 33 Aug 17 20:29 .
drwxr-xr-x. 3 root root 67 Aug 17 20:03 ..
drwxr-xr-x. 4 nginx nginx 35 Aug 19 2019 media
drwxr-xr-x. 9 nginx nginx 113 Aug 18 2019 static

[root@dn1 mywebapp]# ls static/
admin css editor fonts images ImgCrop js
[root@dn1 mywebapp]# ls media/
courses images

4.2.2 更改.5uWSGI服务器配置文件socket
1
2
3
4
5
6
7
[uwsgi]
# 项目所在的根目录
chdir=/opt/mywebapp/
# 指定项目的application,区别于启动命令--wsgi-filemysite/wsgi.py
module=mysite.wsgi:application
# 不再使用loopback地址,对外其他服务器暴露uWSGI服务
socket=192.168.1005:9001
4.2.3 更改.6服务器的nginx配置文件,

为了方便管理,路径与uWSGI一致:
/etc/nginx/conf.d/myblog.conf
远程nginx服务器配置文件

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
upstream blog_app {
# 连接远程uWSGI服务器的socket
server 192.168.88.5:9001;
}

server {
listen 9090;
server_name 192.168.100.6;
access_log /var/log/nginx/access.log;
charset utf-8;
gzip_types text/plain application/x-javascript text/css text/javascript application/x-httpd-php application/json text/json image/jpeg image/gif image/png application/octet-stream;
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;

location / {
include uwsgi_params;
uwsgi_connect_timeout 30;
uwsgi_pass ;
}
# 这就是为何在远程nginx服务器上,保持与uWSGI静态文件路径一致的原因,方便管理和理解配置文件
location /static {
alias /opt/mywebapp/static;
}

location /media {
alias /opt/mywebapp/media;
}
}

以.6IP访问动态资源和静态,可以正常请求。

5 、关于sock文件的理解

在uwsgi.ini和nginx配置文件里面,我们需要配置socket,以便他们之间进行进程通信,这里socket的配置方式,可以选择一个以.sock作为后缀的文件,而大家更熟悉的方式是通过socket pair进行socket通信,这里如何理解sock文件?
这里整理收集的资料概括为:

在Unix/Linux系统里面,“一切皆文件”,这里文件是由什么组成的?这些文件其实是可以读取和写入的普通字节(字节流)的集合。如果你持有一个文件引用(也就是文件描述符),就可以使用“打开open –> 读写write/read –> 关闭close”模式(抽象成一组Linux 文件操作API)来进行 IO 操作,无论设备的类型和底层硬件是什么。

所以进程(进程本身也是文件)之间的通信。个人理解uwsgi.sock文件用到的就是该模式的一个实现,socket就是这么一种特殊的套接字文件,也即是说nginx通过uwsgi.sock作为载体,与本地的uWSGI进程进行通信。

这种基于特殊的套接字文件来保持通信是无法进行远程通信,这时需要用到TCP/IP协议,通过远程IP+端口号的socket pair,基于TCP连接进行远程通信(若用loopback的IP地址127.0.0.1,就变成本地通信),所以这启发我们可以将nginx部署在另外一台服务器上,本文中,uWSGI+application部署在192.168.100.5,另外一台服务器192.168.100.6 作为nginx服务器,要实现他们之间的远程通信,
只需uwsgi配置文件uwsgi.ini里面socket行改为:

1
2
3
4
5
#the local unix socket file than commnuincate to Nginx
# 指定sock的文件路径,则限制本地通信
# 指定loopback 地址+端口号,则限制本地通信
# 指定全网地址或者本机真实IP,则可以实现远程通信
socket=192.168.100.5:9001

在.6服务器上,nginx的配置myblog.conf的upstream模块个改为
1
2
3
4
upstream blog_app {
# 指向远程uWSGI
server 192.168.100.5:9001;
}

通过以上设置,可以把static静态文件目录和media目录放置在远程.6的nginx服务器上,.5服务器则负责application业务逻辑,两服务器之间的静态文件可以通过rsync实时同步。rsync的配置不再给出,也可参考本blog文章

若考虑对uWSGI做负载均衡,比如第二台uWSGI服务器192.168.100.4:9001;可以加入upstream并设定相关负载算法,若还考虑对nginx服务器进行负载均衡,则需要用keepalived,通过VIP对外透明服务,负载均衡的配置会更有趣。