Nginx+uWSGI+Django部署web服务器

Nginx+uWSGI+Django部署web服务器

环境说明

  • 进行本文操作前需己搭建好的环境
    • linux系统,我用的是openSUSE
    • 使用了operation用户的家目录做为测试环境
    • python3.5.6
    • virtualenv 16.0
    • pip3 18.0
    • nginx 1.13.1
  • 后面进行安装的环境
    • django 1.11
    • uwsgi-2.0.17.1

前言

在多年前,基于python的web项目,常见的部署方法有:

  • fcgi:用spawn-fcgi或者框架自带的工具对各个project分别生成监听进程,然后和http服务互动。
  • wsgi:利用http服务的mod_wsgi模块来跑各个project。

不过uwsgi出现后,大部份部署就不会使用以上的两个协议了。
因为uwsgi它既不用wsgi协议也不用fcgi协议,而是自创了一个uwsgi的协议,据作者说该协议大约是fcgi协议的10倍那么快。uWSGI的主要特点如下:

  • 超快的性能。
  • 低内存占用(实测为apache2的mod_wsgi的一半左右)。
  • 多app管理。
  • 详尽的日志功能(可以用来分析app性能和瓶颈)。
  • 高度可定制(内存大小限制,服务一定次数后重启等)。

所以在uwsgi协议的基础上,配合nginx来做python(类如django的框架)的web项目部署就很常见了。

uwsgi 实际上也是一个 http 服务器,只不过它只面向 python 的网络应用程序。
虽然 uwsgi 也是 http 服务器,但是却不便直接使用它部署 python web 应用程序。

uwsgi 所扮演的的角色是后端 http 服务器,nginx 扮演的角色是前端 http 服务器,django项目 是客户所真正要访问到的提供数据方。 用户从网页浏览器中发出请求,nginx 服务器收到请求后,会通过它的 uwsgi 模块将用户的请求转发给 uwsgi 服务器,uwsgi 服务器通过django处理完毕后将结果返回给 nginx,浏览器将最终的结果展现给用户。

搭建项目

  • 建立一个虚拟环境
    • 建议个人学习和测试的话,直接建在家用户目录下,以避免权限引起的各种拒绝问题
    • 或者正式点,系统再建一个dev用户,在专门的工作目录下给好dev用户的权限去运行。
    • 虚拟环境的目录名建议就带个env点明是虚拟环境,如果和后面的项目取相同的名字你会看到三重同名的串在一起,有点不太好认。
      virtualenv -p python3 py3env
  • 启动虚拟环境
    source py3env/bin/activate

  • 安装django1.11,之所以装这个版本是学习所需要,后面自己的项目最好与时俱进。
    pip install django==1.11

  • 直接使用django-admin命令创建一个django项目

    • 建议不要放在py3env目录下,而是在专门的工作目录下建立
      django-admin startproject luffy

如下,己经初步搭建好简单的项目:

cd luffy

(py3env) operation@opensuse-wordpress:~/work/luffy> tree -L 2
.
├── luffy
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py
  • 创建新应用,例如app01
    python3 manage.py startapp app01
    结构如下
(py3env) operation@opensuse-wordpress:~/work/luffy> tree -L 2
.
├── app01
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── luffy
│   ├── __init__.py
│   ├── __pycache__
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

Django部署

编辑luffy/luffy/settings.py

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

ALLOWED_HOSTS = ['*', ]

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01',
]

如上,主要是:

  • 将DEBUG由True改成False(虽然只是在学习和测试搭建的,也要顾及好安全)
  • 在ALLOWED_HOSTS 默认的空列表中填入你自己打算使用的域名,我这里测试的时候填的是*,真正上线部署的时候不建议填成通配符的*,而是要填允许访问的主机域名。一般django用于做后端服务器,而前端会有一个域名。详情可见django官方文档。
  • INSTALLED_APPS 列表下增加appo1,表示将app01应用给安装注册上。
  • 补充,后面配置好nginx和新建好域名之后,我将ALLOWED_HOSTS = ['*', ]修改成ALLOWED_HOSTS = ['luffy.tielemao', ]了,通过域名访问仍然正常。而直接敲ip地址则会报400错误。

编辑luffy/app01/views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

def index(request):
    return HttpResponse('Hello Hero')

由于只是简单演示,所以上面只是让用户访问index页面后,得到一个显示Hello Hero的页面。

编辑luffy/luffy/urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index),
]

运行并测试

首先是运行,由于是单独对django进行运行测试,所以只是在进入项目根目录后命令行敲以下命令:

python manage.py runserver 0.0.0.0:8083
其中0.0.0.0表示捆绑监听服务器上的所有网卡IP地址,当然也就包括了127.0.0.1和外网的公网ip了。

最初我测试的是ALLOWED_HOSTS填的允许的HOSTS是自己的域名.tielemao.com,结果直接在浏览器上敲IP加8083端口接index当然是报错:

Bad Request(400)而由于我将DEBUG值设为了False,所以也不会将调试信息暴露出来。

然后我将.tielemao.com改为了通配符*(不安全,表示允许所有主机头)。
再访问http://39.x.x.x:8083/index/的时候,就能正常访问到了:

  • 注意,我使用的是8083端口并且在防火墙和云控制平台上的安全组处己设置了开放该端口。

django能运行无误后,ctrl+c将它停止,再接下来做下面的操作。

uWSGI部署

同样为了环境的隔离和纯净,这次我也选择在同样的虚拟环境下安装:
pip3 install uwsgi
安装完成后可uwsgi --version查看版本,
uwsgi --python-version还可以间接查看到python的版本。

  • 编写一个用于简单测试uwsgi的python脚本
    vim test-uwsgi.py,内容如下:
def application(env,start_response):
    start_response('200 OK',[('Content-Type','text/html')])
    return [b"Hello Hero, all for one "]

测试运行uWSGI

以下命令表示运行uwsgi服务,同样是在8083端口上开放web访问。
* 注意--http 后是一个空格再接:端口号。
uwsgi --http :8083 --wsgi-file test-uwsgi.py

访问web服务器8083端口,正常显示test-uwsgi.py脚本中返回显示的文本。

因为我们现在要做的是基于nginx和uwsgi部署django,从客户端发起请求到服务器响应请求,会经过一下几个环节:

the web client <-> the web server(nginx) <-> the socket <-> uwsgi <-> Django

而单独测试uWSGI运行,访问显示正常的话,说明下面3个环节是通畅的:

the web client <-> uWSGI <-> Python

ctrl+c中止程序,再来进行以下的测试。

使用uWSGI运行django项目

在虚拟环境下,进入到django根目录下后敲以下命令:

 uwsgi --http :8000 --module luffy.wsgi

效果和直接敲以下命令

python manage.py runserver 0.0.0.0:8000

是一样的。

  • 注:--module luffy.wsgi为加载指定的wsgi模块,你项目起的是什么名字,一般就是项目名.wsgi

这样测试下来,可证明web客户端通过uWSGI到Django是通畅的:

the web client <-> uWSGI <-> Django

ctrl+c中止程序,再来进行以下的测试

uWSGi热加载Djangoa项目

在启动命令后面加上参数:

uwsgi --http :8083 --module luffy.wsgi --py-autoreload=1

同样,这个时候访问服务器8083端口,也就是访问了django项目(luffy)。

而且还多了一个参数:
--py-autoreload 监控python模块以触发重载(仅在开发中使用)

# 类似的发布命令
/home/operation/work/py3env/bin/uwsgi --http 0.0.0.0 :8000 --chdir /home/operation/work/luffy --module luffy.wsgi
# 此时修改django代码,uWSGI会自动加载django程序,页面生效
# 除此外,还可以接上很多参数

ctrl+c中止程序,再进行下面环节:

部署nginx

nginx配置uwsgi和django

  • uwsgi_params文件拷贝到项目文件夹下。
  • uwsgi_params文件一般在/etc/nginx/目录下,也可以从nginx的github页面下载。

uwsgi_params配置文件如下:

uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param  REQUEST_METHOD     $request_method;
uwsgi_param  CONTENT_TYPE       $content_type;
uwsgi_param  CONTENT_LENGTH     $content_length;

uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  PATH_INFO          $document_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  REQUEST_SCHEME     $scheme;
uwsgi_param  HTTPS              $https if_not_empty;

uwsgi_param  REMOTE_ADDR        $remote_addr;
uwsgi_param  REMOTE_PORT        $remote_port;
uwsgi_param  SERVER_PORT        $server_port;
uwsgi_param  SERVER_NAME        $server_name;
  • 拷贝到项目下:
    sudo cp /etc/nginx/uwsgi_params /home/operation/work/luffy/

  • 属主属组等权限重新修改

sudo chown operation:operation uwsgi_params
sudo chmod 664 /home/operation/work/luffy/uwsgi_params
  • 在项目文件夹下创建文件luffy_nginx.conf,填入并修改下面内容:
# luffy_nginx.conf

# the upstream component nginx needs to connect to
upstream django {
    # server unix:///path/to/your/mysite/mysite.sock; 
    # for a file socket
    server 127.0.0.1:8083; 
    # for a web port socket (we'll use this first)
}

# configuration of the server
server {
    # the port your site will be served on
    listen      8000;
    # the domain name it will serve for
    server_name luffy.tielemao.com; 
    # substitute your machine's IP address or FQDN
    charset     utf-8;

    # max upload size
    client_max_body_size 75M;   # adjust to taste

    # Django media
    location /media  {
        alias /home/operation/work/luffy/media;  
        # your Django project's media files - amend as required
    }

    location /static {
        alias /home/operation/work/luffy/static; 
        # your Django project's static files - amend as required
    }

    # Finally, send all non-media requests to the Django server.
    location / {
        uwsgi_pass  django;
        include     /home/operation/work/luffy/uwsgi_params; 
        # the uwsgi_params file you installed
    }
}

这个配置文件告诉nginx从文件系统中拉起media和static文件作为服务,
同时响应django的request。

为此,我也在相应的项目目录下先建立起media和static文件夹。

为方便nginx使用,我在/etc/nginx/vhosts.d下创建了一个luffy_nginx.conf的软链接。当然,也有人是直接用默认的sites-enabled目录,自己看着来就行了。

sudo ln -s /home/operation/work/luffy/luffy_nginx.conf luffy.conf

django部署static文件

  • 在项目文件夹下,建立好静态文件目录:
    mkdir static

  • django的setting文件中,添加一行:
    STATIC_ROOT = os.path.join(BASE_DIR, “static/”)

  • 运行
    python manage.py collectstatic

  • 在项目文件夹下,建立一个media媒体文件目录(用于存放图片音乐等文件做测试)
    mkdir media

在media目录中,我传入了一个图片文件princekin_fox.jpg用于测试nginx。

参数STATIC_ROOT用在哪?
通过python3 manage.py collectstatic收集所有你使用的静态文件保存到STATIC_ROOT

STATIC_ROOT 文件夹 是用来将所有STATICFILES_DIRS中所有文件夹中的文件,以及各app中static中的文件都复制过来,把这些文件放到一起是为了用nginx等部署的时候更方便。

重新加载nginx进行测试

先进行检测,看之前的配置文件有无错误。
sudo /usr/sbin/nginx -t

重新加载nginx让软链接的luffy_nginx.conf生效。
sudo /usr/sbin/nginx -s reload

访问http://luffy.tielemao.com:8000/media/princekin_fox.jpg看能否正常访问到图片资源:

注意在做这一步之前,我是己经配置好域名和传送了一个用于测试的图片到服务器上。

我己经能成功访问到资源了,所以接下来再做下一步测试。

测试nginx 应用 uWSGI 和 test.py

还记得之前写好的一个测试的test-uwsgi.py文件吗?
我们就用配置好的nginx来访问uwsgi启动的test-uwsgi.py好了。
首先,启动uwsgi,并且端口是nginx配置中的负载均衡池8083端口:
uwsgi --socket :8083 --wsgi-file test-uwsgi.py

访问http://luffy.tielemao.com:8000/,实际上访问的就是uwsgi的8083端口。

如上图,能正常访问。表明下面的环节通畅:

the web client <-> the web server <-> the socket <-> uWSGI <-> Python

测试成功后中止程序,以便进行下面环节。

用UNIX socket取代TCP port

修改luffy_nginx.conf

# luffy_nginx.conf

# the upstream component nginx needs to connect to
upstream django {
    server unix:///home/operation/work/luffy/luffy.sock;
    # for a file socket
    # server 127.0.0.1:8083; 
    # for a web port socket (we'll use this first)
}

上面其实是将原来的server 127.0.0.1:8083加以注释,而原来加了#注释的server unix:///home/operation/work/luffy/luffy.sock则取消了注释,也就是不是直接指定端口,而是指定了一个sock文件。

  • 要注意的是,这个luffy.sock并不是自己提前写好的什么配置文件,而是由程序自动生成的。如图:

重新加载nginx,然后在项目下运行uWSGI

uwsgi --socket /home/operation/work/luffy/luffy.sock --wsgi-file test-uwsgi.py 

访问http://luffy.tielemao.com:8000/ 报502网关错误。
检查nginx的错误日志error.log,一般位置会在/var/log/nginx/error.log,建议使用tail察看文件尾部的后10行。
会发现类似以下报权限受阻的错误:

2018/08/27 20:17:44 [crit] 25771#25771: *1847865 connect() to unix:///home/operation/work/luffy/luffy.sock failed (13: Permission denied) while connecting to upstream, client: 223.72.74.11, server: luffy.tielemao.com, request: "GET / HTTP/1.1", upstream: "uwsgi://unix:///home/operation/work/luffy/luffy.sock:", host: "luffy.tielemao.com:8000"

那么就中止uwsgi进程,添加上socket权限后再次运行:

uwsgi --socket /home/operation/work/luffy/luffy.sock --wsgi-file test-uwsgi.py --chmod-socket=664

还是报错的话,就需将operation用户添加到nginx组中了,当然某些系统中nginx组也可能是www-data

将operation用户添加到nginx组中,记得要加上-a参数,不然operation将会离开原来的operation组。

sudo usermod -a -G nginx operation

然后将项目目录也归属到nginx组中

sudo chown operation:nginx -R luffy

这样就能保证nginx能对luffy.sock有足够的权限了。
果然,设置好权限后,就能正常访问了。

每次都运行上面命令来拉起django项目实在不方便,我们可以将这些配置写在.ini文件中,能大大简化工作。

停掉uwsgi服务后,见下一环节。

uwsgi部署加强

使用uwsgi配置文件运行django项目

uwsgi支持ini,xml等多种配置方式,以ini为例,在/home/operation/work/conf目录下新建uwsgi_luffy.ini,示例配置如下:

# luffy_uwsgi.ini file
[uwsgi]

# Django-related settings
# the base directory (full path)
# 指定运行目录,其实就是项目的根目录
chdir = /home/operation/work/luffy

# Django's wsgi file
# 导入django项目的wsgi模块
module = luffy.wsgi:application

# 载入wsgi-file
wsgi-file = luffy/wsgi.py

# the virtualenv (full path)
# 配置虚拟环境python相关依赖包所在路径
home = /home/operation/work/py3env

# 补充,uwsgi启动时的用户和用户组,注意要按你自己的实际权限进行配置
# 我这边是因为项目属主是operation,而operation我设置了添加进了nginx组
# 但这种情况下uid仍不能设置nginx,除非你的项目目录所有文件属主都改为了nginx
uid = operation
gid = nginx

# process-related settings
# 开启master主进程
master = true 

# maximum number of worker processes
# 开启多少个进程数,workers项也等同processes
# threads项则是设置运行线程,测试倒不用设置上线程
processes = 4

# the socket (use the full path to be safe)
# 设置使用的socket端口或socket地址
# socket = 0.0.0.0:8000
# 上面的socket建议配置成一个luffy.socket文件后使用nginx来连接uWSGI运行,不然容易报socket的请求头错误和权限错误等。
socket = /home/operation/work/luffy/luffy.socket

# ... with appropriate permissions - may be needed
# 配置生成的sock文件的权限
chmod-socket = 664 

# clear environment on exit
# 退出时清空环境,其实就是将自动生成的luffy.sock和相关pid文件给干掉。
vacuum = true

uwsgi指定配置文件启动django项目,建议使用nginx用户执行:
uwsgi --ini /home/operation/work/conf/uwsgi_luffy.ini

  • 注:以上启动后如果你是在配置文件中直接指定的socket = 0.0.0.0:8000可能会产生如下问题:浏览器访问服务器8000端口加上url后,浏览器会报连接出错,而服务器运行后台也会看到如下错误信息:
    invalid request block size: 21573 (max 4096)...skip

之前我们直接使用http协议的方式就不会出现块请求大小超出

 uwsgi --http :8000 --module luffy.wsgi

究其原因,使用配置文件启动是以socket方式提供通信端口,默认的协议是tcp,和http不同。socket请求头默认大小是4096,所以请求头超出默认大小后就会出现错误。当然后面我们可以通过和nginx合作的方式解决。

而如果只是想测试,那么只要在上面的命令后面再指定块请求大小-b 24576之类的便可以解决。

我们中止uwsgi后重新指定块大小,运行命令:
uwsgi -b 24576 --ini /home/operation/work/conf/uwsgi_luffy.ini
可以解决请求头错误。

不过ini配置文件主要是用于和nginx配合,这也是为什么前面讲述完nginx的部署后再回过头来讲uwsgi的ini配置文件。

  • 将uWSGI中使用的相同选项放入一个配置文件中,然后要求uWSGI使用该文件运行。这会使得管理配置变得更加容易。

  • 要注意的是,因为要配合nginx,所以生成的项目名.sock文件,nginx需要能有权限读写。

如下图:

由于我前面执行uwsgi命令时使用的是operation用户,
这样子自动生成的luffy.sock文件属组并不是nginx的,所以ini配置文件中最好加上uid和gid的配置项使用nginx用户执行uwsgi。

# uwsgi启动用户名和用户组
uid = operation
gid = nginx

我是修改完luffy.sock的属组为nginx后就能正常访问到django项目了,不然会被nginx报502错误。

安装uWSGI到真实环境中

到目前为止,我们都是在虚拟环境下工作的,接下来介绍将uwsgi装在实际环境中,且将uwsgi加入到nginx组(有的是www-data组)中,就可避免现在所遇到的权限等问题。

  • 退出虚拟环境
    deactivate

  • 安装uWSGI,注意要使用python3中的pip3来进行安装。我系统中是python2.7和python3.5.6共存的,不过默认的环境是2.7。
    sudo /usr/local/python3.5.6/bin/pip3 install uwsgi

  • 之前我使用默认的pip进行安装,结果是python2版本的uwsgi,在运行虚拟环境中的python3的django项目时会报以下错误:

Python version: 2.7.13 (default, Jan 03 2017, 17:41:54) [GCC]
Set PythonHome to /home/operation/work/py3env
ImportError: No module named site
VACUUM: unix socket /home/operation/work/luffy/luffy.sock removed.

报模块导入错误,原因还是在于我真实环境使用的是python2.7版本的原因,ini中配置了虚拟环境家目录是python3的依赖库,pip安装的uwsgi不能正确导入。

  • 再次检查,在真实环境中是否能如同虚拟环境之前一样能运行django项目。
    当然,由于我前面的python3没有导入系统环境中,所以此处仍然要像前面使用python3的pip3一样,需要打全路径:
sudo /usr/local/python-3.5.6/bin/uwsgi --ini /home/operation/work/conf/uwsgi_luffy.ini
  • 模块无法导入的错误己排除了,但是还会报一个bind绑定拒绝的错误。

出现这个错误还是和文件权限有关,因为我之前uwsgi的ini配置文件中uid设置的用户是nginx,而项目真正的属主是operation这个用户,所以我将uid设置回operation就解决问题了,当然将项目整个属主设为nginx用户也是可行的。

# 因为权限问题踩了不少坑,在此继续加强强调。
# 注意的是你要按自己的实际环境配置
uid = operation
gid = nginx

成功运行uwsgi的截图我也放出给大家对比一下看吧。
当然,访问页面成功的截图就不放了,和之前测试成功的是一样的。

uwsgi配置文件更多参数

前面己经给大家看到了uwsgi的ini文件的一些配置参数,在此以luffy项目为例,再介绍一些常用到的参数:

# set an environment variable
# 设置变量环境,其实就是应用上django项目中的settings
env = DJANGO_SETTINGS_MODULE=luffy.settings

# create a pidfile
# 创建pid文件,注意权限,所以一般放置在tmp目录下
pidfile = /tmp/project-master.pid 

# background the process & log
# 后台运行,有用,基本都会设置上,同时还会输出日志
daemonize = /var/log/uwsgi/luffy.log 

# 以固定的文件大小(单位kb),进行切割日志
# 下例为50M大小一个日志文件
log-maxsize = 50000000

# 不记录请求信息的日志。只记录错误以及uWSGI内部消息到日志中。
disable-logging = true
#
# 在指定的地址上,开启状态服务。应该就是你访问该地址,就能看到整个uwsgi
的状态,类似nginx也有。所以一般会只在环回地址上开放。
stats = 127.0.0.1:8009

# 以下都是一些性能和资源上的限制和调配
# respawn processes taking more than 20 seconds
harakiri = 20 
# limit the project to 128 MB
# 控制进程的总内存量
limit-as = 128 
# respawn processes after serving 5000 requests
max-requests = 5000 

  • 访问stats状态服务页面大致如下:

uWSGI开机启动服务

继续以django的luffy项目为例,让uWSGI开机就启动服务,编辑文件/etc/rc.local, 添加下面内容到这行代码之前exit 0:

/usr/local/python-3.5.6/bin/uwsgi --ini /home/operation/work/conf/uwsgi_luffy.ini

要注意的是记得ini配置文件中设置好用户用户组,sock文件,sock文件的权限和后台运行等配置。

end
by 铁乐与猫

参考
* uwsgi官方文档
* py鱼博客相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注