0%

阿里云部署Django项目

使用 Nginx + uWSGI 部署,Nginx完成端口的转发,uWSGI负责启动 Django

Nginx、WSGI、uwsgi、uWSGI、django这几个关系

WSGI,全称 Web Server Gateway Interface,或者 Python Web Server Gateway Interface ,是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口。描述的是Web服务器如何与Web应用间进行通信。它不是服务器、python模块、框架、API或者任何软件,只是一种描述web服务器(如nginx,uWSGI等服务器)如何与web应用程序(如用Django、Flask框架写的程序)通信的规范。实现了WSGI规范的服务器称为WSGI服务器,是专门用来部署WSGI程序(使用Python语言实现的web程序)的服务器,常见的生产级别的WSGI服务器有uWSGI、Gunicorn、Gevent、Waitress等,可以根据不同的使用场景进行选择。

uwsgi协议是一个uWSGI服务器自有的协议,它用于定义传输信息的类型(type of information),每一个uwsgi packet前4byte为传输信息类型描述,用于与nginx等代理服务器通信,它与WSGI相比是两样东西。

uWSGI是实现了uwsgi和WSGI两种协议的Web服务器。

为什么有了uWSGI为什么还需要nginx?因为nginx具备优秀的静态内容处理能力,然后将动态内容转发给uWSGI服务器,这样可以达到很好的客户端响应。

完整的访问过程

  1. Nginx接收到浏览器的http请求,将包进行解析,分析url,如果是静态文件直接访问用户给Nginx配置静态文件目录,如果不是静态文件,是一个动态请求,Nginx会转发给uWSGI。
  2. uWSGI接到请求会进行处理成WSGI可以接受的形式,并发给WSGI。
  3. WSGI根据请求调用应用程序的某个文件,某个文件的某个函数处理完会返回给WSGI,WSGI将返回值进行打包,打包成uWSGI能够接受的格式,uWSGI接受WSGI的发送请求,转发给Nginx,Nginx最终将返回值返回给浏览器。

为什么不用uWSGI作为唯一服务器

1.安全问题,程序不能直接被浏览器访问到,而是通过nginx,nginx只开放某个接口,uwsgi本身是内网接口,这样运维人员在nginx上加上安全性的限制,可以达到保护程序的作用。

2.负载均衡问题,一个uwsgi很可能不够用,即使开了多个work也是不行,毕竟一台机器的cpu和内存都是有限的,有了nginx做代理,一个nginx可以代理多台uwsgi完成uwsgi的负载均衡。

3.静态文件问题,用django或是uwsgi这种东西来负责静态文件的处理是很浪费的行为,而且他们本身对文件的处理也不如nginx好,所以整个静态文件的处理都直接由nginx完成,静态文件的访问完全不去经过uwsgi以及其后面的东西。

工具安装(以CentOS7为例)

mysql

安装完成之后,需要使用进行如下配置

1
2
3
4
在/etc/my.cnf中需要设置
在 [mysqld]:
下面加一行
bind-address = 0.0.0.0

数据库的迁移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 将本地的数据库(music_db)导出为sql文件(music_db.sql)
mysqldump -uroot -p db_name > db_name.sql

# 用scp命令将sql文件发送到服务器
scp music_db.sql 远程登录账号@服务器ip:服务器目录/db_name.sql

# 在服务器上登录mysql
mysql -uroot -p

# 在服务器的mysql中新建同名数据库(music_db),然后退出mysql
create database new_db_name charset=utf8;

# 退出
exit;

# 将通过scp命令传输过来的db_name.sql文件导入到,刚刚建立的同名数据库中
mysql -uroot -p new_db_name < db_name.sql

nginx

1
2
sudo yum install epel-release
sudo yum install nginx

python环境

uWSGI

使用 pip install uwsgi 安装
在项目的根目录下,通过 uwsgi --http :8001 --module 项目名.wsgi 运行项目(使用的是项目下的wsgi.py文件,需要文件内包含一个WSGIapplication对象),以http的方式,让uwsgi启动我们的项目。

注意:端口需要在服务器的 安全策略组 中进行了开放

当输出以下内容时,说明项目启动成功

1
2
3
...
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI worker 1 (and the only) (pid: 24712, cores: 1)

配置

在Django项目根目录下创建conf文件夹,文件夹下创建uwsgi文件夹和nginx文件夹,分别用于存储uwsgi和nginx的配置文件。

nginx

配置nginx文件,后缀名为 .conf,如myproject_nginx.conf

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
# 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)
# 设置启动Django的端口,可以任意命名合法的端口号
}
# configuration of the server

server {
# the port your site will be served on
listen 80;
# the domain name it will serve for
server_name 云服务器公网IP或者域名 ; # substitute your machine's IP address or FQDN
# 配置服务器的IP地址
charset utf-8;

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

# Django media
# 配置使用nginx来代理静态文件,而不使用Django自带的代理,得益与nginx底层的io多路复用,nginx的性能高
location /media {
alias /root/projects/MxShop/media; # 指向django的media目录
}

location /static {
alias /root/projects/MxShop/static; # 指向django的static目录
}

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

使用nginx之后,项目中的静态文件就交给nginx代理了,而不使用Django自带的代理,得益与nginx底层的io多路复用,nginx的性能高。

注意区分:listen监听的这个端口是外部浏览器访问nginx的端口,而uwsgi_pass这个端口是nginx访问uwsgi的端口,所以设置需要和uwsgi.ini配置文件中的socket指定的路径一致。

uwsgi配置

配置uwsgi文件,后缀名为 .ini,如uwsgi.ini

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
# mysite_uwsgi.ini file
[uwsgi]

# Django-related settings
# the base directory (full path) 项目的地址
chdir = /root/MyProject
# Django's wsgi file 项目中的wsgi.py文件的位置,相对chdir的设置
module = MyProject.wsgi
# the virtualenv (full path)

# process-related settings
# master
master = true
# maximum number of worker processes
processes = 10
# the socket (use the full path to be safe )
socket = 127.0.0.1:8001 # 和nginx中的server配置相对应
# ... with appropriate permissions - may be needed
# chmod-socket = 664
# clear environment on exit
vacuum = true
# 虚拟环境的路径 ,当前的python环境文件
virtualenv = /root/.virtualenvs/myproject

pidfile= uwsgi.pid # 生成包含当前uwsgi主进程的文件,用户管理uwsgi服务
# logto = /tmp/mylog.log # 日志文件的存储地址

注意:以上的配置启动后,配合nginx的设置进行使用,直接通过url无法访问。

参数解释

  • socket:socket文件,也可以是地址+端口,用于与某些第三方路由器(例如nginx)一起使用的http选项,如果直接通过http直接访问uWSGI,需要使用http,如http = 0.0.0.0:8001
  • master:是否启动主进程来管理其他进程;
  • chdir:项目的根目录;
  • module:wsgi文件相对路径;
  • vacuum:服务结束后时候删除对应的socket和pid文件;
  • pidfile:指定pid文件,设置该属性之后,uWSGI通过xxx.ini启动后会在相同目录下生成一个xxx.pid的文件,里面只有一行内容是uWSGI的主进程的进程号。

基本命令

启动
uwsgi --ini xxx.ini
重启
uwsgi --reload xxx.pid 需要设置pidfile属性
停止
uwsgi --stop xxx.pid 需要设置pidfile属性
kill -9 uwsgi 此方法会杀死所有的uWSGI服务,在启动了多个uWSGI服务时,谨慎使用

启动Django项目

先启动uWSGI,之后再启动Nginx

启动uWSGI

进入虚拟环境,进入uwsgi配置文件所在的目录,通过 uwsgi --ini uwsgi.ini 命令,使用uwsgi启动Django项目,出现以下输出,表示启动成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
uWSGI running as root, you can use --uid/--gid/--chroot options
*** WARNING: you are running uWSGI as root !!! (use the --uid flag) ***
spawned uWSGI master process (pid: 25690)
spawned uWSGI worker 1 (pid: 25693, cores: 1)
spawned uWSGI worker 2 (pid: 25694, cores: 1)
spawned uWSGI worker 3 (pid: 25695, cores: 1)
spawned uWSGI worker 4 (pid: 25696, cores: 1)
spawned uWSGI worker 5 (pid: 25697, cores: 1)
spawned uWSGI worker 6 (pid: 25698, cores: 1)
spawned uWSGI worker 7 (pid: 25699, cores: 1)
spawned uWSGI worker 8 (pid: 25700, cores: 1)
spawned uWSGI worker 9 (pid: 25701, cores: 1)
spawned uWSGI worker 10 (pid: 25702, cores: 1)

启动Nginx

通过以下命令,将nginx的配置文件放置到 /etc/nginx/conf.d/ 目录下
sudo ln -s 你的目录/Mxonline/conf/nginx/uc_nginx.conf /etc/nginx/conf.d/

  • conf.d 文件夹下可以放置多个启动配置文件,每个项目对应一个。
  • 推荐使用软连接的方式,在conf.d目录下创建配置文件的软链接,这样在项目中修改,会直接同步到配置文件中,比较方便。

最好修改一下 /etc/nginx/nginx.conf中的权限配置,将第一行的user nginx;修改为user root;这样可以避免在后期nginx访问某些文件的时候出现权限问题,当然也可以选择给需要访问的文件给nginx权限。

一切配置完成之后,通过 sudo systemctl start nginx 即可启动nginx服务,此时没有异常则为启动正常(可以通过 sudo systemctl status nginx 查看nginx服务的状态),之后即可用浏览器访问 http://nginx配置文件中server_name指定的地址

后续问题

静态文件的访问

Django中的静态文件访问设置需要分两种情况进行讨论(更加settings.py中DEBUG配置的状态分),即 开发测试场景下 和 生产环境中。

开发测试场景下

DEBUG设置为True。
此场景下Django自带的服务器提供静态文件的代理服务,此时只需要设置 STATIC_URL 以及 STATICFILES_DIRS 即可完成静态文件的访问,并且此时Django自带的服务器提供静态文件的代理服务还会自动代理项目中使用的外部app自带的静态文件(且不需要额外的设置)。

1
2
3
4
5
STATIC_URL = '/static/'
# 全局静态文件配置,开发模式下用于一些公共静态文件的路径提供,因为不是在app下单独设置,开发模式下Django自带的静态文件代理无法定位到这些文件,需要使用STATICFILES_DIRS进行设置。
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'), # 指向项目下的static文件夹
]

注意:STATICFILES_DIRS 和 STATIC_ROOT 的配置不能相同,否则提示 (staticfiles.E002) The STATICFILES_DIRS setting should not contain the STATIC_ROOT setting.。一般情况下,在生产环境中所有的静态资源都会收集到一起放到统一的文件夹下,交由第三方服务代理,此时STATICFILES_DIRS也就不需要使用了。

生产环境下

DEBUG设置为False(此时注意:必须设置ALLOWED_HOSTS参数,用于设置允许访问当前网站的ip地址)
在Django部署上线之后,为了不对外暴露内部程序细节(测试开发过程中,DEBUG设置为True,异常会显示在浏览器),此时需要设置DEBUG为False,而一旦这样设置之后,Django将不再提供静态服务代理的功能,需要手动配置来完成。配置过程和media的配置相同(因为Django是不提供media文件的代理服务的,所以即使在开发阶段也是需要手动配置的)。
手动实现静态文件代理的方式有以下几种:

  1. 通过配置url进行访问(Django只推荐在开发过程中使用,生产环境下最好使用第2种)
    配置的过程和media的配置过程一样,配置内容如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # settings.py中的配置
    STATIC_URL = '/static/'
    STATIC_ROOT = os.path.join(BASE_DIR, 'static') # 指向项目下的static文件夹
    # media文件的路径
    MEDIA_URL = "/media/"
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 指向项目下的media文件夹


    # urls.py中的路由设置
    from MyProject.settings import STATIC_ROOT
    from MyProject.settings import STATIC_ROOT
    from django.views.static import serve # 用于提供静态文件的视图和功能。这些仅在开发过程中使用,不应在生产环境中使用。

    url(r'^media/(?P<path>.*)$', serve, {"document_root": MEDIA_ROOT}),
    url(r'^static/(?P<path>.*)$', serve, {"document_root": STATIC_ROOT}),
  2. 通过第三方服务器,如使用Nginx来提供静态文件的代理服务(Nginx拥有更高的静态资源处理性能)
    此时配置文件中只需要提供 STATIC_URLMEDIA_URL 两个配置,如下:
    1
    2
    STATIC_URL = '/static/'
    MEDIA_URL = '/media/'
    之后,Django所有静态资源的访问都将被转到Nginx,由Nginx提供。
1
2
3
4
5
6
7
8
# nginx下的静态文件代理配置
location /media {
alias /root/projects/MxShop/media; # 指向django项目下的media文件夹
}

location /static {
alias /root/projects/MxShop/static; # 指向django项目下的static文件夹
}

通过以上两种方式既可以完成静态文件的访问了,但是可能某些静态文件依然无法访问,这是因为在开发的时候,除了手动设置的用到的静态文件之外,还有一些安装的app也需要使用到一些静态文件,而这些静态文件一般是app自带的,安装在这些app目录下的static文件夹中,在开发环境下(DEBUG设置为True),Django会自动找到这些app的静态文件并提供相应的静态文件代理,生产环境下由于Django不再提供静态文件代理,我们所有的静态文件的访问路由都指向了项目更目录下的总的静态文件夹下,所以这些app自带的静态文件就无法访问(这也是Django在开发模式下提供自带的静态文件代理服务的原因以及优点),好在django为我们提供了静态文件的收集方案,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 只需要在当前项目的根目录下,使用 python manage.py collectstatic 命令即可完成所有静态文件的收集
# 注意,该条指令依赖STATIC_ROOT = os.path.join(BASE_DIR, 'static')的设置
$ python manage.py collectstatic

You have requested to collect static files at the destination
location as specified in your settings:

/root/projects/MyProject/static

This will overwrite existing files!
Are you sure you want to do this?

Type 'yes' to continue, or 'no' to cancel: yes

646 static files copied to '/root/projects/MyProject/static'.

收集完成之后,所有的静态文件都可以正常访问了。

STATIC_ROOT = os.path.join(BASE_DIR, 'static')的作用有两个:

  1. 用于使用 python manage.py collectstatic 命令完成所有静态文件的收集
  2. 用于在 DEBUG 设置为 False(说明此时是生产模式下,不打印异常栈)时,想由django实现静态文件的访问路由 url(r'^static/(?P<path>.*)$', serve, {"document_root": STATIC_ROOT})

异常

Server Error (500)

出现这种错误,一般是服务器内部,也就是代码出现错误,此时需要设置 DEBUG = True,开启调试模式,打印异常栈才能发现错误的地方。
像我就因为项目中使用了redis服务,但是因为 DEBUG 设置为 False,所以不知道错误在什么地方。

小结

  • 部署上线时,一定要设置DEBUG设置为False,防止错误栈对外输出,造成安全隐患。
  • Django开发模式下提供的静态文件代理服务为我们的开发提供了便利,不用考虑安装的额外app的静态文件的访问问题。
  • DEBUG设置为False后,Django不再提供静态文件代理,需要手动配置。
  • media文件不管在什么环境都需要手动设置,因为Django不提供高media文件的代理服务。
  • Ngingx拥有更高的静态资源处理性能,应该使用这种方式完成静态资源的获取。
  • 一定要使用collectstatic完成所有静态资源的收集,保证所有静态文件的正常访问。此外,collectstatic命令的实现离不开STATIC_ROOT的设置。

参考

uWSGI 文档:https://uwsgi-docs-zh.readthedocs.io/zh_CN/latest/WSGIquickstart.html