介绍
在本指南中,我们将设置一个简单的WSGI应用程序,由uWSGI提供服务。我们将使用Nginx Web服务器作为反向代理到应用服务器,以提供更强大的连接处理。我们将在Ubuntu 14.04服务器上安装和配置这些组件。
定义和概念
澄清一些术语
在我们开始之前,我们应该解释一些与我们将处理的相关概念有关的混淆术语。这三个单独的术语看起来可以互换使用,但实际上具有不同的含义:
- WSGI:一个Python规范,定义了应用程序或框架与应用程序/ Web服务器之间通信的标准接口。这是为了简化和标准化这些组件之间的通信以确保一致性和可互换性而创建的。这基本上定义了一个可以在其他协议上使用的API接口。
- uWSGI:一个应用服务器容器,旨在提供开发和部署 Web 应用程序和服务的完整堆栈。其主要组件是一个应用服务器,可以处理不同语言的应用程序。它使用由 WSGI 规范定义的方法与应用程序通信,并与其他 Web 服务器通过各种其他协议通信。这是将来自传统 Web 服务器的请求转换为应用程序可以处理的格式的组件。
- uwsgi:由 uWSGI 服务器实现的快速、二进制协议,用于与更全面的 Web 服务器通信。这是一种线路协议,而不是传输协议。这是与代理请求到 uWSGI 的 Web 服务器通信的首选方式。
WSGI 应用程序要求
WSGI 规范定义了堆栈的 Web 服务器和应用程序部分之间的接口。在此上下文中,“Web 服务器”指的是 uWSGI 服务器,负责使用 WSGI 规范将客户端请求转换为应用程序。这简化了通信,并创建了松散耦合的组件,因此您可以轻松地交换任一方而不会有太多麻烦。
Web服务器(uWSGI)必须能够通过触发定义的“可调用”向应用发送请求。 可调用只是应用程序的入口点,在这里,Web服务器可以调用带有一些参数的函数。 预期的参数是环境变量字典和Web服务器(uWSGI)组件提供的可调用。
作为响应,应用程序返回一个可迭代对象,该对象将用于生成客户端响应的主体。 它还将调用作为参数接收的Web服务器组件可调用。 触发Web服务器可调用时的第一个参数将是HTTP状态代码,第二个参数将是元组列表,其中每个元组定义要发送回客户端的响应标头和值。
通过在此示例中由uWSGI提供的“Web服务器”组件,我们只需要确保我们的应用程序具有上述描述的特性。 我们还将设置Nginx来处理实际的客户端请求并将其代理到uWSGI服务器。
安装组件
要开始,我们需要在Ubuntu 14.04服务器上安装必要的组件。 我们主要可以使用apt
和pip
来完成这个过程。
首先,刷新您的apt
软件包索引,然后安装Python开发库和头文件,pip
Python软件包管理器,以及Nginx Web服务器和反向代理:
sudo apt-get update
sudo apt-get install python-dev python-pip nginx
一旦包安装完成,您将可以访问pip
Python包管理器。我们可以使用它来安装virtualenv
包,我们将使用它来隔离我们应用程序的Python环境,使其与系统上可能存在的其他环境隔离开来:
sudo pip install virtualenv
一旦完成这一步,我们可以开始创建应用程序的通用结构。我们将创建上面讨论过的虚拟环境,并在该环境中安装uWSGI应用程序服务器。
设置应用程序目录和虚拟环境
我们将首先创建一个用于我们应用的文件夹。这个文件夹可以包含一个嵌套文件夹,其中包含更完整的应用程序代码。对于我们的目的,此目录将简单地保存我们的虚拟环境和我们的WSGI入口点:
mkdir ~/myapp/
接下来,进入该目录,以便为我们的应用程序设置环境:
cd ~/myapp
使用virtualenv
命令创建一个虚拟环境。我们简单地将其命名为myappenv
:
virtualenv myappenv
A new Python environment will be set up under a directory called myappenv
. We can activate this environment by typing:
source myappenv/bin/activate
您的提示符应更改以指示您现在正在虚拟环境中操作。它将类似于这样:
(myappenv)username@host:~/my_app$
如果您希望随时离开此环境,只需输入:
deactivate
如果您已停用您的环境,请重新激活它以继续本指南。
在此环境下,安装的任何Python包都将被包含在此目录结构中。它们不会干扰系统的Python环境。有了这个理解,我们现在可以使用pip
将uWSGI服务器安装到我们的环境中。这个包的名称叫做uwsgi
(这仍然是uWSGI服务器,而不是uwsgi
协议):
pip install uwsgi
您可以通过输入以下命令来验证其是否可用:
uwsgi --version
如果返回一个版本号,则表示uWSGI服务器已经可以使用。
创建一个WSGI应用
接下来,我们将使用之前讨论过的WSGI规范要求创建一个非常简单的WSGI应用程序。为了重申,我们必须提供的应用程序组件应具有以下属性:
- 它必须通过可调用方式提供接口(一个可以调用的函数或其他语言构造)
- 可调用对象必须接受一个包含类似环境变量的键值对的字典和一个在服务器(uWSGI)上可访问的可调用对象作为参数。
- 应用程序的可调用对象应返回一个可迭代对象,该对象将生成要发送给客户端的主体。
- 应用程序应调用Web服务器的可调用对象,传递HTTP状态和请求头。
我们将在我们的应用程序目录中编写一个名为wsgi.py
的文件:
nano ~/myapp/wsgi.py
在这个文件里,我们将创建最简单的符合WSGI标准的应用程序。与所有Python代码一样,请务必注意缩进:
以上代码构成了一个完整的WSGI应用程序。默认情况下,uWSGI将寻找一个名为application
的可调用函数,这就是为什么我们将我们的函数命名为application
的原因。如你所见,它接受两个参数。
第一个参数我们称为environ
,因为它将是一个类似环境变量的键值字典。第二个参数称为start_response
,是应用程序在内部用来引用发送过来的Web服务器(uWSGI)可调用函数的名称。这两个参数名称的选择仅仅是因为它们在定义WSGI交互的PEP 333规范中的示例中的使用。
我们的应用程序必须利用这些信息做两件事。首先,它必须使用收到的可调用函数调用,传递HTTP状态代码和要发送的任何标头。在这种情况下,我们发送了一个“200 OK”的响应,并将Content-Type
标头设置为text/html
。
其次,它需要返回一个可迭代对象作为响应体。在这里,我们只是使用一个包含一个HTML字符串的列表。字符串也是可迭代的,但是在列表中,uWSGI将能够在一次迭代中处理整个字符串。
在现实世界的情境中,这个文件很可能被用作连接到应用程序代码的链接。例如,Django 项目默认包含一个 wsgi.py
文件,它将来自 Web 服务器(uWSGI)的请求转换为应用程序(Django)。简化的 WSGI 接口不管实际应用程序代码有多复杂,都保持不变。这是接口的一个优势。
完成后保存并关闭文件。
为了测试代码,我们可以启动 uWSGI。我们暂时告诉它使用 HTTP 并监听端口 8080
。我们会传递给它脚本的名称(后缀已删除):
uwsgi --socket 0.0.0.0:8080 --protocol=http -w wsgi
现在,如果你在网页浏览器中输入服务器的 IP 地址或域名,后面加上 :8080
,你应该看到我们在 wsgi.py
文件中传递的一级标题文本作为页面内容:
验证这一步骤是否成功后,使用 CTRL-C 停止服务器。
到此为止,我们已经完成了设计我们实际应用程序的工作。如果你愿意,你可以停用我们的虚拟环境:
deactivate
配置 uWSGI 配置文件
在上面的例子中,我们手动启动了 uWSGI 服务器,并在命令行上传递了一些参数。我们可以通过创建配置文件来避免这样做。uWSGI 服务器可以读取各种格式的配置,但为了简单起见,我们将使用 .ini
格式。
为了延续我们迄今所使用的命名,我们将文件称为myapp.ini
,并将其放置在我们的应用程序文件夹中:
nano ~/myapp/myapp.ini
在其中,我们需要创建一个名为[uwsgi]
的部分。此部分是所有配置项的所在地。我们将从标识我们的应用程序开始。uWSGI服务器需要知道应用程序的可调用位置。我们可以提供文件和其中的函数:
[uwsgi]
module = wsgi:application
我们希望将初始uwsgi
进程标记为主进程,然后再生成一些工作进程。我们将从五个工作进程开始:
[uwsgi]
module = wsgi:application
master = true
processes = 5
实际上,我们要更改uWSGI与外部世界通信的协议。当我们测试应用程序时,我们指定了--protocol=http
,以便我们可以在Web浏览器中看到它。由于我们将在uWSGI前配置Nginx作为反向代理,因此我们可以更改这个。Nginx实现了一个uwsgi
代理机制,这是一个快速的二进制协议,uWSGI可以使用它与其他服务器通信。uwsgi
协议实际上是uWSGI的默认协议,因此只需省略协议规范,它就会退回到uwsgi
。
由于我们正在为Nginx设计此配置,因此我们还将更改使用网络端口,并改用Unix套接字。这样更安全、更快。如果我们使用相对路径,套接字将在当前目录中创建。我们将其命名为myapp.sock
。我们将更改权限为“664”,以便Nginx可以写入它(我们将使用Nginx使用的www-data
组启动uWSGI)。我们还将添加vacuum
选项,这将在进程停止时删除套接字:
[uwsgi]
module = wsgi:application
master = true
processes = 5
socket = myapp.sock
chmod-socket = 664
vacuum = true
由于我们将创建一个Upstart文件以在启动时启动应用程序,因此我们需要最后一个选项。Upstart和uWSGI对于SIGTERM信号应如何影响应用程序有不同的看法。为了解决这个不一致,以便可以按预期处理进程,我们只需要添加一个名为die-on-term
的选项,这样uWSGI将杀死进程而不是重新加载它:
[uwsgi]
module = wsgi:application
master = true
processes = 5
socket = myapp.sock
chmod-socket = 664
vacuum = true
die-on-term = true
完成后保存并关闭文件。现在,此配置文件已设置为与Upstart脚本一起使用。
创建一个Upstart文件来管理应用程序
我们可以在启动时启动一个uWSGI实例,以便我们的应用程序始终可用。我们将把它放在Upstart检查的/etc/init
目录中。我们将其命名为myapp.conf
:
sudo nano /etc/init/myapp.conf
首先,我们可以从描述服务开始,并选择应自动运行的系统运行级别。标准用户运行级别为2到5。我们会告诉 Upstart 在此组之外的任何运行级别(例如系统正在重新启动或处于单用户模式时)停止服务:
description "uWSGI instance to serve myapp"
start on runlevel [2345]
stop on runlevel [!2345]
接下来,我们会告诉 Upstart 应以哪个用户和组来运行进程。我们希望在我们自己的帐户下运行应用程序(在本指南中我们使用的是 demo
,但您应该替换为您自己的用户)。然而,我们希望将组设置为 Nginx 使用的 www-data
用户。这是必要的,因为 Web 服务器需要能够读取和写入我们的 .ini
文件将创建的套接字:
description "uWSGI instance to serve myapp"
start on runlevel [2345]
stop on runlevel [!2345]
setuid demo
setgid www-data
接下来,我们将运行实际的命令来启动 uWSGI。由于我们将 uWSGI 安装到了虚拟环境中,我们需要做一些额外的工作。我们可以直接提供 uWSGI 可执行文件的完整路径,但相反,我们将激活虚拟环境。如果我们依赖于在环境中安装的其他软件,这将更容易。
为此,我们将使用一个 script
块。在其中,我们将切换到我们的应用程序目录,激活虚拟环境(在脚本中必须使用 .
而不是 source
),并启动指向我们的 .ini
文件的 uWSGI 实例:
description "uWSGI instance to serve myapp"
start on runlevel [2345]
stop on runlevel [!2345]
setuid demo
setgid www-data
script
cd /home/demo/myapp
. myappenv/bin/activate
uwsgi --ini myapp.ini
end script
有了这些,我们的 Upstart 脚本就完成了。完成后保存并关闭文件。
现在,我们可以通过键入以下命令来启动服务:
sudo start myapp
我们可以通过输入以下命令来验证它是否已启动:
ps aux | grep myapp
demo 14618 0.0 0.5 35868 5996 ? S 15:02 0:00 uwsgi --ini myapp.ini
demo 14619 0.0 0.5 42680 5532 ? S 15:02 0:00 uwsgi --ini myapp.ini
demo 14620 0.0 0.5 42680 5532 ? S 15:02 0:00 uwsgi --ini myapp.ini
demo 14621 0.0 0.5 42680 5532 ? S 15:02 0:00 uwsgi --ini myapp.ini
demo 14622 0.0 0.5 42680 5532 ? S 15:02 0:00 uwsgi --ini myapp.ini
demo 14623 0.0 0.5 42680 5532 ? S 15:02 0:00 uwsgi --ini myapp.ini
demo 15520 0.0 0.0 11740 936 pts/0 S+ 15:53 0:00 grep --color=auto myapp
这将在启动时自动启动。您可以随时通过键入以下内容来停止服务:
sudo stop myapp
配置 Nginx 代理到 uWSGI
此时,我们有一个 WSGI 应用程序,并已验证 uWSGI 能够读取并提供它。我们已创建了一个配置文件和一个 Upstart 脚本。我们的 uWSGI 进程将监听一个套接字,并使用 uwsgi
协议进行通信。
现在我们可以开始配置 Nginx 作为反向代理了。Nginx 有能力使用 uwsgi
协议与 uWSGI 进行通信。这是一个比 HTTP 更快的协议,性能更好。
我们将要设置的 Nginx 配置非常简单。在 Nginx 的配置层次结构中的 sites-available
目录中创建一个新文件。我们将文件命名为 myapp
,以匹配我们一直在使用的应用程序名称:
sudo nano /etc/nginx/sites-available/myapp
在这个文件中,我们可以指定此服务器块应该响应的端口号和域名。在我们的情况下,我们将使用默认端口 80:
server {
listen 80;
server_name server_domain_or_IP;
}
由于我们希望将此域名或 IP 地址上的所有请求发送到我们的 WSGI 应用程序,我们将为以/
开头的请求创建一个单独的位置块,它应该匹配所有内容。在其中,我们将使用include
指令从我们 Nginx 配置目录中的一个文件中包含一些具有合理默认值的参数。包含这些参数的文件称为uwsgi_params
。之后,我们将通过uwsgi
协议将流量传递到我们之前配置的 uWSGI 实例上:
server {
listen 80;
server_name server_domain_or_IP;
location / {
include uwsgi_params;
uwsgi_pass unix:/home/demo/myapp/myapp.sock;
}
}
这实际上是我们一个简单应用程序所需的全部内容。对于一个更完整的应用程序,可以进行一些改进。例如,我们可以在此块之外定义一些上游 uWSGI 服务器,然后将它们传递给它。我们可能会包含一些额外的 uWSGI 参数。我们还可以直接从 Nginx 处理任何静态文件,仅将动态请求传递到 uWSGI 实例。
但在我们的三行应用程序中不需要这些功能,因此我们可以保存并关闭文件。
通过将其链接到sites-enabled
目录来启用我们刚刚创建的服务器配置:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled
检查语法错误的配置文件:
sudo service nginx configtest
如果它返回没有检测到问题,则重新启动服务器以实施更改:
sudo service nginx restart
Nginx 重新启动后,您应该能够转到您服务器的域名或 IP 地址(不带端口号),并看到您配置的应用程序:
结论
如果你已经走到了这一步,你已经创建了一个简单的WSGI应用程序,并且对于设计更复杂的应用程序有一些了解。我们已经将uWSGI应用容器/服务器安装到一个专门的虚拟环境中来提供我们的应用程序服务。我们创建了一个配置文件和一个Upstart脚本来自动化这个过程。在uWSGI服务器前面,我们设置了一个Nginx反向代理,可以使用uwsgi
协议与uWSGI进程进行通信。
当设置一个真正的生产环境时,你可以很容易地看到如何扩展这个过程。例如,uWSGI具有使用称为“emperor mode”的东西来管理多个应用程序的能力。你可以扩展Nginx配置来在uWSGI实例之间进行负载均衡,或者处理应用程序的静态文件。当提供多个应用程序时,根据你的需求,全局安装uWSGI可能更为合适。这些组件都相当灵活,因此你应该能够调整它们的配置来适应许多不同的情况。