Ubuntu 22.04部署Flask应用:uWSGI+Nginx完整配置教程

简介

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

先决条件

在开始这个指南之前,你应该具备以下条件:

  • 一台已安装Ubuntu 22.04的服务器和一个具有sudo权限的非root用户。请遵循我们的初始服务器设置指南获取指导。
  • 已安装Nginx,按照《如何在Ubuntu 22.04上安装Nginx》指南中的步骤1至3进行操作。
  • 一个已配置指向您服务器的域名。您可以在Namecheap上购买一个,或者在Freenom上免费获取一个。您可以通过遵循有关域名和DNS的相关文档来学习如何将域名指向服务器。本教程假设您已创建以下DNS记录:

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

另外,熟悉uWSGI应用服务器、本指南中所设置的应用服务器以及WSGI规范可能会对你有所帮助。这份定义和概念的讨论会详细介绍这两个方面。

第一步 — 从Ubuntu仓库安装组件

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

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

  1. sudo apt update

接下来安装一些软件包,这些软件包能帮助您构建Python环境。其中包括python3-pip以及其他一些软件包和开发工具,这些都是创建强大编程环境所必需的。

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

有了这些软件包,您已经准备好为您的项目创建虚拟环境了。

第二步 – 创建Python虚拟环境

一个Python虚拟环境是一个自包含的项目目录,其中包含特定版本的Python和所需的Python模块。通过单独管理每个应用程序的依赖关系,这对于将一个应用程序与同一系统上的其他应用程序隔离开来非常有用。在这一步中,您将设置一个Python虚拟环境,从中运行Flask应用程序。

首先安装python3-venv软件包,这将安装venv模块。

  1. sudo apt install python3-venv

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

  1. mkdir ~/myproject

在创建目录后进入该目录。

  1. cd ~/myproject

通过键入以下命令,创建一个虚拟环境来存储您的Flask项目的Python依赖项:

  1. python3.10 -m venv myprojectenv

这将在您的项目目录中创建一个名为myprojectenv的文件夹,并安装Python和pip的本地副本。

在安装虚拟环境内的应用程序之前,您需要激活它。通过键入以下内容来激活:

  1. source myprojectenv/bin/activate

您的提示将更改,以指示您现在正在虚拟环境中操作。它将类似于这样:(myprojectenv)user@host:~/myproject$。

第三步 – 创建一个Flask应用程序

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

首先,使用pip的本地实例安装wheel,以确保即使缺少wheel存档文件,您的软件包也能成功安装。

  1. pip install wheel

注意

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

接下来,安装Flask和uWSGI。

  1. pip install uwsgi flask

创建一个示例应用程序

既然你已经可以使用Flask,你可以创建一个示例应用程序。Flask是一个微框架。它不包含许多其他更完整的框架可能有的工具,主要是作为一个模块存在,你可以将其导入到你的项目中,以帮助你初始化一个web应用程序。

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

  1. 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 + X,然后按Y,最后按ENTER来完成。

如果您按照最初的服务器设置指南操作,应该已经启用了UFW防火墙。要测试应用程序,您需要允许访问5000端口。

  1. sudo ufw allow 5000

现在,您可以通过输入以下命令来测试您的Flask应用程序:

  1. python myproject.py

您会看到类似下面的输出,包括一个有用的警告,提醒您不要在生产环境中使用此服务器设置。

输出警告:这是一个开发服务器。不要在生产部署中使用它。请使用生产级WSGI服务器替代。 * 正在运行Flask应用'myproject' * 调试模式:关闭 * 在所有地址上运行 (0.0.0.0) 警告:这是一个开发服务器。不要在生产部署中使用它。 * 运行在 http://127.0.0.1:5000 * 运行在 http://your_server:5000 (按CTRL+C退出)

在您的网页浏览器中访问您服务器的IP地址,后面加上:5000。

http://your_server_ip:5000

您会看到类似这样的内容。

Flask示例应用

当您完成后,在终端窗口内按下CTRL + C以停止Flask开发服务器。

创建WSGI入口点

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

将文件名命名为wsgi.py。

  1. nano ~/myproject/wsgi.py

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

myproject文件夹里的wsgi.py文件
from myproject import app

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

完成时保存并关闭文件。

第四步 – 配置uWSGI

您的应用现在已经编写完成,并已经建立了入口点。您可以继续配置uWSGI。

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

作为第一步,测试uWSGI是否可以正确地通过传递应用程序的入口名称来为您提供服务。该名称由模块的名称(去除.py扩展名)加上应用程序内的可调用名称构成。在本教程的上下文中,入口点的名称是wsgi:app。

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

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

请再次在您的网页浏览器中访问您服务器的IP地址,并在末尾追加”:5000″。

http://your_server_ip:5000

您会再次看到您的应用程序的输出。

Flask示例应用

当您确认它正常工作时,在您的终端窗口按下CTRL + C。

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

  1. deactivate

现在,任何Python命令将再次使用系统的Python环境。

创建一个uWSGI配置文件

您已经测试过uWSGI可以为您的应用提供服务,但是长期使用的话,您可能想要一些更可靠的东西。您可以创建一个带有相关选项的uWSGI配置文件来实现这一点。

将该文件放在您的项目目录中,并将其命名为myproject.ini。

  1. nano ~/myproject/myproject.ini

在文件内部,使用[uwsgi]标头开始文件,以便uWSGI知道要应用这些设置。在下面的部分,指定模块本身——通过引用wsgi.py文件,不包括扩展名——以及文件内的可调用项,即app。

~/myproject/myproject.ini
[uwsgi]
module = wsgi:app

接下来,告诉uWSGI以主进程模式启动,并产生五个工作进程来处理实际请求。

~/myproject/myproject.ini

[uwsgi]
module = wsgi:app

master = true
processes = 5

当你进行测试时,你在一个网络端口上暴露了uWSGI。然而,你将使用Nginx来处理实际的客户端连接,然后将请求传递给uWSGI。由于这些组件在同一台计算机上运行,使用Unix套接字更好,因为它更快速且更安全。将该套接字命名为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服务的形式运行。

第五步——创建一个systemd单元文件

Systemd是一套工具集,为系统服务提供快速灵活的启动模型。创建一个systemd单元文件将允许Ubuntu的init系统在服务器启动时自动启动uWSGI并提供Flask应用程序。

在/etc/systemd/system目录下创建一个以.service结尾的单元文件来启动:

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

在[Unit]部分内开始,该部分用于指定元数据和依赖关系。然后在这里写上服务的描述,并告诉init系统在网络目标达到之后才启动这个服务。

/etc/systemd/system/myproject.service

[Unit]
Description=为myproject提供服务的uWSGI实例
After=network.target

接下来,创建[Service]部分。这将指定您希望进程运行在哪个用户和群组下。将您的常规用户账户设置为该进程的所有者,因为它拥有所有相关文件。然后将群组所有权分配给www-data群组,以便Nginx可以与uWSGI进程进行通信(这是Nginx在Ubuntu上默认运行的群组)。请记得将此处的用户名替换为您的用户名:

myproject.service

[Unit]
Description=为myproject提供服务的uWSGI实例
After=network.target

[Service]
User=用户名
Group=www-data

然后,设置工作目录并设置PATH环境变量,以便初始化系统知道进程的可执行文件位于您的虚拟环境中。此外,还需要指定启动服务的命令。Systemd要求您提供uWSGI可执行文件的完整路径,该文件已安装在您的虚拟环境中。在这里,我们传递您在项目目录中创建的.ini配置文件的名称。

请记得用您自己的信息替换用户名和项目路径。

/etc/systemd/system/myproject.service

[Unit]
Description=为myproject提供服务的uWSGI实例
After=network.target

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

最后,添加一个[Install]部分。这将告诉systemd如果在启动时启用它,将把此服务链接到什么位置。在这种情况下,设置服务在常规多用户系统启动和运行时启动。

/etc/systemd/system/myproject.service

[Unit]
Description=为myproject提供服务的uWSGI实例
After=network.target

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

[Install]
WantedBy=multi-user.target

通过以上步骤,你的systemd服务文件已经完成。现在请保存并关闭它。

在启动uWSGI服务之前,您需要进行权限更改,因为在Ubuntu 22.04及更新版本中,Nginx的www-data用户默认无法读取您的家目录中的文件。这可能会阻止您从家目录中提供Web应用程序。一个快速解决方法是使用chgrp更改与您的家目录关联的组。

  1. sudo chgrp www-data /home/用户名

这将使Nginx能够查看您的主目录内容,这是它访问套接字文件所需要的。它不会将您主目录中的任何文件暴露给网络。

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

  1. sudo systemctl start myproject

然后启用它,使其开机启动。

  1. sudo systemctl enable myproject

查看状态。

  1. sudo systemctl status myproject

你会看到像这样的输出。

输出

● myproject.service - 用于服务myproject的uWSGI实例 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套接字。

第六步 – 配置Nginx以代理请求

您的uWSGI应用程序服务器现已启动,并在项目目录中的套接字文件上等待请求。在此步骤中,您将配置Nginx,使用uwsgi协议将Web请求传递到该套接字。

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

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

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

myproject的配置文件位于/etc/nginx/sites-available/myproject。
server {
    listen 80;
    server_name your_domain www.your_domain;
}

接下来,添加一个能匹配所有请求的位置块。在此块中,包含指定一些需要设置的uWSGI参数的uwsgi_params文件。然后使用uwsgi_pass指令将请求传递到您定义的套接字上。

myproject的位置是在/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目录。

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

当您安装Nginx时,该过程会自动在sites-available目录下设置一个名为default的服务器块配置文件,并在该文件与sites-enabled目录之间创建了一个符号链接。如果保留这个符号链接,将阻止您的网站加载默认配置。您可以使用以下命令删除该链接。

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

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

  1. sudo nginx -t

如果没有出现任何问题,重新启动Nginx进程以读取新的配置。

  1. sudo systemctl restart nginx

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

  1. sudo ufw delete allow 5000
  2. sudo ufw allow 'Nginx Full'

现在您将能够在您的Web浏览器中导航至服务器的域名。

http://your_domain

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

Flask示例应用程序

如果您遇到任何错误,请尝试检查以下内容:

  • 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日志。

步骤七 – 应用程序安全化

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

首先,使用apt安装Certbot及其Nginx插件。

  1. sudo apt install certbot python3-certbot-nginx

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

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

使用 –nginx 插件运行 certbot,并使用 -d 来指定您希望证书有效的名称。

如果这是您第一次在此服务器上运行certbot,系统会提示您输入电子邮件地址并同意服务条款。在执行此操作后,certbot将与Let’s Encrypt服务器进行通信,然后运行一个验证控制您要申请证书的域名的挑战。

配置将会被更新,然后Nginx将重新加载以应用新的设置。Certbot将以一条消息告诉您进程已成功完成并且您的证书存储在何处:

输出
成功接收到证书。
证书保存在:/etc/letsencrypt/live/your_domain/fullchain.pem
密钥保存在:/etc/letsencrypt/live/your_domain/privkey.pem
此证书将于2022-11-03过期。
证书续期时,这些文件将会被更新。
Certbot已设置计划任务,在后台自动续期此证书。

部署证书
已成功为 your_domain 部署证书到 /etc/nginx/sites-enabled/myproject
已成功为 your_domain 部署证书到 /etc/nginx/sites-enabled/myproject
恭喜!您已成功在 https://your_domain 和 https://www.your_domain 上启用HTTPS

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
如果您喜欢Certbot,请考虑通过以下方式支持我们的工作:
 * 向ISRG / Let's Encrypt捐赠:   https://letsencrypt.org/donate
 * 向EFF捐赠:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

如果您按照先决条件中的Nginx安装说明进行操作,您将不再需要冗余的HTTP配置文件允许。

  1. sudo ufw delete allow 'Nginx HTTP'

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

https://your_domain

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

结论

在本指南中,您在Python虚拟环境中创建并保护了一个Flask应用程序。然后,您创建了一个WSGI入口点,以便任何支持WSGI的应用服务器都可以与之交互,接着配置了uWSGI应用服务器以提供此功能。之后,您创建了一个systemd服务文件,以便在启动时自动启动应用程序服务器。您还创建了一个Nginx服务器块,将Web客户端流量传递给应用程序服务器,从而中继外部请求,并通过Let’s Encrypt保护了到您服务器的流量安全。

Flask是一个灵活的框架,旨在为您的应用程序提供功能,而不限制结构或设计。您可以使用本指南中描述的通用堆栈来部署您设计的Flask应用程序。

bannerAds