Beijing, China: ☀️ 🌡️+33°C 🌬️↑4km/h

1 写在前面 🚀

近期,我需要在 Ubuntu 服务器上搭建一个用于生产环境的 Web 后端,该项目基于 Flask 框架的 Python 模块。经过在 Windows 环境下的充分测试后,接下来便开始部署工作,并选用了 Nginx 作为反向代理服务器Gunicorn 作为 WSGI 服务器来托管 Flask 应用。

2 架构原理 🔍

整个 Web 应用的处理流程如下:

  1. 客户端(浏览器或 API)
    用户访问您的网站或 API(例如 http://yourserver.com),请求被发送至 Nginx。
  2. Nginx(反向代理)
    Nginx 接收请求并将其转发给 Gunicorn。
    它通过 Unix 套接字 (myproject.sock) 或 HTTP 端口 (127.0.0.1:8000) 与 Gunicorn 进行通信,同时高效处理静态文件。
  3. Gunicorn(WSGI 服务器)
    Gunicorn 启动多个工作进程,以支持并发请求处理。
    它在这些进程中运行 Flask 应用,并将接收到的请求转交给 Flask 处理。
  4. Flask(Web 应用程序)
    Flask 处理请求,执行应用逻辑,并生成响应(HTML、JSON 等)。
    生成的响应随后返回给 Gunicorn。
  5. Gunicorn 将 Flask 生成的响应传递回 Nginx。
  6. Nginx 进一步处理并最终将响应返回给客户端。
  7. 客户端 接收响应,并在浏览器或 API 接口中展示结果。

3 服务器环境与安装 🛠️

3.1 环境要求

  • Ubuntu 22.04
  • Nginx
  • Gunicorn
  • Flask
  • Python3

3.2 安装依赖

在部署前,请先更新系统软件包,并安装相关依赖:

# 更新系统软件包
sudo apt update

# 安装 Nginx
sudo apt install nginx -y

# 创建 Python 虚拟环境(建议使用 conda 或 venv)
conda create -n web python=3.9 -y
source activate web  # 激活虚拟环境

# 安装 Flask 与 Gunicorn
pip install Flask gunicorn

4 构建 Flask 应用 🐍

4.1 创建 Flask 服务器

首先,在 ~/flask_app/script/ 目录下创建 flask_server.py 文件:

mkdir -p ~/flask_app/script
nano ~/flask_app/script/flask_server.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', port=5090, debug=True)

4.2 测试 Flask 应用

通过以下命令启动 Flask 服务器:

python ~/flask_app/script/flask_server.py

若启动成功,终端会显示类似如下的信息:

 * Running on http://127.0.0.1:5090
Press CTRL+C to quit

在浏览器中访问 http://your-server-ip:5090,若页面显示 “Hello There!” 则表明 Flask 应用正常运行。👌

4.3 创建 WSGI 入口文件

为使 Gunicorn 能正确加载 Flask 应用,需要创建 WSGI 入口文件。在 ~/flask_app/script/ 下创建 wsgi.py 文件:

nano ~/flask_app/script/wsgi.py

添加如下内容:

from flask_server import app

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

此时,Gunicorn 就可以通过 wsgi:app 启动并管理 Flask 应用了。🎯

5 配置 Gunicorn ⚡

确保 Gunicorn 能正常运行 Flask 应用。可使用以下命令启动 Gunicorn,并指定 wsgi:app 作为入口,同时绑定到 0.0.0.0:5090 以允许外部访问:

cd ~/flask_app/script

# 这里我使用指定路径的conda环境启动(大家自行选择)
/home/micl/miniconda3/envs/web/bin/gunicorn --bind 0.0.0.0:5090 wsgi:app

为了实现系统启动时自动运行 Gunicorn,可创建 systemd 服务单元文件:

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

写入以下内容:

[Unit]
Description=Gunicorn instance to serve Flask application
After=network.target

[Service]
User=micl
Group=www-data
WorkingDirectory=/home/micl/flask_app/script
Environment="PATH=/home/micl/miniconda3/envs/web/bin"
ExecStart=/home/micl/miniconda3/envs/web/bin/gunicorn --workers 1 --bind unix:/home/micl/flask_app/script/flask_web.sock -m 007 --timeout 3600 wsgi:app

[Install]
WantedBy=multi-user.target

然后,依次执行以下命令启用并启动该服务:

sudo systemctl daemon-reload
sudo systemctl restart flask_web
sudo systemctl enable flask_web
sudo systemctl status flask_web

注意 🔔:若 Flask 框架下的 Python 代码有修改,只需运行sudo systemctl restart flask_web即可重新导入相关 API 服务。

6 配置 Nginx 🌐

当 Gunicorn 在 Unix 套接字上监听请求后,需配置 Nginx 代理转发这些请求。首先,在 /etc/nginx/sites-available/ 目录下创建 Nginx 配置文件:

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

写入以下配置内容(这里我添加了 requests 的响应时间):

server {
    listen 80;
    server_name 123.123.66.66;  # 请替换为实际的服务器 IP

    location / {
        include proxy_params;
        proxy_pass http://unix:/home/micl/flask_app/script/flask_web.sock;
        proxy_read_timeout 3600;
        proxy_connect_timeout 3600;
        proxy_send_timeout 3600;
    }
}

创建符号链接以启用该站点配置:

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

最后,重新加载 Nginx 配置使其生效:

sudo systemctl restart nginx
sudo systemctl status nginx

7 查看运行日志 📜

7.1 查看 Gunicorn 日志

由于 Gunicorn 默认不会自动保存日志至文件,可通过以下命令实时查看日志(假设服务名为 steel,请根据实际情况修改):

# 实时查看 Gunicorn 日志(类似 tail -f)
sudo journalctl -u steel -f

# 查看最近 100 行日志
sudo journalctl -u steel -n 100

# 查看过去 1 小时内的日志
sudo journalctl -u steel --since "1 hour ago"

7.2 查看 Nginx 日志

当 Nginx 转发请求遇到问题时,可查看其访问日志和错误日志:

# 查看访问日志
sudo tail -f /var/log/nginx/access.log

# 查看错误日志
sudo tail -f /var/log/nginx/error.log

8 异步任务处理 ⏳

为了避免长时间任务阻塞 Flask 主进程,可通过多线程方式在后台执行任务。下面给出一个示例,展示如何在 Flask 中启动并查询后台任务进度。

  1. 创建带异步任务的 Flask 服务器

flask_server.py 文件中,加入如下内容:

from flask import Flask, request, jsonify
import threading
import time

app = Flask(__name__)

tasks = {}  # 用于存储任务进度

def long_running_task(task_id, data):
    """ 模拟长时间执行的任务 """
    for i in range(1, 11):
        time.sleep(5)  # 模拟处理延时
        tasks[task_id] = f"{i * 10}%"  # 更新任务进度
    tasks[task_id] = "Completed"

@app.route('/start_task', methods=['POST'])
def start_task():
    """ 启动后台任务并返回任务 ID """
    task_id = str(len(tasks) + 1)  # 生成任务 ID
    data = request.json.get("data", "default_task_data")
    tasks[task_id] = "0%"  # 初始化任务进度
    thread = threading.Thread(target=long_running_task, args=(task_id, data))
    thread.start()
    return jsonify({'task_id': task_id}), 202  # 202 表示请求已被接受

@app.route('/task_status/<task_id>', methods=['GET'])
def task_status(task_id):
    """ 查询指定任务的进度 """
    progress = tasks.get(task_id, "Not Found")
    return jsonify({'task_id': task_id, 'progress': progress})

if __name__ == '__main__':
    app.run(debug=True)
  1. 运行服务器

通过如下命令启动 Flask 服务器:

python flask_server.py
  1. 启动长任务

使用 curl 命令向服务器发送 POST 请求启动任务:

curl -X POST http://223.223.185.192/start_task -H "Content-Type: application/json" -d '{"data": "steel_experiment"}'

预期响应示例:

{"task_id": "1"}
  1. 查询任务进度

使用 curl 命令查询任务状态:

curl -X GET http://223.223.185.192/task_status/1

可能的响应示例如下:

  • 任务进行中:
    {"task_id": "1", "progress": "50%"}
    
  • 任务已完成:
    {"task_id": "1", "progress": "Completed"}
    

9 补充说明(大模型生成)

9.1 ✨ 为什么选择 Nginx?

在生产环境中,直接让 Flask 处理所有请求并不可行。Flask 既不是高性能 Web 服务器,也难以高效处理并发请求。因此,使用 Nginx 作为反向代理 可以优化请求分发,提升应用性能,主要优势包括:

  1. ⚡ 高效处理静态资源

    • Nginx 可直接提供 CSS、JS、图片 等静态文件,减少 Flask 服务器的额外负担。
    • 与 Flask 相比,Nginx 处理静态资源的效率可提升 10-100 倍 🚀
  2. 🛠️ 负载均衡与高并发

    • Nginx 可将请求分发至多个 Gunicorn 进程,轻松应对 数千级并发连接
    • 确保应用在 高流量场景下依然稳定运行
  3. 🛡️ 增强安全性

    • 通过 隐藏 Gunicorn 端口,防止外部直接访问 WSGI 服务器。
    • 提供 SSL 加密、请求过滤、IP 限制 等安全机制,提高应用防护能力。
  4. 🚨 不使用 Nginx 的风险

    • 📉 静态文件加载缓慢,占用 Flask 服务器的计算资源。
    • 💥 单点故障风险,Gunicorn 崩溃后整个应用将无法访问。
    • ⚠️ 缺乏安全防护,易受到 DDoS 攻击及恶意请求的影响。

9.2 🔧 为什么需要 WSGI ?

Flask 自带的开发服务器 仅适用于调试,不支持高并发,并存在内存泄漏等问题。因此,在生产环境中,我们需要使用 专业的 WSGI 服务器(如 Gunicorn) 作为 Flask 与 Web 服务器(Nginx)之间的桥梁,主要优势如下:

  1. 🚀 高并发处理

    • Gunicorn 采用 多进程 Worker 机制,可同时处理多个请求,提高吞吐量。
    • Flask 自带服务器为 单线程,在生产环境下性能极其有限。
  2. 🔗 协议适配

    • Gunicorn 负责将 HTTP 请求转换为符合 WSGI 规范的请求,让 Flask 专注于业务逻辑,而无需处理底层网络通信。
  3. 🛠️ 生产级特性

    • 提供 进程管理、自动重启、超时重试、日志记录 等功能,确保应用的稳定运行。
  4. ❌ 不使用 WSGI 服务器的风险

    • 🐢 低性能,无法处理高并发请求,导致响应缓慢甚至崩溃。
    • 📌 缺乏扩展性,无法利用多核 CPU 进行并行计算,影响应用可扩展性。
    • ⚠️ 安全隐患,缺乏进程管理和防护机制,易受攻击或资源耗尽。

9.3 🔄 为什么使用 Unix Socket 而非 TCP 端口?

Nginx 与 Gunicorn 之间的通信方式主要有两种:TCP 端口通信(如 127.0.0.1:8000)和 Unix Socket 通信(如 myproject.sock)。在生产环境中,推荐使用 Unix Socket,主要优势包括:

  1. ⚡ 更低的延迟

    • Unix Socket 直接通过 内核传输数据,避免 TCP/IP 协议带来的额外开销。
    • 相比 127.0.0.1:8000可降低 30%-50% 的延迟 🚀,提高数据传输效率。
  2. 🔒 更安全的访问控制

    • Unix Socket 本质上是一个 文件,可以使用 chmod 660 myproject.sock 限制访问权限。
    • 避免 TCP 端口暴露带来的安全风险,降低遭受恶意访问的可能性。
  3. 🎯 资源占用更低

    • 无需额外管理端口,特别适用于 同一服务器部署多个应用 的场景。
  4. ⚠️ TCP 端口的潜在问题

    • 需要额外配置 防火墙 🏰,以防端口被滥用或暴露在公网中。
    • 在极端高并发情况下,可能出现 端口资源耗尽 ⛔,影响服务器稳定性。

10 📖 参考文献

  1. How To Serve Flask Applications with Gunicorn and Nginx on Ubuntu 22.04
  2. How To Install Nginx on Ubuntu 22.04 | DigitalOcean
  3. Welcome to Flask — Flask Documentation (3.1.x)
  4. Gunicorn - Python WSGI HTTP Server for UNIX
  5. 部署 Flask + Gunicorn + Nginx - HackMD