update: 使用docker 可能是个更优雅的方式,本文是比较原始的方式,仅供学习和参考

Django的部署可以有很多方式,采用nginx+uwsgi的方式是其中比较常见的一种方式。

在这种方式中,我们的通常做法是,将nginx作为服务器最前端,它将接收WEB的所有请求,统一管理请求。 nginx把所有静态请求自己来处理(这是NGINX的强项)。 然后,NGINX将所有非静态请求通过uwsgi传递给Django,由Django来进行处理,从而完成一次WEB请求。 可见,uwsgi的作用就类似一个桥接器。起到桥梁的作用。

NOTE:不使用nginx,只使用uwsgi+django也是可以实现WEB服务的。uwsgi也可以直接处理WEB请求

部署过程回顾

在继续深入之前,我们先来回顾一下当我们在本地开发环境下更新了代码后,在服务器上的整个部署过程。

  • 远程连接服务器。

  • 进入项目根目录,从远程仓库拉取最新的代码。

  • 如果项目引入了新的依赖,需要执行 pip install -r requirement.txt 安装最新依赖。

  • 如果修改或新增了项目静态文件,需要执行 python manage.py collectstatic 收集静态文件。

  • 如果数据库发生了变化,需要执行 python manage.py migrate 迁移数据库。

  • 重启 Nginx 和 uwsgi 或者自动化脚本

一些概念

什么是web服务器?

web server直接接触外面的东西,它可以从文件系统直接提供 (HTML, images, CSS, etc)给外部访问.但是它不能直接地与我们的django应用程序进行通信 。它还需要一些东西来运行我们的应用以及处理来自web客户端的请求(比如浏览器)已经返回响应,常见的web服务器有Nginx,IIS等

什么是WSGI?

WSGI是 web server gateway interface的简称,也就是说WSGI负责联通web服务器,WSGI是python的一种协议标准,而uWSGI 是WSGI的一种实现,在本教程中我们会使用uWSIG区创建一个Unix socket (套接字),将response(程序响应)通过socket使用WSGI协议进行通信,最终我们的组件结构如下:

the web client <-> the web server <-> the socket <-> uwsgi <-> Django
eg:

Browser <-> Nginx <-> the unix socket <-> uwsgi <-> Django

在设置uWSGI前

虚拟环境virtualenv

安装所需软件前确保你正在使用虚拟环境(稍后我们会谈系统级的uwsgi)先使用apt-get 安装virtualenv

virtualenv uwsgi-tutorial
cd uwsgi-tutorial
source bin/activate

如果已经有建立了项目,直接去到虚拟环境bin目录下 执行source activate即可激活虚拟环境

Django项目

在虚拟环境下安装Django,创建一个django项目并进入到项目文件夹中

鉴于本文是说部署项目所以这里假设你已经设定好项目,如果实在没创建请参考Django博客创建教程

我的项目创建在

/root/ENV/bin/my_blog/

我的配置文件夹是在

/root/ENV/bin/my_blog/ my_blog

现在进入到项目文件夹

cd /root/ENV/bin/my_blog

关于域名和端口

在本教程,假设你的域名是 example.com. 当然你可以换成你自己的域名或者IP。由始至终我们都将使用端口8000来发布web服务。 没错,就是Django默认使用的那个端口。 实际上你部署的时候你喜欢用什么端口就完全由你决定。 这里之所以选择8000是为了防止和其他的已经存在的web server冲突,比如说我之前已经部署了一个80端口的,这里就不能再用80了

基本的uWSGI安装以及配置

安装uswgi到你的虚拟环境

pip install uwsgi

也有其他的方式安装uWSGI,但是这种方式是最好的,你可能同时还要确保本机已经安装了Python开发环境。 在Debian以及其衍生发行版中例如Ubuntu,可能需要安装pythonX.Y-dev, 这里的 X.Y 是Python版本号.

基础测试

用你最爱的编辑器创建一个文件叫 test.py并输入以下内容:

# test.py
def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return [b"Hello World"] # python3
    #return ["Hello World"] # python2

注:请留意python3和python2的区别

运行uWSGI:

uwsgi --http :8000 --wsgi-file test.py

参数含义:

  • http :8000 u使用http协议并指定8000端口
  • wsgi-file test.py: 加载python web源文件的方式运行,文件为test.py

然后在浏览器使用8000端口进行访问的时候应该会返回 ‘hello world’ 响应

访问: http://example.com:8000 如果确认输出没问题即说明以下环节是正常的

the web client <-> uWSGI <-> Python

(如果你在生产环境,只要命令行怎么测试8000端口已经开启了web服务呢,其实你可以安装一个命令行浏览器w3m,然后运行 w3m http://localhost:8000, 或者curl http://localhost:8000)

测试你的 Django 项目

刚才我们只是试用了一个简单源码的web模块,现在我们换成我们的Django项目再次测试uWSGI

首先使用内置的 manage.py跑起来你的项目(其实manager.py也相当一个小型的web服务器,但是能力有限不足以撑起外部访问,就像我们用visual studio进行调试而实际发布我们采用专用的IIS服务器)。 这一步确保你的项目是可以运行的

python manage.py runserver 0.0.0.0:8000

如果你在生产环境,只要命令行怎么测试8000端口已经开启了web服务呢,其实你可以安装一个命令行浏览器w3m,然后运行 w3m http://localhost:8000

确认上面操作没问题后就使用uWSGI启动项目:

uwsgi --http :8000 --module wsgi

module mysite.wsgi: 加载指定的wsgi模块(wsgi 模块是一个项目的入口,我这里是指wsgi.py 文件,当作为uwsgi的参数的时候只需要指名文件名,不需要后缀“.py” )

wsgi.py:
#!coding:utf-8
import os
from os.path import join,dirname,abspath

import sys

PROJECT_DIR = dirname(dirname(abspath(__file__)))#3
import sys # 4
sys.path.insert(0,PROJECT_DIR) # 5

os.environ["DJANGO_SETTINGS_MODULE"] = "my_blog.settings" # 7
 
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

用你的浏览器打开上面的网址如果能看到你的网站输出,就是说明uWSGI可以在当前虚拟环境下充当的你的web服务器并运行了你的网站,说明以下结构是没问题的:

the web client <-> uWSGI <-> Django

好了现在我们看到客户端是直接与uWSG连接通讯的,就是说uWSGI充当了web server的角色

基本的nginx设置 安装nginx

sudo apt-get install nginx
sudo /etc/init.d/nginx start    # start nginx

现在nginx已经运行了你可以使用80端口访问你的服务器,然后将会看到

> “Welcome to nginx!”.

恭喜你,证明了下面的结构是没问题的:

the web client <-> the web server

如果其他人已经使用了80端口,然后你要继续使用nginx,那就需要重新配置下nginx使用其他端口,前面说了,本篇将始终使用8000端口。

现在我先创建一个文件叫mysite_nginx.conf,然后写入以下内容(实际上你直接修改/etc/nginx/nginx.conf的文件也是可以的):


# mysite_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:8001; # 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 .example.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 /path/to/your/mysite/media;  # your Django project's media files - amend as required
    }

    location /static {
        alias /path/to/your/mysite/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     /path/to/your/mysite/uwsgi_params; # the uwsgi_params file you installed
    }
}

eg: 上面只是个通用示例下面是我自己的配置

upstream django {
    #server unix:/root/ENV35/bin/my_blog/my_blog/my_blog.sock; #unix:/root/ENV35/bin/my_blog/my_blog/my_blog.sock; # for a file socket
    server 127.0.0.1:8077; # 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 128.199.134.134; # substitute your machine's IP address or FQDN
    charset     utf-8;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

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

    # Django media
    location /media  {
        alias /root/ENV35/bin/my_blog/media;  # your media files
    }

    location /static {
        alias /root/ENV35/bin/my_blog/collected_static; # you static files
    }

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

先忽略其他的配置,仅仅


# Django media
    location /media  {
        alias /root/ENV35/bin/my_blog/media;  # your media files
    }

    location /static {
        alias /root/ENV35/bin/my_blog/collected_static; # you static files
    }

这里指明了nginx从文件系统里对静态文件提供web服务,对一些大型的部署,通常他们的静态文件需要单独一个服务器来部署静态文件

和媒体文件。但是现在还是简单的放在一起部署吧

部署静态文件

首先安装上面设置好nginx的配置,现在还需要收集Django的静态文件放置在静态文件夹。请编辑Django的settings.py文件,添加

STATIC_ROOT = os.path.join(BASE_DIR, "static/")

(如果有其他的静态文件,请另行配置)

然后在终端运行:

python manage.py collectstatic

基本的nginx 测试

重启 nginx:

sudo /etc/init.d/nginx restart

为了测试媒体文件是否正确地被nginx服务着。添加一个图片media.png到你项目的媒体目录

/path/to/your/project/project/media directory, 然后访问 http://example.com:8000/media/media.png 如果能看到图片,至少证明目前nginx已经正确地服务着你的文件。

nginx 和 uWSGI 以及 test.py

现在让我们用nginx跑起你的简单test.py程序并输出响应“hello world” 。

uwsgi --socket :8077 --wsgi-file test.py

与前面所做的非常相似,只是这次的参数略有不同:

socket :8077: uwsgi使用socket通讯, 且端口为 8077

nginx 同时也已经设置了同一个端口与uwsgi进行通讯,nginx的设置是包含了:

upstream django {
    #server unix:/root/ENV35/bin/my_blog/my_blog/my_blog.sock; #unix:/root/ENV35/bin/my_blog/my_blog/my_blog.sock; # for a file socket
    server 127.0.0.1:8077; # for a web port socket (we'll use this first)
}

现在访问http://example.com:8000/

检查下如果ok了,那就说明了下面的结构是通的:

the web client <-> the web server(nginx,port:8000) <-> the socket(port:8077) <-> uWSGI <-> Python(django)

这时,你也可以看下在浏览器直接打开8007端口看uwsgi的输出 http://example.com:8077  理论上这个行不通的,因为你的浏览器是使用了http协议进行通讯的,而不是uWSGI协议,你也可以看到uwsgi在终端输出的错误。 而访问8000端口的时候浏览器的请求是先到达了nginx,nginx再通过与uwsgi通讯获取到响应流所以浏览是正常的。

使用Unix socket通信取代端口

目前为止,我们都是使用TCP端口的socket,原因无他-简单。实际上,推荐使用Unix的socket 比端口要好,因为Unix的socket具有更低的开销。

修改nginx配置文件 mysite_nginx.conf, 修改内容:


    server unix:/root/ENV35/bin/my_blog/my_blog/my_blog.sock; #unix:/root/ENV35/bin/my_blog/my_blog/my_blog.sock; # for a file socket, auto created when running uwsgi and nginx
    #server 127.0.0.1:8077; # for a web port socket (we'll use this first)

然后重启 nginx.

再次运行 uWSGI :

uwsgi --socket my_blog.sock --wsgi-file test.py

这一次,我们让uwsgi使用了指定的socket文件了(同nginx里配置的socket,socket文件无须手动创建,uwsgi会自动创建并在结束运行后自动删除)

再在浏览器里打开http://example.com:8000/.

如果不行的哈不妨查看下nginx的错误日志(/var/log/nginx/error.log) 如果你看到类似这样的:

connect() to unix:///path/to/your/mysite/mysite.sock failed (13: Permission
denied)

那么你可能需要修改socket的权限。使nginx具有权限使用它:

可以尝试:

uwsgi --socket my_blog.sock --wsgi-file test.py --chmod-socket=666 # (very permissive)

或者:

uwsgi --socket my_blog.sock --wsgi-file test.py --chmod-socket=664 # (more sensible)

另外你可能还需要添加用户到nginx用户组(比如www-data) ,这样nginx才有权限读写你的socket

同时也强烈建议在终端运行的nginx同时输出它的日志,这样可以轻松地诊断定位故障。

使用uwsgi和nginx运行Django应用程序

现在我们用下面的方式跑我们的Django程序:

uwsgi --socket my_blog.sock --module wsgi --chmod-socket=664

现在uWSGI和nginx不仅仅是运行"Hello World"的简单应用,而是通过模块wsgi入口加载了你的整个Django项目

配置uWSGI使用.ini文件

其实我们大可将一些参数写入到文件中,这样外终端调用的时候只需要指定这个文件即可,uwsgi的配置文件支持多种格式,比如xml或者ini,现在我们创建一个配置文件,然后在运行uwsgi的时候指明这个文件。配置文件的创建和配置都是很简单

现在就在项目的配置文件夹创建一个文件mysite_uwsgi.ini:

#mysite_uwsgi.ini file
[uwsgi]

# Django-related settings
# the base directory (full path)
chdir           = /root/ENV35/bin/my_blog/my_blog/
# Django's wsgi file, here is wsgi.py ,just the name , no extension
module          = wsgi
# the virtualenv (full path)
home            = /root/ENV35/

# process-related settings
# master
master          = true
# maximum number of worker processes
processes       = 10
# the socket (use the full path to be safe
#socket          = :8077  unix socket file,crate a empty file eg:/root/ENV35/bin/my_blog/my_blog/my_blog.sock
socket          = /root/ENV35/bin/my_blog/my_blog/my_blog.sock
# ... with appropriate permissions - may be needed
chmod-socket    = 664
# clear environment on exit
vacuum          = true

创建好之后我们用uwsgi使用这个文件:

uwsgi --ini mysite_uwsgi.ini # the --ini option is used to specify a file

再一次,我们的Django网站又能运行起来了,前提是你的配置是没问题的

安装系统范围可访问的uWSGI.

细心的你可能发现了,目前为止,我们的uWSGI只是安装在虚拟环境中virtualenv,(当然如果你可以设置每次开机后分别运行nginx,虚拟环境以及uwsgi的话外部也是可以正常访问你的网站的)但是更进一步我们现在想要安装整个系统可访问部署环境中

先关闭你的虚拟环境virtualenv:

deactivate

然后安装系统环境下的uWSGI:

sudo pip install uwsgi

#或者你也可以安装LTS版本 (long term support,长期支持).

pip install https://projects.unbit.it/downloads/uwsgi-lts.tar.gz

uWSGI维基提供了详细的安装细节可参考installation procedures. 在安装系统级的uWSGI之前,很有必要考虑具体安装哪一个版本已经最适合你的安装方式

现在和前面一样,再运行uwsgi检查一次你的网站(非虚幻环境中):

uwsgi --ini mysite_uwsgi.ini # the --ini option is used to specify a file

当全部都没问题后即可将nginx设置为监听90端口,并编写脚本在系统启动的时候启动nginx和uwsgi

附调试过程你可能需要的一些命令:

sudo systemctl stop nginx 
sudo /etc/init.d/nginx restart

#80 port is in used:
netstat -ap | grep 80
sudo fuser -k 80/tcp
nginx -s  reload


vim /etc/nginx/nginx.conf
cat /etc/nginx/nginx.conf


cat /var/log/nginx/error.log

#permission
#user www-data--> user root
uwsgi --socket my_blog.sock --module wsgi --chmod-socket=664

鸣谢:

五步教你实现使用Nginx+uWSGI+Django方法部署Django程序(上) uwsgi-docs 五步教你实现使用Nginx+uWSGI+Django方法部署Django程序