Nexus Repository 与 JFrog Artifactory:从依赖代理到企业制品中心
凌晨发布回滚时,流水线下载到的 app-1.8.2.jar 与上周验证过的文件哈希不同。开发者能在旧流水线日志里看到同名附件,却找不到原始字节;Maven Central 又恰好出现网络抖动,新节点无法恢复依赖。事故表面是下载失败,根因却有三层:可变版本覆盖了发布身份,CI 临时附件被误当成长期制品,上游代理与内部发布共用一个没有权限边界的入口。
制品仓库的价值不是多一个上传页面,而是建立一条可重复回答的问题链:字节从哪次构建产生,谁有权发布,消费者解析到了哪一个仓库,缓存何时重新验证,上线和回滚依赖的版本能保留多久,平台损坏后能否把元数据、二进制和密钥恢复到同一时刻。
Nexus Repository 使用 hosted / proxy / group 描述这条链;JFrog Artifactory 使用 local / remote / virtual。名称不同,职责可以对应:
| 工程职责 | Nexus Repository | JFrog Artifactory | 决策要点 |
|---|---|---|---|
| 保存内部构建结果 | hosted | local | 发布账号只写这里 |
| 代理外部依赖 | proxy | remote | 缓存不是审批,也不是永久镜像 |
| 提供统一下载地址 | group | virtual | 成员顺序会改变解析结果 |
| 保存制品字节 | blob store | filestore | 必须与数据库形成一致恢复点 |
Group 和 Virtual 是解析视图,不会天然复制出第三份制品。CI Artifact 通常跟一次流水线绑定且短期保留,也不能代替跨项目、跨流水线的版本仓库。
先把实验节点跑起来
Nexus Repository 单节点
在 Nexus Repository 下载页 和系统要求页确定目标版本、平台和压缩包名称。小型实验可用发行包自带的单节点数据库;当前支持矩阵不支持把 H2 放进容器部署,因此不要为了少写几行配置而用“容器 + H2”冒充受支持形态:
export LAB_ROOT="$(pwd)/repository-lab"
mkdir -p "$LAB_ROOT/nexus"
cd "$LAB_ROOT"
export NEXUS_ARCHIVE='<downloaded-nexus-archive>.tar.gz'
tar xvz --keep-directory-symlink -f "$NEXUS_ARCHIVE" -C nexus
cd nexus
nohup ./nexus-*/bin/nexus run > nexus-console.log 2>&1 &
printf '%s\n' "$!" > nexus.pid使用专用非 root 系统账号运行,安装目录和数据目录只授予该账号。启动后不要用固定睡眠冒充就绪,轮询状态接口:
until [ "$(curl -sS -o /dev/null -w '%{http_code}' \
http://127.0.0.1:8081/service/rest/v1/status)" = 200 ]; do
tail -n 20 nexus-console.log
sleep 5
done
test -s sonatype-work/nexus3/admin.password期望状态接口返回 200,初始密码文件非空。若进程退出,先看 nexus-console.log;出现数据目录拒绝访问时修正运行账号与目录属主,不要用 chmod 777 掩盖身份问题。首次登录后立即更换管理员密码、决定匿名读取策略,并创建独立实验账号。
新安装的小型非关键节点可使用内嵌数据库,但共享团队节点、容器部署和生产节点应评估外部 PostgreSQL。把内嵌数据库所在数据目录直接放进多副本 Deployment,不会得到集群,只会得到并发写入和不可预测损坏。
Artifactory 单节点
从 Artifactory 安装入口 选择许可对应的发行包和容器镜像,先锁定镜像 Digest:
export LAB_ROOT="${LAB_ROOT:-$(pwd)/repository-lab}"
mkdir -p "$LAB_ROOT/artifactory-var"
cd "$LAB_ROOT"
export ARTIFACTORY_IMAGE='<official-artifactory-image>:<validated-tag>'
docker pull "$ARTIFACTORY_IMAGE"
docker image inspect "$ARTIFACTORY_IMAGE" \
--format '{{index .RepoDigests 0}}'
docker run -d --name artifactory-lab \
-p 18082:8082 \
-p 18083:8081 \
-v "$PWD/artifactory-var:/var/opt/jfrog/artifactory" \
"$ARTIFACTORY_IMAGE"
until curl -fsS http://127.0.0.1:18082/router/api/v1/system/health >/dev/null; do
docker logs --tail 20 artifactory-lab
sleep 5
done不同发行版的镜像名、许可和初始化流程会变化,因此镜像地址从目标版本安装页取得,不把示例占位符直接交给流水线。健康接口成功只说明服务可响应;首次登录后还要更换管理员口令、安装许可、确认数据库连接与 Filestore 状态。
实验机可以使用本地卷。共享环境至少要把容器生命周期和数据生命周期拆开:删容器不应删数据库、制品字节和主密钥;重建容器后应能重新读取同一测试制品。
清理实验节点
先导出需要保留的测试证据,再删除容器。数据卷是最后一步,并且只能删除刚才创建的实验目录:
lab_real=$(realpath "$LAB_ROOT")
parent_real=$(realpath "$LAB_ROOT/..")
case "$lab_real" in
"$parent_real"/repository-lab) ;;
*) printf 'refuse unsafe path: %s\n' "$lab_real" >&2; exit 1 ;;
esac
nexus_bin=$(find "$lab_real/nexus" -path '*/bin/nexus' -type f -print -quit)
test -n "$nexus_bin" && test -x "$nexus_bin"
"$nexus_bin" stop
docker rm -f artifactory-lab
# 只删除本次创建的两个实验子目录,且拒绝空变量、根目录和父目录。
test -n "$lab_real" && test "$lab_real" != / && test "$lab_real" != "$parent_real"
rm -rf -- "$lab_real/nexus" "$lab_real/artifactory-var"生产事故中绝不能照搬这组清理命令。生产回退依赖数据库、Blob/Filestore、配置和密钥的完整恢复点,不是删容器重建。
从三个仓库对象建立稳定入口
以 Maven 为例,先建立内部快照库、内部发布库、上游代理库和统一下载入口:
maven-snapshot-hosted / maven-snapshot-local
maven-release-hosted / maven-release-local
maven-central-proxy / maven-central-remote
maven-public-group / maven-public-virtual快照允许频繁替换并采用较短保留周期;发布库拒绝覆盖已存在版本;代理库只接受来自批准上游的内容;开发者和 CI 只从 Group/Virtual 下载。上传端则直接写 Hosted/Local,避免聚合入口把写请求路由到错误目标。
Nexus Group 按成员顺序查找;Artifactory Virtual 也有解析顺序。若内部坐标和公共仓库重名,错误顺序可能让外部恶意包抢先命中。内部命名空间、Nexus Routing Rule、Artifactory include/exclude pattern 与成员顺序要共同形成防线,不能只依赖开发者“记得检查”。
代理缓存还有两个不同计时器:组件字节多久重新检查,元数据索引多久重新检查。元数据缓存太长会让新版本“明明发布了却解析不到”;太短会把上游抖动和限流直接放大到所有构建。团队应分别测量字节命中率、元数据刷新延迟和上游错误率,再设置缓存周期。
数据落在哪里,决定故障怎样恢复
数据库保存仓库配置、用户权限、任务、搜索与制品元数据;Blob Store 或 Filestore 保存真正的字节;节点配置、加密主密钥、TLS 信任和许可决定恢复后的节点能否解密凭证并加入集群。只备份字节,数据库不知道它们属于哪个仓库;只备份数据库,元数据会指向不存在的对象。
Nexus 的数据选择
内嵌 H2 适合小型、单节点和可丢弃环境。请求量、组件数或关键性上升后,外部 PostgreSQL 才能提供受支持的扩展与 HA 路径。数据库应与应用低延迟连接;不要在连接串前放未受支持的 SQL 负载均衡器,也不要把跨地域数据库延迟当作“多活”。
Blob Store 可落本地文件系统、受支持的共享存储或对象存储。对象存储降低节点本地盘绑定,但增加请求费用、网络延迟、权限策略和 SDK 兼容风险;升级前必须验证目标版本对 S3 兼容实现的要求。文件 Blob Store 要监控可用空间和文件句柄。Nexus 的 Soft Quota 只告警,不会阻止读写;若把它误当硬配额,真实磁盘耗尽时仍可能产生写入失败甚至损坏。
Artifactory 的数据选择
JFrog 推荐新部署使用 PostgreSQL;多个 HA 节点连接同一个受支持数据库和同一个 Filestore。system.yaml 位于 $JFROG_HOME/artifactory/var/etc/,先从同目录的模板取目标版本字段,再配置数据库,不能把未知版本的示例原样覆盖生产文件:
configVersion: 1
shared:
database:
type: postgresql
driver: org.postgresql.Driver
url: jdbc:postgresql://db.example.test:5432/artifactory
username: artifactory
password: <injected-from-secret-manager>连接池上限必须与数据库 max_connections、节点数和其他服务连接预算共同计算;把每个节点的 maxOpenConnections 都调大,会先压垮数据库。Filestore 可使用共享文件系统或受支持的对象存储;$JFROG_HOME/artifactory/var/etc/artifactory/binarystore.xml 的 provider 链决定读写、缓存、冗余和对象存储行为。云存储 Provider、HA 和部分高级能力受订阅限制,修改前必须依据目标版本模板确认许可,保留旧配置并做恢复演练。
Artifactory 的 master.key 参与敏感配置解密。所有 HA 节点必须使用同一受控密钥;丢失数据库备份之外的主密钥,恢复出来的系统仍可能无法解密原有凭证。主密钥应放在受限备份域,和普通配置备份分开授权。
用一个自包含制品证明写入、读取和拒绝覆盖
创建不含业务数据的固定文件:
mkdir -p repository-proof
printf 'artifact-proof-v1\n' > repository-proof/proof.txt
sha256sum repository-proof/proof.txt | tee repository-proof/proof.sha256在 Nexus 创建 Raw Hosted,或在 Artifactory 创建 Generic Local,命名为 proof-release。创建只允许该仓库读写的发布账号,将地址和凭证从 Secret 注入:
export REPO_BASE_URL='https://repo.example.test'
# Nexus Raw Hosted:
export REPO_UPLOAD_PATH='repository/proof-release/demo/1.0/proof.txt'
# Artifactory Generic Local 改为:
# export REPO_UPLOAD_PATH='artifactory/proof-release/demo/1.0/proof.txt'
curl --fail-with-body \
--user "$REPO_PUBLISH_USER:$REPO_PUBLISH_TOKEN" \
--upload-file repository-proof/proof.txt \
"$REPO_BASE_URL/$REPO_UPLOAD_PATH"
curl --fail-with-body \
--user "$REPO_READ_USER:$REPO_READ_TOKEN" \
--output repository-proof/downloaded.txt \
"$REPO_BASE_URL/$REPO_UPLOAD_PATH"
sha256sum --check <(sed 's#repository-proof/proof.txt#repository-proof/downloaded.txt#' \
repository-proof/proof.sha256)正向结果是两次 HTTP 请求退出码为 0,哈希检查输出 OK。如果上传路径、仓库格式或权限不匹配,curl --fail-with-body 应以非零退出,并保留 401、403、404 或仓库策略错误体作为第一证据。
随后用只读账号执行反向上传:
set +e
http_code=$(curl -sS -o repository-proof/deny-body.txt -w '%{http_code}' \
--user "$REPO_READ_USER:$REPO_READ_TOKEN" \
--upload-file repository-proof/proof.txt \
"$REPO_BASE_URL/$REPO_UPLOAD_PATH")
rc=$?
set -e
printf 'curl_rc=%s http_code=%s\n' "$rc" "$http_code"
test "$http_code" = 401 -o "$http_code" = 403反向实验必须得到 401 或 403。若意外返回 2xx,不是“验证通过”,而是只读角色、仓库 Target/Permission 或匿名权限配置失效,应立即撤销凭证并审计写入记录。
再尝试用同一版本上传不同字节:
printf 'artifact-proof-mutated\n' > repository-proof/mutated.txt
set +e
overwrite_code=$(curl -sS -o repository-proof/overwrite-body.txt -w '%{http_code}' \
--user "$REPO_PUBLISH_USER:$REPO_PUBLISH_TOKEN" \
--upload-file repository-proof/mutated.txt \
"$REPO_BASE_URL/$REPO_UPLOAD_PATH")
overwrite_rc=$?
set -e
printf 'curl_rc=%s http_code=%s\n' "$overwrite_rc" "$overwrite_code"
case "$overwrite_code" in
2??) printf 'release overwrite unexpectedly succeeded\n' >&2; exit 1 ;;
4??) ;;
*) printf 'unexpected response; inspect overwrite-body.txt\n' >&2; exit 1 ;;
esac
grep -Ei 'redeploy|overwrite|denied|forbidden|policy|already exists' \
repository-proof/overwrite-body.txt先在 Nexus Hosted 仓库启用禁止重部署的 Deployment Policy,或在 Artifactory Local 仓库和目标权限上禁止覆盖/删除,再执行这组实验。期望服务返回 4xx,并在响应体或服务端请求日志留下策略拒绝证据。若返回 2xx,脚本会立即失败;此时要删除意外写入的实验坐标并修正规则。不能只断言 curl 进程成功与否,因为未使用 --fail 时 HTTP 403 仍可能让 curl 返回 0。仅靠流水线先查询再上传存在并发窗口:两个任务都可能判断版本不存在,然后同时写入。不可变性必须由仓库服务端执行,客户端检查只用于提供更清晰的错误信息。
断开上游,验证代理缓存的真实边界
把 Maven/npm 客户端指向 Group/Virtual,首次请求一个批准的公开组件并记录哈希、响应时间与仓库请求日志。随后在受控网络策略中阻断仓库到上游的出口,再请求同一坐标:
export PUBLIC_MAVEN_URL='https://repo.example.test/repository/maven-public/'
# Artifactory Virtual 改为 https://repo.example.test/artifactory/maven-public/
export PROOF_GAV='org.apache.commons:commons-lang3:<approved-version>'
rm -rf repository-proof/client-cache
mvn -B -Dmaven.repo.local=repository-proof/client-cache \
dependency:get \
-Dartifact="$PROOF_GAV" \
-DremoteRepositories="proof::default::$PUBLIC_MAVEN_URL" \
2>&1 | tee repository-proof/online.log
# 阻断仓库服务到上游后,清空客户端本地缓存,再请求同一坐标。
rm -rf repository-proof/client-cache
mvn -B -Dmaven.repo.local=repository-proof/client-cache \
dependency:get \
-Dartifact="$PROOF_GAV" \
-DremoteRepositories="proof::default::$PUBLIC_MAVEN_URL" \
2>&1 | tee repository-proof/offline.log第二次成功且仓库日志显示本地缓存命中,才能证明代理缓存可用。换一个从未请求过的坐标,预期失败并留下上游连接错误或 404。这组反例说明缓存只能保护已经命中的字节,不能把代理库宣传成完整离线镜像。
缓存中的旧版本仍可能包含已撤回或有漏洞的组件。遇到供应链事件时,先阻断坐标和哈希,再决定删除缓存;直接清空整个代理库会制造大面积缓存穿透,并让故障期间所有构建重新依赖外网。
把项目构建接入单一解析入口
Maven 的 settings.xml、npm 的项目级 .npmrc 和 Gradle 仓库配置只保存 URL 与变量名,不提交真实令牌。下载账号和发布账号分开:
<mirrors>
<mirror>
<id>company-public</id>
<mirrorOf>*</mirrorOf>
<url>https://repo.example.test/repository/maven-public/</url>
</mirror>
</mirrors>registry=https://repo.example.test/repository/npm-public/
always-auth=true
//repo.example.test/repository/npm-release/:_authToken=${NPM_TOKEN}CI 在发布时记录至少这些字段:
source_commit
build_run_id
package_coordinate
repository_name
content_sha256
dependency_lock_digest
sbom_digest
publisher_identity
published_at发布流程应先构建一次,再把同一输出上传到候选库;审批后使用仓库支持的复制、移动或 Promotion 能力进入 Release 区。重新执行构建不能保证产生同一字节,因而不能作为晋级方式。部署和回滚都按内容哈希或仓库不可变坐标取回原制品。
TLS、代理、身份和不可信输入
仓库服务应位于 TLS 反向代理或受支持的内置 TLS 入口之后。入口层需要放行大文件上传,统一请求大小、超时和长连接策略,并把真实客户端地址以受信 Header 传递。413 指向请求体限制,502/504 常指向代理超时或后端中断,不能一律归因于仓库性能。
仓库访问外部上游时使用独立出站代理和 CA 信任库。TLS 解密环境必须把企业 CA 导入仓库 JVM/容器信任链,同时验证上游主机名;关闭证书校验会让代理缓存成为长期供应链污染入口。
权限至少拆成:
开发者:读取统一入口,不写 Release。快照发布器:只写本团队 Snapshot。发布晋级任务:读取候选、写入 Release,不管理用户。
仓库管理员:管理仓库与策略,日常构建不使用。备份任务:读取数据库备份和二进制存储,不拥有发布权限。
人员登录接入 OIDC、SAML 或 LDAP 时,先验证组映射、退出、禁用和救援管理员路径。CI 使用可轮换 Token,明确 owner、用途、仓库范围和到期日。访问日志、支持包和构建日志在导出前要清理 Authorization Header、URL 查询令牌、用户名与内部坐标。
上传的包、脚本和元数据都属于不可信输入。仓库负责保存和分发,不会自动证明构建脚本安全。服务端应限制可接受格式、坐标和大小;流水线在发布前执行 SCA、恶意内容检测、SBOM 与签名,消费端仍按锁文件和哈希验证。
清理不是删除任务,而是保留承诺
清理策略先区分四类数据:短命 Snapshot、长期 Release、代理缓存和失效上传。每类由不同证据决定删除:版本数量、最后下载时间、发布时间、发布状态、法律保留和回滚窗口。把所有仓库套用“30 天未下载即删除”,会误删低频但关键的灾备制品。
两款产品的物理回收语义不同,执行顺序不能混写。共同的前半段是:
以 Dry Run、搜索 API 或报告得到候选清单。把生产清单、发布记录和法律保留坐标从候选中排除。删除仓库层引用,记录任务 ID、候选坐标和删除数量。
Nexus Cleanup Policy 需要绑定到具体仓库,并调度 Admin - Cleanup repositories using their associated policies;它先把内容标记为删除。随后按目标版本调度 Admin - Cleanup unused asset blobs,再在低峰期对每个 Blob Store 运行 Admin - Compact blob store 才会回收物理空间。新版本 Compact 任务可用 Blobs Older Than 保留软删除窗口,不能把默认值当作团队恢复承诺。
Artifactory 的 Remote Repository 先设置 Unused Artifacts Cleanup Period,它只决定缓存多久未使用后成为候选;还要调度 Cleanup Unused Cached Artifacts 才会删除缓存引用。Local Repository 的旧版本应先用 AQL/REST、受审查的清理插件或目标订阅提供的生命周期能力生成并审批坐标清单,再通过 Artifactory API 删除。Artifact 引用删除后,Filestore 二进制仍要等 Artifactory GC 确认数据库中没有其他相同校验和引用才会物理回收;手工清空 Trash Can 也不保证下一次小型 GC 立即释放全部空间。不要把 Nexus 的 Compact 任务名套到 Artifactory,也不要直接删除 Filestore 哈希目录。两边最终都要对比逻辑制品数、物理存储量、GC 日志和可回滚版本,而不只看任务“成功”。
二进制可能被多个仓库或坐标去重共享,删除一个组件不会立刻释放等量空间。对象存储生命周期规则也不能绕过仓库直接删除 Blob,否则数据库会留下指向空对象的记录。
容量预算至少拆成原始制品增长、代理缓存增长、重复/去重系数、备份副本、跨站点复制、对象存储请求与出口、数据库增长和扫描元数据。告警看增长斜率、剩余可写时间和清理任务积压,而不是只设一个静态磁盘百分比。
单节点、HA 与跨站点怎样选
| 形态 | 适合的现场 | 主要代价 | 不能解决的问题 |
|---|---|---|---|
| 单节点本地盘 | 实验、可重建的小团队 | 成本低,恢复依赖备份 | 节点和磁盘单点 |
| 单节点外部 DB/存储 | 中等共享环境 | 依赖更多,但迁移较容易 | 应用节点仍单点 |
| 同地域 HA | 构建和发布不能接受单节点中断 | 许可、负载均衡、共享状态与运维成本 | 跨地域灾难 |
| 跨站点复制/联邦 | 异地团队就近下载、分发 | 冲突、延迟、带宽与商业能力边界 | 不自动等于备份或多活 |
| 托管制品平台 | 团队不希望维护 DB/存储 | 订阅、出口、地域与迁移成本 | 组织自身的权限和保留责任 |
Nexus HA 属于 Pro 能力,依赖外部 PostgreSQL 和受支持的共享 Blob 存储;所有节点版本和关键配置一致,数据库和存储保持低延迟。Artifactory Self-Hosted HA 当前要求 Enterprise X 或 Enterprise+ 订阅、两个及以上节点、共享外部数据库、共享 Filestore、统一 master.key 和可达的节点身份;若目标是无中断升级和更强节点容错,官方建议至少三个节点。两者都不能靠“把 Pod 副本数改为 3”完成,采购前还要逐项确认对象存储 Provider、复制、联邦和晋级能力是否包含在目标订阅中。
跨地域首先选择单一发布源和单向分发。双向同步容易出现同坐标不同字节、删除传播和冲突循环。Nexus Content Replication、Artifactory Replication/Federation/Distribution 等名称相近的能力具有不同授权与一致性语义,设计时必须验证目标版本的传输方向、删除行为、冲突处理、延迟和恢复方式。
故障从第一份证据开始定位
| 现象 | 第一证据 | 常见根因 | 修复后证明 |
|---|---|---|---|
客户端 401/403 | 仓库请求日志、Token 到期与权限目标 | 凭证过期、组映射漂移、写错仓库 | 只读账号读成功且写仍被拒绝 |
下载 404,上游存在 | Group/Virtual 成员顺序和代理负缓存 | 路由规则、缓存元数据或坐标错误 | 清除单项负缓存后同坐标成功 |
上传大文件 413/504 | 入口代理与仓库请求时长 | 请求体限制、超时、磁盘或对象存储延迟 | 小/大文件阶梯测试均通过 |
| 删除后空间不降 | 删除任务、Blob 引用与回收任务 | 去重引用、回收未运行或对象仍可达 | 逻辑引用与物理量趋势同时下降 |
| HA 节点间结果不同 | 节点版本、DB、存储和主密钥 | 节点配置漂移或共享状态不可达 | 摘除任一节点后读写结果一致 |
| 恢复后有元数据无字节 | DB 与 Blob 恢复时间戳 | 两套备份不属于同一恢复点 | 抽样坐标下载且哈希匹配 |
| 上游故障拖垮构建 | 缓存命中、连接池、线程与错误率 | 大量未命中、重试风暴 | 已缓存坐标成功,未缓存坐标快速失败 |
升级、备份和回退必须一起演练
升级前读取目标版本的支持矩阵与逐跳路径,检查数据库版本、JDK/容器要求、插件、对象存储驱动、许可证和 API 变化。先停止新的发布,排空或明确取消清理、复制和批量任务,并把服务切入只读或受控停写窗口;只有写入真正冻结,顺序执行的数据库备份与 Blob/Filestore 快照才可能属于同一恢复点:
T0 阻止发布、删除和配置变更,排空或取消后台写任务
T1 记录应用版本、配置摘要、仓库清单、任务状态和冻结时间
T2 在冻结窗口备份数据库并记录事务/快照标识
T3 在同一冻结窗口保护 Blob/Filestore 快照或对象版本
T4 备份主密钥、证书、信任库和许可
T5 在隔离环境恢复,验证登录、上传、下载、代理和删除拒绝
T6 才执行生产升级升级若迁移了数据库 Schema 或二进制布局,回退不能只换回旧镜像。正确动作是恢复旧应用加 T2~T4 的完整恢复点。恢复演练要抽样 Hosted/Local、Proxy/Remote 和 Group/Virtual 三类路径,并比较内容哈希;“服务首页能打开”不是恢复成功。
长期治理需要明确四个 owner:平台团队维护可用性和恢复,安全团队定义上游与制品准入,研发团队维护坐标和保留需求,发布团队维护不可变晋级与回滚证据。每季度至少复核孤儿仓库、管理员账号、长期 Token、缓存命中、清理候选、恢复耗时和许可使用量。
上线前的闭环检查
Hosted/Local 只接收内部发布,Proxy/Remote 只连接批准上游,Group/Virtual 只承担稳定解析。已用固定文件完成上传、下载、哈希比对、只读拒绝和禁止覆盖实验。已在清空客户端缓存后完成代理命中与未命中反例,能从日志区分两者。
内部命名空间、成员顺序和路由规则共同防止依赖混淆。数据库、Blob/Filestore、主密钥、证书和配置属于同一恢复设计。CI 下载、快照发布、Release 晋级、管理和备份账号相互隔离。
TLS、出站代理、CA 信任和大文件超时已做阶梯验证。清理先产出候选并保护发布回滚点,对象存储规则不越过仓库删除字节。容量预算包含代理缓存、备份、复制、数据库、请求与出口成本。
HA 的数据库、存储、负载均衡和密钥都消除了单点,而不只是增加应用副本。跨站点的方向、冲突、删除、延迟与授权边界已经书面决策。升级前完成隔离恢复,回退使用完整恢复点而不是只替换镜像。
