部署实战:Nginx + Gunicorn 把 Flask+Vue 应用搬上公网 10. 部署实战Nginx Gunicorn 把 FlaskVue 应用搬上公网前两篇我们分别跑通了「留言板」与「论坛」两个项目但都是在本机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.htmlapp.jsstyle.css本质是静态文件本不该由 Python 去伺候交给 Nginx 这类专业 Web 服务器效率最高。于是标准生产架构是浏览器 ──HTTP── Nginx (:80) │ 静态资源直接返回js/css/img │ 动态请求反向代理 ▼ Gunicorn (:5000 / :8000) │ ▼ Flask 应用多 worker 进程10.2 服务器准备Ubuntu 24.04 踩坑在两台华为云 ECSUbuntu 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 跑成多进程服务GunicornGreen Unicorn是 WSGI HTTP 服务器。对于我们的两个项目留言板入口app.py里的app对象/opt/appenv/bin/gunicorn-w4-b127.0.0.1:5000 app:app论坛工厂模式入口run.py的app对象/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-cimport 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] DescriptionMessage Board (Flask Vue) Afternetwork.target [Service] Userroot WorkingDirectory/opt/message-board ExecStart/opt/appenv/bin/gunicorn -w 4 -b 127.0.0.1:5000 app:app Restartalways RestartSec3 [Install] WantedBymulti-user.target论坛类似改路径、端口、入口即可[Unit] DescriptionForum SPA (Flask Vue) Afternetwork.target [Service] Userroot WorkingDirectory/opt/forum ExecStart/opt/appenv/bin/gunicorn -w 4 -b 127.0.0.1:8000 run:app Restartalways RestartSec3 [Install] WantedBymulti-user.target启用并启动sudosystemctl daemon-reloadsudosystemctlenablemessage-board forum# 开机自启sudosystemctl start message-board forumsudosystemctl status message-board forum# 确认 active (running)Restartalways意味着进程异常退出会被立刻拉起配合RestartSec3避免疯狂重启。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.6server { 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浏览器会因同源策略拦截。我们后端统一开了 CORSfromflask_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-E5000|8000# 3) 本地接口通不通绕过 Nginxcurl-s-o/dev/null-w%{http_code}\nhttp://127.0.0.1:5000/api/messages# 4) Nginx 通不通curl-s-o/dev/null-w%{http_code}\nhttp://127.0.0.1/# 5) 公网通不通从另一台机器或本地curl-s-o/dev/null-w%{http_code}\nhttp://120.46.128.206/常见故障现象原因解决接口 500no such table没建表python -c import app; app.db.create_all()刷新子路由 404history 模式未兜底Nginxtry_files 后端 catch-all502 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核心流程mkdir -p /opt/message-board/static建表显式db.create_all()写 Systemd unit 到/etc/systemd/system/写 Nginx site 并ln -s启用systemctl daemon-reload enable start、nginx -t reload冒烟测试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让成果可分享、可协作。