使用uWSGI Web服务器和Nginx部署Python WSGI应用

提供:ZStack云计算

前言

我们在之前的《Python Web服务器对比》文章中介绍过,uWSGI是一个宏大的项目,它能做的事情远远超过提供Web服务。不过很多Web应用的部署仍然青睐于它,因为它功能丰富,容易配置,而且跟Nginx配合的很好。

本文将深入介绍uWSGI的安装,以及在上面部署各种Python框架应用的步骤,无论是中小规模的部署还是生产环境的大规模部署都有所涉及。

本文还将同时介绍Nginx的相关配置(以及原理)。Nginx与uWSGI能够默契配合,针对大多数应用部署都是极好的选择。

了解uWSGI和Nginx的用法

uWSGI是一个充满野心的项目,它提供了各式各样的工具,其功能远远超过了Web应用托管的需求。多年以来,它已经成为了很多系统管理员和开发者们必不可少的工具之一。

Nginx自从0.8.40版本以来开始支持uwsgi协议,从此,在uWSGI上运行的WSGI应用就可以以最佳的方式与Nginx进行通讯,在配置的灵活性和性能方面都十分出色,使得该组合成为很多部署场景下的极佳选择。

uWSGI概述

以下是从《Python Web服务器对比》一文中摘抄的段落:

“虽然uWSGI的命名规范有点让人摸不着头脑,不过它是一个包含众多组件的大项目,致力于为托管服务提供一整套解决方案。uWSGI server作为其组件之一提供了运行Python WSGI应用的功能,该组件支持多种协议,包括其自家的uwsgi wire协议——可以说是SCGI的对应。应对在应用服务器之前使用独立HTTP服务器的需求,NGINX和Cherokee web服务器进行了模块化以支持uWSGI自家的uwsgi协议(该协议在uWSGI上是性能最好的),如此可以直接控制其进程。”

uWSGI的亮点

  • uWSGI提供了WSGI适配器,如此可以完全支持运行在USGI上的Python应用。
  • uWSGI提供了libpython。它在启动时加载应用代码并提供Python解释器的功能,对进来的请求进行语法分析以调用Python callable。
  • uWSGI内置NGINX支持(以及Cherokee+和lighttpd支持)。
  • uWSGI采用C语言编写。
  • 由于uWSGI组件众多,所以如果需要扩展功能的时候会比较方便。
  • 项目处于活跃状态,更新周期很快。
  • 它提供了不同的应用引擎(异步、同步都支持)。
  • 它占用的内存比较少。

配合Nginx进行Web应用部署

Nginx是一个高性能的web服务器/代理服务器/反向代理服务器,因为其轻量、易用和可扩展性(插件)而广受欢迎。其架构的设计使得它具备很强的请求处理能力(理论上是无限的),以至于一些大流量的网站几乎没有其他选择。

注:“处理”连接的技术含义是不丢弃并返回一些结果,一次成功的返回也可能是一条错误信息。要返回用户期待的结果,还需要应用和数据库方面能够保持正常工作。

uWSGI配合Nginx反向代理

很多框架和应用服务器都可以对静态文件的请求(如JavaScript、CSS、图片等)返回来自应用的响应,不过更好的做法是用反向代理服务器(比如Nginx)来处理此类请求,减轻应用服务器的负载,获得更好的性能。

随着应用扩大,可能会需要分布到不同的服务器(云主机)上,此类需求在早期可以用反向代理实现。

此外,Nginx的可扩展性(除了缓存之外,还有failover等其他机制)对于一些比较复杂的Web应用也很有帮助。

一个简单的服务器架构:

客户端请求      ----> Nginx (反向代理)
                        |
                       /|\                           
                      | | `-> App. Server I.   127.0.0.1:8081
                      |  `--> App. Server II.  127.0.0.1:8082
                       `----> App. Server III. 127.0.0.1:8083

注:只监听127.0.0.1的应用只能从本地访问,而如果设置成0.0.0.0就能够从外部访问了。

针对生产环境预备云主机

云主机的预备将执行如下步骤:

  • 更新操作系统
  • 下载安装通用Python工具(pip、virtualenv等)
  • 创建应用运行的虚拟环境(提供了uWSGI等依赖)

有关Python工具的安装可参考这篇文章。

更新操作系统

以下操作将在一台新建的云主机上进行。如果你打算在一台经常使用的云主机上操作,我的建议是切换到一台新主机上。

Debian类系统(Ubuntu、Debian等)的更新:

aptitude    update
aptitude -y upgrade

RHEL类系统的更新:

yum -y update

安装配置Python、pip和virtualenv

CentOS/RHEL系统默认状态下是比较裸的,大部分工具都需要自己安装(比如通过yum)。这里有一篇文章介绍如何在CentOS 6.4和5.8上安装Python 2.7.6和3.3.3。

Ubuntu和Debian的最新版本默认包含了Python解释器,我们只需要安装下面的这些软件包就行:

  • python-dev(开发工具)
  • pip(包管理工具)
  • virtualenv(用于创建隔离的虚拟环境)

python-dev

python-dev是系统级的软件包,包含了用于构建Python模块的开发工具。

使用aptitude进行安装:

aptitude install python-dev

pip

pip软件包管理器可用于安装我们需要的应用。

运行如下命令以安装pip(需要sudo权限):

curl https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py | python -
curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python -
export PATH="/usr/local/bin:$PATH"

virtualenv

每个Python应用最好是跟它的依赖项一起在一个独立的环境下运行。针对一个环境,最简单的描述方式就是一个独立的路径。virtualenv就是应对环境管理的需求而诞生的工具。

使用pip安装virtualenv:

sudo pip install virtualenv

创建独立的虚拟(Python)环境

我们将创建一个环境用于我们的应用部署。如果你之前在本地开发机上的Python应用还没有设置过virtualenv,我建议你创建一个环境,将你的应用以及其依赖项都转移到该环境下。

首先创建一个目录。目录的命名按照自己的需求来,这里使用my_app作为命名。

mkdir my_app

进入该目录,创建一个新的虚拟环境。这里使用my_app_venv作为该环境的命名,你可以根据自己的需要进行命名:

cd my_app
virtualenv my_app_venv

创建一个目录用于存放Python应用模块:

mkdir app

在虚拟环境中激活解释器:

source my_app_venv/bin/activate

至此,你的目录结构应该是这样的:

my_app              # 主目录
  |
  |=== my_app_venv  # 虚拟环境目录,包含Python解释器
  |=== app          # 应用模块所在目录
  |..
  |.

下载安装uWSGI

应用管理的最佳实践之一是在虚拟环境下包含所有的应用依赖项,所以我们之后的下载安装都要在刚才创建的环境中进行。

在刚才的虚拟环境目录下执行安装操作,软件会安装在该虚拟环境下。如果执行安装操作时不在任何虚拟环境下,则会全局生效,而这是我们不想要的。请随时记得使用virtualenv。

用pip安装uWSGI:

pip install uwsgi

下载安装Nginx

CentOS/RHEL用户可参考这篇文章用yum安装Nginx。Ubuntu/Debian用户请执行如下命令(也可参考这篇文章):

sudo aptitude install nginx

然后启动nginx:

sudo service nginx start

Nginx服务可通过如下命令停止:

sudo service nginx stop

以及重启(每次更改Nginx配置后都需要重启):

sudo service nginx restart

用uWSGI托管Python WSGI应用

uWSGI的用法跟其他应用容器没有太大差别,它需要你的应用提供一个接入点(callable)。在启动时,这个callable与其他的配置变量一起被传递给uWSGI,然后uWSGI开始工作。当请求到达uWSGI时,它对请求进行处理,然后将结果传递给应用控制器。

架构:

........
      /|\                           
     | | `-> App. Server I.   127.0.0.1:8080  <--> Application
     |  `--> App. Server II.  127.0.0.1:8081  <--> Application
      .....

WSGI

WSGI是web服务器和应用之间的一个接口,以确保不同的服务器和应用(框架)之间的通讯满足一组标准,从而满足兼容性的需求(比如从生产环境切换到生产环境的场景)。这在现代软件开发中很有必要。(有关WSGI的详细说明可参考这篇文章。)

WSGI应用对象(Callable):“wsgi.py”

如上所述,运行在WSGI上的Web服务器需要一个应用对象(也就是你的应用的callable)。大部分框架和应用会包含一个wsgi.py作为这个对象。

下面,我们会创建一个wsgi.py,将其导入应用以提供给uWSGI。你也可以使用其他的文件名,不过wsgi.py是最常用的(比如Django使用的就是这个文件名。)

创建wsgi.py文件(我使用nano,你可以使用自己习惯的编辑器):

nano wsgi.py

将基本的WSGI应用代码复制粘贴到该文件内(此处是最基本的示范文件,到时候你需要把自己应用的内容替换进来):

def application(env, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return ["Hello!"]

每次请求到达服务器时,服务器在解析URL的时候(比如 mysite.tld/controller/method/variable)都会使用这个callable来运行应用的请求处理器(也就是控制器)。

代码粘贴完毕后,保存退出。至此,你的应用目录应该是如下结构:

my_app              # Main Folder to Contain Everything Together
  |
  |=== my_app_venv  # V. Env. folder with the Python Int.
  |=== app          # Your application module
  |
  |--- wsgi.py      # File containing application callable
  |..
  |.

运行服务器

uWSGI的配置项众多,我们先从最简单的配置开始。

uWSGI的常规用法如下:

uwsgi [option] [option 2] .. -w [wsgi file with app. callable]

从wsgi.py运行uWSGI:

uwsgi --socket 127.0.0.1:8080 --protocol=http -w wsgi

该命令会在前台运行服务,用CTRL+C快捷键停止。后台运行的命令如下:

uwsgi --socket 127.0.0.1:8080 --protocol=http -w wsgi &

注意:稍后配置Nginx的时候需要移除--protocol=http这一部分,否则Nginx和uWSGI之间会无法通讯。

应用在后台运行之后,我们需要进程管理器来对其进行管理。

管理uWSGI服务器进程

进程管理的命令列表如下:

  • SIGHUP -HUP 正常重启worker和应用
  • SIGTERM -TERM 强制重启
  • SIGINT -INTSIGQUIT -QUIT 立刻终止所有worker
  • SIGUSR1 -USR1 输出状态数据(stdout)
  • SIGUSR2 -USR2 输出worker状态
  • SIGURG -URG 从快照恢复
  • SIGTSTP -TSTP 暂停或恢复一个实例的运行
  • SIGWINCH -WINCH 唤醒之前被syscall阻塞的worker

示范:用SIGHUP重启服务

该命令按正常方式重启服务,即,它会等待worker完成当前的任务,然后将其终止。下次启动时会继承本次的配置。

kill -HUP [PID]

你也可以不使用PID,而使用pidfile选项通过文件控制进程。

示范:用SIGINT停止服务

服务和进程的停止需要INT信号,该命令会终止所有后台中运行的进程。

kill -INT [PID]

配置Nginx

接下来我们将配置Nginx以使其与uWSGI服务器对话。打开nginx.conf文件进行编辑:

sudo nano /etc/nginx/nginx.conf

将如下内容复制粘贴到文件中以启用反向代理功能。(有关SSL证书的配置可参考这篇文章。)

针对Web应用的配置示范:

worker_processes 1;

events {

    worker_connections 1024;

}

http {

    sendfile on;

    gzip              on;
    gzip_http_version 1.0;
    gzip_proxied      any;
    gzip_min_length   500;
    gzip_disable      "MSIE [1-6]\.";
    gzip_types        text/plain text/xml text/css
                      text/comma-separated-values
                      text/javascript
                      application/x-javascript
                      application/atom+xml;

    # Configuration containing list of application servers
    upstream uwsgicluster {

        server 127.0.0.1:8080;
        # server 127.0.0.1:8081;
        # ..
        # .

    }

    # Configuration for Nginx
    server {

        # Running port
        listen 80;

        # Settings to by-pass for static files 
        location ^~ /static/  {

            # Example:
            # root /full/path/to/application/static/file/dir;
            root /app/static/;

        }

        # Serve a static file (ex. favico) outside static dir.
        location = /favico.ico  {

            root /app/favico.ico;

        }

        # Proxying connections to application servers
        location / {

            include            uwsgi_params;
            uwsgi_pass         uwsgicluster;

            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;

        }
    }
}

保存退出,运行如下命令重启Nginx以激活变更的配置:

sudo service nginx stop
sudo service nginx start

有关Nginx配置的更多说明,可参考这篇文章。

配置uWSGI

uWSGI在启动时有多种方法接收必要的配置(如运行在哪个socket、进程的数量、主进程设置等),下面将介绍其中三种:

  • 以参数方式传递配置变量
  • 使用.ini文件
  • 使用.json文件

注:uWSGI还提供了若干协议以将这些配置文件转换成stdin和HTTP。

方案1:以参数方式传递配置变量

最基础的方法,但容易造成混乱且不易管理。该方法的使用就跟其他shell脚本一样,将配置作为参数放到命令中。

用法:

# uwsgi [option] [option 2] .. -w [wsgi.py with application callable]

# Simple server running *wsgi*
uwsgi --socket 127.0.0.1:8080 -w wsgi

# Running Pyramid (Paster) applications
uwsgi --ini-paste production.ini

# Running web2py applications
uwsgi --pythonpath /path/to/app --module wsgihandler

# Running WSGI application with specific module / callable names
uwsgi --module wsgi_module_name --callable application_callable_name
uwsgi -w wsgi_module_name:application_callable_name

更完整的列表可参考其官方文档页面。

方案2:使用.ini文件

使用.ini文件的方案可能比第一种好一些,此类文件的结构很容易理解,只需要每次uWSGI启动脚本执行的时候一起加载即可生效。

.ini文件示范(example_config.ini):

[uwsgi]
# -------------
# Settings:
# key = value
# Comments >> #
# -------------

# socket = [addr:port]
socket = 127.0.0.1:8080

# Base application directory
# chdir = /full/path
chdir  = /my_app

# WSGI module and callable
# module = [wsgi_module_name]:[application_callable_name]
module = app:application

# master = [master process (true of false)]
master = true

# processes = [number of processes]
processes = 5

..

用法:

uwsgi --ini example_config.ini    

方案3:使用.json文件

Working with .json files is, apart from the structure, same as the examples shown above.

跟上面的用法差不多,只是文件结构不一样。

.json文件示范(example_config.json):

{
    "uwsgi": {
        "socket": ["127.0.0.1:8080"],
        "module": "my_app:app",
        "master": true,
        "processes": 5,
    }
}

用法:

uwsgi --json example_config.json

有关uWSGI配置的更多信息可参考其官方文档页面。

uWSGI的常用配置

uWSGI默认是可以通吃各种框架/应用/平台的,不过有时候我们可能还是需要根据自己的需求指定一些配置。

根据uWSGI文档的说明,可能的配置组合几乎是无限的,本文只能讨论一些最常用的或者最关键的部分,并对其实现进行说明。

下面的示范是配合.ini文件使用的。使用.json文件的同学需要将其修改成json的格式。

Sockets

http-socket

设定uWSGI绑定的HTTP socket,如:

http-socket = :8080

socket

通过默认协议将uWSGI绑定到特定的socket,如:

socket = 127.0.0.1:8080

processes (workers)

设定可用于处理请求的进程数,如:

processes = 5

protocol

默认协议是uwsgi,这个配置项可以对其进行修改,如:

protocol = http

管理

master

用于启用或禁用uWSGI主进程。主进程可对其他接收请求的进程进行管理,这样有很多好处,比如无需打扰sockets就能优雅的重启workers,从而避免当机时间。配置方法:

master = true

max-requests

设定最大可以处理的请求数。如果你担心内存泄露而又没有更好的预防办法,可以考虑这个配置项。用法:

max-requests = 1001

threads

设定每个进程内运行的线程数量(rethread模式)。可以配合processes设置项使用以调整并发的规模。用法:

threads = 2

日志

disable-logging

关闭日志功能。用法:

disable-logging = true

uWSGI processes

procname

可自行设定进程名称,如:

procname = My Application

uid

指定uWSGI服务器运行的用户uid,如:

uid = 1001

gid

指定uWSGI服务器运行的用户组gid,如:

gid = 555

vacuum

退出时移除所有生成的pidfile/socket。

daemonize

将uWSGI设置为守护进程,并将信息写入指定的参数(也就是日志文件)。用法:

daemonize = /tmp/uwsgi_daemonize.log

pidfile

让uWSGI将进程的PID写入指定文件。对于uWSGI进程管理很有用。用法:

pidfile = /tmp/proj_pid.pid

其他

harakiri

设置一个进程在被终止或回收(一般由内存管理或进程管理服务触发)之前被允许存活的最大时间。用法:

harakiri = 30

针对上百个配置项的完整配置说明请参考官方文档。

延伸阅读:

防火墙

  • Setting up a firewall using IP Tables

SSH加固

  • How To Protect SSH with fail2ban on Ubuntu
  • How To Protect SSH with fail2ban on CentOS 6

创建报警

  • How To Send E-Mail Alerts on a CentOS VPS for System Monitoring

每日的访问日志监控

  • How To Install and Use Logwatch Log Analyzer and Reporter

本文来源自DigitalOcean Community。英文原文:How to Deploy Python WSGI Applications Using uWSGI Web Server with Nginx by O.S. Tezer

翻译:lazycai

你可能感兴趣的:(使用uWSGI Web服务器和Nginx部署Python WSGI应用)