部署背景:之前开发了一个本地的个人blog项目,还未部署云服务器,有些功能还未完善,先尝试在本地部署。公司上的项目考虑到其业务环境需要依赖windows server环境,所以都用python3.5+Apache2.4+mod_wsgi部署业务(windows做开发比较头疼,linux is the best!)。本篇内容则是基于centos+uwsgi+nginx来部署项目,实现高性能web服务,其中还给出uwsgi和nginx分别部署在不同服务器上步骤,这一点,很多文章并未给出相关探讨。
安装nginx。配置官方镜像源,这里baseurl里面有$basearch变量,用来检索yum命令安装所需要的包。
$ 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虚拟环境部署项目,跟系统不冲突
$ 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
# uwsgi需要使用gcc环境编译否则无法安装成功
yum install gcc -y
# 激活py36环境
(py36) [root@nn mywebapp]# pip install uwsgi
Successfully installed uwsgi-2.0.18
数据库安装和配置可以参考本人这篇文章
以上后台技术栈的相关版本总揽如下:
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,这一过程相信也会积累不少知识和经验,学有余力的同学一定要试试。
查看项目,对项目路径必须清楚那些文件在哪里路径下,否则使用uwsgi启动设置参数,容易出错,以本项目为例,项目根目录路径为:/opt/mywebapp
,项目根目录内容:
(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启动中使用
(py36) [root@nn mysite]# ls
__init__.py __pycache__ settings.py urls.py wsgi.py
wsgi.py代码逻辑:
"""
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文件
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将无法找到相关静态文件
uwsgi --http 0.0.0.0:9000 --chdir /opt/mywebapp/ --wsgi-file mysite/wsgi.py --static-map=/static=static --master --processes 4 --threads 2
注意,对于这种启动方式,动、静态资源都可以访问
以上两种启动直接在命令加入环境参数,比较繁琐,可通过在配置文件中放置这些环境参数,方便启动,在manage.py 同目录下(项目根目录),新建一个目录uwsgi_conf用来放置uwsgi.ini配置文件,目录路径:/opt/mywebapp/uwsgi_conf
(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文件,配置如下
# 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项目
(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部分注释掉。
在/etc/nginx/conf.d/,新建一个myblog.conf文件,配置为:
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模块注释,其实就是关闭多余其他服务端口而已。
# 要确保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 {
......省略
# }
}
理解了nginx的include配置文件方式,那么我们可以不需要在/etc/nginx/conf.d/目录下创建myblog.conf,直接在django项目的根目录下mywebapp/,新建一个nginx_conf目录,在这里放置myblog.conf,
[root@nn nginx_conf]# pwd
/opt/mywebapp/nginx_conf
[root@nn nginx_conf]# ls
myblog.conf
然后把该配置路径/opt/mywebapp/nginx_conf/myblog.conf
加入到nginx默认的主配置文件里面
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服务,至于选哪种方式,看个人需求或项目文件管理习惯。
这个问题,在csdn绝大部分nginx+uWSGI+django部署文章都没提及如何处理,因为大部分文章没有测试到这部分内容,只是测试nginx可以正常获取static目录下的js、css、html文件,页面显示正常即结束,但如果项目的代码中,例如有视频课程这么一个功能,上传视频是放在media目录下的course目录里,那么当网页访问该视频时,就会提示该资源403 forbidden状态。
原因:nginx worker对media整个目录没有读权限
nginx默认使用名字为“nginx”用户,这一点可在其主配置文件找到,也可以通过进程详情看到
[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用户
-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两个目录赋权
[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配置了什么用户。
uid=root
gid=root
在里面,我们配置了root用户,而且对于django整个项目文件的rwx权限,root用户本已具备,因此不需要再赋权,除非uwsgi.ini配置了一个非root用户,例如blog_user用户,那么就需要重启赋权,目录是整个django项目,例如以下可行的赋权:
[root@nn mywebapp]# chown -R blog_user:blog_user /opt/mywebapp
[root@nn mywebapp]# chmod -R ug+r /opt/mywebapp
关于静态文件的配置,其过程有些地方非常容易引起混淆,在这里一一指出。
首先,在本文中,nginx服务和uWSGI服务部署在同一台服务器,因此在nginx配置中,location的静态文件因为的是本地django项目里面的静态文件,个人把这种配置过程nginx代理本地静态文件配置,另外一种django项目里面的静态文件放置在远程服务器上,由远程的nginx来代理,这种称为nginx代理远程静态文件配置。
为什么要做这样的部署测试?
大部分文章都是基于同一台服务器进行nginx服务和uWSGI服务部署,很少有讨论在不同服务器上部署,事实上,如果生产环境比较严格,nginx服务器本身要做冗余和负载均衡,uWSGI服务器也是要做冗余和负载均衡,数据库MariaDB本身主-主模式。
在第三部分的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配置的项目静态文件路径为:
# 如果写成/static/,nginx无法找到项目静态文件路径,注意避免配置语法出错
location /static {
alias /opt/mywebapp/static;
}
查看其目录文件,发现并没有django项目admin的后台所需的静态文件
[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项目静态文件目录
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
STATIC_ROOT='all_static'
注意:若STATIC_ROOT=‘static’,collect无法拷贝admin静态文件到项目中,会有提示
# django.core.exceptions.ImproperlyConfigured: The STATICFILES_DIRS setting should not contain the STATIC_ROOT setting
能否在settings.py不指定STATIC_ROOT,利用已指定的mywebapp/static目录放置collect static?不行,看以下提示
# django.core.exceptions.ImproperlyConfigured: You're using the staticfiles app without having set the STATIC_ROOT setting to a filesystem path.
当正确拷贝所有静态文件到mywebapp/all_static目录后,查看其内容:
(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目录路径不做改变,还是位于根项目路径下。
如果已经理解了nginx代理本地静态文件的配置,其实远程方式其实也简单,本文的远程nginx服务器地址为192.168.100.6,在创建与uWSGI相同的application目录(可以使用不同路径,这里是为方便管理):/opt/mywebapp/
,该目录只放置static和media目录,文件可以手工同步过来,注意要赋权给nginx用户
[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
[uwsgi]
# 项目所在的根目录
chdir=/opt/mywebapp/
# 指定项目的application,区别于启动命令--wsgi-filemysite/wsgi.py
module=mysite.wsgi:application
# 不再使用loopback地址,对外其他服务器暴露uWSGI服务
socket=192.168.1005:9001
为了方便管理,路径与uWSGI一致:
/etc/nginx/conf.d/myblog.conf
远程nginx服务器配置文件
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访问动态资源和静态,可以正常请求。
在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行改为:
#the local unix socket file than commnuincate to Nginx
# 指定sock的文件路径,则限制本地通信
# 指定loopback 地址+端口号,则限制本地通信
# 指定全网地址或者本机真实IP,则可以实现远程通信
socket=192.168.100.5:9001
在.6服务器上,nginx的配置myblog.conf的upstream模块个改为
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对外透明服务,负载均衡的配置会更有趣。