自带存储 + CDN
在 API Velocity 和 Momentum 套餐中连接您自己的对象存储和 CDN:各服务商的设置方法、最小权限、连接检查,以及切换模式时会发生什么。
适用套餐: API Velocity 和 Momentum 套餐。不适用于 Ignite(不含 CDN)、开发者沙盒(沙盒文件是临时的)或 WordPress 套餐(使用托管的 smallPict CDN)。
简而言之: 在 BYO 模式下,smallPict 会将您的原图和优化后的图片写入 您的 存储桶,并由 您的 CDN 分发。存储和 CDN 始终一起连接:不能只接入其中一个。
什么是 BYO
Velocity 和 Momentum 有两种分发模式。您可以在 控制台 → CDN 与存储 中选择其一。
- 托管(默认): smallPict 将您的原图保存在其加密存储中(90 天后转入长期归档),并通过
cdn.smallpict.app分发优化后的图片。无需任何设置。 - 使用自己的存储 + CDN(BYO): 您连接自己账户中的一个 S3 兼容存储桶,以及自己账户中的一个 CDN。每个新任务都会将原图和优化后的文件写入您的存储桶,API 返回的是您 CDN 域名上的 URL。
之所以要求两者一起连接,是因为 smallPict 必须能完成分发的两部分工作:写入文件,以及从缓存中清除过期副本。只有存储桶而没有 CDN,缓存清除请求将无处可发;只有 CDN 而没有存储桶,则没有可分发的内容。
托管与 BYO 对比
| 托管(默认) | 使用自己的存储 + CDN | |
|---|---|---|
| 原图 | smallPict 加密存储 | 您存储桶中的 originals/<job_id>/<file> |
| 优化后的文件 | 从 cdn.smallpict.app 分发 | 您存储桶中的 optimized/<job_id>.<ext>,从您的 CDN 域名分发 |
| 存储配额 | Velocity 50 GB,Momentum 100 GB | 不计入。费用由您向存储服务商支付。 |
| CDN 流量 | Velocity 30 GB/月,Momentum 200 GB/月 | 不计入。费用由您向 CDN 服务商支付。 |
| 转换次数 | 计入您的套餐 | 计入您的套餐 |
| smallPict 保留的副本 | 有,在您的账户有效期间保留 | 无。仅有用于处理的临时上传文件,会在 24 小时内删除。 |
| 缓存清除 | 自动 | 自动,通过您 CDN 的 API |
| CDN 性能、域名和费用 | smallPict | 您自己。smallPict 不对您 CDN 的性能、域名或账单负责。 |
支持的服务商
存储(现已可用): Amazon S3、Cloudflare R2、Google Cloud Storage(S3 互操作)、Alibaba Cloud OSS、Tencent Cloud COS、DigitalOcean Spaces、SumoPod Storage、MinIO,以及通过自定义 Endpoint 接入的任何其他 S3 兼容服务(例如 Hetzner、Vultr 或 Wasabi)。
CDN(现已可用): Cloudflare 和 Amazon CloudFront。
计划中: Azure Blob Storage 及其他 CDN。您可以在 云服务商 页面申请抢先体验。
开始之前
- 您使用的是 API Velocity 或 Momentum 套餐,并以账户管理员身份登录。
- 您已有一个存储桶,以及一个通过 HTTPS 分发该存储桶中文件的 CDN。
- 存储 Endpoint 可通过 HTTPS 从互联网访问。私有、内部及纯 HTTP 的 Endpoint 会被拒绝。
- 您已为 smallPict 创建了权限范围最小的密钥(见下文 最小权限)。不要使用账户的 root 或管理员密钥。
然后打开 控制台 → CDN 与存储,选择 使用自己的存储 + CDN,填写两个部分并点击 保存存储 + CDN。smallPict 会运行连接检查(见下文),只有检查通过才会将您切换到 BYO。
设置指南
A. Amazon S3 + Amazon CloudFront
- 创建存储桶,选择所需区域,例如在
ap-southeast-1中创建my-images。保持 Block Public Access 开启。 - 创建 CloudFront 分配(distribution),以该存储桶作为源站。使用 Origin access control (OAC),让 CloudFront 能在存储桶保持私有的情况下读取它,并应用 CloudFront 提供的存储桶策略。
- 可选:使用自己的域名。 为分配添加备用域名(例如
images.example.com)和证书,并将 DNS 记录指向该分配。 - 为存储创建一个 IAM 用户(或角色),使用以下策略:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject" ], "Resource": "arn:aws:s3:::my-images/*" } ]}- 为 CloudFront 创建一个 IAM 用户(可以是同一个用户),使用以下策略。
cloudfront:GetDistribution为可选项;有了它,当您将 CDN 域名留空时,smallPict 可以自动检测该分配的域名。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "cloudfront:CreateInvalidation", "cloudfront:GetDistribution" ], "Resource": "arn:aws:cloudfront::123456789012:distribution/E2QWRUHAPOMQZL" } ]}- 在控制台中:
- 存储服务商选择 Amazon S3,存储桶
my-images,区域ap-southeast-1,Endpoint 留空(使用该区域的标准 Endpoint),并填写存储的 access key ID 和 secret。 - CDN 选择 Amazon CloudFront,填写 Distribution ID(例如
E2QWRUHAPOMQZL)、CloudFront 的 access key ID 和 secret,以及可选的 CDN 域名(https://images.example.com)。将域名留空则使用该分配自身的*.cloudfront.net域名。
- 存储服务商选择 Amazon S3,存储桶
B. Cloudflare R2 + Cloudflare
- 创建一个 R2 存储桶,例如
my-images。 - 为存储桶连接自定义域名(R2 → 您的存储桶 → Settings → Custom Domains),例如同一 Cloudflare 账户下某个区域中的
images.example.com。对该域名的请求会经过 Cloudflare 缓存。生产环境中不要使用r2.dev开发 URL。 - 创建 R2 API 令牌(R2 → Manage API tokens),授予 Object Read & Write 权限,并仅限此存储桶。复制显示的 Access Key ID 和 Secret Access Key。
- 创建 Cloudflare API 令牌(My Profile → API Tokens),为该区域授予 Zone → Cache Purge → Purge 权限。Zone → Zone → Read 为可选。
- 在控制台中:
- 存储服务商选择 Cloudflare R2,存储桶
my-images,Endpointhttps://<account_id>.r2.cloudflarestorage.com(显示在存储桶的 S3 API 信息中),区域auto,以及 R2 的 Access Key ID 和 Secret Access Key。 - CDN 选择 Cloudflare,填写 Zone ID(域名的 Overview 页面,API 区域)、API 令牌和 CDN 域名
https://images.example.com。
- 存储服务商选择 Cloudflare R2,存储桶
C. Google Cloud Storage + Cloudflare
Google Cloud Storage 通过其 S3 兼容的 XML API 并使用 HMAC 密钥连接。
- 创建存储桶,例如
my-images。 - 创建服务账号,并仅在此存储桶上授予其 Storage Object User(
roles/storage.objectUser)角色。 - 为服务账号创建 HMAC 密钥: Cloud Storage → Settings → Interoperability → Create a key for a service account。复制 access ID 和 secret。
- 让您的 CDN 可以读取优化后的文件。 Cloudflare 通过 HTTPS 从 Cloud Storage 获取文件,因此
optimized/下的对象必须可公开读取。在统一存储桶级访问(uniform bucket-level access)下,向allUsers授予 Storage Object Viewer 角色会使整个存储桶可读,包括originals/。如果原图必须保持私有,请在前面加一层身份验证(例如一个为发往存储桶的请求签名的 Cloudflare Worker)。 - 将一条开启代理的 Cloudflare DNS 记录指向 Cloud Storage,例如
images.example.com。可以按主机名命名存储桶并使用指向c.storage.googleapis.com的代理 CNAME,也可以使用 Cloudflare Origin Rule 将请求发送到storage.googleapis.com,并在路径开头加上存储桶名称。 - 创建 Cloudflare API 令牌,为该区域授予 Zone → Cache Purge → Purge 权限(Zone → Zone → Read 可选)。
- 在控制台中: 存储服务商选择 Google Cloud Storage,填写存储桶、Endpoint
https://storage.googleapis.com、区域auto、HMAC access ID 和 secret;CDN 选择 Cloudflare,填写 Zone ID、API 令牌和 CDN 域名https://images.example.com。
D. MinIO 或任何 S3 兼容存储 + Cloudflare
本节适用于 MinIO 以及 Hetzner、Wasabi、Vultr、SumoPod、Alibaba Cloud OSS、Tencent Cloud COS 和 DigitalOcean Spaces 等 S3 兼容服务。
| 服务商 | 在控制台中选择 | Endpoint | 说明 |
|---|---|---|---|
| MinIO | MinIO | 您的服务器,例如 https://minio.example.com | 开启路径样式(path-style)请求。服务器必须能通过 HTTPS 从互联网访问。 |
| Hetzner Object Storage | 自定义 S3 兼容存储 | https://<location>.your-objectstorage.com | |
| Wasabi | 自定义 S3 兼容存储 | https://s3.<region>.wasabisys.com | |
| Vultr Object Storage | 自定义 S3 兼容存储 | https://<region>.vultrobjects.com | |
| SumoPod Storage | SumoPod Storage | 从 SumoPod 存储控制台获取 | 默认开启路径样式。 |
| Alibaba Cloud OSS | Alibaba Cloud OSS | https://oss-<region>.aliyuncs.com | 仅支持虚拟主机样式。 |
| Tencent Cloud COS | Tencent Cloud COS | https://cos.<region>.myqcloud.com | 存储桶名称包含您的 APPID,例如 my-images-1250000000。 |
| DigitalOcean Spaces | DigitalOcean Spaces | https://<region>.digitaloceanspaces.com |
- 创建存储桶,以及一个仅限该存储桶、对对象拥有写入、读取和删除权限的密钥(见下文 最小权限)。
- 让您的 CDN 可以读取
optimized/,例如使用仅对optimized/*允许匿名s3:GetObject的存储桶策略。在 MinIO 上:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Action": [ "s3:GetObject" ], "Resource": [ "arn:aws:s3:::my-images/optimized/*" ] } ]}- 在存储桶前面放置 Cloudflare: 为
images.example.com创建一条开启代理的 DNS 记录,指向存储桶的公共主机。如果服务商要求请求中使用其自身的主机名,请添加一条重写 Host 头的 Cloudflare Origin Rule(对于路径样式的主机,还需在路径中加上存储桶名称)。 - 创建 Cloudflare API 令牌,为该区域授予 Zone → Cache Purge → Purge 权限(Zone → Zone → Read 可选)。
- 在控制台中: 从表中选择服务商,填写存储桶、Endpoint、区域(服务商没有区域时填
auto)和密钥;在表中注明的情况下开启路径样式请求;然后 CDN 选择 Cloudflare,填写 Zone ID、API 令牌和 CDN 域名。
最小权限
只给 smallPict 能完成必要操作、且仅限一个存储桶的密钥。
| 服务商 | 权限 |
|---|---|
| Amazon S3 | 对 arn:aws:s3:::<bucket>/* 的 s3:PutObject、s3:GetObject、s3:DeleteObject |
| Amazon CloudFront | 对该分配的 cloudfront:CreateInvalidation;可选 cloudfront:GetDistribution,以便自动检测域名 |
| Cloudflare R2 | 具有 Object Read & Write 权限、仅限该存储桶的 R2 API 令牌 |
| Cloudflare(CDN) | 该区域具有 Zone → Cache Purge → Purge 权限的 API 令牌;可选 Zone → Zone → Read |
| Google Cloud Storage | 在该存储桶上拥有 roles/storage.objectUser 的服务账号的 HMAC 密钥 |
| Alibaba Cloud OSS | 在该存储桶上拥有 oss:PutObject、oss:GetObject、oss:DeleteObject 的 RAM 用户 |
| Tencent Cloud COS | 在该存储桶上拥有 cos:PutObject、cos:GetObject、cos:DeleteObject 的 CAM 子用户(存储桶名称包含 APPID) |
| DigitalOcean Spaces | 仅限该存储桶、具有读取、写入和删除权限的 Spaces 访问密钥 |
| MinIO / 自定义 S3 兼容存储 | 对 <bucket>/* 的写入、读取和删除权限;Endpoint 必须是公网 HTTPS |
连接检查
每次保存时(以及点击 重新检查 时),smallPict 都会在使用前检查整个连接:
- 存储: 在您存储桶的
.smallpict-probe/下写入一个小测试文件,读回后再删除。 - Cloudflare: 向您的区域发送一次单个 URL 的测试清除请求。
- CloudFront: 为
/.smallpict-probe/...路径创建一次测试失效(invalidation)。它会计入您当月的 CloudFront 失效路径数。
任何一步失败,都不会切换任何内容。控制台会在相关字段旁显示原因,例如存储桶名称或 API 令牌。连接检查限制为每分钟 5 次。
为 CDN 开放存储桶访问
optimized/必须能被您的 CDN 读取:可以对该前缀开放公开读取,也可以让 CDN 通过源站访问私有存储桶(CloudFront origin access control,或 R2 自定义域名)。originals/可以保持私有。您的 CDN 永远不需要访问它。- 只有当浏览器通过 JavaScript 跨源获取图片时(例如
fetch()或 canvas),才需要 CORS。普通的<img>标签不需要。如确有需要,请允许来自您网站源的GET和HEAD请求。
对象布局与 URL
| 内容 | 在您存储桶中的键 | URL |
|---|---|---|
| 原图 | originals/<job_id>/<file> | 不对外分发 |
| 优化后的文件 | optimized/<job_id>.<ext> | <cdn_domain>/optimized/<job_id>.<ext> |
CDN 域名可以包含路径前缀,例如 https://example.com/images;此时 URL 为 https://example.com/images/optimized/<job_id>.<ext>。
缓存头
优化后的文件写入时带有 Cache-Control: public, max-age=31536000, immutable。每个任务都有唯一的键,因此新结果总有新的 URL,很少需要清除缓存。请确保您 CDN 的缓存规则不会用更短的时间覆盖此头。
缓存清除
- 通过 API 发起的清除请求(
POST /v1/purge)会批量发送到 您的 CDN。 - 在 Cloudflare 上,按 URL 清除文件。“全部清除”只清除您的分发主机和路径前缀,而不是整个区域。
- 在 CloudFront 上,通过失效(invalidation)清除文件。“全部清除”会创建一个
/*失效。超出 CloudFront 每月免费额度的失效路径由 AWS 向您的账户计费。
切换模式
切换模式 不会移动现有文件。已分发的文件保留当前 URL;只有新任务会使用新模式。
- 托管 → BYO: 新任务写入您的存储桶,并由您的 CDN 分发。已在
cdn.smallpict.app上的文件保留在原处。 - BYO → 托管: 新任务重新使用托管存储与 CDN。您的 BYO 设置和密钥会继续保存但不使用,因此在您点击 断开连接 之前,无需重新输入即可切换回来。
- 断开连接: 删除已保存的密钥和 BYO 设置,并将您切回托管模式。您存储桶中的文件不受影响。
出现故障时
- 如果 smallPict 无法写入您的存储桶,它会重试,然后 将任务标记为失败,并在 API 响应中给出可读的原因。
- 控制台会显示最近一次错误及其发生时间,并提供 重新检查 按钮。
- 您会收到 每次事件一封邮件,而不是每个失败任务一封。
- smallPict 绝不会回退到托管存储。您的文件绝不会存放在您未选择的地方。
安全
- 密钥保存在 加密的密钥存储 中。之后不会再显示,不会通过 API 返回,也不会写入日志。
- 要轮换密钥,输入新密钥并保存即可;连接检查会使用新密钥运行。将密钥字段留空则保留已保存的密钥。
- 存储和 CDN 的 Endpoint 必须使用 HTTPS。解析到私有、回环(loopback)、链路本地(link-local)或其他内部地址的 Endpoint 会被拒绝。
- 请为 smallPict 使用单独的、权限范围最小的密钥;如果不再使用 BYO,请在服务商控制台中吊销它们。