如何在Ubuntu 22.04上用uWSGI和Nginx服务Flask应用程序

665 阅读16分钟

简介

在本指南中,你将在Ubuntu 22.04上使用Flask微框架构建一个Python应用程序。 本文的主要内容是如何设置uWSGI应用服务器,如何启动应用程序并配置Nginx作为前端反向代理。

前提条件

在开始本指南之前,你应该有:

  • 一台安装了Ubuntu 22.04的服务器和一个具有sudo权限的非root用户。

  • 配置一个域名,指向你的服务器。你可以在Namecheap购买一个,或者在Freenom上免费获得一个。

    • 一个A记录的 your_domain指向你的服务器的公共 IP 地址。
    • 一个A记录 www.your_domain指向你的服务器的公共IP地址。

此外,对uWSGI(你将在本指南中设置的应用服务器)和WSGI规范有一些熟悉可能会有所帮助。这个关于定义和概念的讨论将详细介绍这两方面的内容。

第1步 - 从Ubuntu软件库中安装组件

你的第一步将是安装所有你需要的Ubuntu软件库中的组件。 你需要安装的包包括pip ,Python 包管理器,用来管理你的 Python 组件。你还会得到构建uWSGI所需的Python开发文件。

首先,更新本地软件包索引。

sudo apt update

然后安装允许你建立Python环境的软件包。这些包将包括python3-pip ,以及一些其他的包和开发工具,这些都是一个强大的编程环境所必需的。

sudo apt install python3-pip python3-dev build-essential libssl-dev libffi-dev python3-setuptools

有了这些软件包,你就可以继续为你的项目创建一个虚拟环境了。

第2步 - 创建一个 Python 虚拟环境

Python 虚拟环境是一个独立的项目目录,它包含了特定版本的 Python 和指定项目所需的 Python 模块。这对于将一个应用程序与同一系统上的其他应用程序隔离开来是很有用的,可以分别管理每个应用程序的依赖关系。在这一步中,你将建立一个 Python 虚拟环境,从那里运行你的 Flask 应用程序。

首先安装python3-venv 包,它将安装venv 模块。

sudo apt install python3-venv

接下来,为你的Flask项目创建一个父目录。

mkdir ~/myproject

在你创建目录后移入该目录。

cd ~/myproject

创建一个虚拟环境,通过输入来存储你的Flask项目的Python需求。

python3.10 -m venv myprojectenv

这将安装一个本地的Python和pip ,在你的项目目录下的一个名为 myprojectenv的目录中。

在虚拟环境中安装应用程序之前,你需要激活它。 通过键入这样做。

source myprojectenv/bin/activate

你的提示将改变,表明你现在在虚拟环境中操作。 它将看起来像这样。 (myprojectenv)user@host:~/myproject$.

第3步 - 设置Flask应用程序

现在你在你的虚拟环境中,你可以安装Flask和uWSGI,然后开始设计你的应用程序。

首先,用pip 的本地实例安装wheel ,以确保你的包会被安装,即使它们缺少轮子存档。

pip install wheel

注意:无论你使用哪个版本的Python,当虚拟环境被激活时,你应该使用pip 命令(而不是pip3)。

接下来,安装Flask和uWSGI。

pip install uwsgi flask

创建一个示例应用程序

现在你有了Flask,你可以创建一个示例应用程序。 Flask是一个微框架。 它不包括许多功能更全面的框架的工具,它主要是作为一个模块存在的,你可以把它导入你的项目中,帮助你初始化一个Web应用。

虽然你的应用程序可能更复杂,但在这个例子中,你将在一个文件中创建你的Flask应用程序,名为myproject.py 。使用nano 或你喜欢的文本编辑器打开myproject.py

nano ~/myproject/myproject.py

应用程序的代码将存在于这个文件中。它将导入Flask并实例化一个Flask对象。 你可以用它来定义你希望在请求特定路线时运行的功能。

~/myproject/myproject.py

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "<h1 style='color:blue'>Hello There!</h1>"

if __name__ == "__main__":
    app.run(host='0.0.0.0')

从本质上讲,这定义了向访问根域的人展示哪些内容。 完成后保存并关闭该文件。如果你用nano 来编辑文件,就像前面的例子一样,按CTRL + XY ,然后按ENTER

如果你遵循初始服务器设置指南,你应该启用了UFW防火墙。 为了测试应用程序,你需要允许访问端口5000

sudo ufw allow 5000

现在,你可以通过输入来测试你的Flask应用。

python myproject.py

你会看到如下的输出,包括一个有用的警告,提醒你不要在生产中使用这个服务器设置。

Output
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Serving Flask app 'myproject'
 * Debug mode: off
 * Running on all addresses (0.0.0.0)
   WARNING: This is a development server. Do not use it in a production deployment.
 * Running on http://127.0.0.1:5000
 * Running on http://your_server:5000 (Press CTRL+C to quit)

在你的网络浏览器中访问你的服务器的IP地址,然后是:5000

http://your_server_ip:5000

你会看到类似这样的东西。

Flask sample app

完成后,在终端窗口点击CTRL + C ,停止Flask开发服务器。

创建WSGI入口点

接下来,创建一个文件,作为应用程序的入口点。 这将告诉你的uWSGI服务器如何与之交互。

将该文件称为wsgi.py

nano ~/myproject/wsgi.py

在这个文件中,从你的应用程序中导入Flask实例,然后运行它。

~/myproject/wsgi.py

from myproject import app

if __name__ == "__main__":
    app.run()

完成后保存并关闭该文件。

第4步 - 配置uWSGI

现在你的应用程序已经建立了一个入口点。你可以继续配置uWSGI。

测试uWSGI是否能够为应用程序提供服务

第一步,测试uWSGI是否能够正确的为你的应用程序提供服务,把你的入口点的名字传给它。 这是由模块的名称(减去.py 扩展名)加上应用程序中的可调用名称构成的。 在本教程中,入口点的名称是wsgi:app

同时,指定套接字,使其在一个公开可用的接口上启动,以及指定协议,使其使用HTTP而不是uwsgi 二进制协议。 使用你之前打开的相同的端口号,5000

uwsgi --socket 0.0.0.0:5000 --protocol=http -w wsgi:app

在你的网络浏览器中再次访问你的服务器的IP地址,并在最后加上:5000

http://your_server_ip:5000

你将再次看到你的应用程序的输出。

Flask sample app

当你确认它运行正常时,在你的终端窗口中按CTRL + C

现在你已经完成了你的虚拟环境,所以你可以停用它。

deactivate

现在任何Python命令都将重新使用系统的Python环境。

创建一个 uWSGI 配置文件

你已经测试了uWSGI能够为你的应用程序提供服务,但是你还需要一些更强大的东西来长期使用。 你可以创建一个带有相关选项的uWSGI配置文件。

将该文件放在你的项目目录下,并将其称为myproject.ini

nano ~/myproject/myproject.ini

在文件内部,以[uwsgi] 头部开始,这样uWSGI就知道要应用这些设置。 在这下面,指定模块本身--参考wsgi.py 文件,去掉扩展名--以及文件中的可调用项,app

~/myproject/myproject.ini

[uwsgi]
module = wsgi:app

接下来,告诉uWSGI以主模式启动,并产生5个工作进程来服务于实际请求。

~/myproject/myproject.ini

[uwsgi]
module = wsgi:app

master = true
processes = 5

当你在测试时,你在网络端口上暴露了uWSGI。 然而,你将使用Nginx来处理实际的客户端连接,然后将请求传递给uWSGI。 由于这些组件是在同一台计算机上运行的,所以最好使用Unix套接字,因为它更快、更安全。 调用socket myproject.sock并把它放在这个目录中。

接下来,改变套接字的权限。 稍后你将把uWSGI进程的所有权交给Nginx组,所以你需要确保套接字的组主可以从中读取信息并写入。 另外,添加vacuum 选项,并将其设置为true;这将在进程停止时清理套接字。

~/myproject/myproject.ini

[uwsgi]
module = wsgi:app

master = true
processes = 5

socket = myproject.sock
chmod-socket = 660
vacuum = true

最后要做的是设置die-on-term 选项。 这可以帮助确保init系统和uWSGI对每个进程信号的含义有相同的假设。 设置这个选项可以使两个系统组件保持一致,实现预期的行为。

~/myproject/myproject.ini

[uwsgi]
module = wsgi:app

master = true
processes = 5

socket = myproject.sock
chmod-socket = 660
vacuum = true

die-on-term = true

你可能已经注意到,这些行没有像你在命令行中那样指定协议。这是因为默认情况下,uWSGI使用uwsgi 协议,这是一个快速的二进制协议,旨在与其他服务器通信。 Nginx可以使用这个协议,所以使用这个协议比强制使用HTTP通信要好。

完成后,保存并关闭该文件。

这样,uWSGI就在你的系统上配置好了。为了让你更灵活地管理你的Flask应用,你现在可以把它配置成一个systemd服务来运行。

第5步 - 创建systemd单元文件

systemd 是一套工具,为管理系统服务提供了一个快速而灵活的 init 模型。创建一个systemd单元文件可以让Ubuntu的init系统在服务器启动时自动启动uWSGI并为Flask应用程序提供服务。

/etc/systemd/system 目录下创建一个以.service 结尾的单元文件来开始。

sudo nano /etc/systemd/system/myproject.service

在里面,从[Unit] 部分开始,这是用来指定元数据和依赖关系的。然后在这里写上服务的描述,并告诉init系统只有在达到联网目标后才启动这个服务。

/etc/systemd/system/myproject.service

[Unit]
Description=uWSGI instance to serve myproject
After=network.target

接下来,创建[Service] 部分。这将指定你希望该进程运行的用户和组。给你的普通用户账户以进程的所有权,因为它拥有所有相关文件。然后,将组的所有权交给www-data组,这样Nginx就可以与uWSGI进程进行通信(在Ubuntu上,Nginx默认以这个组运行)。记住把这里的用户名替换成你的用户名。

/etc/systemd/system/myproject.service

[Unit]
Description=uWSGI instance to serve myproject
After=network.target

[Service]
User=sammy
Group=www-data

接下来,映射出工作目录,并设置PATH 环境变量,以便init系统知道进程的可执行文件位于你的虚拟环境中。同时,指定启动服务的命令。Systemd要求你提供uWSGI可执行文件的完整路径,该文件已安装在你的虚拟环境中。这里,我们传递你在项目目录下创建的.ini 配置文件的名称。

记住,用你自己的信息替换用户名和项目路径。

/etc/systemd/system/myproject.service

[Unit]
Description=uWSGI instance to serve myproject
After=network.target

[Service]
User=sammy
Group=www-data
WorkingDirectory=/home/sammy/myproject
Environment="PATH=/home/sammy/myproject/myprojectenv/bin"
ExecStart=/home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini

最后,添加一个[Install] 部分。这将告诉systemd,如果你让这个服务在启动时启动,应该把它链接到什么地方。在这种情况下,将服务设置为在普通多用户系统启动时启动。

/etc/systemd/system/myproject.service

[Unit]
Description=uWSGI instance to serve myproject
After=network.target

[Service]
User=sammy
Group=www-data
WorkingDirectory=/home/sammy/myproject
Environment="PATH=/home/sammy/myproject/myprojectenv/bin"
ExecStart=/home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini

[Install]
WantedBy=multi-user.target

这样,你的 systemd 服务文件就完成了。现在保存并关闭它。

在启动uWSGI服务之前,你需要做一个权限修改,因为在Ubuntu 22.04和更新版本中,Nginxwww-data 用户默认不能读取主目录中的文件。这可能会阻止你从主目录中提供网络应用。一个快速的解决方案是使用chgrp ,改变与主目录相关的组。

sudo chgrp www-data /home/sammy

这将允许Nginx看到主目录的内容,它需要这些内容来访问socket文件。它不会将你的主目录中的任何文件暴露给网络。

现在你可以启动你创建的uWSGI服务。

sudo systemctl start myproject

然后启用它,让它在启动时启动。

sudo systemctl enable myproject

检查状态。

sudo systemctl status myproject

你会看到这样的输出。

Output
 myproject.service - uWSGI instance to serve myproject
     Loaded: loaded (/etc/systemd/system/myproject.service; enabled; vendor preset: ena>
     Active: active (running) since Fri 2022-08-05 17:22:05 UTC; 6s ago
   Main PID: 4953 (uwsgi)
      Tasks: 6 (limit: 2327)
     Memory: 20.9M
        CPU: 241ms
     CGroup: /system.slice/myproject.service
             ├─4953 /home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini
             ├─4954 /home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini
             ├─4955 /home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini
             ├─4956 /home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini
             ├─4957 /home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini
             └─4958 /home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini

如果你看到任何错误,一定要在继续学习教程之前解决它们。否则,你可以继续配置你的Nginx安装,将请求传递到 myproject.sock套接字。

第6步 - 配置Nginx来代理请求

你的uWSGI应用服务器现在已经启动并运行,在项目目录下的socket文件中等待请求。在这一步,你将配置Nginx使用uwsgi 协议将Web请求传递到该套接字。

首先,在Nginx的sites-available 目录中创建一个新的服务器块配置文件。 为了与本指南的其他部分保持一致,下面的例子中把它称为 myproject:

sudo nano /etc/nginx/sites-available/myproject

打开一个服务器块,告诉Nginx在默认端口80 。 此外,告诉它使用这个块来处理对服务器域名的请求。

/etc/nginx/sites-available/myproject

server {
    listen 80;
    server_name your_domain www.your_domain;
}

接下来,添加一个与每个请求相匹配的位置块。 在这个块中,包括uwsgi_params 文件,该文件指定了一些需要设置的通用uWSGI参数。 然后将请求传递给你用uwsgi_pass 指令定义的套接字。

/etc/nginx/sites-available/myproject

server {
    listen 80;
    server_name your_domain www.your_domain;

    location / {
        include uwsgi_params;
        uwsgi_pass unix:/home/sammy/myproject/myproject.sock;
    }
}

完成后,保存并关闭该文件。

为了启用刚才创建的Nginx服务器块配置,将该文件链接到sites-enabled 目录中。

sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled

当你安装Nginx时,程序自动在sites-available 目录下设置了一个名为default 的服务器块配置文件,然后在该文件和sites-enabled 目录之间建立了一个符号链接。如果你保留这个符号链接,default 配置将阻止你的网站加载。你可以用下面的命令删除这个链接。

sudo unlink /etc/nginx/sites-enabled/default

在这之后,你可以通过键入来测试语法错误。

sudo nginx -t

如果返回时没有显示任何问题,重新启动Nginx进程以读取新的配置。

sudo systemctl restart nginx

最后,再一次调整防火墙。 你不再需要通过端口5000 ,所以你可以删除该规则。 然后,你可以允许访问Nginx服务器。

sudo ufw delete allow 5000
sudo ufw allow 'Nginx Full'

现在,你将能够在你的网络浏览器中导航到服务器的域名。

http://your_domain

你将看到你的应用程序输出。

Flask sample app

如果你遇到任何错误,尝试检查以下内容。

  • sudo less /var/log/nginx/error.log:检查Nginx的错误日志。
  • sudo less /var/log/nginx/access.log:检查Nginx的访问日志。
  • sudo journalctl -u nginx:检查Nginx的进程日志。
  • sudo journalctl -u myproject纪录:检查你的Flask应用程序的uWSGI日志。

第7步 - 保护应用程序的安全

为了确保进入服务器的流量是安全的,为你的域名获得一个SSL证书。有多种方法可以做到这一点,包括从Let's Encrypt获得一个免费证书,生成一个自签名证书,或从商业供应商那里购买一个。为了方便起见,本教程解释了如何从Let's Encrypt获得免费证书。

首先,用apt 安装Certbot和它的Nginx插件。

sudo apt install certbot python3-certbot-nginx

Certbot提供了多种通过插件获得SSL证书的方法。Nginx插件将负责重新配置Nginx并在必要时重新加载配置。要使用这个插件,请输入以下内容。

sudo certbot --nginx -d your_domain -d www.your_domain

这是用--nginx 插件运行certbot ,用-d 来指定你希望证书有效的名字。

如果这是你第一次在这个服务器上运行certbot ,你会被提示输入一个电子邮件地址并同意服务条款。这样做后,certbot 将与Let's Encrypt服务器通信,然后运行一个挑战,以验证你控制着你所申请证书的域。

配置将被更新,Nginx将重新加载以接受新的设置。certbot ,最后会有一条消息告诉你这个过程是成功的,以及你的证书存放在哪里。

Output
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/your_domain/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/your_domain/privkey.pem
This certificate expires on 2022-11-03.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

Deploying certificate
Successfully deployed certificate for your_domain to /etc/nginx/sites-enabled/myproject
Successfully deployed certificate for your_domain to /etc/nginx/sites-enabled/myproject
Congratulations! You have successfully enabled HTTPS on https://your_domain and https://www.your_domain

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

如果你遵循先决条件中的Nginx安装说明,你将不再需要多余的HTTP配置文件津贴。

sudo ufw delete allow 'Nginx HTTP'

为了验证配置,再次导航到你的域名,使用https://

https://your_domain

你将再次看到你的应用程序的输出,以及你的浏览器的安全指示器,这应该表明该网站是安全的。

总结

在本指南中,你在Python虚拟环境中创建了一个Flask应用程序并保证其安全性。然后你创建了一个 WSGI 入口点,这样任何具有 WSGI 功能的应用服务器都可以与之对接,然后配置了 uWSGI 应用服务器来提供这个功能。 之后,你创建了一个systemd服务文件,在启动时自动启动应用服务器。你还创建了一个Nginx服务器块,将Web客户端的流量传递给应用服务器,从而转发外部请求,并通过Let's Encrypt来保证流量的安全。

Flask是一个灵活的框架,旨在为你的应用程序提供功能,而不会对结构或设计有太多限制。 你可以使用本指南中描述的一般堆栈,为你设计的flask应用提供服务。