8.6 KiB
部署指南
生产部署
仓库提供完整的 docker/Dockerfile 与 docker/docker-compose.prod.yml。生产编排包含 API、Worker、PostgreSQL 和 Redis;数据库与 Redis 不发布宿主机端口,API 和 Worker 使用同一上传卷,并以非 root、只读根文件系统运行。
环境要求
- Linux x86_64
- Docker Engine 26+ 与 Docker Compose 2.20+
- 最低 2 核 CPU、4GB 内存;启用 AVIF 和独立 Worker 时建议 4 核、8GB 内存
- 首次构建可访问 Docker Hub 与 crates.io
Debian 13、4 核 CPU、8GB 内存的起始值建议为 WORKER_TASK_CONCURRENCY=2、WORKER_CONCURRENCY=2 和 IMAGE_PROCESSING_CONCURRENCY=2;8 核应用服务器可从 4/2/4 开始。三者分别表示同时处理的任务数、单任务内文件数和单进程 CPU 图片处理上限。最后一项是全局 CPU 闸门,因此不要把它设置为 CPU 核数的数倍。任务并发提高后,数据库连接池建议至少为 WORKER_TASK_CONCURRENCY * WORKER_CONCURRENCY + 4,生产示例使用 16。
生产 Compose 默认按 8 核 16GB 主机设置可覆盖的资源上限:API 3GB、Worker 8GB、PostgreSQL 2GB、Redis 1GB;对应变量为 API_MEMORY_LIMIT、WORKER_MEMORY_LIMIT、POSTGRES_MEMORY_LIMIT 和 REDIS_MEMORY_LIMIT。Redis 的 REDIS_MAXMEMORY 默认 768MB,达到上限后返回写入错误而不是继续挤占宿主机内存。
首次启动
cp docker/.env.production.example .env.production
chmod 600 .env.production
# 分别生成 POSTGRES_PASSWORD、JWT_SECRET、API_KEY_PEPPER 和管理员密码。
# hex 不包含数据库 URL 与 .env 需要转义的保留字符。
openssl rand -hex 32
# 编辑公开地址和全部 replace-with-* 值后检查配置。
docker compose \
--env-file .env.production \
-f docker/docker-compose.prod.yml \
config --quiet
docker compose \
--env-file .env.production \
-f docker/docker-compose.prod.yml \
up -d --build
API 健康后 Worker 才会启动,避免两个进程在首次部署时同时执行迁移。
管理员可用邮箱或用户名登录。首次启动时设置 ADMIN_USERNAME 和 ADMIN_PASSWORD 即可创建管理员;若未设置 ADMIN_EMAIL,系统会生成仅用于满足内部数据约束的 用户名@local.invalid 占位邮箱。确认账号创建后应从生产环境文件中移除 ADMIN_PASSWORD,避免每次重启都重置密码。
docker compose --env-file .env.production -f docker/docker-compose.prod.yml ps
curl --fail http://127.0.0.1:8080/health
curl --fail http://127.0.0.1:8080/metrics
预期健康响应:
{"status":"healthy","database":"connected","redis":"connected"}
更新与回滚
更新代码后保留 .env.production 和命名卷。包含迁移 017 至 019 的版本不能直接让旧、新 Worker 并行滚动:先备份数据库并停止旧 Worker,再构建新镜像。
迁移 017 会在发现重复 Customer 或同用户多条未取消 Stripe 订阅时主动失败,迁移 019 会在发现同一 Stripe 发票对应多行时主动失败。部署前先检查并人工对账,三个查询都必须返回 0 行:
SELECT billing_customer_id, COUNT(*)
FROM users
WHERE billing_customer_id IS NOT NULL AND billing_customer_id <> ''
GROUP BY billing_customer_id
HAVING COUNT(*) > 1;
SELECT user_id, COUNT(*)
FROM subscriptions
WHERE provider = 'stripe' AND status <> 'canceled'
GROUP BY user_id
HAVING COUNT(*) > 1;
SELECT provider, provider_invoice_id, COUNT(*)
FROM invoices
WHERE provider_invoice_id IS NOT NULL
GROUP BY provider, provider_invoice_id
HAVING COUNT(*) > 1;
推荐顺序:
git pull --ff-only
docker compose --env-file .env.production -f docker/docker-compose.prod.yml stop worker
docker compose --env-file .env.production -f docker/docker-compose.prod.yml build api
docker compose --env-file .env.production -f docker/docker-compose.prod.yml up -d postgres redis api
docker compose --env-file .env.production -f docker/docker-compose.prod.yml up -d worker
新 API 启动后会消费迁移 018 创建的 Stripe 对账队列。启动 Worker 前应确认 API 健康、STRIPE_SECRET_KEY 可用且服务器能访问 STRIPE_API_BASE_URL;对账可以后台继续,但必须监控失败项:
SELECT status, COUNT(*)
FROM stripe_subscription_reconciliations
GROUP BY status;
SELECT provider_object_id, reconciliation_reason, updated_at
FROM provider_object_event_watermarks
WHERE provider = 'stripe' AND requires_reconciliation = true
ORDER BY updated_at;
failed 会指数退避重试;持续失败通常表示 Stripe 凭据、网络、Customer/Price 映射不完整。上线验收要求 pending/processing/failed 最终归零,且 requires_reconciliation=true 为 0。生产镜像应使用不可变的 IMAGEFORGE_TAG。数据库迁移已应用后,不能只回滚旧二进制;应保留新 schema,并使用兼容该 schema 的修复镜像。
反向代理
生产 Compose 默认把 API 端口绑定到 127.0.0.1。若确实需要绕过反向代理直接通过服务器地址访问,显式设置 IMAGEFORGE_BIND_ADDRESS=0.0.0.0,同时保持 TRUST_PROXY_HEADERS=false 并配置主机防火墙。只有当 API 端口不对客户端开放、所有请求都经过可信反向代理时,才设置 TRUST_PROXY_HEADERS=true,并由代理覆盖 X-Forwarded-For 与 X-Forwarded-Proto。
代理至少需要:
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 100m;
proxy_read_timeout 300s;
}
/metrics 包含运行状态与队列数据,不应通过公开域名暴露。生产 Nginx 应单独拒绝该路径,Prometheus 直接抓取只绑定回环地址的 API 端口:
location = /metrics {
allow 127.0.0.1;
allow ::1;
deny all;
proxy_pass http://127.0.0.1:8080;
}
应用会为所有响应设置 CSP、X-Content-Type-Options、X-Frame-Options、Referrer-Policy 和 Permissions-Policy。TLS 网关还应设置 Strict-Transport-Security。
日志与备份
docker compose --env-file .env.production -f docker/docker-compose.prod.yml logs -f api worker
docker compose --env-file .env.production -f docker/docker-compose.prod.yml \
exec -T postgres pg_dump -U imageforge imageforge | gzip > imageforge.sql.gz
除数据库外,还要备份 Compose 的 uploads 命名卷。恢复前停止 API 与 Worker,避免数据库记录与文件卷产生时间差。
本地开发
cp .env.example .env
docker compose -f docker/docker-compose.dev.yml up -d
# 终端 1
IMAGEFORGE_ROLE=api cargo run
# 终端 2
IMAGEFORGE_ROLE=worker cargo run
# 终端 3
cd frontend
npm ci
npm run dev
API 或 Worker 启动时会通过 SQLx 顺序执行尚未应用的迁移。迁移失败时进程退出,不会在不完整的数据库结构上继续提供服务。
压缩质量回归
基准工具会下载固定 Picsum 照片,分别生成 JPEG、PNG、WebP、AVIF 输入,再通过真实 HTTP API 测量目标达标率、格式签名、SSIM、PSNR、分辨率和耗时。
python3 -m venv .venv-benchmark
. .venv-benchmark/bin/activate
pip install -r scripts/requirements-benchmark.txt
python scripts/benchmark_compression.py generate --output-dir .bench/corpus
IMAGEFORGE_BENCH_EMAIL=admin@example.com \
IMAGEFORGE_BENCH_PASSWORD='replace-me' \
python scripts/benchmark_compression.py run \
--base-url http://127.0.0.1:8080 \
--input-dir .bench/corpus \
--output-dir .bench/results \
--formats jpeg,webp,avif \
--rates 30,50,70 \
--strict
--strict 会在请求失败、输出格式不匹配或目标体积未达标时返回非零退出码。测试素材与结果位于 .bench/,不会提交到 Git。
故障排查
# 容器与健康状态
docker compose --env-file .env.production -f docker/docker-compose.prod.yml ps
# 最近日志
docker compose --env-file .env.production -f docker/docker-compose.prod.yml logs --tail=200 api worker
# 数据库迁移状态
docker compose --env-file .env.production -f docker/docker-compose.prod.yml \
exec -T postgres psql -U imageforge -d imageforge \
-c 'SELECT version, success FROM _sqlx_migrations ORDER BY version;'
# 主机资源
docker stats
df -h
若图片压缩长时间排队,先检查 CPU,再调整 IMAGE_PROCESSING_CONCURRENCY;大任务阻塞小任务时提高 WORKER_TASK_CONCURRENCY,单个批量任务推进过慢时再评估 WORKER_CONCURRENCY。若 API 健康但批量任务不推进,检查 Worker 日志、Redis pending 数和数据库连接池等待情况。