部署背景:之前开发了一个本地的个人blog项目,还未部署云服务器,有些功能还未完善,先尝试在本地部署。本篇内容则是基于centos+uwsgi+nginx来部署项目,实现高性能web服务,其中还给出uwsgi和nginx分别部署在不同服务器上步骤,这一点,很多文章并未给出相关探讨。
1、相关服务安装配置
安装nginx。配置官方镜像源,这里baseurl里面有$basearch变量,用来检索yum命令安装所需要的包。
1 | vi /etc/yum.repos.d/nginx.repo |
安装python3.6,采用python虚拟环境部署项目,跟系统不冲突
1 | yum -y install python36 python36-devel |
安装uwsgi1
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
7Centos 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 | (py36) [root@nn mywebapp]# pwd |
==其中非常关键的wsgi入口==,在mysite目录下,也就是django项目总settings.py所在的目录,mysite目录下的wsgi.py,将在之后的uwsgi启动中使用
1 | (py36) [root@nn mysite]# ls |
wsgi.py代码逻辑:
1 | """ |
确认项目使用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 | (py36) [root@nn mywebapp]# ls |
在uwsgi_conf目录下新建uwsgi.ini文件,配置如下
1 | uwsig使用配置文件启动 |
之所以要新建一个uwsgi_conf目录,是为了集中放置uWSGI配置以及日志、进程等文件,方便管理,配置语法可参考官方配置文档说明
基于配置文件uwsgi启动django项目
1 | (py36) [root@nn uwsgi_conf]# uwsgi --ini uwsgi.ini |
以上说明使用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
38upstream 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 | 要确保nginx用户对django项目根目录下静态文件具有读权限,否则会出现403 Forbidden |
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
10http {
......省略
# 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 user1
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
2uid=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 | 如果写成/static/,nginx无法找到项目静态文件路径,注意避免配置语法出错 |
查看其目录文件,发现并没有django项目admin的后台所需的静态文件
1 | [root@nn mywebapp]# ls |
通过python manage.py collectstatic
将admin后台包含所有的静态文件都拷贝到mywebapp根目录下,在执行命令之前,需要在settings.py设置一个放置整个mywebapp项目静态文件目录
- 原静态文件目录的设置:
1 |
|
- 加入admin静态文件的设置
1 | STATIC_URL = '/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 | (py36) [root@nn mywebapp]# ls all_static/ |
原来 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 | [uwsgi] |
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
28upstream 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
4upstream 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对外透明服务,负载均衡的配置会更有趣。