Files
ystp/docs/s3-storage-plan.md
2026-07-25 16:41:31 +08:00

134 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](https://garagehq.deuxfleurs.fr/documentation/design/goals/)
- Garage 单节点与 2.3.0 镜像:[Quick Start](https://garagehq.deuxfleurs.fr/documentation/quick-start/)
- Garage S3 兼容性:[S3 Compatibility](https://garagehq.deuxfleurs.fr/documentation/reference-manual/s3-compatibility/)
- Garage 配置项:[Configuration](https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/)
- MinIO 单节点资源说明:[Deploy MinIO Single-Node](https://min.io/docs/minio/container/operations/install-deploy-manage/deploy-minio-single-node-single-drive.html)
## 2. 目标架构
```mermaid
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
```
核心规则:
1. Bucket 始终私有,不开放匿名读。
2. 用户先访问 ImageForge 下载接口;后端检查用户、匿名会话和过期时间后,返回 `307` 到 5 分钟预签名 URL。
3. 图片和 ZIP 的实际下载字节只经过 119118 不再承担下载出口。
4. 批处理原图只在 118 的共享 `uploads/orig` 临时保存Worker 完成或失败后删除,不上传 S3。
5. 批量 ZIP 在 118 临时生成64 MiB 以上自动使用 S3 分片上传,上传后删除临时目录。
6. 每个成品记录写入时的端点 ID。以后切换端点不会使旧文件失联旧端点凭据会保留到关联对象清空。
7. 活动 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` 精确删除对象删除成功后才删除数据库任务。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。
## 7. 后续部署顺序(本次不执行)
1. 为下载域名添加 DNS确认 119 的 80/443 可用,并建立 WireGuard。
2. 在 119 创建 `/srv/garage/{meta,data,snapshots}`,生成三个权限为 `0600` 的 secret 文件。
3. 使用 `docker/storage/garage.toml.example``docker-compose.storage.yml.example` 启动 Garage。
4. 创建 `imageforge-results` Bucket 和仅限该 Bucket 的应用 Key应用生命周期文件 `lifecycle.json`
5. 新增 `nginx-files.conf.example` 对应的 HTTPS 站点;确认访问日志不记录签名查询字符串。
6. 在 118 备份数据库,部署新版本并执行迁移;首次启动没有活动 S3 时仍使用本地存储。
7. 管理后台保存 119 端点,执行测试并启用。
8. 使用 JPEG、PNG透明/非透明、WebP、AVIF、GIF、BMP、TIFF、ICO以及中文名、同名文件、大图和批量 ZIP 做验收。
Garage 初始化命令示例(实际部署时执行,输出的 Secret Key 只录入管理后台,不提交仓库):
```bash
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
```
## 8. 上线验收
- 管理端测试必须完成读、写、删Bucket 内不能残留健康检查对象。
- 普通下载接口先返回 `307``Location` 指向下载域名且有效期约 300 秒。
- 未授权用户无法取得签名 URL任务过期后应用下载接口返回 404。
- 单文件与批任务数据库均记录正确的 `storage_endpoint_id` 和对象键。
- 切换到第二端点后,新对象进入第二端点,第一端点历史对象仍可下载。
- S3 停止时同步请求和批任务均完成本地兜底,数据库记录 `storage_backend=local`,下载接口直接从应用服务器返回文件。
- S3 恢复后下一次新对象重新记录为 `storage_backend=s3`,下载接口恢复 `307` 到签名 URL。
- 删除 S3 对象失败时,过期任务数据库记录保留并在下一轮重试。
- 119 磁盘 70%/85% 告警、容器重启、NTP 时间同步和证书续期均验证通过。