Harbor 容器镜像仓库:从 Push/Pull 到可信 OCI 制品治理
生产回滚到 payment:1.4.0 后故障没有消失。集群事件显示镜像拉取成功,Harbor 里也确实存在这个 Tag,但运行 Pod 的 Digest 与上周发布记录不同。有人在修复流水线里覆盖了同名 Tag;扫描结果属于旧 Digest,异地仓库又只复制了镜像,没有复制完整的签名和 SBOM 关联对象。
这类事故不能靠“禁止使用 latest”解决。Tag 是便于人类阅读的可变引用,Digest 才是 Manifest 或 Manifest Index 的内容身份。Harbor 要建立的是从构建字节到运行字节的证据链:谁能 Push,哪个 Project 接收,Tag 是否允许覆盖,扫描与签名绑定哪个 Digest,异地复制得到什么,删除与 GC 会不会破坏回滚点,灾难后数据库和 Registry Storage 能否一致恢复。
registry.example.test/team/app:1.4.0
└──────── registry ────────┘ └project/repository┘ └tag┘
registry.example.test/team/app@sha256:<digest>
└ 内容身份 ┘多架构镜像的顶层 Digest 指向 OCI Image Index,各平台 Manifest 还有自己的 Digest。发布记录必须保存实际部署使用的顶层 Digest,排查单一平台问题时再下钻到平台 Manifest。
在隔离主机完成第一次 Push/Pull
Harbor 的在线和离线安装器都面向 Linux 主机。Windows 或 macOS 开发机可使用 Linux VM 做实验,桌面容器环境不作为生产形态。先从 Harbor Releases 选择目标版本,验证发布资产后解压:
export HARBOR_VERSION='<validated-version>'
export HARBOR_ARCHIVE="harbor-online-installer-${HARBOR_VERSION}.tgz"
gpg --keyserver hkps://keyserver.ubuntu.com \
--receive-keys 7722D168DAEC457806C96FF9644FF454C0B4115C
gpg --fingerprint 7722D168DAEC457806C96FF9644FF454C0B4115C
gpg --verify "${HARBOR_ARCHIVE}.asc" "$HARBOR_ARCHIVE"
tar -xzf "$HARBOR_ARCHIVE"
cd harbor
cp harbor.yml.tmpl harbor.yml导入后要人工核对完整指纹 7722 D168 DAEC 4578 06C9 6FF9 644F F454 C0B4 115C,不能只相信短 Key ID。签名校验失败立即停止,不要解压来源不明的安装包。准备一个由客户端信任的测试 CA 和证书,将密码从 Secret 注入后生成配置:
hostname: registry.example.test
https:
port: 443
certificate: /etc/harbor/tls/server.crt
private_key: /etc/harbor/tls/server.key
harbor_admin_password: <rendered-from-secret-manager>
data_volume: /data/harbor用受控部署步骤把 Secret Manager 中的值渲染进 harbor.yml,权限收紧到部署账号可读。安装器目录和该配置还用于后续重配置与升级,不能安装后随手删除;应将它们纳入受限配置备份,或保证能从版本化模板与 Secret Manager 确定性重建。不要把占位符直接交给安装器,也不要把真实管理员密码、Robot Secret 或对象存储密钥提交到仓库。
执行安装并观察每个组件:
sudo ./install.sh --with-trivy
docker compose ps
docker compose logs --tail=80 core registry jobservice
curl --fail-with-body https://registry.example.test/api/v2.0/health期望 docker compose ps 中核心组件为运行状态,健康接口返回成功。若出现 x509: certificate signed by unknown authority,先把测试 CA 导入 Docker/Containerd 客户端信任库并重启对应守护进程;不要用 insecure-registries 作为长期修复。
首次登录后立即更换管理员密码,关闭不需要的匿名入口,创建私有 Project proof-release。管理员账号只用于初始化和救援,不进入 CI。
把 Project 设计成信任域
Project 同时承载命名空间、RBAC、Quota、Tag Immutability、Retention、扫描和复制策略。按生命周期与信任级别建 Project,比按个人或临时项目建 Project 更容易治理:
proxy-cache 代理上游,只作为开发依赖入口
team-snapshot 高频构建,允许短期可变 Tag,较短保留
team-candidate 候选 Digest,等待扫描、签名和审批
team-release 已批准制品,Tag 不可变,长期回滚
platform-base 基础镜像,严格写权限和持续重扫公开 Project 允许匿名 Pull,不等于匿名 Push。生产基础镜像、内部应用和证据 Artifact 通常保持私有。Project 之间复制或 Retag 会改变配额统计和权限边界,因此晋级策略应记录源、目标和 Digest。
Quota 保护谁
Project Quota 用于阻止一个团队吃掉整个 Registry 容量。Harbor 在 Blob 和 Manifest 上传过程中计算用量;并发 Push 可能都先上传 Layer,到 Manifest 到达时才有一个请求因配额不足被拒绝。因此配额不是精确的瞬时磁盘隔离,也不能替代全局容量告警。
共享 Blob 在 Project 内只计一次,跨 Project Retag 可能增加计量;删除 Tag 后,空间也要等 Artifact 引用删除和 GC 才真正释放。容量看板应同时展示:
每个 Project 的配额、已用量和增长斜率。Registry Storage 的物理容量、对象数量与请求错误。未完成上传、无 Tag Artifact、Retention 候选和 GC 释放趋势。
扫描、复制与 Job Service 队列年龄。
Quota 应来自团队增长预算和平台剩余可写时间,不把一个演示数值复制到所有环境。
Robot Account 只拿完成动作所需的权限
为 proof-release 创建 Project Robot,分别建立发布和消费身份:
robot-publisher: repository pull + push,仅 proof-release
robot-runtime: repository pull,仅 proof-releaseRobot Secret 通常只在创建或刷新时交付,立即写入 Secret Manager;Shell 历史、Wiki、工单和聊天记录都不是凭证存储。设置有限生命周期、owner 和轮换窗口。跨 Project 自动化才评估 System Robot,不能为省事直接授予全局管理权限。
用标准输入登录,避免 Secret 出现在参数和进程列表:
printf '%s' "$HARBOR_PUBLISHER_SECRET" | \
docker login registry.example.test \
--username "$HARBOR_PUBLISHER_NAME" \
--password-stdinCI 结束后执行 docker logout registry.example.test,并让 Runner 使用短生命周期凭证或隔离的 Docker 配置目录。长期复用 Runner 时,~/.docker/config.json 是容易被后续任务继承的敏感状态。
用固定字节验证不可变发布链
创建一个不依赖业务源码的最小 OCI 镜像:
mkdir -p harbor-proof
cat > harbor-proof/Dockerfile <<'DOCKERFILE'
FROM scratch
COPY proof.txt /proof.txt
DOCKERFILE
printf 'harbor-proof-v1\n' > harbor-proof/proof.txt
docker build -t registry.example.test/proof-release/proof:1.0 harbor-proof
docker push registry.example.test/proof-release/proof:1.0 \
2>&1 | tee harbor-proof/push-v1.log从 Push 输出或 Registry API 取得 Digest,写入只读证据文件:
digest=$(docker buildx imagetools inspect \
registry.example.test/proof-release/proof:1.0 \
--format '{{json .Manifest.Digest}}' | tr -d '"')
printf '%s\n' "$digest" | tee harbor-proof/release.digest
test "${digest#sha256:}" != "$digest"
docker pull "registry.example.test/proof-release/proof@$digest"正向结果是 Push、Digest 格式断言和按 Digest Pull 均以 0 退出。Harbor UI/API 中应看到同一 Artifact 的 Tag、Digest、大小和扫描状态。若使用多架构镜像,检查取得的是 Index Digest,而不是开发机平台的子 Manifest Digest。
在 Project Policy 中为 Release Tag 启用不可变规则。修改文件并尝试覆盖:
printf 'harbor-proof-mutated\n' > harbor-proof/proof.txt
docker build -t registry.example.test/proof-release/proof:1.0 harbor-proof
set +e
docker push registry.example.test/proof-release/proof:1.0 \
>harbor-proof/immutable-deny.log 2>&1
rc=$?
set -e
printf 'push_rc=%s\n' "$rc"
test "$rc" -ne 0
grep -Ei 'immutable|denied|cannot overwrite|conflict' \
harbor-proof/immutable-deny.log反向实验必须非零退出,并在客户端或 Harbor 日志留下不可变冲突。若覆盖成功,立即停止发布,检查规则的 Repository/Tag 匹配表达式和优先级。Tag 不可变只防止同名覆盖;发布证据、Kubernetes Manifest 和回滚记录仍要保存 Digest。
退出发布 Robot,改用只读 Robot 对一个新 Tag 尝试 Push,避免不可变规则先于权限规则拒绝请求:
docker logout registry.example.test
printf '%s' "$HARBOR_RUNTIME_SECRET" | \
docker login registry.example.test \
--username "$HARBOR_RUNTIME_NAME" --password-stdin
docker tag registry.example.test/proof-release/proof:1.0 \
registry.example.test/proof-release/proof:runtime-must-not-push
set +e
docker push registry.example.test/proof-release/proof:runtime-must-not-push \
>harbor-proof/runtime-deny.log 2>&1
runtime_rc=$?
set -e
printf 'runtime_push_rc=%s\n' "$runtime_rc"
test "$runtime_rc" -ne 0
grep -Ei 'denied|unauthorized|forbidden' harbor-proof/runtime-deny.log
docker logout registry.example.test预期得到 denied、unauthorized 或 forbidden 且命令非零退出;若成功,说明 Project 权限、Robot 权限或匿名策略越界,应立即删除意外 Tag、停用该 Robot 并审计写入。反向权限实验和不可变实验验证的是不同控制点,不能互相替代。
Kubernetes 上的 HA 不是复制 Compose
官方 Helm Chart 是 Kubernetes 部署入口:
helm repo add harbor https://helm.goharbor.io
helm repo update
helm search repo harbor/harbor --versions | head
helm show values harbor/harbor > harbor-values.reference.yaml先锁定 Chart 版本,再维护环境 Values。生产结构至少包含:高可用 Ingress/Load Balancer、外部 PostgreSQL、外部 Redis、可被多副本访问的 Registry Storage、稳定 Secret、多个 Core/Registry/Portal/Job Service 副本、资源请求与限制、反亲和或拓扑分布、PDB、监控和备份。
PostgreSQL 保存项目、成员、策略、任务和制品元数据;Redis 承载缓存与任务相关临时状态;Registry Storage 保存 Manifest、Config、Layer 和其他 OCI Artifact 字节;Job Service 执行复制、扫描和 GC 等后台任务。TLS 私钥、OIDC Client Secret、Robot Secret、数据库密码和 Harbor Secret Key 决定恢复后的身份链能否继续工作。
单机安装器的 harbor.yml 至少要理解这些生产字段,而不是只改 hostname:
external_database:
harbor:
host: pg.example.test
port: 5432
db_name: registry
username: harbor
password: <injected-from-secret-manager>
ssl_mode: require
max_idle_conns: 20
max_open_conns: 100
external_redis:
host: redis-sentinel-1.example.test:26379,redis-sentinel-2.example.test:26379
sentinel_master_set: harbor-master
password: <injected-from-secret-manager>
registry_db_index: 1
jobservice_db_index: 2
trivy_db_index: 5
storage_service:
s3:
region: <validated-region>
bucket: <validated-bucket>
rootdirectory: /harbor
redirect:
disable: false字段名必须以目标版本随包的 harbor.yml.tmpl 为准。若 Harbor 位于会改写公开地址的外部反向代理之后,再单独评估 external_url;启用后它会覆盖 hostname 生成的外部 URL,不能在不了解入口拓扑时两套值一起填。max_open_conns 要乘 Core 副本数后再与 PostgreSQL 连接上限比较;Sentinel 主节点名和 DB Index 必须与 Redis 规划一致。对象存储的认证方式、redirect 子字段以及 S3 兼容端点能力随环境而异,先用专用测试 Bucket 验证 Push、Pull、删除和重定向,不能把示例占位符直接部署。Harbor 当前模板支持 Redis Sentinel,并提供仅服务端认证的 TLS 选项,但 Redis Cluster、mTLS 和不同小版本仍有能力边界;选型前应以目标版本兼容矩阵复核,不能看到“外部 Redis”就推断任意拓扑都受支持。
使用对象存储时检查延迟、请求费用、重定向、服务端加密、版本控制和最小权限;不要配置会越过 Harbor 直接删除 Registry 对象的生命周期规则。使用 RWX 文件系统时检查元数据性能、吞吐、锁和存储自身的故障域。多副本只能消除无状态组件故障,DB、Redis、Storage、Ingress 和 DNS 任一单点仍会让整套仓库不可用。
Proxy Cache 能抗抖动,不能制造信任
Proxy Cache Project 按需缓存上游 Registry。第一次未命中仍依赖上游网络、限流和凭证;命中后可降低出口和上游压力。客户端镜像名会增加 Harbor Proxy Project 路径,构建模板需要统一改写。
做一次正反实验:先通过 Proxy Project 拉取固定 Digest,清空客户端本地缓存;阻断 Harbor 到上游的出口后再次拉取同一 Digest,预期成功。再拉取一个从未命中的 Digest,预期失败并在 Job/Registry 日志看到上游连接证据。
缓存不是审批。上游 Tag 漂移、恶意替换、撤回和新漏洞都不会因为经过 Harbor 自动消失。基础镜像晋级到 Release Project 前仍需锁定 Digest、扫描、生成 SBOM、验证签名,并记录来源。
Retention 与 GC 是两段不同的删除链
Tag Retention 根据规则保留或删除 Tag/Artifact 引用。配置后先执行 Dry Run,检查无 Tag Artifact、签名/SBOM 等 OCI 关联对象、拉取时间和多条规则组合。以最后 Pull 时间为依据时,还要确认扫描等内部动作是否会更新该时间,否则保留结果可能偏离真实消费。
Garbage Collection 才回收已经不可达的 Blob。Layer 可能仍被其他 Manifest 共享,因此删除一个 Tag 不会等量释放空间。当前 GC 会为近期上传的 Layer 保留两小时安全窗口,并允许 Push/Pull 继续进行;它不是数据库与 Storage 一致备份的替代品。执行顺序是:
冻结或记录受保护的 Release Digest。对 Retention 规则做 Dry Run 并人工抽样。执行 Retention,核对删除的 Artifact 与 Tag。
先执行 GC Dry Run,审查候选 Blob、预计释放量、无 Tag Artifact 选项和两小时保护窗口。确认没有受保护 Digest、关联 Artifact 或仍在上传的对象进入候选后,再运行正式 GC。保存任务 ID、状态和日志,对比 GC 前后可达对象、物理容量和回滚清单。
不要手工删除对象存储中的 Layer,也不要让云生命周期策略绕过 Harbor。数据库仍认为对象存在时,Pull 会在读取 Blob 阶段失败,这种损坏通常比“磁盘满”更难恢复。
扫描证据必须带时间
Harbor 可集成 Trivy 或兼容 Scanner Adapter,在 Push、定时任务或人工操作后触发扫描。一次扫描结果至少关联:Artifact Digest、扫描器及版本、漏洞库更新时间、执行时间、严重级别来源、Fix 状态、例外 owner 和例外到期日。
同一 Digest 今天可能通过,明天因为漏洞库更新出现 Critical;字节没有变化,风险情报发生了变化。因此 Release Project 需要周期重扫和在役镜像回溯。扫描长期 Pending 时,先看 Job Service 队列年龄、Scanner 健康、漏洞库更新和网络出口,不要重复点击扫描制造更大积压。
扫描结论也有边界:它不等于运行时防护,不证明业务配置安全,也不证明签名可信。对离线环境,要设计漏洞库镜像、更新周期、陈旧阈值和更新失败告警。
签名被保存,不等于部署端会验证
Cosign、Notation 等客户端生成签名或 Attestation,并把关联 OCI Artifact 存入 Registry。Harbor 能展示或保存这些对象,但签名私钥、证书身份、Issuer、Repository 约束和 Kubernetes 准入策略仍在 Harbor 之外完成。
可信发布链至少要证明:
source commit
-> build run
-> image digest
-> SBOM digest
-> scanner evidence + DB time
-> signature / attestation subject
-> promotion approval
-> deployed digest验证端按 Digest 检查签名 Subject 和受信身份。只在 Harbor 页面看到“有签名”不能证明签名来自可信发布者,也不能证明集群拒绝未签名镜像。Retention 与复制策略还要包含这些关联 Artifact;只保留主镜像会留下断裂证据链。
复制用于分发,不用于掩盖灾备缺失
Harbor 可按 Push/Pull、手动、定时或事件触发复制,并按名称、Tag、Label 等筛选。生产设计优先采用单一发布源和单向分发;双向复制容易形成同名冲突、覆盖循环和删除传播。
上线复制规则前,用测试 Project 依次验证:
首次全量复制后源、目标顶层 Digest 一致。新 Digest 能增量复制,旧 Release 不被覆盖。签名、SBOM 和其他 OCI 关联 Artifact 是否随目标适配器完整复制。
网络中断后任务进入可观察失败并能幂等重试。目标限流、权限不足和存储满留下可定位任务日志。删除是否传播,以及目标端是否保留独立回滚策略。
复制是在线分发,可能把误删和污染同步到目标。灾备仍需要隔离、版本化或不可变的 Registry Storage 保护,数据库备份,配置和 Secret 备份,以及恢复演练。
把构建、晋级和运行身份彻底拆开
Build Robot 只向 Candidate/Snapshot Push,并可 Pull 基础镜像;Promotion Job 读取候选 Digest,验证 SBOM、扫描、签名和审批后复制或 Retag 到 Release;Runtime Robot 只 Pull Release;Replication Robot 只拥有源读和目标写所需权限。
流水线输出至少记录:
source_commit
build_run_id
image_repository
human_readable_tag
image_index_digest
platform_manifest_digests
base_image_digest
sbom_digest
signature_or_attestation_reference
scanner_and_database_timestamp
promotion_approvalKubernetes Manifest 可以保留便于阅读的 Tag 注释,但实际 image 使用批准 Digest。回滚是重新部署上一个已知安全 Digest,不是从旧 Git Tag 重新构建。重新构建会受到基础镜像、包仓库、构建器和时间戳变化影响,无法自动还原原字节。
人员身份接入 OIDC/LDAP 前,在测试实例验证组映射、登录退出、Token 刷新、用户禁用和救援管理员。认证模式迁移会影响现有本地账号,不能在没有救援入口时直接切换。Harbor 访问对象存储、远端 Registry、OIDC、LDAP 和 Scanner 的凭证分别使用独立身份,避免一个泄漏扩展为全平台控制权。
常见故障沿数据路径排查
| 现象 | 第一证据 | 常见根因 | 修复后的证明 |
|---|---|---|---|
x509: certificate signed by unknown authority | 客户端和 Harbor 证书链 | CA 未导入、SAN 不匹配、链不完整 | 登录、Push、Pull 都通过 TLS |
Push 返回 denied | Project/Robot 权限与审计日志 | Secret 过期、Project 错、无 Push 权限 | Publisher 成功且 Runtime 仍被拒绝 |
| Push 卡在 Layer | Ingress、Registry、Storage 日志 | 请求体、超时、对象存储延迟 | 小/大 Layer 阶梯测试通过 |
| Manifest Push 时配额失败 | Project Quota 与并发任务 | Layer 已上传后配额被耗尽 | 清理未完成上传并降低并发后成功 |
| 扫描长期 Pending | Job 队列和 Scanner 健康 | 漏洞库、网络或 worker 积压 | 小镜像扫描完成,队列年龄回落 |
| 删除后空间不降 | Artifact 引用和 GC 报告 | 共享 Layer、只删 Tag、GC 未运行 | 不可达对象与物理量同时下降 |
| 复制成功但目标缺对象 | 任务明细、过滤器、目标适配器 | 关联 Artifact 不兼容或规则遗漏 | 源目标 Digest 与证据对象逐一匹配 |
多副本间歇 500 | DB、Redis、Storage、Secret 一致性 | 外部状态抖动或密钥漂移 | 摘除任一 Pod 后读写结果一致 |
| 按 Tag 回滚内容仍错误 | 运行时 ImageID 与发布 Digest | Tag 被覆盖或节点缓存 | 按批准 Digest 部署且 ImageID 匹配 |
备份、恢复与升级要围绕同一恢复点
Harbor 的恢复对象至少包括 PostgreSQL、Registry Storage、配置、Harbor Secret Key、TLS/OIDC/对象存储等 Secret,以及目标版本需要的任务状态。Redis 通常承载临时状态,但恢复方案仍要说明任务如何取消、重建或核对,不能假设所有 Pending Job 会自动安全继续。官方 Velero 示例只覆盖内置数据库等特定形态,不负责外部 PostgreSQL 备份;采用外部状态时必须由数据库与存储平台分别提供恢复能力。
恢复演练按以下顺序进行:
T0 启用 Repository Read Only,阻止 Push、删除和配置变更
T1 停止新复制、Retention、GC 与扫描任务,排空或记录未完成任务
T2 记录 Harbor/Chart 版本、配置摘要、Project、策略与冻结时间
T3 在冻结窗口备份 PostgreSQL并记录事务/快照标识
T4 在同一冻结窗口保护 Registry Storage 的对应快照或对象版本
T5 备份 Secret、证书和身份配置
T6 在隔离环境恢复,先保持只读
T7 验证登录、Project/RBAC、按 Digest Pull、拒绝写、扫描和复制
T8 全部核对完成后才解除只读抽样恢复不能只看首页。至少选择一个多层镜像、一个多架构镜像和一个带 SBOM/签名关联对象的 Release,按 Digest Pull 并比较源记录。再用只读 Robot 做拒绝 Push 实验,证明权限也被正确恢复。
升级可能迁移 harbor.yml、Helm Values、数据库 Schema 和组件版本。先根据目标版本升级文档验证支持的起始版本,在隔离副本演练完整路径。升级后检查 Push/Pull、OIDC、Robot、扫描、复制、Retention/GC 和审计。Helm 部署发生 Schema 迁移后不支持用 helm rollback 自动降级数据库;回退必须恢复旧版本加 T3~T5 的完整恢复点,不能只把 Chart 或镜像标签改回去。
架构选型看故障域与责任,不只看功能
| 形态 | 合适的团队 | 主要成本 | 关键风险 |
|---|---|---|---|
| 单机安装器 | 实验、可重建小团队 | 最低,操作直观 | 主机、DB、Redis、存储同故障域 |
| Kubernetes HA Harbor | 有平台团队、需要私有 OCI 治理 | 外部状态、升级和观测复杂 | 伪 HA、对象存储延迟、任务积压 |
| 托管 Registry | 希望减少基础设施维护 | 订阅、出口、地域和迁移成本 | 身份、配额、证据与保留仍需自理 |
| 多站点 Harbor | 异地就近访问和受控分发 | 带宽、冲突、双站点治理 | 复制误当备份或多活 |
自建 Harbor 的许可成本低,不代表总成本为零。数据库、Redis、对象存储、漏洞库、备份、升级、证书、值守和事故响应都需要 owner。托管 Registry 减少组件运维,但仍要核对 OCI Artifact 兼容、私网、身份联邦、跨地域复制、审计保留、扫描/签名生态、出口费用、数据地域和迁出方式。
长期治理每月复核 Robot 到期与越权、Project 配额增长、可变 Tag、扫描陈旧度、Retention Dry Run、GC 释放效率和复制积压;每季度做一次恢复抽样和证书/Secret 轮换演练。平台团队维护可用性与恢复,安全团队维护信任与例外,研发团队维护镜像生命周期,发布团队维护晋级和回滚证据。
上线前的闭环检查
已用固定镜像完成 Push、按 Digest Pull、不可变覆盖拒绝和只读 Robot 拒绝实验。Project 按信任和生命周期划分,公开读取、写权限与跨 Project 晋级边界明确。Quota 同时配合全局容量、并发上传、未完成上传和 GC 趋势监控。
Release Tag 不可变,发布、复制、部署和回滚都保存顶层 Digest。Proxy Cache 已验证命中与未命中反例,没有被当作审批或离线全量镜像。Retention 先 Dry Run,并保护 Release、签名、SBOM 和其他关联 Artifact。
GC 只走 Harbor 支持流程,对象存储生命周期不直接删除 Registry 对象。扫描证据包含扫描器、漏洞库时间、Fix 状态、例外 owner 和到期日。签名信任根与部署准入在 Harbor 之外形成强制验证,未把“已保存签名”误当成“已验证”。
复制已验证方向、过滤、重试、删除、冲突和关联 Artifact 完整性。PostgreSQL、Redis、Registry Storage、Ingress、DNS 和 Secret 的故障域均已识别。Build、Promotion、Runtime、Replication Robot 权限分离并可轮换。
备份同时保护 DB、Storage、配置和 Secret,并完成隔离恢复与按 Digest 抽样。升级和回退使用受支持路径与完整恢复点,不以换回旧镜像代替恢复。自建、托管和多站点方案已经比较容量、出口、值守、数据地域和迁出成本。
