如何在 Ubuntu 22.04 上使用 Gunicorn 和 Nginx 提供 Flask 应用

A previous version of this tutorial was written by Kathleen Juell.

介绍

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

先决条件

开始本指南之前,您应该具备以下条件:

  • 已安装Ubuntu 22.04的服务器,并拥有具有sudo特权的非root用户。请参考我们的初始服务器设置指南进行操作。

  • 已安装Nginx,请按照在Ubuntu 22.04上安装Nginx的步骤1和2进行操作。

  • 配置为指向您服务器的域名。您可以在Namecheap购买一个,或在Freenom免费获取一个。您可以通过查阅相关的域和DNS文档了解如何将域指向DigitalOcean。请确保创建以下DNS记录:

    • 一个A记录,将your_domain指向您服务器的公共IP地址。
    • 一个A记录,将www.your_domain指向您服务器的公共IP地址。
  • 熟悉WSGI规范,Gunicorn服务器将使用它与您的Flask应用程序进行通信。这个讨论详细介绍了WSGI。

步骤1 — 从Ubuntu存储库安装组件

第一步是从Ubuntu存储库安装所有必需的组件。这包括pip,Python包管理器,它将管理Python组件。您还将获取构建一些Gunicorn组件所需的Python开发文件。

首先,更新本地包索引并安装允许您构建Python环境的软件包。这些将包括python3-pip,以及一些其他软件包和开发工具,这些工具对于一个强大的编程环境是必要的:

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

安装这些软件包后,下一步是为您的项目创建虚拟环境。

步骤2 — 创建Python虚拟环境

接下来,您将设置一个虚拟环境,以将Flask应用程序与系统上的其他Python文件隔离开来。

首先安装python3-venv软件包,它将安装venv模块:

  1. sudo apt install python3-venv

接下来,为您的Flask项目创建一个父目录。创建完成后,使用cd命令进入该目录:

  1. mkdir ~/myproject
  2. cd ~/myproject

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

  1. python3 -m venv myprojectenv

这将在您的项目目录中安装一个名为myprojectenv的目录中安装一个本地的Python副本和pip

在虚拟环境中安装应用程序之前,您需要激活它。通过输入以下命令来激活:

  1. source myprojectenv/bin/activate

您的提示符将会更改,指示您现在正在虚拟环境中操作。它看起来可能像这样:(myprojectenv)user@host:~/myproject$

第3步 — 设置Flask应用程序

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

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

  1. pip install wheel

注意

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

接下来,安装Flask和Gunicorn:

  1. pip install gunicorn flask

创建一个示例应用

现在你已经安装了 Flask,你可以创建一个简单的应用。 Flask 是一个微框架。它不包含许多更全面的框架可能包含的工具,主要作为一个你可以导入到项目中的模块,来帮助你初始化一个 web 应用。

虽然你的应用可能会更复杂,但我们将在一个单独的文件中创建我们的 Flask 应用,命名为 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')

这基本上定义了在访问根域时应该呈现的内容。完成后保存并关闭文件。

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

  1. sudo ufw allow 5000

现在你可以通过输入以下命令来测试你的 Flask 应用:

  1. python myproject.py

你将会看到类似以下的输出,包括一个有用的警告提醒你不要在生产环境中使用这个服务器设置:

Output
* Serving Flask app "myproject" (lazy loading) * Environment: production WARNING: Do not use the development server in a production environment. Use a production WSGI server instead. * Debug mode: off * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

在你的 Web 浏览器中访问服务器的 IP 地址,后面跟着 :5000:

http://your_server_ip:5000

你应该会看到类似以下的内容:

完成后,在终端窗口中按下 CTRL-C 停止 Flask 开发服务器。

创建WSGI入口点

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

将文件命名为wsgi.py:

  1. nano ~/myproject/wsgi.py

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

~/myproject/wsgi.py
from myproject import app

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

完成后保存并关闭文件。

第四步 —— 配置Gunicorn

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

在继续之前,请确保Gunicorn能够正确地提供应用程序。

你可以通过传递应用程序的入口点名称来进行检查。这是由模块的名称(去掉.py扩展名)和应用程序中的可调用对象的名称构成的。在这种情况下,它是wsgi:app

还要指定要绑定的接口和端口,使用0.0.0.0:5000参数,这样应用程序将在公共可用的接口上启动:

  1. cd ~/myproject
  2. gunicorn --bind 0.0.0.0:5000 wsgi:app

你应该会看到如下输出:

Output
[2020-05-20 14:13:00 +0000] [46419] [INFO] Starting gunicorn 20.0.4 [2020-05-20 14:13:00 +0000] [46419] [INFO] Listening at: http://0.0.0.0:5000 (46419) [2020-05-20 14:13:00 +0000] [46419] [INFO] Using worker: sync [2020-05-20 14:13:00 +0000] [46421] [INFO] Booting worker with pid: 46421

访问您服务器的 IP 地址,在网址末尾加上:5000

http://your_server_ip:5000

您应该会看到应用程序的输出:

确认一切正常后,在您的终端窗口按下CTRL-C

当您使用完虚拟环境后,您可以将其停用:

  1. deactivate

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

接下来,创建 systemd 服务单元文件。创建 systemd 单元文件将允许 Ubuntu 的初始化系统在服务器启动时自动启动 Gunicorn 并为 Flask 应用提供服务。

/etc/systemd/system 目录中创建一个以.service结尾的单元文件:

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

在其中,您将以 [Unit] 部分开始,用于指定元数据和依赖项。在此处添加对服务的描述,并告知初始化系统只有在达到网络目标后才启动此服务:

/etc/systemd/system/myproject.service
[Unit]
Description=Gunicorn instance to serve myproject
After=network.target

接下来,添加一个 [Service] 部分。这将指定您希望进程在其中运行的用户和组。将进程的所有权分配给您的常规用户帐户,因为它拥有所有相关文件的所有权。还将组的所有权分配给 www-data 组,以便 Nginx 可轻松与 Gunicorn 进程通信。记得在这里将用户名替换为您的用户名:

/etc/systemd/system/myproject.service
[Unit]
Description=Gunicorn instance to serve myproject
After=network.target

[Service]
User=sammy
Group=www-data

接下来,映射出工作目录并设置PATH环境变量,以便初始化系统知道进程的可执行文件位于我们的虚拟环境中。同时指定启动服务的命令。此命令将执行以下操作:

  • 启动3个工作进程(根据需要进行调整)
  • 在我们的项目目录中创建并绑定到一个Unix套接字文件myproject.sock。我们将设置umask值为007,以便创建套接字文件时只允许所有者和组访问,而限制其他访问
  • 指定WSGI入口文件名,以及该文件中的Python可调用项(wsgi:app

Systemd要求您提供Gunicorn可执行文件的完整路径,该文件已安装在您的虚拟环境中。

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

/etc/systemd/system/myproject.service
[Unit]
Description=Gunicorn 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/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app

最后,添加一个[Install]部分。这将告诉systemd如果启用了服务以在启动时启动,要将此服务链接到什么位置。您希望此服务在常规多用户系统启动并运行时启动:

/etc/systemd/system/myproject.service
[Unit]
Description=Gunicorn 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/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app

[Install]
WantedBy=multi-user.target

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

您现在可以启动您创建的Gunicorn服务并启用它,以便它在启动时启动:

  1. sudo systemctl start myproject
  2. sudo systemctl enable myproject

让我们来检查一下状态:

  1. sudo systemctl status myproject

您应该会看到如下输出:

Output
● myproject.service - Gunicorn instance to serve myproject Loaded: loaded (/etc/systemd/system/myproject.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2022-05-10 19:40:41 UTC; 9s ago Main PID: 17300 (gunicorn) Tasks: 4 (limit: 2327) Memory: 56.0M CPU: 514ms CGroup: /system.slice/myproject.service ├─17300 /home/sammy/myproject/myprojectenv/bin/python3 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app ├─17301 /home/sammy/myproject/myprojectenv/bin/python3 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app ├─17302 /home/sammy/myproject/myprojectenv/bin/python3 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app └─17303 /home/sammy/myproject/myprojectenv/bin/python3 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app May 10 19:40:41 r systemd[1]: Started Gunicorn instance to serve myproject. . . .

如果您看到任何错误,请确保在继续教程之前解决这些错误。

步骤 5 — 配置 Nginx 代理请求

您的 Gunicorn 应用服务器现在应该已经启动并正在运行,等待在项目目录中的套接字文件上接收请求。现在您可以通过对其配置文件进行一些小的修改,来配置 Nginx 将 Web 请求传递到该套接字。

首先,在 Nginx 的 sites-available 目录中创建一个新的服务器块配置文件。将其命名为 myproject,以保持与指南的其余部分一致:

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

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

/etc/nginx/sites-available/myproject
server {
    listen 80;
    server_name your_domain www.your_domain;
}

接下来,添加一个匹配每个请求的位置块。在此块内,您将包含指定一些需要设置的通用代理参数的 proxy_params 文件。然后,您将使用 proxy_pass 指令将请求传递到您定义的套接字:

/etc/nginx/sites-available/myproject
server {
    listen 80;
    server_name your_domain www.your_domain;

    location / {
        include proxy_params;
        proxy_pass http://unix:/home/sammy/myproject/myproject.sock;
    }
}

完成后保存并关闭文件。

要启用您刚刚创建的 Nginx 服务器块配置,请将该文件链接到 sites-enabled 目录:

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

将文件放在该目录中后,您可以测试语法错误:

  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

您应该能够看到您应用程序的输出:

注意:如果Nginx无法访问gunicorn的套接字文件,您将收到HTTP 502网关错误。通常情况下,这是因为用户的主目录不允许其他用户访问其中的文件。

如果您的套接字文件名为/home/sammy/myproject/myproject.sock,请确保/home/sammy至少具有0755权限。您可以使用chmod这样更改权限:

  1. sudo chmod 755 /home/sammy

然后重新加载页面,查看HTTP 502错误是否消失。

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

  • 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应用程序的Gunicorn日志。

步骤6 —— 安全应用程序

为了确保您服务器的流量保持安全,让我们为您的域名获取一个SSL证书。有多种方法可以实现这一点,包括从Let’s Encrypt获取免费证书,生成自签名证书,或者从其他提供商购买证书并按照《在Ubuntu 22.04上为Nginx创建自签名SSL证书的步骤2到6》进行配置Nginx来使用它。为了迅速起见,我们将选择第一种选项(Let’s Encrypt)。

使用apt安装Certbot的Nginx包:

  1. sudo apt install 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 服务器通信,然后运行一个挑战来验证您控制您正在请求证书的域名。

如果成功,certbot将询问您如何配置 HTTPS 设置:

Output
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access. ------------------------------------------------------------------------------- 1: No redirect - Make no further changes to the webserver configuration. 2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for new sites, or if you're confident your site works on HTTPS. You can undo this change by editing your web server's configuration. ------------------------------------------------------------------------------- Select the appropriate number [1-2] then [enter] (press 'c' to cancel):

选择您的选项然后按ENTER键。配置将会更新,Nginx 将重新加载以获取新的设置。 certbot将以成功的消息结束,并告知您证书存储在何处:

Output
IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/your_domain/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/your_domain/privkey.pem Your cert will expire on 2020-08-18. To obtain a new or tweaked version of this certificate in the future, simply run certbot again with the "certonly" option. To non-interactively renew *all* of your certificates, run "certbot renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal. - 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 配置:

  1. sudo ufw delete allow 'Nginx HTTP'

要验证配置,请再次转到您的域名,使用https://

https://your_domain

您应该再次看到您的应用输出,以及您浏览器的安全指示器,该指示器应该表明该站点已经安全。

结论

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

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

Source:
https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-gunicorn-and-nginx-on-ubuntu-22-04