云主机部署图片/视频管理系统的实践经验(Nginx + FastAPI + 数据盘)
读者对象 :准备把个人 Web 应用(尤其是带大量图片、视频存储的)部署到云服务器的开发者。
解决的问题:从「本地能跑」到「服务器上稳定对外」这一步里,那些官方文档不会写、但你几乎一定会撞上的部署、防火墙、数据盘挂载与存储选型问题。
本文记录一次将 Python (FastAPI) + Vite 前端 + SQLite + 本地文件存储的媒体类应用部署到云服务器(OpenCloudOS,RHEL 系)的过程。文中路径与参数均为示例,不涉及具体项目信息。
文章目录
- [云主机部署图片/视频管理系统的实践经验(Nginx + FastAPI + 数据盘)](#云主机部署图片/视频管理系统的实践经验(Nginx + FastAPI + 数据盘))
-
- 一、部署架构:后端不直接对外
- 二、依赖与环境
- 三、二进制依赖(ffmpeg)
- 四、远程操作与文件传输
- 五、云服务器的两层防火墙
- 六、数据盘挂载
- [七、存储选型:SSD / 机械盘 / 对象存储](#七、存储选型:SSD / 机械盘 / 对象存储)
- 八、可扩展性:趁数据量小先把结构定对
- 九、验证:以实测为准
- 十、安全注意事项
- 小结
一、部署架构:后端不直接对外
不要让 uvicorn / gunicorn 直接监听公网端口,统一走 Nginx 反向代理:
浏览器 ──> Nginx
├─ / → 前端静态产物(SPA 回退 index.html)
├─ /media/ → Nginx 直接发送上传的图片/视频(不经过后端)
└─ /api/ → 反向代理到 127.0.0.1:8000(后端只监听本机)
关键点是媒体文件交给 Nginx 直发 ,而不是用后端读文件返回。用后端 StaticFiles 吐大文件时,进程既要处理请求又要搬运字节,内存和并发都吃亏;换成 Nginx alias 后,Range 请求(视频拖动)、缓存头、高并发读取都由 Nginx 承担,后端只处理业务接口。
后端进程用 systemd 托管,配置 Restart=always 并设置开机自启:
ini
# /etc/systemd/system/gallery.service
[Unit]
Description=Gallery API
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/gallery
ExecStart=/opt/gallery/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000 --workers 1
Restart=always
RestartSec=3
Environment=PYTHONUNBUFFERED=1
Environment=TZ=Asia/Shanghai
[Install]
WantedBy=multi-user.target
workers 数量受内存限制,小内存机器保持 1 即可。
二、依赖与环境
如果项目缺少 requirements.txt,可以先扫描源码里的 import 反推第三方依赖,再逐个锁定版本:
bash
grep -rhE "^(import|from) " . | sort -u
几个固定要装的:fastapi、uvicorn[standard]、sqlalchemy、pydantic、Pillow,以及------上传接口必须装 python-multipart ,FastAPI 处理 UploadFile / Form 依赖它,漏装会直接 500。
关于 Redis 有一个判断技巧:如果代码里用的是 redis.from_url(...),它在构造时并不会建立连接,且调用处若都包了 try/except,那么只需要安装 redis 这个包让 import 通过即可,不必真的部署 Redis 服务 。不要因为看到 import redis 就去装中间件。
虚拟环境隔离:
bash
python3 -m venv /opt/gallery/venv
/opt/gallery/venv/bin/pip install -r requirements.txt
三、二进制依赖(ffmpeg)
视频封面依赖 ffmpeg,两个常见误区:
- 平台必须对应 。Windows 的
ffmpeg.exe无法在 Linux 运行,服务器需要的是 Linux x86_64 版本。 - 下载方式。服务器直连境外源可能很慢或超时,此时可以「本地下载好 Linux 静态包 → 上传到服务器 → 解包安装」,往往比服务器直接拉更快更稳。
在 Windows 的 curl 上做 HTTPS 下载,若遇到证书吊销检查离线报错 CRYPT_E_REVOCATION_OFFLINE,加参数绕过:
bash
curl --ssl-no-revoke -L -o ffmpeg.tar.xz <下载地址>
安装到 /usr/local/bin 后,systemd 服务默认 PATH 即可找到,无需额外配置。
四、远程操作与文件传输
Git Bash 的路径转换问题 (Windows 下高频坑):在 Git Bash(MSYS)里,命令行参数中形如 /opt/gallery/deploy.tgz 的绝对路径会被自动转换成 C:/Program Files/Git/opt/gallery/deploy.tgz 再传给程序。这会导致远程路径错乱,表现为 SFTP 报 ENOENT、cat 重定向失败等,很容易被误判为「SFTP 子系统坏了」。解决方式是关闭转换:
bash
export MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'
免交互 SSH 在 Windows 上可用 Python 的 paramiko,密码通过环境变量传入脚本,避免写进文件。传文件优先用 sftp.put;确实遇到 SFTP 不可用时,可退化为 exec_command("cat > 远端路径") 流式写入,或分块 base64 追加(可靠但慢)。
打包上传用 tar 并排除 __pycache__、日志等;上传的脚本若带 Windows 换行符,执行前用 sed -i 's/\r$//' 处理一下。
五、云服务器的两层防火墙
轻量应用服务器通常存在两道独立的防火墙,外网可访问要求两道都放行:
- 系统层(firewalld / iptables);
- 云平台层(控制台或云 API 配置的防火墙,SSH 无法修改)。
系统层放行时注意:启用 firewalld 会接管并可能清空原有 iptables 规则(含厂商注入的安全链),操作前先备份,并确认 ssh 仍在放行列表,避免把自己锁在外面:
bash
iptables-save > /root/iptables.bak.$(date +%s)
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload
firewall-cmd --query-service=ssh
云平台层可用 SDK 调 API 添加规则。若子账号密钥缺少对应资源权限会报 NoPermission,需要主账号在访问管理里给子账号补上相应预设策略(如轻量服务器的 FullAccess)。
六、数据盘挂载
为媒体单独挂一块数据盘时,注意三个容易出问题的点:
- 挂载点权限 。不要把媒体放在
/root下------/root通常是550/700,Nginx 的运行用户无法穿越该目录,导致文件 403。挂载点应放在可被 web 用户遍历的位置,例如/data(755)。 - fstab 必须用绝对路径 + 真实 UUID 。自动生成的 fstab 有时会把挂载点写成相对路径,重启后挂载失败。用
blkid取真实 UUID 后重写,并加nofail(避免磁盘异常时卡住开机):
bash
blkid /dev/vdb
# /etc/fstab 追加:
UUID=<真实uuid> /data ext4 defaults,nofail 0 2
mount -a
findmnt /data # 确认源确实是 /dev/vdb
用脚本改 fstab 时,尽量用 printf 直接写入字面值,避免 shell 变量在多层引号中未展开、把 ${UUID} 原样写进文件。
- 在线扩容。云盘在控制台扩容容量后,操作系统内再扩展文件系统即可,无需重启:
bash
resize2fs /dev/vdb # ext4
如果希望应用「不改代码」就把文件写到数据盘,可以用软链把应用写入目录指向数据盘目录:
bash
ln -sfn /data/uploads /opt/gallery/uploads
应用仍写 uploads/,实际落到数据盘;数据库文件则保留在 SSD。验证是否真的落到目标盘:调用一次上传接口后 find /data/uploads 查看文件、df 看容量变化,确认无误后清理测试数据。
需要提醒的是,软链属于过渡方案,路径真相分散、且备份要同时覆盖两块盘(SSD 的数据库 + 数据盘的媒体)。更彻底的做法见第八节。
七、存储选型:SSD / 机械盘 / 对象存储
判断云硬盘介质看 IOPS / 吞吐公式,不要看名字。 例如某云的「高性能云硬盘」IOPS 上限约 6000、吞吐上限约 150 MB/s,本质是机械盘;「SSD 云硬盘」IOPS 可达数万、吞吐更高,才是固态。名字会营销,规格参数不会。
按负载分工:
- 数据库/元数据(随机小 IO、延迟敏感)→ SSD;
- 原图/视频(大文件顺序读写、容量大、访问偏冷)→ 机械盘,成本低且够用。
对象存储(COS / OSS)要算总账:存储单价低,但外网下行流量和请求次数是隐藏成本。单人自用、访问量小的场景,用对象存储往往是「省了存储费、多了流量费和复杂度」,本地数据盘更划算。对象存储更适合海量访客、需要 CDN、多实例共享或冷数据归档的场景。
选型前先做容量估算:平均单文件大小 × 目标数量,判断是否需要额外数据盘,不要等写满才处理。
八、可扩展性:趁数据量小先把结构定对
真正「便宜时不做、贵时做不完」的是数据布局与结构,建议在早期一次做对:
- 存储抽象层:定义统一的存储接口(save / delete / url / exists),默认本地盘实现。将来换更大的盘或对象存储时只替换实现,不改业务代码。
- 内容哈希分桶目录 :用
{ab}/{cd}/{hash}.ext这样的路径,不依赖拍摄时间或导入时间(元数据可能缺失),避免单目录堆积海量文件,也基本不需要重新布局。 - 数据库结构:标签用关联表而非 JSON 列(可索引、跨库可移植);只存相对 key、不存绝对路径;为高频查询建复合索引;EXIF 等大字段拆分存储。趁表还是空的,这些改动几乎零迁移成本。
- 可延后的运行时优化:后台任务队列、把重 IO 移出事件循环、keyset 分页、缓存、CDN------等数据量上来再按需加,且都不影响数据结构。
九、验证:以实测为准
改完不要只凭代码推断,尽量实际验证:
- 界面:用无头浏览器截图验证渲染结果,稳定可控:
bash
msedge --headless=new --disable-gpu --hide-scrollbars \
--window-size=1440,900 --virtual-time-budget=8000 \
--screenshot=shot.png http://localhost:8080/
--virtual-time-budget 给前端加载数据留时间,--window-size 决定视口宽度(否则响应式布局可能折叠侧栏,导致误判)。
- 功能:不要只测首页 200,把「上传 → 落盘核对 → 经 Web 读取 200」这条链完整走一遍。
- 服务 :
systemctl is-active、journalctl -u <svc>、nginx -t。
十、安全注意事项
- 服务器密码、云 API 密钥等凭据不要明文写进代码或文档;一旦泄露应立即在控制台轮换。
- 端口对
0.0.0.0/0放行等于全网可访问,含隐私的站点应加访问控制:Nginxauth_basic、来源 IP 白名单,或至少 HTTPS + 登录。 - 生产环境把 CORS 从
*收紧到具体域名。 - 调试类、一次性全表载入内存的接口,上线前移除或加保护,既防数据泄露也防 OOM。
小结
回到开头那个问题------从「本地能跑」到「服务器上稳定对外」,卡住人的往往不是业务代码,而是这些平台差异、权限、网络分层和存储选型的细节。几条可复用的结论:
- 后端只监听本机,统一走 Nginx 反代;媒体由 Nginx 直发。
- 上传接口记得装
python-multipart;惰性连接的客户端不必真部署服务。 - Windows Git Bash 会篡改
/开头的参数,远程操作用MSYS_NO_PATHCONV=1。 - 云服务器两层防火墙都要放行,动 firewalld 前备份并保住 ssh。
- 数据盘挂载点别放
/root下,fstab 用绝对路径 + 真实 UUID +nofail。 - 存储按负载分介质,对象存储注意流量费;选型前先估容量。
- 结构类改动(存储抽象、哈希分桶、schema)趁数据小做对,运行时优化可延后。
- 一切改动以无头截图 + 端到端实测为准。
标签 :Linux Nginx FastAPI 云服务器 防火墙 云硬盘 对象存储 运维部署
背景说明:本文是个人项目上云过程中的一次实践整理,聚焦通用踩坑与取舍,命令与路径已做脱敏与泛化处理,具体参数请以你所用云厂商的文档为准。