利用Django构建web应用及其部署
注:很久之前就有了学习Django的想法,最近终于有机会做了一次尝试。由于Django的详细教程很多,我在这里就不再详述了,只是将整个开发流程以及自己在学习Django中的一些思考记录在此。
System:CentOS Linux release 7.2.1511 (Core)
Django: 1.10
Python: 2.7.5
推荐两个非常好的教程:
The Django Book(中文版):我自己一开始是参考这个教程学习的,非常有意思的是这个教程中有大量的评论,几乎每段都有,从10年开始一直到现在。虽然这本书比较老,有很多内容都过时了,但在这些评论中有讲解,有勘误和最新的实践,这些评论让整个学习过程变得非常有趣。我自己也留下了不少评论,哈哈。
被解放的姜戈:这是博客园的另一位博友Vamei(中文读作,挖煤)写的关于Django简介的系列文章,基本概念,简单的实践都有。
创建一个名为mysite的Django项目的命令:
$ django-admin startproject mysite
$ cd mysite
$ python manage.py startapp myApp # 创建一个app,名称为myApp
一个刚创建的Django项目的目录结构
图1:django项目的基本结构
不是要点的要点
- 创建一个专门的用户,例如www-data:在后面部署Django的过程中,我遇到了自使用Linux以来,最多的权限问题。很大的原因是因为不同进程间需要通信,或相互访问,这就需要这些用户间都有某个文件的读写权限。例如两个进程都要访问同一个socket文件时,这两个进程的用户都要对该socket文件有读写权限。因此将所有这些进程都交给一个专门的用户来操作,可以避免很多权限问题。
- url是web开发的核心要素之一:在客户端,每一个url就是一个页面;从开发者来看,所有的开发都是围绕如何正确的路由这些url到正确的html文件来展开的。
- stackoverflow是一个很棒的网站,大部分的问题都可以在上面找到解决方案,如果没有找到答案,就提问吧,很快就可以得到回复。
概述
用户从浏览器访问一个django开发的页面,整个流程是这样的:
the web client <-> the web server <-> the socket <-> WSGI <-> Django
用户从浏览器访问一个url,该请求从用户发送到web server,web server通过socket(或者约定一个端口)与WSGI进行通信,再由WSGI将请求发给django。web server和WSGI都有很多种选择,常见的组合有Apache + mod_wsgi和nginx + uWSGI。我用的是nginx + uWSGI。下面先介绍几个名词。
web server:
虽然每个网页服务器程序有很多不同,但有一些共同的特点:每一个网页服务器程序都需要从网络接受HTTP请求,然后提供HTTP回复给请求者。HTTP回复一般包含一个HTML文件,有时也可以包含纯文本文件、图像或其他类型的文件。
一般来说这些文件都存储在网页服务器的本地文件系统里,而URL和本地文件名都有一个阶级组织结构的,服务器会简单的把URL对照到本地文件系统中。当正确安装和设置好网页服务器软件,服务器管理员会从服务器软件放置文件的地方指定一个本地路径名为根目录。
web server是面对用户请求的第一道门,有些请求由web server自己处理,例如静态文件的访问等;还有一些请求则交给WSGI处理,如对动态页面的访问。
socket: 最近在学习《计算机网络》这门课,socket相关的内容在网络模型中属于传输层,位于网络层和应用层之间。主要用于实现进程间通讯。在这里主要是实现nginx和uWSGI两个不同进程之间的通讯。
WSGI:
Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。自从WSGI被开发出来以后,许多其它语言中也出现了类似接口。
可以将WSGI看做是一种协议,据说之所以Python中有很多web框架,就是因为WSGI调用非常方便。uWSGI是WSGI这一协议的实现。在实际使用过程中,uWSGI代替了python manage.py runserver的作用,当然还有其他作用。
最早的Web服务器只支持静态html。随着网站也越来越复杂,出现了动态技术。但是服务器并不能直接运行 php,asp这样的文件,因此需要一个第三方,与第三方做个约定,我把请求参数发送给你,然后我接收你的处理结果给客户端。这个约定就是 common gateway interface,简称cgi。这个协议可以用vb,c,php,python 来实现。
简单地说,cgi是Web App与Http Server之间的桥梁。
除了cgi,还有wsgi(Web Service Gateway Interface)。WSGI所在层的位置低于CGI,与CGI不同的是WSGI具有很强的伸缩性且能运行于多线程或多进程的环境下,这是因为WSGI只是一份标准并没有定义如何去实现。实际上WSGI并非CGI,因为其位于web应用程序与web服务器之间,而web服务器可以是CGI。
所有均开始于settings文件
如果我们已经创建了一个hello应用(如何创建,请参考本文开始时提到的两个教程),并且使用下面的语句运行:
python manage.py runserver 0.0.0.0:8000
ROOT_URLCONF = 'mysite.urls'
# urls.py from django.conf.urls import url, include from django.contrib import admin from mysite.views import hello admin.autodiscover() urlpatterns = [ url(r'^hello/$', hello), ]
# viwes.py from django.http import HttpResponse def hello(request): return HttpResponse("Hello World!")
uWSGI与Django之间的通讯
uWSGI与Django之间的通讯是通过wsgi.py文件实现的,这个文件在创建Django project后就生成了。这里的设置中最主要的一点就是正确的指出settings.py文件的位置。因为Django中的一切都始于这个文件。这些都使用Django的默认配置就可以了。
# 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 # 这里已经指定了settings文件的位置 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") application = get_wsgi_application()
一些命令:
# 安装uwsgi
$ pip install uwsgi
# 如果 python manage.py runserver 0.0.0.0:8000 可以运行成功,则可以使用下面的命令代替django本身的runserver功能(为了可以使用8000端口,需要先修改防火墙设置)
$ uwsgi --http :8000 --module mysite.wsgi # 在项目的根目录运行该命令(与运行python manage.py的目录相同),参考"图1"中的目录结构
但是在与nginx联合使用时,端口号由nginx中的配置决定,在uwsgi中就不用配置端口号了(由于nginx与uwsgi之间通过socket通信,所以直接访问该端口号就可以访问整个django程序)。
下面是一个配置文件的例子,配置完成后可以在6001端口访问django程序,nginx与uwsgi之间通过"/run/uwsgi/metDNA.sock"进行通信。
server { listen 6001; server_name localhost; # 这里可以根据实际情况填写域名或localhost charset utf-8; client_max_body_size 50M; location / { include uwsgi_params; # include conf.d/corsheaders.conf; uwsgi_pass unix:///run/uwsgi/metDNA.sock; } # Django media location /media { # 设置www.ourlab.cn/media的具体路径,这里是django项目配置文件中的`MEDIA_ROOT`的位置 # your Django project's media files - amend as required alias /mnt/data/metdna-upload; } location /static { # django项目的静态文件夹位置,即`STATIC_ROOT`的位置 # your Django project's static files - amend as required # first project's static files path alias /mnt/data/www/metDNA/metDNA/static/static_root; } }
更多详情,可以参考:https://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html
uwsig的配置:
- 具体配置,可以参考这里;
- 因为在配置文件中设置了touch-reload参数,所以更新django代码后,可以使用 touch /path/to/wsgi.py 加载更新,而不用重启整个django项目;
- 使用uwsgi的emperor模式:/usr/bin/uwsgi --emperor /etc/uwsgi/sites,sites文件夹中放的是uwsgi的配置文件mysite.ini(可以放多个django项目相关的配置文件)。该模式始终处于运行状态,如果要停止该django项目,可以重命名sites文件夹下对应的配置文件;
Instances under the control of the emperor should never dies.
If you want to fully stop an instance, simply remove/rename/move the instance config file in a way that the emperor rule will never match.
nginx的配置
由于一开始不熟悉,因此nginx的配置花了很长时间。其中的关键点是:不同的server之间是无法共用端口号(port)的,因此如果要使用同一个端口号,例如80端口,那么所有的路由都要配置在一个server中。
- 配置文件位置:/etc/nginx/ ;
- 可以将自定义的配置文件放在目录 /etc/nginx/conf.d/,由于在主配置文件 /etc/nginx/nginx.conf 中包含有 include /etc/nginx/conf.d/*.conf; 这条语句,所以该目录下所有的 .conf 文件都会被加载;
- 自定义的配置文件,可以参考这里nginx.conf,主要是指定端口号和server name(网站域名);
值得注意的是,上面配置文件中的sock文件,例如unix:/run/uwsgi/lipidCCS.sock,nginx的用户和uwsgi的用户需要同时对其进行读写操作,因此都需要相应的权限。靠近前端用户的nginx和靠近后端django程序的uwsgi就是通过这个sock文件进行通信的。
一些命令:
# 重启服务:
$ sudo systemctl restart nginx.service
其他
进程管理:进程管理的工具也有很多,uWSGI中的Emperor mode与进程管理工具具有相似的功能,两者似乎无法共用。我选择的是Supervisor。
uwsgi程序中的Emperor mode,会与Supervisor相互影响;Emperor mode的作用与Supervisor差不多,但自己感觉Supervisor更好用一些
重大修订:
第一次修订于2017年12月20日,补充了uwsgi相关配置的说明,对配置文件添加了注释;
第二次修订于2018年12月27日,勘误以及修复链接(这是每年这个时间都要写网站的节奏啊!);
参考:
https://zh.wikipedia.org/wiki/%E7%B6%B2%E9%A0%81%E4%BC%BA%E6%9C%8D%E5%99%A8
https://zh.wikipedia.org/wiki/Berkeley%E5%A5%97%E6%8E%A5%E5%AD%97
https://zh.wikipedia.org/wiki/Web%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3
http://djangobook.py3k.cn/2.0/
http://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html
https://www.quora.com/What-are-the-differences-between-nginx-and-gunicorn # 这里讲了为什么要使用nginx和gunicorn(作用同uWSGI)
http://data-eater.com/python-worker/
http://www.shellwjl.com/2016/03/07/ali_flask_wsgi_Nginx/