OCI Referrers 与 Registry 证据保留:复制、GC 和恢复闭环
发布平台把 app:release 从构建仓库复制到生产仓库,任务显示成功,目标镜像也能拉取。部署门禁随后却报告“缺少签名和 SBOM”。源仓库 UI 明明显示两个附件,目标仓库只剩镜像。复制工具搬走了主体 manifest 及其 config、layers,却没有追踪从附件反向指回主体的关系;同一个 digest 出现在新 repository,也不会自动继承旧 repository 的发现索引。
另一次事故发生在例行清理后。镜像 tag 仍在,部署也正常,但几个月前的 provenance 已无法获取。Registry 的保留任务只把 tag 当作存活根,无 tag 的 referrer manifest 被垃圾回收;备份任务又只导出了镜像。subject 和 Referrers API 解决的是“如何表达与发现关联”,不是验签、复制、保留、级联删除或灾备承诺。团队必须把主体和证据当成一个闭包治理。
subject 指向主体,referrers 负责反向发现
OCI 内容以 descriptor 串成 Merkle DAG。descriptor 至少携带 mediaType、digest 和 size,manifest 再引用 config 与 layers。OCI Image Spec 1.1 为 manifest 增加可选 artifactType 和 subject:artifactType说明这份 artifact 的类型,subject descriptor 指向它所关联的另一份 manifest。官方规范称 subject 为弱关联,它不是数据库外键,也不会阻止主体被删除。
例如一份 SBOM artifact 的 manifest 可以表达:
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"artifactType": "application/vnd.example.sbom.v1+json",
"config": {
"mediaType": "application/vnd.oci.empty.v1+json",
"digest": "sha256:<EMPTY_CONFIG_DIGEST>",
"size": 2
},
"layers": [{
"mediaType": "application/vnd.example.sbom.v1+json",
"digest": "sha256:<SBOM_BLOB_DIGEST>",
"size": 1234
}],
"subject": {
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:<SUBJECT_DIGEST>",
"size": 567
}
}主体 manifest 不会反向列出谁引用自己,因此 OCI Distribution Spec 1.1 提供 repository-scoped 的 Referrers API:GET /v2/<name>/referrers/<digest>。Registry 返回一个 image index,manifests 中是同一 repository 内所有以该 digest 为 subject 的 descriptor。这个列表只表示“可发现关联”,不验证 payload、签名证书、issuer、时间戳、透明日志、撤销或组织策略。
最小链路是:先把 tag 解析成主体 digest,上传 referrer manifest,Registry 处理其 subject,消费者按主体 digest发现 referrers,再逐个拉取 manifest 和 blob,最后交给对应 verifier。任何一步只使用 tag,都会让证据跟随可变指针漂移。
用隔离 Registry 跑通 attach 与 discover
实验需要 Docker 和与目标平台一致的 ORAS CLI。安装入口和校验方式以 ORAS 官方安装文档及 release 为准;先执行 oras version 固定客户端基线。下面使用本机无认证 Registry,仅用于隔离实验,不能照搬到共享或生产网络。
docker run -d --rm --name evidence-registry -p 127.0.0.1:5000:5000 registry:2
oras version
printf '{"name":"demo-subject"}\n' > subject.json
printf '{"components":[]}\n' > sbom.json
oras push --plain-http localhost:5000/evidence/app:release \
subject.json:application/vnd.example.subject.v1+json
SUBJECT_DIGEST="$(oras resolve --plain-http localhost:5000/evidence/app:release)"
oras attach --plain-http \
--artifact-type application/vnd.example.sbom.v1+json \
"localhost:5000/evidence/app@${SUBJECT_DIGEST}" \
sbom.json:application/vnd.example.sbom.v1+json
oras discover --plain-http --format json \
"localhost:5000/evidence/app@${SUBJECT_DIGEST}" > discover.json
jq -e '.referrers | length == 1' discover.json预期 SUBJECT_DIGEST 是 sha256:...,attach 返回 referrer descriptor,discover 中恰有一个指定 artifactType 的条目。失败时保留 ORAS debug 输出、HTTP 状态码和 Registry 日志:401/403 指向认证 scope,MANIFEST_UNKNOWN 指向 repository、digest 或上传顺序,空列表可能是 API/fallback、repository 作用域或关联未被 Registry 处理。
实验清理先记录测试 digest,再删除整个一次性容器和本地文件:
docker stop evidence-registry
rm -f subject.json sbom.json discover.json这会销毁匿名容器内全部 Registry 数据,不能用于有其他项目的环境。共享 Registry 应使用独立测试 repository,并按产品 API 删除测试 manifest、tag 和 blob,再验证不存在悬挂 referrer。
原生 Referrers API 的状态码和响应头都有语义
OCI Distribution Spec 的内容发现章节规定,支持 Referrers API 的 Registry 对合法查询返回 200 OK 和 image index;即使没有匹配项,也不能用 404 表示空集合。404 的含义是客户端应尝试旧 Registry 的 fallback tag schema,而不是立即宣布“没有证据”。非法 digest 则应得到 400。
推送带 subject 的 manifest 时,原生支持 Referrers API 的 Registry 用响应头 OCI-Subject: <subject digest> 表示已处理关联。没有这个响应头,兼容客户端必须维护 fallback index。调试 attach 时不仅看 201/202,还要保存响应头;manifest 存储成功但关联未进入发现索引,后续 discover 仍可能为空。
Referrers 可以分页。响应存在 Link: ...; rel="next" 时,消费者必须拉到最后一页并按 descriptor digest 去重。只读第一页会在证据增多后产生静默假阴性。请求可带 ?artifactType=<media-type>,但只有响应包含 OCI-Filters-Applied: artifactType 才能证明服务端应用了过滤;没有该头时,客户端拉取完整集合后自行过滤。
可以用 curl 把这些信号固定成契约测试:
curl -sS -D headers.txt \
-H 'Accept: application/vnd.oci.image.index.v1+json' \
"https://registry.example.test/v2/evidence/app/referrers/sha256:<SUBJECT_HEX>" \
-o referrers.json
grep -i '^HTTP/\|^Link:\|^OCI-Filters-Applied:' headers.txt
jq -e '.mediaType == "application/vnd.oci.image.index.v1+json"' referrers.json使用认证 Registry 时以 oras login --password-stdin 或独立凭据文件取得 token,不把密码写进 URL、参数历史或日志。对 filter 的正向验证要同时获取未过滤全集;两者客户端过滤后的 descriptor 集合应一致。
404 fallback 是兼容协议,不是空结果
旧 Registry 没有 /referrers/<digest>。客户端把主体 digest 转成 fallback tag:算法和 encoded 部分按规范截断并替换 tag 不允许的字符;常见 SHA-256 结果是 sha256-<64-hex>。该 tag 指向一个 OCI image index,manifests 保存 referrer descriptors。维护这个 index 是客户端责任。
查询流程是:Referrers API 返回 404,拉取 fallback tag;若 tag 不存在,视为没有已登记 referrer;若返回合法 image index,读取全部 descriptors;若 tag 被别的对象占用或内容损坏,报告失败,不能覆盖未知数据。推送流程是在没有 OCI-Subject 响应头时读取旧 index、去重追加 descriptor,再推回同一 tag。
fallback 最大风险是 lost update。客户端 A、B 同时读取含一项的 index,A 加入 SBOM,B 加入签名;A 先推,B 再用旧基线覆盖,最终只剩签名。规范允许在 Registry 支持时使用 ETag 条件更新,但最可靠的消除竞争方式是启用原生 API。
T0 A/B 读取 index = [provenance]
T1 A 计算 [provenance, sbom]
T2 B 计算 [provenance, signature]
T3 A 覆盖 fallback tag
T4 B 覆盖 fallback tag
结果 [provenance, signature],SBOM 条目静默丢失反向实验可在明确不支持原生 API 的隔离 Registry 上并发执行两次 read-modify-write,并比较期望集合与最终集合。预期要么条件请求返回 412 Precondition Failed,客户端重读重试;要么复现条目丢失并让验收失败。绝不能把“两个 push 都返回成功”当成正确结果。删除 referrer 时也要同步从 fallback index 移除 descriptor,否则 discover 会留下指向 404 manifest 的悬挂条目。
普通复制成功不代表证据闭包完整
证据闭包至少包含主体 manifest/index、主体 config/layers/blobs、每一页 referrer descriptor、每个 referrer manifest 及其 config/layers/blobs、嵌套 referrers、兼容所需 fallback index,以及离线验证需要的信任根、透明日志 bundle、策略和撤销快照。闭包清单按 digest、size、mediaType 记录,不能只保存 tag 和对象数量。
先做故意失败的普通复制。源 repository 有主体和一个 SBOM referrer,目标使用另一个 repository:
oras cp --plain-http \
"localhost:5000/source/app@${SUBJECT_DIGEST}" \
"localhost:5000/target/app:release"
oras discover --plain-http --format json \
"localhost:5000/target/app@${SUBJECT_DIGEST}" > target-plain.json
jq '.referrers | length' target-plain.json预期主体可拉取,但普通复制可能得到 0 个 referrer;这就是失败证据。digest 相同也没有跨 repository 的全局反查,因为 Referrers API 以 <name> 为作用域。Blob mount 只能减少数据传输,不会自动重建目标 repository 的发现关系。
再执行递归复制:
oras cp --plain-http --recursive \
"localhost:5000/source/app@${SUBJECT_DIGEST}" \
"localhost:5000/target-full/app:release"
oras discover --plain-http --format json \
"localhost:5000/target-full/app@${SUBJECT_DIGEST}" > target-full.json
jq -e '.referrers | length == 1' target-full.json当前 ORAS cp --recursive 文档把递归复制标为 Preview,因此流水线必须固定 ORAS 版本,并对真实源/目标 Registry 组合执行闭包集合比对。预期目标的 subject 与 referrer descriptor digest 集合等于源端,所有 manifest/blob 均可 fetch。只看命令退出码或 UI 的附件数量不够。
清理时删除三个测试 repository 中的 manifests/tags,确认 discover 为空或 repository 不存在,再按产品规则运行测试环境 GC。若 Registry 不支持安全删除,销毁整个隔离实例,禁止在共享实例试验强制 GC。
Registry 差异必须用能力探测矩阵管理
OCI 规范定义对象 push/pull、内容发现和兼容行为,却不定义每个产品的复制、保留、GC、备份与 UI。托管 Registry、CNCF Distribution、Harbor 和其他实现可能在原生 Referrers、fallback、分页、filter、ETag、未知 media type、删除、跨区域复制及附件保留上不同。产品文档写“支持 OCI 1.1”也不能替代组合实验。
接入每个 Registry 时建立机器可运行的能力探测:推主体;推两个不同 artifactType 的 referrer;检查 OCI-Subject;查询不存在 digest;验证分页与 filter header;删除一个 referrer;跨 repository 复制;跨 Registry 复制;执行 retention/GC dry-run;备份恢复。记录产品版本、客户端版本和结果,不记录真实主机、凭据或内部 digest。
Harbor 等产品可能把特定签名作为 accessory 随主体复制或联动保留,但这属于产品行为。复制到异构 Registry 后,目标端可能保存 blob 却不维护关联。任何“附件随主体”的承诺都要绑定源版本、目标版本、复制规则和 artifact 类型;不能泛化为 OCI 的保证。
未知 artifactType 要分两层判断。Registry 应按内容分发协议存储合法对象,策略消费者则可以拒绝不在允许清单中的类型。把“Registry 能存”误写成“策略可信”,会让攻击者用自定义类型伪装证据;把“验证器不认识”误写成“Registry 应拒绝”,又会破坏协议的内容类型扩展能力。
保留和 GC 要以主体加证据闭包为单位
删除 tag、删除 manifest、删除 blob 和从发现索引移除是不同动作。tag 只是指针;删除主体 tag 不一定删除 manifest,删除主体 manifest 也不保证级联删除 referrers。删除 referrer manifest 后,fallback index 还可能保留悬挂 descriptor。清理流程必须分别观察四层状态。
OCI Distribution Spec 不定义 GC 算法。CNCF Distribution GC 文档描述该实现的 mark-and-sweep,并要求 GC 期间 Registry 只读或停止,避免上传中的 layer 被误删;--delete-untagged 会把无 tag manifest 纳入删除候选。这是具体实现行为。referrer manifest 常常没有人类 tag,如果实现只从 tag 标记可达对象,证据可能早于主体消失。
保留策略以 subject digest + evidence closure 为原子治理单元:主体保留多久,签名、SBOM、provenance 和验证材料至少同寿命;legal hold 同时冻结整个闭包;删除按“阻止新引用、冻结清单、删发现关系、删 manifests、产品擦除、重验”推进。GC 前导出源闭包 descriptor 集合,运行产品 dry-run,人工审查无 tag referrers 是否被标记。
正向 GC 实验在隔离 repository 建立主体及两种 referrer,运行 retention 和 GC,再重复 subject pull、discover、每个 manifest/blob fetch 与策略验证。反向实验打开 delete-untagged 或缩短附件保留,预期 dry-run 能暴露证据候选;若正式运行后任一 referrer 丢失,策略必须阻止晋级并从备份恢复。清理实验规则时恢复原保留配置,删除临时 repository,核对 GC 日志未涉及其他 namespace。
离线备份必须包含验证材料和恢复演练
OCI Image Layout 能保存内容寻址对象和索引,但“把主体导出到 layout”不自动表示所有 referrers 都在。ORAS 1.3 的 backup、restore 在官方命令入口中仍标为 Experimental,discover 标为 Preview;备份恢复指南明确建议供应链 artifact 存在时使用 --include-referrers,并在前后 discover 验证。
oras backup --include-referrers \
registry.example.test/evidence/app@sha256:<SUBJECT_HEX> \
evidence-backup.tar
oras discover --oci-layout evidence-backup.tar:release --format json \
> offline-referrers.json具体参数以固定版本 oras backup --help 为准,并在流水线锁定。归档同时保存闭包清单、每个 blob digest/size/mediaType、ORAS 版本、Registry 能力结果、验证工具版本、策略版本、信任根、证书链、透明日志 bundle/checkpoint 和撤销快照。keyless 签名在断网后可能无法访问 OIDC、透明日志或在线信任材料,只保存 OCI blobs 不等于可离线验证。
恢复实验必须在无源 Registry、无公网的隔离环境执行。先校验归档摘要,恢复到空 Registry,再按 digest 拉取主体、discover 所有层级、fetch payload、运行签名/SBOM/provenance 策略。反向实验制作一个不带 --include-referrers 的归档;预期主体恢复成功但 discover 或 verify 失败,灾备门禁应明确拒绝,而不是把“镜像可拉”报告为恢复完成。
归档包含内部依赖、builder URI、签名身份和漏洞信息,敏感度可能高于镜像本身。使用独立加密密钥、最小读取权限、不可变保留和访问审计。恢复结束删除明文临时目录、撤销临时 Registry 凭据、销毁隔离实例,并验证备份目录不存在残留解包文件。
排障从 repository、digest、发现模式和对象可达性分层
第一类现象是 attach 成功但 discover 为空。确认查询使用同一 repository 和 subject digest,再查看 push 是否返回 OCI-Subject。没有响应头时检查 fallback tag 是否存在且为合法 image index。随后确认 referrer manifest 的 subject.digest 与目标完全一致,不能只比 tag。
第二类是 API 返回 404。客户端必须继续 fallback;fallback tag 也 404 才能在旧 Registry 语义下得到空集合。支持原生 API 的 Registry 对合法无结果查询应返回 200 空 index,若它返回 404,说明能力配置、代理路由或实现兼容存在问题。401/403 则检查 referrers endpoint、manifest 和 blob 是否使用一致 repository pull scope。
第三类是 filter 查询漏项。检查 OCI-Filters-Applied;没有该头就拉全集客户端过滤。继续处理所有 Link rel=next。分页终止条件错误、代理丢响应头或客户端只取第一页,都会在证据数量增长后出现偶发缺失。
第四类是复制任务成功但目标不完整。分别比较源/目标 subject descriptor、直接 referrers、嵌套 referrers、每个 manifest 和 blob。跨 repository、跨区域和异构 Registry 分开测试。目标有 blob 不代表 manifest 已注册,有 manifest 不代表 Referrers API 能发现,有 referrer 不代表 verifier 接受。
第五类是清理后 payload 404。查询 retention/GC 审计,确认是 tag、manifest、blob 还是索引被删;检查 fallback 是否悬挂。停止后续晋级,从闭包备份恢复到隔离 repository,验证完成后再恢复服务。不要通过重新生成一份“看起来相同”的 SBOM 冒充历史证据,因为 digest、生产者和观察时间已经改变。
容量、权限与高可用要覆盖附件扇出
容量不能只按镜像 layers 估算。一个主体可能有多份架构 SBOM、多个签名、provenance、VEX、扫描报告和嵌套时间戳;每次 discover 还可能分页。预算包括 manifest 数、blob 字节、descriptor 索引、跨区复制流量、备份、审计日志、GC 标记内存与恢复时间。高基数 annotations 不应直接变成监控标签。
复制并发提高会放大 Registry QPS、带宽和 fallback 竞争。分批按 digest 复制,限制并发并保存可恢复 checkpoint;重试必须幂等,不能在 fallback 中反复追加重复 descriptor。故障恢复时大量消费者同时 discover 与 fetch,容量峰值可能高于日常 push,灾备演练要测冷缓存 p95/p99 和限流行为。
权限至少区分主体 push、referrer push、discover/pull、replication、delete/retention、GC、backup 和 restore。Referrers endpoint 必须继承 repository 授权,不能让无主体读取权的人枚举 SBOM 或签名身份。fallback tag 若可被普通 tag writer 覆盖,攻击者就能隐藏发现列表;优先原生 API,或使用不可变规则、条件更新和审计补强。
高可用需要 Registry 元数据、blob 存储和发现索引在同一恢复点可解释。对象存储跨区复制完成而数据库索引滞后时,blob 存在但 discover 为空;数据库恢复得更晚则可能引用尚未复制的 blob。每个副本或区域都执行闭包探针,不以负载均衡器 200 代替业务恢复。
升级、迁移和退出以集合相等为门槛
旧 Registry 升级到原生 Referrers API 前,先枚举 fallback tags、index descriptors 和其对应 manifests。规范要求启用 API 后将 fallback index 中具有有效 subject 的既有 manifests 纳入响应。升级后对每个代表性 subject 比较 API 集合与 fallback 集合,并验证分页、filter、删除和新 push。所有客户端都能处理 404 fallback、所有历史关联都已迁入后,才逐步停止旧写入;不能启用 API 当天删除 fallback tags。
迁移到新 Registry 时先固定源/目标版本和 ORAS 版本,暂停 tag 漂移,以 digest 生成闭包清单;递归复制后做集合与 blob 摘要比对,再运行真实 verifier。灰度消费者只读目标,失败回源必须显式告警,不能隐藏证据缺失。切换写入权后保留回滚窗口,源端只读冻结,防止两个 Registry 各自增加 referrers。
回滚先停止目标写入,把消费者切回仍完整的源闭包;目标期间新增证据按原始文档和 digest 回灌源端,不能只复制 tag。GC、retention 与 replication 规则恢复到切换前版本。任何未知差异都阻止扩大迁移批次。
退出 Registry 或 repository 时,冻结 subject 清单和全部 referrers,完成含验证材料的离线归档并在空环境恢复。随后撤销 replication token、机器人账号、backup key 和跨区规则;删除 tags、manifests、blobs、fallback indexes、缓存、备份副本和日志时遵守法律保留与隐私删除要求。最后用旧凭据 push/pull、用旧地址 discover、从已删除 repository fetch digest,预期全部得到可解释拒绝或不可达;同时离线归档仍能完成验证。这才证明旧存储不再是隐藏信任入口。
闭包枚举要有确定算法。以主体 descriptor 为根,先沿正向 manifest DAG 收集 config、layers 和子 manifests,再通过 Referrers API 或 fallback index 收集反向 descriptors;对每个新 referrer 重复正向收集,并按业务需要继续发现嵌套 referrers。每个 digest 放入 visited 集合防止重复,记录发现来源、深度、mediaType、size 和 fetch 结果。完成条件不是“队列为空”这么简单,还包括所有分页已结束、所有 descriptor 内容可取、摘要与大小匹配、允许的最大深度未被截断。
深度和规模必须设限,否则攻击者可以提交大量 referrers 或构造极深关系消耗验证资源。限制每个主体的 descriptor 数、总字节、递归深度、单 manifest 大小、分页次数和总耗时;达到限制时返回“闭包不完整”,不能截断后继续判定通过。对未知 artifactType 仍可保存 descriptor,但只为策略允许的类型下载和解析 payload。拒绝结论要包含触发的限制和主体 digest,日志不打印凭据或完整敏感载荷。
复制的一致性窗口也需要建模。复制开始后源端若继续新增签名,第一次枚举和最后一次枚举可能不同。可采用两遍收敛:记录源集合 S0,复制全部对象,再重新枚举得到 S1;若 S0 与 S1 不同,复制差集并重复,直到连续两次集合相等或达到停止阈值。高风险晋级更适合先冻结该 subject 的证据写入,或给证据批次定义不可变发布点。只靠复制工具重试无法判断源集合是否仍在变化。
跨区域复制还会出现“manifest 先到、blob 后到”和“索引先到、manifest 后到”。目标 discover 能看到 descriptor 时,payload 可能仍返回 404;主体可拉取时,附件索引可能尚未同步。健康探针必须执行完整 fetch,而不是只数 descriptors。业务切流等待目标闭包连续多次可达,并记录复制水位。超过允许延迟时停止晋级,避免消费者在区域间得到不同信任结论。
fallback 的删除顺序尤其敏感。先从 index 移除 descriptor,再删除 referrer manifest,可以让新读者立即停止发现,同时保留短暂恢复窗口;先删 manifest 会留下悬挂条目。并发写入期间更新 index 必须使用条件请求或外部串行锁,冲突后重读、重新计算集合,不允许盲目重放旧 PUT。每次修改保存旧 index digest、新 index digest、操作者和目标 referrer digest,以便恢复误删条目。
保留规则需要把法律留存和隐私删除分开处理。legal hold 作用于主体闭包时,普通 retention、tag 删除和 GC 都不得释放对象;解除 hold 后再进入正常删除队列。隐私删除则不能只移除 Referrers 索引,因为 blob 仍可能从 digest、备份或跨区域副本恢复。产品若不支持可证明擦除,要通过加密分区、独立密钥、备份到期和副本核销补足,并明确哪些共享 blob 因内容寻址去重不能单独物理删除。
一次安全 GC 变更分三批推进。第一批只在合成 repository 运行 dry-run,集合必须与预期删除清单相等;第二批选择可从备份恢复的低风险真实 repository,运行后重验所有闭包;第三批才扩大范围。任一批出现无 tag referrer、fallback index 或 legal hold 对象进入候选,立即停止并恢复旧规则。GC 完成还要观察读取错误和策略失败一段时间,因为冷门历史制品不会在执行后立刻被访问。
备份的恢复点要同时覆盖 Registry 元数据与 blob。只快照对象存储,恢复时可能不知道哪些 manifest、tag 和 subject 关系曾经存在;只备份数据库,引用的 blob 又可能处于不同时间点。较稳妥的做法是冻结批次,导出闭包清单和 Registry 元数据水位,等待对象复制完成,再生成归档摘要。恢复时先放 blobs,再注册 manifests 和 tags,最后建立或验证发现索引,避免索引长期指向不存在对象。
团队操作上,构建流水线只负责提交主体和它产生的证据;晋级服务负责冻结 digest、枚举和验证闭包;Registry 管理员负责产品能力、复制、保留和 GC;安全团队负责信任根及验证策略;数据治理负责留存与擦除。任何单一机器人同时拥有 push、delete、GC 和 backup 解密权限,都会让证据丢失或篡改缺少独立审计。紧急修复使用限时身份和双人审批,完成后撤销并重做闭包验证。
Registry 选型不应只比较镜像吞吐。先用能力探测确认原生 Referrers、fallback 兼容、分页和过滤,再比较同仓与异仓复制、跨区域收敛、附件感知保留、GC dry-run、不可变规则、条件写、审计、备份导出和对象擦除。原生 API 完整但没有闭包复制的产品,仍需客户端编排;复制功能丰富但无法证明无 tag referrers 被保留的产品,也不能承担长期证据档案。托管产品还要核对区域、配额、出口费、数据驻留和删除证明,动态数字从实际租户和官方服务说明获取。
容量估算可从每个主体的平均直接 referrer 数、平均嵌套深度、平均 payload 字节、每日新 subject 数和保留周期出发,再叠加多区域副本、备份系数与日志。压力测试同时覆盖大量小 manifest 和少量大 payload,因为前者压元数据索引与 API QPS,后者压带宽、对象存储和恢复时间。以峰值而非均值设复制并发、discover 限流与 GC 窗口,并预留故障恢复期间重新扫描全闭包的读放大。
监控至少区分主体 push、referrer push、discover、manifest fetch、blob fetch、复制、GC 和恢复。关键指标是按状态码分类的错误率、发现集合大小分布、悬挂 descriptor 数、分页深度、复制集合差异、无 tag manifest 候选、备份闭包完整率和离线验证成功率。告警标签只保留环境、产品版本、操作类型和错误类,不把 repository、digest、签名身份或内部包名变成高基数公开标签。
升级客户端时也要考虑输出格式漂移。不要用终端树形文本做集合比较,优先使用固定版本的 JSON 输出并提取 descriptor digest、artifactType、mediaType 和 size。候选 ORAS 先在能力探测 repository 回归 API、fallback、分页、filter、recursive copy、backup 和 restore,再进入灰度。Preview 或 Experimental 命令的参数与输出可能变化,流水线必须在未知字段、缺失字段或集合不相等时失败,而不是静默兼容。
最终上线门槛可以归结为四个反向证明:移动 tag 后历史证据仍绑定旧 digest;普通复制遗漏 referrers 时晋级被拒绝;删除或 GC 触及任一闭包对象时恢复演练能还原;源 Registry 和公网都不可用时,离线包仍能完成同一策略验证。四项中任何一项只能靠人工解释,都说明证据保留链尚未形成可操作合同。
这些证明还应进入定期演练,而不是等到迁移或事故时才第一次执行。每次演练使用独立测试 repository 和不可用于生产的身份,结束后核销对象、凭据、归档与临时容量,并把闭包差异转成下一轮产品升级和保留策略的输入。
OCI Image Spec 1.1.1 Manifest:artifactType 与 subject。OCI Distribution Spec 1.1.1:Referrers、fallback 与升级
