设计双活 Harbor 并解决主键冲突问题

解读

国内金融、运营商及大型央企在信创与等保 2.0 背景下,对镜像仓库的RPO=0、RTO<30s有刚性要求,单实例 Harbor 已无法满足。双活(Active-Active)Harbor 指两个独立 Harbor 实例同时对外提供推送与拉取服务,任一机房整体掉线业务不中断。核心难点是:两实例共用一套底层存储时,PostgreSQL 主键自增序列重复导致 artifact、project、repository 表冲突,进而出现镜像索引覆盖或 500 错误。面试官希望候选人给出可落地的架构、数据层冲突避免策略、Docker 层配置及回滚方案,并能在 10 分钟内把思路讲清。

知识点

  1. Harbor 数据模型:artifact、project、repository、tag、blob、schedule 六张核心表均依赖序列自增 ID。
  2. PostgreSQL 序列冲突本质:两实例本地序列步长为 1,起始值相同,并发写入必冲突。
  3. 双活存储前提:国内主流采用共享型分布式存储(华为 OceanStor、XSKY、阿里云 NAS)或对象存储跨区域桶复制(MinIO 双活、腾讯云 COS 回源),保证 blob 层一致。
  4. 序列步长拆分法:PostgreSQL 序列通过 INCREMENT BY 2START WITH 1/2 把奇偶 ID 段分给两机房,零代码侵入,回滚只需改序列。
  5. 应用层 UUID 兜底:Harbor v2.5+ 已支持 UUID_GENERATE_V4() 作为 artifact 唯一键,开启后可降级到 UUID 主键,序列仅做兼容。
  6. Docker Compose 层关键
    • 两实例 external_url 必须分别指向本机房 SLB 域名,禁止交叉写流量
    • registry.relativeurls=true 强制回源到本地域,避免跨机房拉取;
    • jobservice.max_job_workers 减半,防止并发任务重复。
  7. 脑裂与回切:借助Keepalived+VIP 或 DNS 权重实现流量切换;回切前需用 pg_rewindrsync --checksum 校验 blob 一致性,防止静默损坏。

答案

一、总体架构

  1. 两地三机房(A、B 同城双活,C 异地冷备),A、B 同时 Active
  2. 存储层:
    • 块存储:采用华为 OceanStor Dorado 双活 NAS,通过双活引擎保证 A-A 时延 <2 ms,blob 目录全局强一致
    • 对象存储:若已 MinIO,开启site replication(v2.1+),mc admin replicate add 双向同步,版本号由 MinIO 内部时钟向量保证
  3. 数据库层:
    • 两机房各部署独立 PostgreSQL 14 实例,不采用流复制,通过序列步长拆分实现逻辑双写。
    • 序列脚本(以 harbor 库为例):
      -- A 机房
      ALTER SEQUENCE artifact_artifact_id_seq INCREMENT BY 2 START WITH 1;
      -- B 机房
      ALTER SEQUENCE artifact_artifact_id_seq INCREMENT BY 2 START WITH 2;
      
    • 其余序列同理,一次性脚本由 Ansible 批量下发
  4. 应用层:
    • Harbor 版本锁定 v2.8.2 国产化麒麟 v10 镜像registry.cn-hangzhou.aliyuncs.com/goharbor/harbor-core:v2.8.2-ky10)。
    • harbor.yml 关键配置:
      external_url: https://harbor-a.example.com
      database:
        host: 192.168.1.10
        port: 5432
        db: harbor
        sslmode: disable
      data_volume: /mnt/nas/harbor
      
    • 关闭 clair(已废弃),trivy.cache_dir 指向本地 NVMe,减少跨机房锁等待
  5. 流量入口:
    • 采用阿里云全球流量管理(GTM) 基于 EDNS-Client-Subnet 把用户解析到就近机房;
    • 每个机房内部用 Keepalived+HAProxy 做 4 层负载,VIP 与 GTM 地址联动,实现秒级切换。

二、主键冲突解决验证

  1. 压测工具:使用官方 harbor-load 镜像goharbor/harbor-load:v1.1)并发 200 线程同时 push 相同 busybox:latest 到 A、B 两实例。
  2. 验证 SQL:
    SELECT COUNT(*) FROM artifact WHERE digest='sha256:xxx' GROUP BY digest HAVING COUNT(*)>1;
    
    结果应为 0,证明无重复主键
  3. 回滚预案:
    • 若序列拆分失败,30 秒内切换为单写模式
      • 关闭 B 机房入口,GTM 权重置 0;
      • B 实例 docker-compose stop;
      • A 机房序列改回 INCREMENT BY 1业务无感知

三、Docker Compose 片段(A 机房)

version: '2.4'
services:
  portal:
    image: registry.cn-hangzhou.aliyuncs.com/goharbor/harbor-portal:v2.8.2-ky10
    networks:
      - harbor
  core:
    image: registry.cn-hangzhou.aliyuncs.com/goharbor/harbor-core:v2.8.2-ky10
    environment:
      - _REDIS_URL=redis://redis:6379/0
      - _POSTGRES_HOST=192.168.1.10
    volumes:
      - /mnt/nas/harbor:/data
    networks:
      - harbor
  registry:
    image: registry.cn-hangzhou.aliyuncs.com/goharbor/registry-photon:v2.8.2-ky10
    environment:
      - REGISTRY_STORAGE_REDIRECT_DISABLE=true
      - REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/storage
    volumes:
      - /mnt/nas/harbor/registry:/storage
    networks:
      - harbor
networks:
  harbor:
    external: false

关键点REGISTRY_STORAGE_REDIRECT_DISABLE=true 强制流量走 core,避免客户端直连存储出现跨机房 302 漂移

拓展思考

  1. 三机房多活:若未来扩展为 3 活,序列步长改为 3,起始值 1/2/3;但 PostgreSQL 序列上限 2^63-1,提前评估 10 年增量
  2. Operator 化:采用KubeSphere 开源的 harbor-operator,通过 CRD HarborCluster 把序列拆分逻辑写进 init-container,实现 GitOps 自动 rollout。
  3. 冲突监控:在 Prometheus 增加 pg_stat_user_tables_n_tup_ins 指标,若两机房同一表插入速率长期接近,说明序列步长拆分不均,可动态 setval 调整。
  4. 国产化替代华为 openGauss 2.1 已支持全局唯一索引(GUC enable_global_unique_index=on),可彻底废弃序列,但需验证与 Harbor ORM 的兼容性