如何在CentOS7上设置uWSGI和Nginx以服务Python应用程序
介绍
在本指南中,我们将设置一个由 uWSGI 服务的简单 WSGI 应用程序。 我们将使用 Nginx Web 服务器作为应用程序服务器的反向代理,以提供更强大的连接处理。 我们将在 CentOS 7 服务器上安装和配置这些组件。
定义和概念
澄清一些条款
在我们开始之前,我们应该解决一些与我们将要处理的相互关联的概念相关的令人困惑的术语。 这三个独立的术语看起来可以互换,但实际上具有不同的含义:
- 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 服务器。
安装组件
首先,我们需要在 CentOS 7 服务器上安装必要的组件。 我们主要可以使用 yum
和 pip
来做到这一点。
首先,我们需要安装 EPEL 存储库,以便我们可以访问更广泛的包。 我们可以通过键入以下命令在单个 yum
命令中轻松完成此操作:
sudo yum install epel-release
现在,我们可以安装我们的组件了。 我们需要获取 Python 开发库和头文件、pip
Python 包管理器以及 Nginx Web 服务器和反向代理。 我们还需要一个编译器来立即构建 uWSGI 二进制文件:
sudo yum install python-pip python-devel nginx gcc
包安装完成后,您将可以访问 pip
Python 包管理器。 我们可以使用它来安装 virtualenv
包,我们将使用它来隔离我们应用程序的 Python 环境与系统上可能存在的任何其他环境:
sudo pip install virtualenv
一旦完成,我们就可以开始为我们的应用程序创建一般结构。 我们将创建上面讨论的虚拟环境,并将在此环境中安装 uWSGI 应用程序服务器。
设置 App Directory 和 Virtualenv
我们将从为我们的应用程序创建一个文件夹开始。 这可以包含一个嵌套文件夹,其中包含更完整应用程序中的实际应用程序代码。 出于我们的目的,这个目录将简单地保存我们的虚拟环境和我们的 WSGI 入口点:
mkdir ~/myapp/
接下来,进入目录,以便我们可以为我们的应用程序设置环境:
cd ~/myapp
使用 virtualenv
命令创建虚拟环境。 为简单起见,我们将其称为 myappenv
:
virtualenv myappenv
在名为 myappenv
的目录下将建立一个新的 Python 环境。 我们可以通过键入以下内容来激活此环境:
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) 作为参数。
- 应用程序的可调用对象应返回一个可迭代对象,该可迭代对象将生成发送给客户端的主体。
- 应用程序应该使用 HTTP 状态和请求标头调用 Web 服务器的可调用对象。
我们将在我们的应用程序目录中的一个名为 wsgi.py
的文件中编写我们的应用程序:
nano ~/myapp/wsgi.py
在这个文件中,我们将创建最简单的 WSGI 兼容应用程序。 与所有 Python 代码一样,请务必注意缩进:
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return ["<h1 style='color:blue'>Hello There!</h1>"]
上面的代码构成了一个完整的 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
现在,如果您在 Web 浏览器中访问服务器的 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 套接字。 这更安全、更快捷。
我们将指定我们自己的用户名来运行 uwsgi
服务器并拥有套接字文件。 我们将在 /run
下创建一个目录来放置套接字文件,以便 uWSGI 和 Nginx 都可以访问它。 我们将调用套接字本身 myapp.sock
。 我们将权限更改为“664”,以便 Nginx 可以对其进行写入(我们将使用 Nginx 使用的 www-data
组启动 uWSGI。 我们还将添加 vacuum
选项,它将在进程停止时删除套接字:
[uwsgi] module = wsgi:application master = true processes = 5 uid = user socket = /run/uwsgi/myapp.sock chown-socket = user:nginx chmod-socket = 660 vacuum = true
我们需要最后一个选项,因为我们将创建一个 systemd 文件来在启动时启动我们的应用程序。 Systemd 和 uWSGI 对于 SIGTERM 信号应该对应用程序做什么有不同的想法。 为了解决这种差异,以便使用 Systemd 可以按预期处理进程,我们只需要添加一个名为 die-on-term
的选项,以便 uWSGI 将终止进程而不是重新加载它:
[uwsgi] module = wsgi:application master = true processes = 5 uid = user socket = /run/uwsgi/myapp.sock chown-socket = user:nginx chmod-socket = 660 vacuum = true die-on-term = true
完成后保存并关闭文件。 此配置文件现在设置为与 Upstart 脚本一起使用。
创建一个 Systemd 单元文件来管理应用程序
我们可以在启动时启动一个 uWSGI 实例,以便我们的应用程序始终可用。 为此,我们可以创建一个 systemd 单元文件。 我们将把它放在 /etc/systemd/system
目录中,这是用户创建的单元文件的最佳位置。 我们将调用单元文件 uwsgi.service
:
sudo nano /etc/systemd/system/uwsgi.service
首先,我们从 [Unit]
部分开始,我们可以在其中调整元数据。 我们将在这里放置的唯一内容是对我们服务的描述:
[Unit] Description=uWSGI instance to serve myapp
接下来,我们将打开 [Service]
部分。 因为我们使用的是虚拟环境,所以我们的服务启动命令将比传统的更复杂。 我们将使用 ExecStartPre
命令来确保我们的套接字目录由正确的各方创建和拥有。 如果它们已经设置,这将允许失败(通过在等号后放置 -
)。 这将被传递到对 bash
的单个调用中。
对于将启动 uWSGI 的实际 ExecStart
命令,我们还将实际命令传递给 bash
。 这允许我们执行一些不同的命令,因为该指令只能运行单个命令(在本例中为 bash
)。 我们将使用它来切换到我们的应用程序目录,激活虚拟环境,并使用我们创建的 .ini
文件启动 uWSGI:
[Unit] Description=uWSGI instance to serve myapp [Service] ExecStartPre=-/usr/bin/bash -c 'mkdir -p /run/uwsgi; chown user:nginx /run/uwsgi' ExecStart=/usr/bin/bash -c 'cd /home/user/myapp; source myappenv/bin/activate; uwsgi --ini myapp.ini'
现在,剩下要做的就是制定 [Install]
部分。 这将决定当我们 enable
单元时会发生什么。 基本上,它指定单元应自动启动的状态。 我们要指定启用后,只要服务器处于多用户模式,该单元就应该启动:
[Unit] Description=uWSGI instance to serve myapp [Service] ExecStartPre=-/usr/bin/bash -c 'mkdir -p /run/uwsgi; chown user:nginx /run/uwsgi' ExecStart=/usr/bin/bash -c 'cd /home/user/myapp; source myappenv/bin/activate; uwsgi --ini myapp.ini' [Install] WantedBy=multi-user.target
写出上述配置后,保存并关闭文件。
现在,我们可以通过键入以下内容来启动服务:
sudo systemctl start uwsgi
通过键入以下内容检查它是否启动没有问题:
systemctl status uwsgi
如果没有错误,请启用该服务,以便它在启动时通过键入以下内容启动:
sudo systemctl enable uwsgi
您可以随时通过键入以下内容来停止服务:
sudo systemctl stop uwsgi
将 Nginx 配置为代理到 uWSGI
至此,我们有了一个 WSGI 应用程序,并且验证了 uWSGI 可以读取和服务它。 我们已经创建了一个配置文件和 Systemd 单元文件。 我们的 uWSGI 进程将监听一个套接字并使用 uwsgi
协议进行通信。
我们现在可以将 Nginx 配置为反向代理。 Nginx 能够使用 uwsgi
协议代理与 uWSGI 通信。 这是一个比 HTTP 更快的协议,并且性能更好。
我们将要设置的 Nginx 配置非常简单。 我们将修改现有的 nginx.conf
文件并添加一个新的服务器块。 用 sudo
打开文件进行编辑:
sudo nano /etc/nginx/nginx.conf
在默认服务器块之前,我们将添加我们自己的服务器块:
http { . . . include /etc/nginx/conf.d/*.conf; server { } server { listen 80 default_server; server_name localhost; . . .
我们创建的块将保存我们的 uWSGI 代理的配置。 下面的其余配置项都放置在此块中。 服务器块应该在端口 80 上侦听并响应您的服务器的域名或 IP 地址:
server { listen 80; server_name server_domain_or_IP; }
之后,我们可以打开一个处理所有请求的位置块。 在此块中,我们将包含 /etc/nginx/uwsgi_params
文件中的 uwsgi
参数,并将流量传递到 uWSGI 正在侦听的套接字:
server { listen 80; server_name server_domain_or_IP; location / { include uwsgi_params; uwsgi_pass unix:/run/uwsgi/myapp.sock; } }
这实际上就是我们需要的一个简单应用程序。 对于更完整的应用程序,可以进行一些改进。 例如,我们可能会在该块之外定义许多上游 uWSGI 服务器,然后将它们传递给该块。 我们可能会包含更多的 uWSGI 参数。 我们也可以直接处理来自 Nginx 的任何静态文件,并且只将动态请求传递给 uWSGI 实例。
不过,我们的三行应用程序中不需要任何这些功能,因此我们可以保存并关闭文件。
您可以通过键入以下内容进行测试以确保您的 Nginx 配置有效:
sudo nginx -t
如果返回没有任何错误,请键入以下命令启动服务:
sudo systemctl start nginx
通过启用服务在启动时启动 Nginx:
sudo systemctl enable nginx
您应该能够访问您的服务器的域名或 IP 地址(没有端口号)并查看您配置的应用程序:
结论
如果您已经做到了这一步,那么您已经创建了一个简单的 WSGI 应用程序,并且对需要如何设计更复杂的应用程序有了一些了解。 我们已将 uWSGI 应用程序容器/服务器安装到一个专用的虚拟环境中来为我们的应用程序提供服务。 我们制作了一个配置文件和一个 Systemd 单元文件来自动化这个过程。 在 uWSGI 服务器前面,我们设置了一个 Nginx 反向代理,它可以使用 uwsgi
有线协议与 uWSGI 进程通信。
在设置实际生产环境时,您可以轻松了解如何扩展它。 例如,uWSGI 能够使用称为“帝王模式”的东西来管理多个应用程序。 您可以扩展 Nginx 配置以在 uWSGI 实例之间进行负载平衡,或者为您的应用程序处理静态文件。 在为多个应用程序提供服务时,根据您的需要,全局安装 uWSGI 而不是在虚拟环境中安装可能更符合您的利益。 这些组件都相当灵活,因此您应该能够调整它们的配置以适应许多不同的场景。