132 lines
8.5 KiB
Markdown
132 lines
8.5 KiB
Markdown
# 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 的实际下载字节只经过 119,118 不再承担下载出口。
|
||
4. 批处理原图只在 118 的共享 `uploads/orig` 临时保存,Worker 完成或失败后删除,不上传 S3。
|
||
5. 批量 ZIP 在 118 临时生成,64 MiB 以上自动使用 S3 分片上传,上传后删除临时目录。
|
||
6. 每个成品记录写入时的端点 ID。以后切换端点不会使旧文件失联;旧端点凭据会保留到关联对象清空。
|
||
|
||
`/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`。活动端点或仍有关联对象的端点不能直接编辑、归档,需先新增并启用替代端点,等待旧对象过期后再移除。
|
||
|
||
## 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 停止时同步请求返回 503,批任务保留临时原图并重试,不静默写回本地。
|
||
- 删除 S3 对象失败时,过期任务数据库记录保留并在下一轮重试。
|
||
- 119 磁盘 70%/85% 告警、容器重启、NTP 时间同步和证书续期均验证通过。
|