欢迎光临
我们一直在努力

Flask 博客腾讯云 CVM 部署实战

Flask 博客腾讯云 CVM 部署实战:VPC 规划、Gunicorn + Nginx 反向代理与 80 端口拒绝排查

本文记录一个 Flask 个人博客(my_blog_V1.3)从腾讯云 VPC/子网规划,到 CVM 创建、Gunicorn 生产常驻、Nginx 反向代理配置,再到公网访问被拒绝的完整排查过程。不满足于“命令能跑通”,重点解释 为什么开发服务器不能直接扛生产流量、 Systemd 托管 Gunicorn 时 Unix socket 与 TCP 端口的取舍、 X-Forwarded-Proto 与 ProxyFix 为什么必须成对出现,以及部署后浏览器报 ERR_CONNECTION_REFUSED 时如何用“服务—回环—协议—安全组”四层法逐级定位。适合正在把 Flask 项目搬上云服务器、希望一次把地基搭稳的读者参考。

一、腾讯云网络规划:VPC、子网与安全组的边界

1.1 地域选择:为什么必须和 CVM 保持一致

VPC、子网、安全组均为地域级资源,A 地域的 VPC 无法直接承载 B 地域的 CVM。同地域内 CVM、安全组、弹性公网 IP 走内网互通,延迟低且不计流量费;跨地域只能走公网,延迟和成本同时上升。因此本次部署固定在同一地域内完成。

1.2 VPC 与子网:两层隔离的实际含义

层级作用域本次取值
VPC 地域级,独立私有网络 10.0.0.0/16
子网 可用区级,绑定具体 AZ 10.0.0.0/24
CVM 内网 IP 从子网网段分配 10.0.0.11
子网必须与 CVM 的可用区一致,因为子网本身就是可用区级资源,CVM 只能放入相同可用区的子网。内网 IP 10.0.0.11 正是在 10.0.0.0/24 网段内分配的地址。

避坑说明:如果 CVM 创建后才发现子网选错了可用区,实例无法原地挂载到另一个子网,需要重建实例。规划阶段先把 VPC → 子网 → 可用区这条链路定下来,比事后补救成本低得多。

1.3 安全组:VPC 内的状态化防火墙

安全组只对当前 VPC 内的实例生效,默认入站策略拒绝大部分端口。部署前需要明确两组规则:

  • 已放行:SSH 22,用于远程登录;
  • 待放行:TCP 80,Nginx 对外提供网页访问。

安全组规则的作用范围是实例级别,修改后即时生效,不需要重启 CVM。但入站规则的授权对象需要显式指定,漏配 0.0.0.0/0 或指定了错误的源 IP 段,都会导致公网请求在到达 Nginx 之前就被拦截。

二、CVM 创建与系统初始化

实例规格为 2 核 2G、带宽 3 Mbps,镜像 Ubuntu 22.04 LTS 64-bit,私有网络和子网沿用第一步的选择,公网侧分配弹性公网 IP。创建完成后得到公网 IP 106.52.34.17、内网 IP 10.0.0.11,通过 SSH 22 端口以 root 登录。
时区建议统一为 Asia/Shanghai,避免日志时间与操作时间对不上:

timedatectl set-timezone Asia/Shanghai

三、环境准备:隔离与依赖一次到位

3.1 系统更新与基础依赖

apt update && apt upgrade -y
apt install python3 python3-pip python3-venv nginx -y

升级过程中如果出现服务重启弹窗,只勾选 nginx.service,其余服务保持现状,避免在部署前引入无关变更。

3.2 拉取源码并创建虚拟环境

cd /root
git clone https://github.com/Wasd2490/my_blog_V1.3.git
cd my_blog_V1.3
python3 -m venv venv
source venv/bin/activate
pip install flask gunicorn

这里一次性安装 gunicorn,是为了后续 Systemd 的 ExecStart 可以直接指向虚拟环境内的绝对路径,不依赖 Shell 的 PATH。虚拟环境在这里的实际作用不是“整洁”,而是让服务启动时解释器路径确定化,避免系统 Python 升级或依赖变动导致服务行为漂移。

3.3 数据库:已有 site.db 时不要画蛇添足

项目自带 SQLite 文件 site.db。如果代码中没有通过工厂函数初始化 db 对象,强行执行 db.create_all() 可能触发 from app import db 的导入错误。在 SQLite 文件已经存在的情况下,跳过建表是合理的,部署阶段应尽量减少不必要的写操作。

四、从开发服务器到生产常驻:为什么必须换掉 python3 app.py

4.1 开发服务器测试:只验证应用本身可启动

python3 app.py

看到 Running on 0.0.0.0:5000 说明代码和依赖没有硬性缺失,Ctrl+C 终止。Flask 自带的开发服务器是单线程、同步阻塞的设计,没有 worker 进程管理,也没有崩溃自动重启机制。即使交给 systemd 托管,本质也只是把开发服务器变成了守护进程,生产稳定性不会因此改善。

4.2 用 Gunicorn 做 WSGI 服务器,并选择 Unix socket

2 核 2G 的配置,worker 不需要开大。Gunicorn 的经典起点是 2 × CPU 核心数 + 1,但小内存机器上 2 个 worker 更稳妥,开过多反而增加内存压力。生产环境优先使用 Unix socket 而不是 TCP 端口:socket 文件只在本机文件系统中可见,不经过网络协议栈,权限也可以通过文件系统直接控制。
先在前台验证 Gunicorn 能拉起应用并创建 socket:

gunicorn –bind unix:/run/myblog.sock –workers 2 app:app

确认无误后 Ctrl+C 终止,进入 Systemd 配置。

4.3 Systemd 服务文件:托管 Gunicorn,而非 Flask 开发服务器

创建 /etc/systemd/system/myblog.service:

[Unit]
Description=my_blog_V1.3 Flask Blog (Gunicorn)
After=network.target
[Service]
User=root
Group=www-data
WorkingDirectory=/root/my_blog_V1.3
ExecStart=/root/my_blog_V1.3/venv/bin/gunicorn \\
–workers 2 \\
–bind unix:/run/myblog.sock \\
–access-logfile /var/log/myblog/access.log \\
–error-logfile /var/log/myblog/error.log \\
app:app
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target

三个需要说明的配置点:
第一,ExecStart 使用虚拟环境内的 gunicorn 绝对路径,Systemd 不依赖交互式 shell 的环境变量,重启后也能找到正确的可执行文件。
第二,日志显式落盘到 /var/log/myblog/。如果不指定,Gunicorn 的访问日志和错误日志会进入 journald,后续配合 logrotate 做轮转时不如独立文件直观。
第三,User=root 在当前路径(/root/my_blog_V1.3)下是过渡选择。更规范的做法是使用非特权用户,但前提是项目目录、site.db 和上传目录都对该用户可读写;否则会遇到发文或上传时 500 的权限问题。Gunicorn 官方明确不建议以 root 运行,这一步应当作为后续收权限的起点,而不是终点。
创建日志目录并启动:

mkdir -p /var/log/myblog
systemctl daemon-reload
systemctl start myblog
systemctl enable myblog
systemctl status myblog

active (running) 且 /run/myblog.sock 存在,说明 Gunicorn 已在 Systemd 下常驻。

坑点:服务占用 5000 端口或 socket 后,再次前台执行 python3 app.py 会出现 Address already in use,这是端口/socket 冲突,不是应用故障。

五、Nginx 反向代理:proxy_set_header 不只是转发 Host

修改 /etc/nginx/sites-available/default:

server {
listen 80;
server_name _;
location / {
proxy_pass http://unix:/run/myblog.sock;
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;
}
}

proxy_pass 指向 Unix socket,与 Gunicorn 的 –bind 保持一致。关键补充是 X-Forwarded-Proto $scheme :这个头告诉 Flask“客户端到 Nginx 之间用的是 HTTP 还是 HTTPS”。如果后续配置 SSL 证书时缺少它,Nginx 虽然已经做了 TLS 终止,Flask 仍会把每个请求当成 HTTP,导致后台回跳、绝对链接和重定向生成错误的协议前缀,严重时出现重定向死循环。
校验并重启:

nginx -t
systemctl restart nginx
systemctl status nginx

六、公网访问排查:ERR_CONNECTION_REFUSED 的四层定位法

浏览器访问公网 IP 报连接拒绝时,不要急于修改 Nginx,按以下顺序逐层排除。

第一层:进程是否在跑

systemctl status myblog 和 systemctl status nginx 均为 active (running),进程层排除。

第二层:本机回环是否通

curl -I http://127.0.0.1

本地 curl 正常,说明 Nginx → Gunicorn 的代理链路是通的,问题不在应用或 Nginx 配置本身。

第三层:协议是否匹配

如果浏览器地址栏是 https://106.52.34.17,而当前 Nginx 只监听 80 端口、尚未配置 SSL 证书,就会直接得到 ERR_CONNECTION_REFUSED。排查时首先确认访问的是 http:// 而不是 https://,这是“配置全对但打不开”最常见的原因之一。

第四层:安全组与本机防火墙

如果前三层都正常但公网仍然拒绝,几乎可以断定是安全组入站规则未放行 TCP 80。处理方式:

  • 腾讯云控制台 → 对应 VPC 安全组 → 入站规则 → 添加 TCP:80,授权范围 0.0.0.0/0;
  • 如果 Ubuntu 本机启用了 ufw,同步放行:
  • ufw status
    ufw allow 80/tcp

    这套四层法的有效性在于,它把“连接拒绝”拆成了四个彼此独立的假设:进程在不在、回环通不通、协议对不对、网络规则放没放。逐层排除比反复改配置更省时间。

    七、部署结果与优化优先级

    当前状态:

    • ✅ VPC、子网、地域规划完成,CVM 与网络资源同地域同可用区
    • ✅ CVM 创建、系统初始化完成
    • ✅ 虚拟环境、依赖就位,SQLite 数据库无需迁移建表
    • ✅ Gunicorn 在 Systemd 下常驻,Unix socket 绑定,异常自动重启
    • ✅ Nginx 反向代理本地链路正常,已配置 X-Forwarded-Proto
    • ⏳ 安全组放行 80 端口后,公网可通过 http://106.52.34.17/ 访问
      后续优化的优先级建议如下:
      第一优先级:安全组放行 80。这是当前唯一阻断公网访问的因素,其他优化在此之前无法被真实用户体验到。
      第二优先级:HTTPS 与 ProxyFix 收尾。申请证书后,Nginx 增加 listen 443 ssl,并在 Flask 侧启用 ProxyFix,且 x_proto 的可信层数必须与 Nginx 层数一致。本文只有一层 Nginx,因此 x_proto=1:

    from werkzeug.middleware.proxy_fix import ProxyFix
    app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_for=1)

    如果 Nginx 前面还有一层 CDN 或负载均衡,x_proto 和 x_for 需要相应增加,否则 Flask 仍然取不到正确的原始协议和客户端 IP。
    第三优先级:日志轮转与数据库备份。Gunicorn 的日志已经独立落盘,下一步用 logrotate 按大小或时间切割;SQLite 文件虽然简单,仍然需要定时备份,避免单点磁盘故障导致数据丢失。
    第四优先级:替换 User=root 。创建专用系统用户,同步调整项目目录、socket 文件和日志目录的属主与权限,使服务以最小权限运行。这一步不会立刻改变“能不能访问”,但它是从“能跑”走向“可维护”的必要动作。

    如果这篇部署日志对你有帮助,麻烦点赞、收藏,有疑问或者发现问题都可以在评论区留言,谢谢大家!

    赞(0)
    未经允许不得转载:171主机测评 » Flask 博客腾讯云 CVM 部署实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址