Python系列之-Nginx+uWSGI+virtualenv多项目部署

3,731 阅读10分钟

配置virtualenv及Python环境

1、新建独立运行环境,命名为venv

[root@vultr ~]# mkdir projects   # 测试的项目总目录
[root@vultr ~]# pip3 install virtualenv
[root@vultr ~]# cd projects
[root@vultr projects]# virtualenv venv --python=python3 --no-site-packages

--python:指定Python版本
--no-site-packages:不复制系统已安装Python包

2、激活虚拟环境

[root@vultr projects]# source venv/bin/activate

执行后命令提示符前面会出现一个venv,变成(venv)[root@vultr opt]#,退出虚拟环境执行deactivate即可。

3、安装项目依赖:pip3 install, 在虚拟环境中安装的包,不会对系统环境造成影响。

Django项目配置

1、上传Django项目: Hello项目

目录结构:
Hello/
    apps/
    Hello/
    manage.py

2、配置项目的数据库信息:vi Hello/Hello/settings.py

如果是远程服务器,需要修改setting.py文件中的ALLOWED_HOSTS:
ALLOWED_HOSTS = ['*']

3、数据迁移

(venv)[root@vultr Hello]# python3 manage.py makemigrations
(venv)[root@vultr Hello]# python3 manage.py migrate

执行python3 manage.py makemigrations的一些错误处理:
因为版本问题,可能会提示以下两个问题 参考文档:blog.csdn.net/weixin_3312… 问题一:

django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.13 or newer is required; you have 0.9.3.

即使在__init__.py文件里添加了如下语句,依然会提示这个错误,这里有两种办法,一种是使用低版本的Django,一种是修改一些文件,这里采用第二种。如下:

#__init__.py
import pymysql
pymysql.install_as_MySQLdb()
[root@vultr ~]# vi /opt/MiniProgram/Notes/venv/lib/python3.6/site-packages/django/db/backends/mysql/base.py  # 路径可能不同,看提示信息

将以下语句注释:
if version < (1, 3, 3):
     raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)

继续执行python3 manage.py makemigrations还有一个错误:

AttributeError: ‘str’ object has no attribute ‘decode’

这里,修改修改operations.py,同样在错误信息前有这个文件的路径

[root@vltur ~]# vi /opt/MiniProgram/Notes/venv/lib/python3.6/site-packages/django/db/backends/mysql/operations.py

将以下内容
query = getattr(cursor, '_executed', None)
if query is not None:
    query = query.decode(errors='replace')
return query

将query.decode修改为query.encode
query = query.encode(errors='replace')

4、收集静态文件:vi Hello/Hello/settings.py

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

:wq保存后,执行

(venv)[root@vultr Hello]# python3 manage.py collectstatic --noinput

5、用runserver启动项目,看是否正常运行

(venv)[root@vultr Hello]# python3 manage.py runserver 0.0.0.0:8088

uWSGI配置

deactivate退出虚拟环境
1、安装uWSGI

[root@vultr Hello]# pip3 install uWSGI

2、命令行运行测试
项目目录Hello下,执行以下命令:

[root@vultr Hello]# uwsgi --http ip:端口 --home /root/env/ --file Hello/wsgi.py --static-map=/static=static

--home:指定虚拟环境的目录
wsgi.py:Django创建项目时生成的文件

如果访问URL正常,说明Python虚拟环境和uWSGI没有问题.
说明: 使用阿里云服务器ECS,这里执行不通过,ip使用公网IP和私网IP都提示bind(): Cannot assign requested address [core/socket.c line 769]。

3、使用ini配置文件来启动uWSGI
我习惯性创建projects目录,目录结构如下:

/root/projects/
            script/     --> 存放uWSGI相关的文件,例如uwsgi.ini, uwsgi.pid...
            Hello/  --> 项目目录
                    apps/  --> 应用程序目录
                    Hello/  --> settings.py等文件所在目录
                    static/
            env/  --> 虚拟环境目录
[root@vultr projects]# vi script/uwsgi.ini

[uwsgi]
# 项目目录
chdir=/root/projects/Hello/
# 虚拟环境目录
home=/root/projects/venv/
# 启动uwsgi的用户名和用户组
uid=root
gid=root
# 指定项目的application
module=Hello.wsgi:application
# 指定sock的文件路径
socket=/root/projects/script/uwsgi.sock
# 启用主进程
master=true
# 进程个数
workers=5
pidfile=/root/projects/script/uwsgi.pid
# 自动移除unix Socket和pid文件当服务停止的时候
vacuum=true
# 序列化接受的内容,如果可能的话
thunder-lock=true
# 启用线程
enable-threads=true
# 设置自中断时间
harakiri=30
# 设置缓冲
post-buffering=4096
# 设置日志目录
daemonize=/root/projects/script/uwsgi.log

4、后台启动停止uWSGI的命令

[root@vultr projects]# uwsgi --ini script/uwsgi.ini  # 启动
[root@vultr projects]# uwsgi --stop script/uwsgi.pid # 停止

5、可能出现的问题 问题一:

no /etc/mime.types file found  # 缺少文件

解决:

[root@vltur ~]# wget http://mirror.centos.org/centos/6/os/x86_64/Packages/mailcap-2.1.31-2.el6.noarch.rpm
[root@vltur ~]# rpm -ivh mailcap-2.1.31-2.el6.noarch.rpm

问题二:

no internal routing support, rebuild with pcre support

解决:

[root@vltur ~]# pip3 uninstall uwsgi
[root@vltur ~]# yum install -y pcre pcre-devel
[root@vltur ~]# pip3 install uwsgi -I --no-cache-dir 

-I 强制重新安装,不使用缓存里的, 不加-I --no-cache-dir的话,重新安装还是提示这个。

问题三:

bind(): Cannot assign requested address [core/socket.c line 769]

参考文档:blog.csdn.net/JT31520/art… 这是用阿里云可能会出现的问题,翻译为无法分配请求的地址。但我不在uwsgi.ini指定ip和端口,我是在Nginx指定的,所以这个错误对我后面搭配Nginx不影响,只是单独测试uWSGI时有问题。

Nginx配置

1、源码安装

  1. 装相关的库
[root@vultr ~]# yum -y install make zlib zlib-devel gcc-c++ libtool  openssl openssl-devel
  1. 安装PCRE
[root@vultr ~]# wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.43.tar.gz
[root@vultr ~]# tar -zxvf pcre-8.43.tar.gz
[root@vultr ~]# cd pcre-8.43
[root@vultr ~]# ./configure --prefix=/usr/local/pcre
[root@vultr ~]# make && make install
[root@vultr ~]# /usr/local/pcre/bin/pcre-config --version   # 查看是否安装成功
8.43  # 出现8.43说明已经安装成功
  1. 下载并解压Nginx安装包
[root@vultr ~]# wget http://nginx.org/download/nginx-1.16.0.tar.gz
[root@vultr ~]# tar -zxvf nginx-1.16.0.tar.gz
[root@vultr ~]# cd nginx-1.16.0
[root@vultr ~]# ./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-pcre=/root/pcre-8.43
[root@vultr ~]# make && make install
[root@vultr ~]# /usr/local/nginx/sbin/nginx -v   # 查看Nginx版本,如果显示了版本说明安装正常

注意:--with-pcre=/root/pcre-8.43需要改成自己下载解压的pcre源码目录。

3、/usr/local/nginx/conf/nginx.conf下添加,是在http{}里

[root@vultr projects]# vi /usr/local/nginx/conf/nginx.conf  

server {
    listen 84;  # 端口
    server_name 10.129.205.183 ;  # 域名
    access_log  /usr/local/nginx/logs/access.log  main;
    charset  utf-8;
    gzip on;
    gzip_types text/plain application/x-javascript text/css text/javascript application/x-httpd-php application/json text/json image/jpeg image/gif image/png application/octet-stream;
    
    error_page  404           /404.html;
    error_page   500 502 503 504  /50x.html;
    # 指定项目路径uwsgi
    location / {
        include uwsgi_params; # 加载nginx和uwsgi的通信协议模块
        uwsgi_connect_timeout 30; # 超时时间
        uwsgi_pass unix:/root/projects/script/uwsgi.sock;
    }
    # 指定静态文件路径
    location /static/ {
    alias  /root/projects/Hello/static/;
    index  index.html index.htm;
    }
}

启动Nginx可能有的问题, 指的是**access_log /usr/local/nginx/logs/access.log main;**有错:

nginx: [emerg] unknown log format "proxy_log" in /usr/local/macports/etc/nginx/nginx.conf:147

解决,在HTTP配置的部分,去掉log_format的注释,如下:

http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                     '$status $body_bytes_sent "$http_referer" '
                     '"$http_user_agent" "$http_x_forwarded_for"';
...后面省略

3、常用命令

[root@vultr ~]# /usr/local/nginx/sbin/nginx -s reload  # 重新载入配置文件
[root@vultr ~]# /usr/local/nginx/sbin/nginx -s reopen  # 重启Nginx
[root@vultr ~]# /usr/local/nginx/sbin/nginx            # 启动Nginx
[root@vultr ~]# /usr/local/nginx/sbin/nginx -s stop    # 关闭Nginx

也可以将路径添加到$PATH里。

4、启动与停止Nginx

  1. 检查uWSGI是否启动了
[root@vultr projects]# ps -ef | grep uwsgi
root      2299     1  0 06:22 ?        00:00:00 uwsgi --ini script/uwsgi.ini
root      2301  2299  0 06:22 ?        00:00:00 uwsgi --ini script/uwsgi.ini
root      2302  2299  0 06:22 ?        00:00:00 uwsgi --ini script/uwsgi.ini
root      2303  2299  0 06:22 ?        00:00:00 uwsgi --ini script/uwsgi.ini
root      2304  2299  0 06:22 ?        00:00:00 uwsgi --ini script/uwsgi.ini
root      2305  2299  0 06:22 ?        00:00:00 uwsgi --ini script/uwsgi.ini
root      2306  2299  0 06:22 ?        00:00:00 uwsgi --ini script/uwsgi.ini
root      2361  2016  0 06:32 pts/1    00:00:00 grep uwsgi
  1. 启动Nginx
[root@vultr projects]# /usr/local/nginx/sbin/nginx 
  1. 访问URL,见证奇迹的时刻到了,然后...

ok,报错了,莫慌。度娘查了502是服务器错误,然而前面测试了Django+uWSGI没问题,所以最有可能是在Nginx出错了。

来,我们查看一下Nginx的错误日志文件,日志文件在哪呢???
如果是源码编译安装,那错误日志在/usr/local/nginx/logs/error.log,如果是yum安装,通过以下方式查找。

[root@vultr projects]# find / -name nginx.conf
/etc/nginx/nginx.conf
[root@vultr projects]# vi /etc/nginx/nginx.conf

error_log参数就是错误日志文件了,让我们再打开error.log文件,找到最后一条记录:

2019/05/12 06:41:43 [crit] 1514#1514: *2 connect() to unix:/root/projects/script/uwsgi.sock failed (13: Permission denied) while connecting to upstream, ...(后面省略)

failed (13: Permission denied) while connecting to upstream可以看出是连接uwsgi.sock没有权限???原因是我贪图方便,直接把项目文件以及uWSGI文件放在了/root/目录下!!!

好,修改路径,先停止Nginx和uWSGI,再修改路径/root/projects/更改为/opt/projects/:

[root@vultr projects]# uwsgi --stop script/uwsgi.pid 
[root@vultr projects]# /usr/local/nginx/sbin/nginx -s stop
停止 nginx:                                               [确定]
[root@vultr projects]# cd ..
[root@vultr ~]# mv projects /opt/
[root@vultr ~]# cd /opt/projects/

然后将script/uwsgi.ini和/usr/local/nginx/conf/redis.conf中关于路径的都修改过来,修改好后,再次启动uWSGI和Nginx:

[root@vultr projects]# uwsgi --ini script/uwsgi.ini 
[uWSGI] getting INI configuration from script/uwsgi.ini
[root@vultr projects]# /usr/local/nginx/sbin/nginx
正在启动 nginx:                                           [确定]

再次访问URL, 访问正常。

多项目部署

利用virtualenv可以在服务器上配置多个Python运行环境,因此根据Nginx、uWSGI、virtualenv可以实现一个服务器上运行多个项目,且互不干扰。

首先我们先来了解一下Nginx+uWSGI通信原理。

请求首先交给Nginx,如果是静态内容Nginx就直接处理了,如果是动态内容就将请求交给uWSGI服务器,Nginx和uWSGI之间是通过Socket来通信的,通信协议就是/etc/nginx/conf.d/Hello.conf里配置的uwsgi_params文件。

那么,现在我们来梳理一下,Nginx是怎么知道uWSGI在哪里?通过什么和uWSGI做Socket通信,回看/etc/nginx/conf.d/Hello.conf文件:

原来是根据uwsgi_pass指定了Nginx与uWSGI通信的Socket文件路径,看到这,就知道好办了,一个项目配置一个uwsgi.ini文件和nginx.conf里的一个server,那既然需要部署多个项目,那就是多个uwsgi.ini和nginx.conf里的多个server。

好的,我们开始测试:
1、配置虚拟环境以及测试用runserver运行Django项目是否正常。 我的目录结构是:

opt/
    projects/
        Hello/   --> 第一个Django项目
        venv/     --> 第一个Django项目的虚拟环境
        World/   --> 第二个Django项目
        venv_1/   --> 第二个Django项目的虚拟环境
        script/  --> uwsig.ini等文件存放

2、配置World项目的uwsgi_w.ini文件

[root@vultr projects]# vi script/uwsgi_w.ini

[uwsgi]
# 项目目录
chdir=/opt/projects/World/
# 虚拟环境目录
home=/opt/projects/venv_1/
# 启动uwsgi的用户名和用户组
uid=root
gid=root
# 指定项目的application
module=World.wsgi:application
# 指定sock的文件路径
socket=/opt/projects/script/uwsgi_w.sock
# 启用主进程
master=true
# 进程个数
workers=5
pidfile=/opt/projects/script/uwsgi_w.pid
# 自动移除unix Socket和pid文件当服务停止的时候
vacuum=true
# 序列化接受的内容,如果可能的话
thunder-lock=true
# 启用线程
enable-threads=true
# 设置自中断时间
harakiri=30
# 设置缓冲
post-buffering=4096
# 设置日志目录
daemonize=/opt/projects/script/uwsgi_w.log

3、配置Nginx

[root@vultr projects]# vi /usr/local/nginx/conf/nginx.conf

server {
    listen 84;  # 端口,请注意端口
    server_name 10.129.205.183 ;  # 域名
    access_log  /usr/local/nginx/logs/access.log  main;
    charset  utf-8;
    gzip on;
    gzip_types text/plain application/x-javascript text/css text/javascript application/x-httpd-php application/json text/json image/jpeg image/gif image/png application/octet-stream;

    error_page  404           /404.html;
    error_page   500 502 503 504  /50x.html;
    # 指定项目路径uwsgi
    location / {
        include uwsgi_params; # 加载nginx和uwsgi的通信协议模块
        uwsgi_connect_timeout 30; # 超时时间
        uwsgi_pass unix:/opt/projects/script/uwsgi.sock;
    }
    # 指定静态文件路径
    location /static/ {
    alias  /opt/projects/Hello/static/;
    index  index.html index.htm;
    }
}

server {
    listen 86;  # 端口,请注意端口
    server_name 10.129.205.183 ;  # 域名
    access_log  /usr/local/nginx/logs/access.log  main;
    charset  utf-8;
    gzip on;
    gzip_types text/plain application/x-javascript text/css text/javascript application/x-httpd-php application/json text/json image/jpeg image/gif image/png application/octet-stream;

    error_page  404           /404.html;
    error_page   500 502 503 504  /50x.html;
    # 指定项目路径uwsgi
    location / {
        include uwsgi_params; # 加载nginx和uwsgi的通信协议模块
        uwsgi_connect_timeout 30; # 超时时间
        uwsgi_pass unix:/opt/projects/script/uwsgi_w.sock;
    }
    # 指定静态文件路径
    location /static/ {
    alias  /opt/projects/World/static/;
    index  index.html index.htm;
    }
}

4、启动uWSGI和Nginx,访问两个端口的URL。 ok,访问正常。

一些问题:
问题一:

error] 959#0: *116 open() "/usr/local/nginx/html/favicon.ico" failed (2: No such file or directory), client: 111.68.59.75, server: 127.0.0.1, request: "GET /favicon.ico HTTP/1.1"  

参考文档:blog.csdn.net/ygm_linux/a… 解决, 关闭favicon.ico不存在时记录日志, 在server{}中添加:

location /favicon.ico {
    log_not_found off;
    access_log off;
}

添加ssl证书

阿里云有免费的个人用ssl证书,可以去申请一个,把个人网站提升为HTTPS,具体怎么申请请Google吧。
1、购买ssl证书,并下载pem和key文件,将.pem和.key放置到/usr/local/nginx/cert下
2、修改nginx.conf配置文件,在HTTP{}里添加如下配置

server {
    listen 443 ssl;
    server_name localhost;
    ssl_certificate   /usr/local/nginx/cert/2353008_www.incisor.cn.pem;
    ssl_certificate_key  /usr/local/nginx/cert/2353008_www.incisor.cn.key;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    location / {
     root html;
     index index.html index.htm;
    }
}

443是HTTPS的默认端口,我的博客就在这个端口。如果其他端口需要添加ssl证书,就按照这个样式添加,这些都是ssl相关配置,比如:

server {
    listen 84 ssl;
    ......省略
    ssl_certificate   /usr/local/nginx/cert/2353008_www.incisor.cn.pem;
    ssl_certificate_key  /usr/local/nginx/cert/2353008_www.incisor.cn.key;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ......省略
}