kira-imgproxy 是基于 imgproxy 的图片处理服务,提供动态图片缩放、格式转换、模糊等功能。
imgproxy 是当前的图片变体路径。 源图从 Cloudflare R2(经 cdn.kira.art / kira-cdn)拉取。kira-image(Rust 变体生成器)拟在未来替代它,但尚未部署。
部署信息
| 配置 | 值 |
|---|
| 平台 | Fly.io |
| 域名 | img.kira.art |
| 区域 | fra / nrt / sjc(fly.toml primary_region=sjc,运行时多区),min_machines_running = 2 |
| 内存 | 8 GB |
| CPU | 4 vCPU (performance 独占核) |
| 端口 | 8080(主 HTTP,变体处理)+ 8081(Prometheus /metrics,内网、与主 HTTP 端口分离) |
URL 签名
imgproxy 使用 HMAC-SHA256 签名保护 URL,防止滥用。
签名算法
import crypto from "crypto";
function signImgproxyPath(path: string): string {
const key = Buffer.from(IMGPROXY_KEY, "hex");
const salt = Buffer.from(IMGPROXY_SALT, "hex");
const hmac = crypto.createHmac("sha256", key);
hmac.update(salt);
hmac.update(path);
return hmac.digest("base64url");
}
URL 格式
https://img.kira.art/{signature}/{processing}/{source}@{format}
示例:
https://img.kira.art/CQTBc5cY.../rs:fit:400:0/plain/https%3A%2F%2F...@webp
处理参数
缩放 (resize)
rs:{type}:{width}:{height}
| 参数 | 说明 |
|---|
rs:fit:400:0 | 宽度 400px,高度自适应 |
rs:fill:200:200 | 填充到 200x200 |
rs:auto:800:600 | 自动选择最佳方式 |
模糊 (blur)
示例:bl:5 - 高斯模糊,sigma=5
质量 (quality)
示例:q:80 - 压缩质量 80%
环境变量
性能配置
IMGPROXY_WORKERS = "8" # 工作线程数 (> CPU: 负载主要是下载源图的 I/O 等待)
IMGPROXY_REQUESTS_QUEUE_SIZE = "64" # 超出 worker 数的请求排队
IMGPROXY_TIMEOUT = "30" # 处理超时 30 秒 (截断卡死渲染、快释放 worker)
IMGPROXY_DOWNLOAD_TIMEOUT = "20" # 下载超时 20 秒 (R2 源下载正常 <2s)
IMGPROXY_TTL = "2592000" # Cache-Control max-age (30 天)
安全限制
IMGPROXY_ALLOWED_SOURCES = "*"
IMGPROXY_MAX_SRC_RESOLUTION = "150" # 最大 150MP (upscale 工具可能产出 >100MP)
IMGPROXY_MAX_SRC_FILE_SIZE = "157286400" # 最大 150MB (高清 upscale PNG)
IMGPROXY_MAX_ANIMATION_FRAMES = "100" # 最大动画帧数
图片质量
IMGPROXY_QUALITY = "80"
IMGPROXY_FORMAT_QUALITY = "webp=75,jpeg=80"
IMGPROXY_STRIP_METADATA = "true" # 移除 EXIF 等元数据
IMGPROXY_AUTO_ROTATE = "true" # 自动修正方向
格式检测
IMGPROXY_AUTO_WEBP = "true"
IMGPROXY_PREFERRED_FORMATS = "webp,jpeg"
不出 AVIF:AVIF 编码 CPU 是 WebP 的 5-10 倍(libaom),worker 易饱和、p95 高;体积只比 WebP 小 ~20%,不值。WebP 是体积 / CPU 最佳平衡,不支持的客户端 fallback JPEG。CSAM(PhotoDNA 不认 AVIF/WebP)走 f:jpeg 强制 JPEG 的 imgproxy URL。
内存管理
IMGPROXY_MALLOC = "jemalloc" # 内存分配器 (减少碎片 / OOM)
IMGPROXY_FREE_MEMORY_INTERVAL = "10" # 每 10 秒强制释放内存
IMGPROXY_DOWNLOAD_BUFFER_SIZE = "20971520" # 下载缓冲区 20MB
IMGPROXY_BUFFER_POOL_CALIBRATION_THRESHOLD = "256"
GOMEMLIMIT = "1500MiB" # Go 运行时内存限制 (留余量)
请求与 CORS
IMGPROXY_ALLOW_ORIGIN = "*"
IMGPROXY_USER_AGENT = "Mozilla/5.0 (compatible; KiraImgProxy/1.0; +https://kira.art)"
Fallback 图片
IMGPROXY_FALLBACK_IMAGE_URL = "https://cdn.kira.art/public/place_holder/image_error.png"
IMGPROXY_FALLBACK_IMAGE_HTTP_CODE = "200" # 返回 200 而非错误码,避免触发报警
IMGPROXY_FALLBACK_IMAGE_TTL = "10" # 占位图只缓存 10s,不跟主 TTL 走(避免毒化)
占位图 TTL 必须远小于主 IMGPROXY_TTL(30 天):否则源图瞬时 404(上传中 / R2 transient)会让占位图被 CF 缓存 30 天,即使源图恢复也不 self-heal。同理,img.kira.art 的 CF Cache Rule 必须把 status ≥ 400 设为 No-store,否则一次瞬时 504/429 会被 CF + 浏览器缓存。
健康检查
fly.toml 中配置:
[[http_service.checks]]
grace_period = "5s"
interval = "30s"
method = "GET"
path = "/health"
timeout = "5s"
VM 与自动扩缩
fly.toml 中配置:
[http_service]
internal_port = 8080
auto_stop_machines = 'stop'
auto_start_machines = true
min_machines_running = 2
[[vm]]
memory = '8gb'
cpu_kind = 'performance' # 独占核:图像服务热路径
cpus = 4
密钥配置
签名密钥存储在 Fly.io secrets 中:
fly secrets set IMGPROXY_KEY=<hex-key> IMGPROXY_SALT=<hex-salt>
密钥必须是 hex 编码的字符串,至少 32 字节。
缓存策略
响应头
imgproxy 返回的 Cache-Control 头:
Cache-Control: public, max-age=2592000
Cloudflare CDN
在 Cloudflare Dashboard 为 img.kira.art 配置 Cache Rules:
- Cache eligibility: Eligible for cache
- Edge TTL: Use origin Cache-Control header
- Browser TTL: Use origin Cache-Control header
本地运行
cd kira-imgproxy
# 使用 Docker
docker run -p 8080:8080 \
-e IMGPROXY_KEY=your_key \
-e IMGPROXY_SALT=your_salt \
ghcr.io/imgproxy/imgproxy:latest
Prometheus → kira-otel-collector → Dash0
imgproxy 通过 Prometheus /metrics 暴露指标,由 kira-otel-collector 拉取(scrape kira-imgproxy.internal:8081/metrics)后转发到 Dash0:
IMGPROXY_PROMETHEUS_BIND = ":8081"
imgproxy 的 OTLP exporter 只发 runtime gauge(workers / vips / in-progress),不发 RED 三件套(imgproxy_requests_total / imgproxy_request_duration_seconds / imgproxy_errors_total)—— 后者只在 Prometheus exporter 上有,所以用 collector 拉 Prometheus 而非推 OTLP。service.name / namespace 在 collector 侧打标。无 BetterStack / Sentry。
可用指标
| 指标 | 说明 |
|---|
imgproxy_requests_total | 请求总数(RED) |
imgproxy_request_duration_seconds | 请求时延(RED) |
imgproxy_errors_total | 错误总数(RED) |
workers_utilization | Worker 利用率 (0-1) |
vips_memory_bytes | libvips 内存使用量 |
images_in_progress | 正在处理的图片数 |
goroutines | Go 协程数量 |
process_mb | 进程总内存 |
建议报警阈值
| 指标 | Warning | Critical |
|---|
| Worker Utilization | > 50% | > 80% |
| Process Memory | > 6GB | > 7GB |
| Images In Progress | > 100 | > 150 |
| Goroutines | > 50 | > 100 |
Fly.io 监控
通过 Fly.io 监控面板查看:
文件结构
kira-imgproxy/
├── .github/
│ └── workflows/ # CI/CD
├── Dockerfile # 基于 imgproxy 官方镜像
└── fly.toml # Fly.io 配置