部署实战:Nginx + Gunicorn 把 Flask+Vue 应用搬上公网
2026/7/12 1:05:39 网站建设 项目流程

10. 部署实战:Nginx + Gunicorn 把 Flask+Vue 应用搬上公网

前两篇我们分别跑通了「留言板」与「论坛」两个项目,但都是在本机python app.py/flask run直接启动。这种方式只能用于开发:单进程、无守护、崩溃即停、并发能力近乎为零。本文带你把这两个项目以生产级姿态搬上华为云 ECS,用Gunicorn 做应用服务器 + Systemd 做进程守护 + Nginx 做反向代理,并实现开机自启、公网访问、平滑排错。


10.1 为什么不能直接flask run上生产

Flask 自带的 Werkzeug 开发服务器,官方文档明确写着:

Do not use the development server in production.

原因有四点:

维度开发服务器 (flask run)生产服务器 (Gunicorn)
并发单线程,串行处理请求多 worker 进程,真正并行
稳定性进程挂了整个服务没了worker 异常会被 master 拉起
性能无优化,慢预 fork、长连接、keep-alive
安全暴露调试器(危险)关闭调试,隔离环境

Vue 的前端(index.html+app.js+style.css)本质是静态文件,本不该由 Python 去伺候,交给 Nginx 这类专业 Web 服务器效率最高。于是标准生产架构是:

浏览器 ──HTTP──> Nginx (:80) │ 静态资源直接返回(js/css/img) │ 动态请求反向代理 ▼ Gunicorn (:5000 / :8000) │ ▼ Flask 应用(多 worker 进程)

10.2 服务器准备(Ubuntu 24.04 踩坑)

在两台华为云 ECS(Ubuntu 24.04)上,第一步是建立隔离的 Python 运行环境。这里有个大坑:Ubuntu 24.04 默认没有venv模块,python3 -m venv会静默失败或报ensurepip缺失。必须先装包:

sudoapt-getupdatesudoapt-getinstall-ypython3-pip python3-venv nginxgitcurl# 关键:没有下面这行,venv 创建会失败sudoapt-getinstall-ypython3.12-venv

创建专用虚拟环境(我们用/opt/appenv,与代码分离,方便多项目复用):

sudopython3-mvenv /opt/appenv /opt/appenv/bin/pipinstall--upgradepip /opt/appenv/bin/pipinstallflask flask-sqlalchemy flask-cors flask-wtf gunicorn

经验:把依赖装进/opt/appenv而不是系统 Python,避免污染系统、也避免权限问题。多台机器用同一条命令即可批量准备——我们正是用脚本对 4 台 ECS并行完成的。


10.3 Gunicorn:把 Flask 跑成多进程服务

Gunicorn(Green Unicorn)是 WSGI HTTP 服务器。对于我们的两个项目:

留言板(入口app.py里的app对象):

/opt/appenv/bin/gunicorn-w4-b127.0.0.1:5000 app:app

论坛(工厂模式,入口run.pyapp对象):

/opt/appenv/bin/gunicorn-w4-b127.0.0.1:8000 run:app

参数含义:

  • -w 4:开 4 个 worker 进程。经验公式(2 × CPU核数) + 1;本机 8 vCPU,取 4~9 皆可。
  • -b 127.0.0.1:5000只监听本机回环地址,不直接暴露公网——公网流量统一由 Nginx 反代进来,更安全。
  • app:app/run:app:格式为模块:对象,即「从 app.py / run.py 里导入名为 app 的 Flask 实例」。

⚠️致命坑:Gunicorn 启动时不会执行if __name__ == '__main__':里的db.create_all()(那是给python app.py用的)。所以数据库表必须显式建一次:

cd/opt/message-board /opt/appenv/bin/python-c"import app; app.db.create_all()"

论坛同理(from app import db后在app.app_context()里建表)。否则服务起来了,一访问接口就报no such table


10.4 Systemd:让服务崩溃自启、开机自启

手动跑 Gunicorn 一旦关掉 SSH 就停了。用 Systemd 托管才能持久。

/etc/systemd/system/message-board.service

[Unit] Description=Message Board (Flask + Vue) After=network.target [Service] User=root WorkingDirectory=/opt/message-board ExecStart=/opt/appenv/bin/gunicorn -w 4 -b 127.0.0.1:5000 app:app Restart=always RestartSec=3 [Install] WantedBy=multi-user.target

论坛类似,改路径、端口、入口即可:

[Unit] Description=Forum SPA (Flask + Vue) After=network.target [Service] User=root WorkingDirectory=/opt/forum ExecStart=/opt/appenv/bin/gunicorn -w 4 -b 127.0.0.1:8000 run:app Restart=always RestartSec=3 [Install] WantedBy=multi-user.target

启用并启动:

sudosystemctl daemon-reloadsudosystemctlenablemessage-board forum# 开机自启sudosystemctl start message-board forumsudosystemctl status message-board forum# 确认 active (running)

Restart=always意味着进程异常退出会被立刻拉起,配合RestartSec=3避免疯狂重启。


10.5 Nginx:反向代理 + 静态资源

/etc/nginx/sites-available/message-board(留言板):

server { listen 80; server_name 120.46.128.206; # 也可填域名 # 静态前端 location / { root /opt/message-board/static; try_files $uri $uri/ /index.html; } # 动态接口反代到 Gunicorn location /api/ { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }

论坛的 Nginx 多了一层Vue history 模式处理(见 10.6):

server { listen 80; server_name 124.71.226.254; location / { root /opt/forum/static; try_files $uri $uri/ /index.html; # SPA 兜底,刷新不 404 } location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }

启用站点并校验:

sudoln-s/etc/nginx/sites-available/message-board /etc/nginx/sites-enabled/sudoln-s/etc/nginx/sites-available/forum /etc/nginx/sites-enabled/sudonginx-t# 语法检查sudosystemctl reload nginx

注意:华为云安全组要放行入方向 80 端口(TCP),否则外网访问不通。我们两个应用都已验证公网 200,说明安全组已正确放行。


10.6 解决 Vue history 模式「刷新 404」

Vue-Router 用history模式时,URL 是/posts/123这种干净路径。但用户直接访问或刷新/posts/123时,Nginx 找不到这个物理文件,会返回 404。

解决有两层:

前端层:Nginx 的try_files $uri $uri/ /index.html;已经把所有未命中文件名的请求兜底到index.html,由 Vue-Router 在前端再路由。这是 SPA 标准做法。

后端层(双保险):Flask 也要能接住任意前端路由,返回index.html

# app/__init__.py 或 run.py 中@app.route('/',defaults={'path':''})@app.route('/<path:path>')defcatch_all(path):# 只兜底前端路由,API 交给蓝图ifpath.startswith('api/'):returnabort(404)returnapp.send_static_file('index.html')

静态文件由 Nginx 直接返回,只有/api/才进 Flask,职责清晰、性能最好。


10.7 CORS 与跨域

开发时前端npm run dev跑在localhost:8080,后端在:5000,浏览器会因同源策略拦截。我们后端统一开了 CORS:

fromflask_corsimportCORS CORS(app,resources={r"/api/*":{"origins":"*"}})

生产上前端和后端同域同端口(都走 Nginx 的 80),其实已不存在跨域,CORS 可以收紧为具体域名。但保留*对调试友好,正式上线建议改成白名单:

CORS(app,origins=["http://124.71.226.254","https://your.domain"])

10.8 健康检查与排错清单

部署后按这个顺序自查:

# 1) Gunicorn 进程在不在systemctl status message-board# 2) 端口在不在监听ss-tlnp|grep-E'5000|8000'# 3) 本地接口通不通(绕过 Nginx)curl-s-o/dev/null-w"%{http_code}\n"http://127.0.0.1:5000/api/messages# 4) Nginx 通不通curl-s-o/dev/null-w"%{http_code}\n"http://127.0.0.1/# 5) 公网通不通(从另一台机器或本地)curl-s-o/dev/null-w"%{http_code}\n"http://120.46.128.206/

常见故障:

现象原因解决
接口 500,no such table没建表python -c "import app; app.db.create_all()"
刷新子路由 404history 模式未兜底Nginxtry_files+ 后端 catch-all
502 Bad GatewayGunicorn 没起/端口错systemctl status看日志
公网连不上安全组未放行 80云控制台加入方向 80
静态 JS 404root路径写错确认指向static目录

日志位置:

journalctl-umessage-board-n50--no-pager# 应用日志tail-f/var/log/nginx/error.log# Nginx 错误

10.9 一键部署脚本(复用思路)

我们把上述步骤写成mb_setup.sh/fm_setup.sh,核心流程:

  1. mkdir -p /opt/message-board/static
  2. 建表(显式db.create_all()
  3. 写 Systemd unit 到/etc/systemd/system/
  4. 写 Nginx site 并ln -s启用
  5. systemctl daemon-reload && enable && startnginx -t && reload
  6. 冒烟测试curl

这样同一份脚本在 4 台 ECS 上并行跑,几分钟就完成两套环境的交付。这也正是本次训练营「5 台服务器高效并行」的落地方式。


10.10 小结与下一步

至此,两个项目都已以生产姿态上线:

  • 留言板:http://120.46.128.206
  • 论坛:http://124.71.226.254(测试账号testuser / 123456

它们都跑在Gunicorn + Systemd + Nginx之上,开机自启、崩溃自拉、公网可达。

进阶( Production hardening,建议):

  • HTTPS:用 Let’s Encrypt 免费证书,certbot --nginx一行搞定,把listen 80升级为 443。
  • 静态资源上 CDN / 对象存储,减轻 ECS 压力。
  • 数据库换 PostgreSQL,敏感配置走环境变量而非硬编码。
  • Markdown 渲染务必加bleach做 XSS 净化(论坛帖子正文就是典型风险点)。

下一篇(11)我们讲如何把源码与博客发布到 Gitee,让成果可分享、可协作。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询