当 Harbor 数据库发生脑裂时如何恢复镜像元数据

解读

Harbor 的镜像元数据(artifact、tag、label、权限、CVE 扫描结果等)全部持久化在 PostgreSQL 核心库(registry 数据库)。
“脑裂”在国内生产环境通常指 双主或多主 PostgreSQL 集群因网络分区、VIP 漂移或 K8s 脑裂导致同时接受写请求,结果同一行记录在多个节点出现 LSN 分叉,重启后数据库拒绝启动或报 duplicate key/conflict,Harbor 核心服务(core、jobservice、registryctl)大面积 500,镜像 push 成功但 UI 看不到,或 UI 能看到但 docker pull 报 manifest unknown
面试想考察的是:

  1. 能否第一时间 阻断写入口 防止脏数据继续扩散;
  2. 能否 在缺乏官方工具 的情况下,用 PostgreSQL 手工手段 选出权威副本
  3. 能否 让 Harbor 在元数据与 blob 不一致时重新对齐,最终 零数据丢失上线

知识点

  • PostgreSQL 脑裂判定:对比 pg_controldata 的 Latest checkpoint’s REDO location,取 LSN 最大且 timeline 最高的节点为权威;若 LSN 相同,选 拥有最多 WAL 段的节点。
  • Harbor 元数据表级联关系
    artifact 表记录 digest、repository_id、push 时间;
    tag 表外键到 artifact,name 唯一;
    blob 表记录实际层 digest,与 artifact 通过 artifact_blob 多对多关联;
    project_metadata 存机器人账号、CVE 白名单。
  • Harbor 的只读开关helm upgrade 设置 core.configureMode=readonlykubectl set env deploy/harbor-core CONFIG_OVERWRITE_JSON='{"read_only":true}',可 秒级关闭写入口 而无需停 registry。
  • registry 存储与元数据解耦:只要 blob 在 S3/MinIO/本地挂载 未被 GC,digest 不变即可重新关联
  • Harbor 提供的元数据重建脚本harbor-migrate -t registry 仅做 schema 升级,不负责冲突合并;真正需要手工 INSERT … ON CONFLICT DO UPDATE
  • 国内云厂商 RDS 只读实例无法自动修复脑裂,需 提工单开启超级用户自建 pglogical 手动比对

答案

  1. 止血
    a. 立即在 Ingress 或 SLB 层将 Harbor 域名指向 只读副本,同时 kubectl set env deploy/harbor-core CONFIG_OVERWRITE_JSON='{"read_only":true}',确保 不再有新的 push/删除
    b. 若使用外置 PostgreSQL,通过 云控制台关闭故障主库写权限(阿里云 RDS 可调用 ModifyDBInstanceSpec-ReadOnly=1)。

  2. 选出权威数据库
    a. 在每一疑似主节点执行
    pg_controldata -D $PGDATA | grep 'Latest checkpoint location'
    LSN 最大的节点;若 LSN 相同,比较 pg_stat_file('pg_wal/'||max(wal_name)) 大小,最大者胜出
    b. 以 pg_rewind重建备库 方式把其余节点回退到该 LSN,形成单主。

  3. 校验元数据与 blob 一致性
    a. 在权威库执行
    SELECT digest FROM artifact WHERE repository_id IN (SELECT repository_id FROM repository);
    得到所有期望存在的 digest 列表。
    b. 对 registry 存储(S3 或本地)跑
    docker run --rm -v $STORAGE:/storage -e REGISTRY_STORAGE=file -e REGISTRY_STORAGE_FILE_ROOTDIRECTORY=/storage registry:2.8 bin/registry garbage-collect --dry-run /etc/docker/registry/config.yml
    记录 缺失的 blob digest;若缺失的是 config 层,则该 artifact 需 从备份恢复或重新 push

  4. 补录缺失的元数据
    a. 若 blob 存在而元数据缺失,用 Harbor sysadmin 账号调用
    POST /api/v2.0/repositories/${repo}/artifacts/${digest}/tags
    重新打 tag;若 artifact 记录整行缺失,则手工 INSERT INTO artifact (...) VALUES (...) ON CONFLICT (digest) DO UPDATE SET push_time=EXCLUDED.push_time;
    b. 若 blob 缺失,通知业务 重新执行 CI 构建并 push 同名 tag,Harbor 会自动复用已存在的 manifest 记录,不会重复占用空间

  5. 重启 Harbor 并验证
    a. 关闭只读开关,滚动重启 core、jobservice、registry。
    b. 抽样 docker pull <harbor>/library/nginx:latest比对 digest 与 CI 构建产物一致;在 UI 触发 “扫描全部”,确认 CVE 数据重新写入。

  6. 复盘加固
    把 PostgreSQL 改为 Patroni + DCS(etcd) 部署,启用 synchronous_mode_strict,并给 Harbor 配置 externalDatabase.maxOpenConns=30 防止雪崩;定期用 pg_dumpall > obs://bucket/harbor-逻辑备份,保留 7 天。

拓展思考

  • 如果脑裂期间 同名 tag 被指向了不同 digest,如何在 零停机 条件下让业务无感?
    可借助 Harbor immutable tag 策略提前禁止覆盖;若已覆盖,先 把旧 digest 保存为带时间戳的标签,再 通过 webhook 通知业务重新部署,实现 蓝绿回滚
  • 当 Harbor 采用 双中心主备(北京+上海)且使用 跨地域 Ceph RGW 时,元数据脑裂可能伴随 blob 异步复制延迟;此时需 以元数据为准,对 Ceph 对象做 radosgw-admin object rewrite,强制把 digest 相同的对象提前拉取到主中心,避免 pull 超时。