# 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。以后切换端点不会使旧文件失联;旧端点凭据会保留到关联对象清空。 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 时间同步和证书续期均验证通过。