当 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。
面试想考察的是:
- 能否第一时间 阻断写入口 防止脏数据继续扩散;
- 能否 在缺乏官方工具 的情况下,用 PostgreSQL 手工手段 选出权威副本;
- 能否 让 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=readonly或kubectl 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 手动比对。
答案
-
止血
a. 立即在 Ingress 或 SLB 层将 Harbor 域名指向 只读副本,同时kubectl set env deploy/harbor-core CONFIG_OVERWRITE_JSON='{"read_only":true}',确保 不再有新的 push/删除。
b. 若使用外置 PostgreSQL,通过 云控制台关闭故障主库写权限(阿里云 RDS 可调用ModifyDBInstanceSpec-ReadOnly=1)。 -
选出权威数据库
a. 在每一疑似主节点执行
pg_controldata -D $PGDATA | grep 'Latest checkpoint location'
取 LSN 最大的节点;若 LSN 相同,比较pg_stat_file('pg_wal/'||max(wal_name))大小,最大者胜出。
b. 以 pg_rewind 或 重建备库 方式把其余节点回退到该 LSN,形成单主。 -
校验元数据与 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。 -
补录缺失的元数据
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 记录,不会重复占用空间。 -
重启 Harbor 并验证
a. 关闭只读开关,滚动重启 core、jobservice、registry。
b. 抽样docker pull <harbor>/library/nginx:latest并 比对 digest 与 CI 构建产物一致;在 UI 触发 “扫描全部”,确认 CVE 数据重新写入。 -
复盘加固
把 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 超时。