云主机部署图片/视频管理系统的实践经验

云主机部署图片/视频管理系统的实践经验(Nginx + FastAPI + 数据盘)

读者对象 :准备把个人 Web 应用(尤其是带大量图片、视频存储的)部署到云服务器的开发者。

解决的问题:从「本地能跑」到「服务器上稳定对外」这一步里,那些官方文档不会写、但你几乎一定会撞上的部署、防火墙、数据盘挂载与存储选型问题。

本文记录一次将 Python (FastAPI) + Vite 前端 + SQLite + 本地文件存储的媒体类应用部署到云服务器(OpenCloudOS,RHEL 系)的过程。文中路径与参数均为示例,不涉及具体项目信息。

文章目录

一、部署架构:后端不直接对外

不要让 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,两个常见误区:

  1. 平台必须对应 。Windows 的 ffmpeg.exe 无法在 Linux 运行,服务器需要的是 Linux x86_64 版本。
  2. 下载方式。服务器直连境外源可能很慢或超时,此时可以「本地下载好 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$//' 处理一下。

五、云服务器的两层防火墙

轻量应用服务器通常存在两道独立的防火墙,外网可访问要求两道都放行:

  1. 系统层(firewalld / iptables);
  2. 云平台层(控制台或云 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)。

六、数据盘挂载

为媒体单独挂一块数据盘时,注意三个容易出问题的点:

  1. 挂载点权限 。不要把媒体放在 /root 下------/root 通常是 550 / 700,Nginx 的运行用户无法穿越该目录,导致文件 403。挂载点应放在可被 web 用户遍历的位置,例如 /data(755)。
  2. 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} 原样写进文件。

  1. 在线扩容。云盘在控制台扩容容量后,操作系统内再扩展文件系统即可,无需重启:
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 放行等于全网可访问,含隐私的站点应加访问控制:Nginx auth_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 云服务器 防火墙 云硬盘 对象存储 运维部署

背景说明:本文是个人项目上云过程中的一次实践整理,聚焦通用踩坑与取舍,命令与路径已做脱敏与泛化处理,具体参数请以你所用云厂商的文档为准。

相关推荐
代码山河5 小时前
Python就业方向全解析:后端、爬虫、数据分析、AI你选哪个?
python·学习·架构·教程·面向对象·项目
在世修行6 小时前
智学文枢:题目流水线
python·多线程·状态机·pyside
voipmaker6 小时前
GB28181 对接踩坑实录:注册不上、目录不全、流卡顿,一次说清(附 2022 版变更清单)
物联网·音视频·实时音视频·信息与通信·iot·gb28181·国标gb28181
梯度下降者6 小时前
零基础入门Python 爬虫
爬虫·python·数据分析·入门指南·webcrawler
2601_958352907 小时前
23.5×19mm 换掉一整条音频链?|A-68 集成了什么:ADC / DAC / 24.576MHz 时钟 / AEC / 波束降噪,主控仅需 6 根线
前端·音视频·ai降噪·回声消除·语音模块
2601_965448878 小时前
AI视频制作:做系列历史人物视频音色不搭,AI 音色克隆的方法实测
人工智能·音视频
码上观世界8 小时前
多余的一声「嗯」:Fun-CosyVoice3 zero_shot 开头伪影排查实录
音视频
“AI国潮设计-小江”8 小时前
【AIGC实战】Python+Stable Diffusion打造“英歌舞财神”IP,解锁“马到成功”等爆款情绪营销文案设计
开发语言·人工智能·python·prompt·aigc
manyingAi8 小时前
AI漫剧生产系统架构解析:从文本拆解到音视频合成
人工智能·音视频
benchmark_cc9 小时前
用 Python 拉 A 股全市场实时快照:Notebook 请求、结果校验与易错点
python·数据分析·pandas·量化交易·股票数据·quantdash