9.9 KiB
ImageForge 双服务器 S3 方案
本文是部署前方案与验收清单。本次开发未在
118.145.177.79或119.29.142.248上安装、修改或启动任何服务。
1. 现状与结论
| 节点 | 实测资源 | 建议职责 | 关键约束 |
|---|---|---|---|
118.145.177.79 |
8 vCPU、31 GiB RAM、约 104 GiB 可用磁盘 | ImageForge API、Worker、PostgreSQL、Redis、ZIP 临时生成 | 已有较多容器,9000 端口已占用,后续建议应用只绑定 127.0.0.1:18080 |
119.29.142.248 |
2 vCPU、1.9 GiB RAM、约 29 GiB 可用磁盘、200M 共享带宽 | Garage S3、Nginx/TLS、下载出口 | 只能作为首期临时对象存储;系统盘容量而不是带宽会最先成为瓶颈 |
首期选择 Garage 2.3,而不是 MinIO。Garage 面向低资源和普通互联网环境,支持本项目所需的 SigV4、Path-style、预签名 URL、对象读写删除、分片上传和生命周期。MinIO 官方单节点说明包含 2 GiB 预分配并建议 32 GiB 内存,不适合 119 当前的 2 GiB 机器。
- Garage 目标与适用场景:Goals and use cases
- Garage 单节点与 2.3.0 镜像:Quick Start
- Garage S3 兼容性:S3 Compatibility
- Garage 配置项:Configuration
- MinIO 单节点资源说明:Deploy MinIO Single-Node
2. 目标架构
flowchart LR
U["用户浏览器"] -->|"上传、鉴权、任务查询"| A["118: ImageForge API"]
A --> P["118: PostgreSQL"]
A --> R["118: Redis"]
A --> W["118: Image Worker"]
W -->|"WireGuard / S3 PUT"| S["119: Garage S3"]
A -->|"鉴权通过后返回 307"| U
U -->|"5 分钟预签名 GET"| N["119: Nginx TLS"]
N --> S
核心规则:
- Bucket 始终私有,不开放匿名读。
- 用户先访问 ImageForge 下载接口;后端检查用户、匿名会话和过期时间后,返回
307到 5 分钟预签名 URL。 - 图片和 ZIP 的实际下载字节只经过 119,118 不再承担下载出口。
- 批处理原图只在 118 的共享
uploads/orig临时保存,Worker 完成或失败后删除,不上传 S3。 - 批量 ZIP 在 118 临时生成,64 MiB 以上自动使用 S3 分片上传,上传后删除临时目录。
- 每个成品记录写入时的端点 ID。以后切换端点不会使旧文件失联;旧端点凭据会保留到关联对象清空。
- 活动 S3 仍是首选后端;连接、凭据或上传失败时,本次成品自动写入 118 本地卷并记录
storage_backend=local。S3 恢复后,后续新对象会再次优先写入 S3。
/api/v1/compress/direct 保留原有“响应体直接返回图片”的 API 语义,避免破坏现有调用方;网站下载、历史记录下载、普通压缩结果和批量 ZIP 均走 S3。
3. 网络与端口
建议先在两台服务器之间建立 WireGuard:
| 用途 | 118 | 119 |
|---|---|---|
| WireGuard 地址 | 10.70.0.1/24 |
10.70.0.2/24 |
| S3 内部 Endpoint | 客户端 | 10.70.0.2:3900 |
| Garage RPC | 后续扩容节点 | 10.70.0.2:3901 |
| 公网下载 | 不开放 | files.<你的域名>:443 |
119 防火墙只允许:公网 80/443、受限来源的 22、WireGuard 对端访问 3900/3901。Garage 管理端口 3903 只绑定回环地址。不要把 3900/3901/3903 直接暴露到公网。
119 已有 Nginx 和 wy.workyai.cn,后续只新增独立的 files.<你的域名> server block,不能覆盖现有站点配置。公网 Nginx 只允许 GET/HEAD,应用上传走 WireGuard 内部 Endpoint。
4. 保留与清理
| 用户层级 | 数据库精确保留 | 对象前缀 | S3 兜底生命周期 |
|---|---|---|---|
| 未登录、免费用户 | 24 小时 | results/1d/、archives/1d/ |
3 天 |
| 低级会员 Pro | 7 天 | results/7d/、archives/7d/ |
9 天 |
| 高级会员 Business | 15 天 | results/15d/、archives/15d/ |
17 天 |
Worker 每 5 分钟按 expires_at 精确删除对象,删除成功后才删除数据库任务。ZIP 构建 attempt 位于对应 archives/<retention>/.../attempts/ 前缀,发布失败会立即删除,进程崩溃遗留项仍由同一前缀生命周期兜底。S3 生命周期多保留 2 天,只负责处理数据库故障、进程崩溃或上传后未能落库的孤儿对象,不能作为精确会员权限判断。未完成的分片上传 1 天后由生命周期中止。
5. 119 首期容量
119 当前约 29 GiB 可用,建议至少给系统、Docker/Nginx 日志和 Garage 元数据保留 9 GiB,因此首期只按约 20 GiB 对象容量规划:
- 70%(约 14 GiB)告警:检查日新增量和清理是否正常。
- 85%(约 17 GiB)紧急告警:停止营销放量并准备扩盘或新端点。
- 90%(约 18 GiB)写入保护:不要继续依赖该系统盘接收新对象。
粗略容量公式:日均压缩后新增量 × 加权平均保留天数 × 1.2。例如每天 1 GiB、平均保留 5 天,仅对象约 6 GiB;每天 4 GiB 时就会接近首期上限。200 Mbps 理论上约 25 MB/s,但共享带宽、磁盘随机读和 2 核 CPU 会使实际吞吐更低。
正式增长前优先给 119 挂载独立的 100 GiB 以上数据盘到 /srv/garage/data。单节点 replication_factor = 1 没有冗余,官方也不建议用于长期生产数据;本项目对象最长 15 天且可重新生成,低用户量阶段可以接受,但不能替代备份。后续可新增 Garage 节点并提升副本数,或在管理后台新增另一套 S3 端点并切换新对象。
6. 管理后台配置
部署 S3 并创建 Bucket/Key 后,在“管理后台 -> 对象存储”新增:
| 字段 | 首期建议值 |
|---|---|
| 名称 | 119 高带宽 S3 |
| 内部 Endpoint | http://10.70.0.2:3900 |
| 公网 Endpoint | https://files.<你的域名> |
| Bucket | imageforge-results |
| Region | garage |
| Force path style | 开启 |
| 签名有效期 | 300 秒 |
Access Key 和 Secret Key 使用项目现有 AES-256-GCM 机制加密入库,页面只显示掩码。保存后先执行“全链路测试”,再点“验证并启用”。后端激活前会再次执行 HeadBucket + 内部 PutObject/GetObject + 公网预签名 GET + 内部 DeleteObject。编辑现有端点时先对候选配置执行同等测试,并抽查历史对象是否仍可访问;测试通过后才保存,活动状态不变。删除采用软删除,立即停止新写入并隐藏端点,但保留历史对象所需的加密配置;历史关联清空 30 天后再彻底移除。若删除活动端点,新文件自动回退应用服务器本地,直到启用其他 S3。
浏览器通过公网预签名地址直接读取对象,因此 Bucket 必须允许应用站点跨域 GET/HEAD。将 docker/storage/cors.json 中的站点域名改成实际应用域名后,通过兼容 S3 的管理工具执行 PutBucketCors。后台全链路测试会携带 PUBLIC_BASE_URL 的 Origin,并拒绝缺失或不匹配的 Access-Control-Allow-Origin,避免对象存在但浏览器无法下载。
7. 后续部署顺序(本次不执行)
- 为下载域名添加 DNS,确认 119 的 80/443 可用,并建立 WireGuard。
- 在 119 创建
/srv/garage/{meta,data,snapshots},生成三个权限为0600的 secret 文件。 - 使用
docker/storage/garage.toml.example和docker-compose.storage.yml.example启动 Garage。 - 创建
imageforge-resultsBucket 和仅限该 Bucket 的应用 Key,应用生命周期文件lifecycle.json。 - 新增
nginx-files.conf.example对应的 HTTPS 站点;确认访问日志不记录签名查询字符串。 - 在 118 备份数据库,部署新版本并执行迁移;首次启动没有活动 S3 时仍使用本地存储。
- 管理后台保存 119 端点,执行测试并启用。
- 使用 JPEG、PNG(透明/非透明)、WebP、AVIF、GIF、BMP、TIFF、ICO,以及中文名、同名文件、大图和批量 ZIP 做验收。
Garage 初始化命令示例(实际部署时执行,输出的 Secret Key 只录入管理后台,不提交仓库):
docker compose -f docker-compose.storage.yml exec garage /garage bucket create imageforge-results
docker compose -f docker-compose.storage.yml exec garage /garage key create imageforge-app
docker compose -f docker-compose.storage.yml exec garage /garage bucket allow \
--read --write --owner imageforge-results --key imageforge-app
aws --endpoint-url http://127.0.0.1:3900 \
s3api put-bucket-lifecycle-configuration \
--bucket imageforge-results \
--lifecycle-configuration file://lifecycle.json
aws --endpoint-url http://127.0.0.1:3900 \
s3api put-bucket-cors \
--bucket imageforge-results \
--cors-configuration file://cors.json
8. 上线验收
- 管理端测试必须完成读、写、删,Bucket 内不能残留健康检查对象。
- 普通下载接口先返回
307,Location指向下载域名且有效期约 300 秒。 - 携带应用站点
Origin请求预签名地址时返回匹配的Access-Control-Allow-Origin,浏览器可读取完整对象。 - 未授权用户无法取得签名 URL;任务过期后应用下载接口返回 404。
- 单文件与批任务数据库均记录正确的
storage_endpoint_id和对象键。 - 切换到第二端点后,新对象进入第二端点,第一端点历史对象仍可下载。
- S3 停止时同步请求和批任务均完成本地兜底,数据库记录
storage_backend=local,下载接口直接从应用服务器返回文件。 - S3 恢复后下一次新对象重新记录为
storage_backend=s3,下载接口恢复307到签名 URL。 - 删除 S3 对象失败时,过期任务数据库记录保留并在下一轮重试。
- 119 磁盘 70%/85% 告警、容器重启、NTP 时间同步和证书续期均验证通过。