Django + uWSGI + Nginx 生产环境部署

官方文档:https://uwsgi-docs.readthedocs.io/en/latest/Options.html

背景

如何在生产上部署 Django?

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

概念介绍

为什么需要web协议

开发出的应用程序需要和服务器程序配合,才能为用户提供服务。但是不同的框架有不同的开发方式,需要有一种协议来规范不同的框架,这就是web 协议。

web 协议本质:就是定义了 Web 服务器和 Web 应用程序或框架的一种简单而普通的接口规范。

Web协议出现顺序:CGI -> FCGI -> WSGI -> uwsgi

  1. CGI:最早的协议
  2. FCGI:比CGI快
  3. WSGI:Python 专用的协议
  4. uwsgi:比 FCGI 和 WSGI 快,是 uWSGI 项目自有的协议,主要特征是采用二进制来存储数据,其他协议都是使用字符串,所以在存储空间和解析速度上,都优于字符串型协议。

WSGI 是什么?

  1. WSGI 是一种规范,它定义了使用 Python 编写的 web app(Django) 与 web server(uWSGI) 之间接口格式,实现 web app 与 web server 间的解耦。
  2. WSGI 没有官方的实现,因为 WSGI 更像一个协议,只要遵照这些协议,WSGI 应用(Application)都可以在任何服务器(Server)上运行。
  3. WSGI 实质:WSGI 是一种描述 web 服务器(如 Nginx、uWSGI 等服务器)如何与 web 应用程序(如用 Django、Flask 框架写的程序)通信的规范、协议。

uwsgi 是什么?

  1. 它是一个二进制协议,可以携带任何类型的数据。
  2. 一个 uwsgi 分组的4个字节描述了这个分组包含的数据类型。
  3. uwsgi 是一种线路协议而不是通信协议,在此常用于在 uWSGI 服务器与其他网络服务器的数据通信;

uwsgi 性能非常高

uWSGI 是什么?(web 服务器 和 Nginx 类似)

  1. uWSGI 是一个全功能的HTTP服务器,实现了 WSGI协议,uWSGI协议,HTTP协议 等。Nginx 中 HttpUwsgiModule 的作用是与 uWSGI 服务器进行交换。
  2. uWSGI 作用:它要做的就是把 HTTP 协议转化成语言支持的网络协议,比如把 HTTP 协议转化成 WSGI 协议,让 Python 可以直接使用。
  3. uWSGI 特点:轻量级,易部署,性能比 Nginx 差很多。

注:

  • 如果架构是 Nginx+uWSGI+APP,uWSGI 是一个中间件。
  • 如果架构是 uWSGI+APP,uWSGI 是一个服务器。

uWSGI 的主要特点如下

  1. 超快的性能。
  2. 低内存占用(实测为 apache2 的 mod_wsgi 的一半左右)。
  3. 多app管理(终于不用冥思苦想下个app用哪个端口比较好了-.-)。
  4. 详尽的日志功能(可以用来分析app性能和瓶颈)。
  5. 高度可定制(内存大小限制,服务一定次数后重启等)。

总而言之 uWSGI 是个部署用的好东东,正如 uWSGI 作者所吹嘘的:

If you are searching for a simple wsgi-only server, uWSGI is not for you, but if you are building a real (production-ready) app that need to be rock-solid, fast and easy to distribute/optimize for various load-average, you will pathetically and morbidly fall in love (we hope) with uWSGI.

总结:WSGI / uwsgi / uWSGI 这三个概念的区分

  1. WSGI 是一种通信协议,通常用于户 Django 框架和 uWSGI 服务器之间通信。(如果说的更细致的话就是用来和 Python WSGI model通信)
  2. uwsgi 是一种线路协议而不是通信协议,在此常用于在 uWSGI 服务器与其他网络服务器的数据通信。
  3. 而 uWSGI 是实现了uwsgi 和 WSGI 等协议的 Web 服务器

Django 是什么?

Django 是一个 Web 框架,框架的作用在于处理 request 和 reponse,其他的不是框架所关心的内容。

所以如何部署 Django 不是 Django 所需要关心的。

请求处理整体流程

Nginx 接收到浏览器发送过来的 HTTP 请求,将包进行解析,分析 url。

静态文件请求:就直接访问用户给 Nginx 配置的静态文件目录,直接返回用户请求的静态文件。

动态接口请求:那么 Nginx 就将请求转发给 uWSGI,最后到达 Django 处理。

各模块作用

  1. Nginx:是对外的服务器,外部浏览器通过url访问 Nginx, Nginx 主要处理静态请求。
  2. uWSGI:是对内的服务器,主要用来处理动态请求。
  3. uwsgi:是一种 web 协议,接收到请求之后将包进行处理,处理成 WSGI 可以接受的格式,并发给 WSGI 服务器。
  4. WSGI:是 Python 专用的 web 协议,根据请求调用应用程序(Django)的某个文件,某个文件的某个函数。
  5. Django:是真正干活的,查询数据等资源,把处理的结果再次返回给 WSGI,WSGI 将返回值进行打包,打包成 uWSGI 能够接收的格式。
  6. uWSGI 接收发送的请求,并转发给 Nginx,Nginx 最终将返回值返回给浏览器。

Django + uWSGI 方案

没有 Nginx 而只有 uWSGI 的服务器,则是 Internet 请求直接由 uWSGI 处理,并反馈到 web 项目中。

Nginx 可以实现安全过滤,防 DDOS 等保护安全的操作,并且如果配置了多台服务器, Nginx 可以保证服务器的负载相对均衡.

而 uWSGI 则是一个 web 服务器,它可以接收和处理请求,发出响应等。所以只用 uWSGI 也是可以的。

Nginx 和 uWSGI 特点

  • Nginx 的作用

    • 反向代理,可以拦截一些 web 攻击,保护后端的 web 服务器
    • 负载均衡,根据轮询算法,分配请求到多节点 web 服务器
    • 缓存静态资源,加快访问速度,释放 web 服务器的内存占用专项专用
  • uWSGI 的适用

    • 单节点服务器的简易部署
    • 轻量级,好部署

uWSGI 安装使用

安装

1
2
3
pip install uwsgi
# ... or if you want to install the latest LTS (long term support) release,
pip install https://projects.unbit.it/downloads/uwsgi-lts.tar.gz

基本测试

1
2
3
4
5
6
7
8
9
10
11
Create a file called 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

# 运行
uwsgi --http :8000 --wsgi-file test.py

# 在浏览器内输入:http://127.0.0.1:8001,查看是否有"Hello World"输出,若没有输出,检查安装过程。

uWSGI 参数解析

常用参数如下

  • http: 协议类型和端口号,例如 --http 127.0.0.1:8080

  • socket: 指定 uWSGI 的客户端将要连接的 socket 的路径(使用UNIX socket的情况)或者地址(使用网络地址的情况)。你最多可以同时指定 8 个socket选项。当使用命令行变量时,可以使用“-s”这个缩写

    • 例如: --socket /tmp/uwsgi.sock
    • --socket 127.0.0.1:8080
  • processes: 开启的进程数量,等同于 workers(官网:spawn the specified number ofworkers / processes)

  • workers: 开启的进程数量,等同于 processes(官网:spawn the specified number ofworkers / processes)

    • 要根据代码实际运行情况来定夺, 通常为 cpu * 2
    • 引用官网:There is no magic rule for setting the number of processes or threads to use. It is very much application and system dependent. Simple math like processes = 2 * cpucores will not be enough. You need to experiment with various setups and be prepared constantly monitor your apps. uwsgitop could be a great tool to find the best values.
  • threads: 每个 process 开启的线程。由于 GIL 的存在,我觉得这个没啥用。(官网:run each worker in prethreaded mode with the specified number of threads)

  • chdir: 指定运行目录(官网:chdir to specified directory before apps loading)

  • wsgi-file: 载入 wsgi-file(load .wsgi file)

  • module: 载入 wsgi-file(load .wsgi file),和 wsgi-file 配置一个就行

  • master: 允许主进程存在(enable master process)

  • daemonize: 使进程在后台运行,并将日志打到指定的日志文件或者udp服务器。实际上最常用的,还是把运行记录输出到一个本地文件上。

  • vacuum: 当服务器退出的时候自动清理环境,删除unix socket文件和pid文件(try to remove all of the generated file/sockets)

  • post-buffering: 启用post缓冲,如果一个HTTP请求有一个主体(像一个表单生成的post请求),你必须在你的应用程序中读取(消费)它。如果不这样做,与web服务器的通信套接字可能会被破坏。如果你比较懒,你可以使用post-buffering选项,它会自动为你读取数据。对于机架应用程序,这是自动启用的。

  • buffer-size: 设置用于 uWSGI 包解析的内部缓存区大小。默认是 4k。

    • 如果你打算接受一个拥有很多请求头的大请求,你可以增加这个值到 64k。--buffer-size 32768
  • max-requests: reload workers after the specified amount of managed requests。一个 worker 完成多少个请求以后就重启。可以使用这个选项来默默地对抗内存泄漏(尽管这类情况使用 reload-on-as 和 reload-on-rss 选项更有用)

    • max-requests 100000
  • disable-logging: disable request logging,禁掉请求的系统日志,不记录请求信息的日志。只记录错误以及uWSGI内部消息到日志中,调试模式下要打开,生产环境注意关闭,这个东西很影响效率

  • stats 监控程序的url: 在指定的地址上,开启状态服务,只有设置了这个参数以后才能用 uwsgitop ip:port 来观看监控,类似于 linux 的 top 命令

    • 引用官网:enable the stats server on the specified address)
    • 示例: state=127.0.0.1:1717
  • listen: 设置 socket 的监听队列大小(默认:100)。每一个 socket 都有一个相关联的队列,请求会被放入其中等待进程来处理。当这个队列满的时候,新来的请求就会被拒绝。队列大小的最大值依赖于系统内核。

    • 这个值要可以稍大一点 --listen 8192

每一个选项都可以使用在任何一种支持的配置方式里(如命令行参数、环境变量、xml文件、ini文件、yaml格式文件以及LDAP)。有些选项的使用需要某些插件的支持。当使用某一种配置风格或者将一种风格转换另一个风格时,需要注意以下规则:

  • 命令行参数(command line args):需要给选项增加 -- 前缀。

    • 例如 socket 选项:--socket
  • 环境变量(environment variable):选项名都要换成大写,并且加上 UWSGI_ 前缀,所有原来选项名中的 - 都要换成下划线 _

    • 例如max-vars选项将变成:UWSGI_MAX_VARS=""
  • xml文件:xml文件中的根结点应该是,所有的选项值是作为文本节点。标识符类型的选项可以没有对应的值。

    • socket选项和master选项可以如下配置:
      1
      2
      3
      4
      <uwsgi>
      <socket>127.0.0.1:3031</socket>
      <master/>
      </uwsgi>
  • ini文件:配置域应该是 uwsgi,标识符类型的选项的值可以设为 true 或者 1。

    • socket 选项和 master 选项可以如下配置:
      1
      2
      3
      [uwsgi]
      socket =127.0.0.1:3031
      master =true
  • yaml 格式文件:根元素需要设置为 uwsgi,标识符类型的选项的值可以设为 true 或者 1。

    • socket 选项和 master 选项可以如下配置:
      1
      2
      3
      uwsgi:  
      socket:127.0.0.1
      master:1
  • lda 格式:这个格式比较复杂,你应该查阅专门的wiki文档。见 useLDAP

一些配置文件

配置1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 非多站模式时 vhost = true 和 no-site = true 需要注释掉,否则后续 Nginx 配置文件中设置的入口文件则不生效
[uwsgi]
socket = 127.0.0.1:9090
master = true # 主进程
vhost = true # 多站模式
no-site = true # 多站模式时不设置入口模块和文件
workers = 2 # 子进程数
reload-mercy = 10
vacuum = true # 退出、重启时清理文件
max-requests = 1000
limit-as = 512
buffer-size = 30000
pidfile = /var/run/uwsgi9090.pid # pid文件,用于下面的脚本启动、停止该进程
daemonize = /website/uwsgi9090.log

配置2

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
38
39
40
41
42
[uwsgi]
# socket 为上线使用,http 为直接作为服务器使用
socket=/home/fenglepeng/dsp_backend/log/uwsgi.sock
http=0.0.0.0:8080

# uwsgi 内部解析的数据包大小,默认 4k
buffer-size=65536

post-buffering=65536

# 项目目录
chdir=/home/fenglepeng/dsp_backend/dsp_backend
wsgi-file=wsgi.py

# 静态文件映射路径
# static-map=/static=/data/my_env1/monitor1/static

# 指定项目的application,这个和 wsgi-file 配置一个就行
# mysite1.mysite1.wsgi这一句表示mysite1项目下的mysite1目录下的wsgi.py文件。
module=mysite1.mysite1.wsgi:application


# 虚拟环境目录
# home = home/ray/MxOnline/mxonlineEnv

# 允许主进程存在,开启的进程数量,线程数量
master=true
processes=8
threads=2

# uwsgi.pid 和uwsgi.log会在启动uwsgi时自动生成在项目目录下。
pidfile=uwsgi.pid
daemonize=uwsgi.log

# max-requests=2000
# chmod-socket=664

# 退出、重启时清理文件
vacuum=true

# 修改python文件后立刻生效
py-autoreload=1

uWSGI 命令

1
2
3
4
5
6
7
8
9
10
11
# 启动
uwsgi -ini uwsgi.ini

# 重启
uwsgi --reload uwsgi/uwsgi.pid

# 停止
uwsgi --stop uwsgi/uwsgi.pid

# uwsgi进程和worker的状态很详细
uwsgi --connect-and-read uwsgi/uwsgi.status

uwsgitop

只需要简单的在 uwsgi 的运行参数里加上:--stats /tmp/stats.socket 或者 --stats 127.0.0.1:1717

1
2
3
uwsgi --socket :3031 --module welcome --master --processes 8 --stats /tmp/stats.socket
# or
uwsgi --socket :3031 --module welcome --master --processes 8 --stats 127.0.0.1:1717

安装 uwsgitop

uwsgitop 是其他 top 类似,使用状态服务来获取服务数据。在 PyPI 上有 uwsgitop,所以可以通过 pip 或者 easy_install 安装。

1
pip install uwsgitop

使用 uwsgitop

1
2
uwsgitop /tmp/stats.socket
uwsgitop 127.0.0.1:1717

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
28
29
30
31
32
33
34
35
36
37
38
# 负载均衡
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 {
listen 8000; # 监听的端口
server_name example.com; # 监听的域名,或者ip
charset utf-8;

# max upload size
client_max_body_size 75M; # request请求body

# Django media
location /media {
alias /path/to/your/mysite/media; # 项目的media路径
}

location /static {
alias /path/to/your/mysite/static; # 项目的static 路径
}

# 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

include uwsgi_params;
uwsgi_pass 127.0.0.1:9090; # 必须和uwsgi中的设置一致
# uwsgi_pass unix:///home/sock/[xxx].sock; # 此为Nginx 使用 sock 的重点

# uwsgi_param UWSGI_SCRIPT demosite.wsgi; # 入口文件,即wsgi.py相对于项目根目录的位置,“.”相当于一层目录
# uwsgi_param UWSGI_CHDIR /demosite; # 项目根目录
index index.html index.htm;
}
}

静态文件

在运行 Nginx 之前,你必须将所有的 Django 静态文件收集到静态文件夹中。首先,你必须编辑 mysite/settings.py 添加:

1
2
3
4
5
STATIC_URL = 'static'
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
STATICFILES_DIRS = [os.path.join(BASE_DIR,'static'),]

在项目目录下迁移静态文件

1
python manage.py collectstatic

此时启动Nginx 和Uwsgi,你的Django项目就可以实现高并发啦!

异常处理

socket 配置不一致

1
upstream prematurely closed connection while reading response header from upstream, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", upstream: "uwsgi://127.0.0.1:8080", host: "127.0.0.1"

uwsgi.ini 文件中的 socket = 127.0.0.1:8001 配置一定要和 Nginx 中的配置相匹配,不匹配会出现上述报错

uwsgi 出现 invalid request block size: 21573 (max 4096)…skip

uwsgi 内部解析的数据包大小,默认 4k。如果准备接收大请求,你可以增长到 64k。可以设置 uwsgi.ini 参数解决

1
buffer-size = 65536

uwsgi_temp Permission denied

1
connect() to unix:*.sock failed (13: Permission denied) while connecting to upstream, client: *, server: *, request: "GET / HTTP/1.1", upstream: "uwsgi://unix:*.sock:", host: "*"

解决方案一:

uwsgi_temp 目录及上层目录 增加 x 权限

解决方案二:

1
2
3
4
5
6
7
8
9
10
11
12
# 检查SELinux是否开启
getenforece

# 设置暂时关闭

setenforce 0 # 关闭
setenforce 1 # 开启

# 设置永久关闭
vim /ect/selinux/config
# 设置
SELINUX = disabled

socket Permission denied

1
2022/03/08 11:32:00 [crit] 104178#0: *64 connect() to unix:/home/fenglepeng/dsp_backend/log/uwsgi.sock failed (13: Permission denied) while connecting to upstream, client: 10.122.107.133, server: localhost, request: "GET /api/v1/bucket?offset=0&limit=10&bucket_name=&bucket_security_level=&attribute= HTTP/1.0", upstream: "uwsgi://unix:/home/fenglepeng/dsp_backend/log/uwsgi.sock:", host: "***.com", referrer: "https://***.com/app/bucket/myBucket"

解决办法:

Nginx 启动的用户(一般是nobody)需要对 socket 文件具有 x 权限

参考

Django 中 settings.py 中的五个设置参数的一些故事:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
1、MEDIA_ROOT 与 MEDIA_URL
事实上MEDIA_ROOT和MEDIA_URL代表的是用户上传后的文件一般保存的地方。我的理解是,可变文件的文件夹。
与这两个参数有联系的,是在Django的FileField和ImageField这样的Model类中,有upload_to参数可选。
当upload_to设置相关的地址后,如:upload_to="username"
文件上传后将自动保存到 os.path.join(MEDIA_ROOT, upload_to)。
而MEDIA_URL,,则代表用户通过URL来访问这个本地地址的URL。
如本机http://127.0.0.1/, MEDIA_URL设置为"/site_media/",
那么通过 http://127.0.0.1/site_media/*** 就可以访问相关的上传图片或者其他资源。

2、STATIC_ROOT 与 STATIC_URL
STATIC_ROOT和STATIC_URL则是网站中,用于网站显示的静态图片、CSS、JS等文件的保存地址。
我的理解是,运行中不会再变文件的文件夹(即不会删除或者新增)

2.1 STATIC_URL 同 MEDIA_URL类似;STATIC_URL为"/static/"时候,
通过http://127.0.0.1/static/***就可以访问相关的静态文件了。

2.2 STATIC_ROOT是一个比较特殊的文件夹。
这是区别Django的开发模式和部署模式下最大的地方了。
通常我们在开发模式下,可以在我们所在的project下建立相应的app, 然后每个app下都建立相应的static文件夹。
在开发模式下(Debug=True),Django将为我们自动查找这些静态文件(每个app)并在网页上显示出来。
然而,在部署模式下,Django认为这些工作交由web服务器来运行会更有效率。
因此,在部署时,我们需要运行一下python manage.py collectstatic 这个命令。
这个命令将会把每个app里的static目录下的文件copy到STATIC_ROOT这个文件夹下,
这时候如果在部署模式下(Debug=False),网页中相关的,如: http://127.0.0.1/static/*** 的访问,
将不会访问Django下各个App中的static,而是STATIC_ROOT中所指定的文件夹。


3、Debug=False后,为何无法访问图片和js等文件了?
其实这个问题,是在于web服务器没有对STATIC_ROOT以及MEDIA_ROOT这两个文件夹进行映射所导致的。
以apache为例,假定:
STATIC_ROOT="/home/user/static/"
STATIC_URL="/static/"
MEDIA_ROOT="/home/user/media/"
MEDIA_URL="/media/"

那么可以在apache的配置文件中,增加以下:
<Location "/static/">
Order deny,allow
Allow from all
Satisfy Any
</Location>
Alias /static/ "/home/user/static"
<Location "/media/">
Order deny,allow
Allow from all
Satisfy Any
</Location>
Alias /media/ "/home/user/media/"

4、STATICFILES_DIRS:
和TEMPLATE_DIRS的含义差不多,就是除了各个app的static目录以外还需要管理的静态文件,
添加到这里的文件会在collectstatic时 copy到STATIC_ROOT中

Django + uWSGI + Nginx 生产环境部署
https://flepeng.github.io/021-Python-32-框架-Django-Django-uWSGI-Nginx-生产环境部署/
作者
Lepeng
发布于
2021年8月8日
许可协议