私有镜像仓库:Registry、Harbor 与镜像同步实战

1. 为什么需要私有镜像仓库

在 Kubernetes、Docker 等容器化技术大规模落地的今天,镜像仓库已经成为基础设施中不可或缺的一环。虽然 Docker Hub 等公共仓库使用方便,但在企业生产环境中,直接依赖公共仓库会带来几个现实问题:

  • 网络不稳定:从国内拉取 Docker Hub 镜像经常超时或速度极慢,影响发布效率。
  • 安全合规:企业内部镜像可能包含专有代码、敏感配置,不适合存放在第三方平台。
  • 版本可控:公共仓库中的镜像 tag 可能被覆盖或删除,无法保证生产环境拉取到的镜像与测试环境一致。
  • 离线交付:在隔离网络(如内网、政务云)中,必须自建仓库才能完成镜像分发。

因此,搭建一套可靠的私有镜像仓库体系,是容器化落地的基础工程。本文从最轻量的 registry:2 讲起,逐步深入到企业级 Harbor,最后给出跨机房镜像同步与保留策略的完整配置样例。

2. 前置依赖:01(私有仓库基础)

在动手搭建之前,先明确几个基础概念和前置条件。

2.1 镜像仓库的组成

一个完整的镜像仓库体系通常包含三部分:

  • 存储后端:镜像层数据实际存放的位置,可以是本地磁盘、NFS、S3、OSS 等。
  • 认证与授权:控制谁能推送、谁能拉取,以及不同用户的操作权限。
  • 元数据服务:记录镜像的 tag、digest、manifest 等索引信息。

registry:2 是 Docker 官方提供的轻量级镜像仓库实现,只包含存储和基础 API,认证需要额外配置。Harbor 则是在 registry 之上封装了完整的认证、RBAC、漏洞扫描、复制等企业级能力。

2.2 环境准备

本文所有示例基于以下环境:

  • Docker Engine 20.10+
  • Docker Compose v2
  • 一台 Linux 服务器(CentOS 7.9 / Ubuntu 22.04 均可)
  • 域名与证书(用于 TLS 配置,测试环境可用自签证书)

3. registry:2 基础搭建

3.1 最简部署

bash 复制代码
# 创建数据目录
mkdir -p /data/registry

# 启动 registry:2
docker run -d \
  --name registry \
  -p 5000:5000 \
  -v /data/registry:/var/lib/registry \
  --restart=always \
  registry:2

启动后,在 /etc/docker/daemon.json 中配置 insecure-registries(仅限 HTTP 或自签证书场景):

json 复制代码
{
  "insecure-registries": ["registry.example.com:5000"]
}

重启 Docker 使配置生效:

bash 复制代码
systemctl restart docker

3.2 推送与拉取验证

bash 复制代码
# 打 tag
docker tag nginx:latest registry.example.com:5000/nginx:1.25

# 推送
docker push registry.example.com:5000/nginx:1.25

# 拉取
docker pull registry.example.com:5000/nginx:1.25

3.3 坑位总结:推送 401 与认证配置

默认情况下 registry:2 是匿名可读写的,这在生产环境非常危险。配置 htpasswd 认证:

bash 复制代码
# 生成密码文件
mkdir -p /data/registry-auth
docker run --rm --entrypoint htpasswd registry:2 \
  -Bbn admin 'YourStrongPass' > /data/registry-auth/htpasswd

# 带认证启动
docker run -d \
  --name registry \
  -p 5000:5000 \
  -v /data/registry:/var/lib/registry \
  -v /data/registry-auth:/auth \
  -e "REGISTRY_AUTH=htpasswd" \
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
  -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
  --restart=always \
  registry:2

此时未登录直接 push 会返回 401 Unauthorized,需要先登录:

bash 复制代码
docker login registry.example.com:5000 -u admin -p 'YourStrongPass'

坑位提醒 :htpasswd 必须使用 -B(bcrypt)算法生成,否则 registry 会拒绝启动并报 htpasswd: invalid hash format。

3.4 TLS 配置

生产环境必须启用 HTTPS。假设已有证书 server.crt 和 server.key:

bash 复制代码
docker run -d \
  --name registry \
  -p 443:5000 \
  -v /data/registry:/var/lib/registry \
  -v /data/certs:/certs \
  -e "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/server.crt" \
  -e "REGISTRY_HTTP_TLS_KEY=/certs/server.key" \
  --restart=always \
  registry:2

启用 TLS 后,daemon.json 中不再需要 insecure-registries,直接使用 https://registry.example.com 访问。

3.5 存储目录与 GC 机制

registry 的镜像数据默认存放在 /var/lib/registry/docker/registry/v2 下,目录结构如下:

text 复制代码
v2/
├── blobs/          # 镜像层数据(按 digest 存储)
│   └── sha256/
├── repositories/   # 仓库与 tag 索引
│   └── nginx/
└── revisions/      # manifest 修订记录

坑位总结:GC 机制 。registry:2 的 docker registry garbage-collect 命令用于清理未被引用的 blob。但需要注意:

  • GC 必须在停止写入的情况下执行,否则可能删除正在使用的层。
  • 删除镜像 tag 后,blob 不会立即释放,需要手动触发 GC。
  • GC 是阻塞式操作,大仓库执行时会导致 registry 短暂不可用。
bash 复制代码
# 进入容器执行 GC(先停容器写入)
docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml

重要提醒:registry 原生的 tag 删除与 GC 机制并不适合生产环境频繁清理的场景,这也是很多团队转向 Harbor 的原因------Harbor 提供了更安全的保留策略和回收机制。

4. Harbor:企业级镜像仓库

Harbor 是基于 registry 的开源企业级镜像仓库,由 VMware 发起,现为 CNCF 毕业项目。它在 registry 之上补齐了认证、RBAC、漏洞扫描、复制、保留策略等能力。

4.1 部署 Harbor

以 Harbor 2.10 为例,使用离线安装包:

bash 复制代码
# 下载并解压
wget https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz
tar xzf harbor-offline-installer-v2.10.0.tgz
cd harbor

# 复制配置模板
cp harbor.yml.tmpl harbor.yml

编辑 harbor.yml 核心配置:

yaml 复制代码
hostname: harbor.example.com
http:
  port: 80
https:
  port: 443
  certificate: /data/certs/server.crt
  private_key: /data/certs/server.key
harbor_admin_password: 'Admin@123456'
database:
  password: 'DB@123456'
data_volume: /data/harbor

执行安装:

bash 复制代码
sudo ./install.sh --with-trivy --with-chartmuseum

--with-trivy 启用漏洞扫描,--with-chartmuseum 启用 Helm Chart 仓库。

4.2 项目与 RBAC

Harbor 通过「项目」隔离镜像,每个项目可设置不同的访问级别:

  • 公开:所有人可拉取,无需登录。
  • 私有:仅项目成员可访问。

RBAC 角色从高到低:

角色 权限
项目管理员 管理成员、配置策略、推送/拉取
开发者 推送/拉取镜像
访客 仅拉取
受限访客 仅拉取公开镜像

创建项目并添加成员:

bash 复制代码
# 通过 API 创建项目
curl -u admin:Admin@123456 \
  -X POST https://harbor.example.com/api/v2.0/projects \
  -H "Content-Type: application/json" \
  -d '{"project_name":"prod-app","metadata":{"public":"false"}}'

# 添加成员并赋予开发者角色
curl -u admin:Admin@123456 \
  -X POST https://harbor.example.com/api/v2.0/projects/prod-app/members \
  -H "Content-Type: application/json" \
  -d '{"role_id":2,"member_user":{"username":"dev01"}}'

4.3 漏洞扫描

Harbor 集成 Trivy 扫描器,可对镜像进行 CVE 漏洞检测。扫描策略建议:

  • 推送时自动扫描:在项目配置中开启「自动扫描图像」。
  • 定时扫描:设置每日扫描,覆盖新披露的漏洞。
  • 阻止漏洞镜像:可配置「阻止有严重漏洞的镜像被推送」。

在项目「配置管理」中开启:

yaml 复制代码
# 项目配置(Web UI 或 API)
auto_scan: true
prevent_vul: true
severity: high

4.4 镜像复制(跨机房同步)

Harbor 的复制功能支持拉取模式 和推送模式,是实现双机房镜像同步的核心能力。

场景:主机房 Harbor → 备机房 Harbor

在备机房 Harbor 中创建「复制规则」,采用拉取模式从主机房同步:

yaml 复制代码
# 复制规则配置(Web UI 或 API)
name: sync-from-primary
src_registry:
  id: 1  # 主机房 Harbor 的注册 ID
  type: harbor
  endpoint: https://harbor-primary.example.com
  credential:
    access_key: robot$sync
    access_secret: 'RobotToken123'
src_namespace: prod-app
dest_namespace: prod-app
filters:
  - type: resource
    value: image
  - type: tag
    value: 'v*'
trigger:
  type: event_based  # 事件触发,也可用 scheduled 定时

坑位提醒 :跨机房复制建议使用机器人账户(Robot Account)而非普通用户,权限更可控且支持密钥轮换。

完整配置样例:双机房复制 + 保留策略

以下是一个生产可用的完整配置组合:

yaml 复制代码
# 主机房 Harbor(harbor-primary)
# 项目 prod-app 配置:
# 1. 自动扫描开启
# 2. 保留策略:保留最近 30 个 tag,且保留所有带 prod 后缀的 tag
# 3. 复制规则:事件触发推送到备机房

# 备机房 Harbor(harbor-dr)
# 项目 prod-app 配置:
# 1. 复制规则:拉取模式,从主机房同步
# 2. 保留策略:保留最近 60 个 tag(比主机房多,防止主备切换时丢镜像)

保留策略配置(Web UI 或 API):

yaml 复制代码
# 保留规则示例
rules:
  - action: retain
    scope: repository
    repository: prod-app/*
    tag_count: 30
    tag_patterns:
      - '*-prod'   # 带 prod 后缀的 tag 永久保留
    untagged: false

4.5 保留策略实战

Harbor 的保留策略(Retention Policy)用于自动清理过期镜像,避免存储无限增长。常见策略:

  • 按数量保留:每个仓库保留最近 N 个 tag。
  • 按时间保留:保留最近 N 天的镜像。
  • 按 tag 规则保留 :匹配特定模式的 tag 永久保留(如 *-prod、*-release)。
  • 保留带 digest 的:防止被 GC 误删。

坑位总结:保留策略与 GC 的配合。Harbor 执行保留策略后,被删除的 tag 对应的 blob 不会立即释放,需要等待 Harbor 的 GC 任务执行。建议在非业务高峰期手动触发 GC:

bash 复制代码
# 通过 API 触发 GC
curl -u admin:Admin@123456 \
  -X POST https://harbor.example.com/api/v2.0/system/gc \
  -H "Content-Type: application/json" \
  -d '{"schedule":{"type":"Manual"}}'

5. 公网拉取加速与镜像代理

5.1 国内镜像源

在国内直接拉取 Docker Hub 镜像经常失败或极慢,常见解决方案:

  • 配置 Docker 镜像加速器(阿里云、腾讯云等提供的加速地址)。
  • 使用代理仓库:在 Harbor 中配置「代理缓存」项目,指向 Docker Hub 或国内镜像源。

Harbor 代理缓存项目配置:

yaml 复制代码
# 创建代理缓存项目
project_name: dockerhub-proxy
registry_proxy:
  endpoint: https://registry-1.docker.io
  type: docker-hub

之后拉取 dockerhub-proxy/library/nginx:1.25 即可,Harbor 会自动从 Docker Hub 拉取并缓存到本地。

5.2 阿里云 ACR 与镜像代理

阿里云容器镜像服务(ACR)提供个人版和企业版,支持:

  • 镜像加速:内置国内加速节点。
  • 跨地域同步:类似 Harbor 的复制功能。
  • 公网与 VPC 访问:企业版支持专有网络内网拉取。

坑位总结:镜像源失效切换。国内镜像源经常变动,建议:

  • 配置多个加速器地址,Docker 会依次尝试。
  • 关键镜像提前同步到私有仓库,避免依赖公共源。
  • 使用 Harbor 代理缓存时,定期验证上游源可用性。
json 复制代码
{
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com",
    "https://mirror.baidubce.com"
  ]
}

6. 镜像 tag 规范

统一的 tag 规范是镜像可追溯、可回滚的基础。推荐组合:语义化版本 + git sha + 环境后缀。

6.1 规范格式

text 复制代码
<应用名>:<语义化版本>-<git短sha>-<环境后缀>

示例:

text 复制代码
order-service:1.4.2-a1b2c3d-prod
order-service:1.4.2-a1b2c3d-staging
order-service:1.4.2-a1b2c3d-dev

6.2 各字段说明

字段 说明 示例
语义化版本 主版本.次版本.修订号,遵循 SemVer 1.4.2
git sha 构建时的 git 提交短哈希,保证可追溯 a1b2c3d
环境后缀 标识部署环境 prod / staging / dev

6.3 配套 CI 实践

在 CI 中自动生成 tag:

bash 复制代码
# GitLab CI / Jenkins 示例
VERSION=$(cat VERSION)  # 如 1.4.2
GIT_SHA=$(git rev-parse --short HEAD)
ENV_SUFFIX=${DEPLOY_ENV:-dev}

IMAGE_TAG="${VERSION}-${GIT_SHA}-${ENV_SUFFIX}"
docker build -t harbor.example.com/prod-app/order-service:${IMAGE_TAG} .
docker push harbor.example.com/prod-app/order-service:${IMAGE_TAG}

坑位提醒 :latest tag 只适合开发环境,生产环境必须使用不可变的完整 tag,否则无法精确定位线上运行的镜像版本。

7. 完整配置样例:Harbor 双机房复制 + 保留策略

下面给出一个可直接落地的完整配置组合,覆盖双机房高可用场景。

7.1 架构说明

#mermaid-svg-a5yBcHhz6n0IThqT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a5yBcHhz6n0IThqT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a5yBcHhz6n0IThqT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a5yBcHhz6n0IThqT .error-icon{fill:#552222;}#mermaid-svg-a5yBcHhz6n0IThqT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a5yBcHhz6n0IThqT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a5yBcHhz6n0IThqT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a5yBcHhz6n0IThqT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a5yBcHhz6n0IThqT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a5yBcHhz6n0IThqT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a5yBcHhz6n0IThqT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a5yBcHhz6n0IThqT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a5yBcHhz6n0IThqT .marker.cross{stroke:#333333;}#mermaid-svg-a5yBcHhz6n0IThqT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a5yBcHhz6n0IThqT p{margin:0;}#mermaid-svg-a5yBcHhz6n0IThqT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-a5yBcHhz6n0IThqT .cluster-label text{fill:#333;}#mermaid-svg-a5yBcHhz6n0IThqT .cluster-label span{color:#333;}#mermaid-svg-a5yBcHhz6n0IThqT .cluster-label span p{background-color:transparent;}#mermaid-svg-a5yBcHhz6n0IThqT .label text,#mermaid-svg-a5yBcHhz6n0IThqT span{fill:#333;color:#333;}#mermaid-svg-a5yBcHhz6n0IThqT .node rect,#mermaid-svg-a5yBcHhz6n0IThqT .node circle,#mermaid-svg-a5yBcHhz6n0IThqT .node ellipse,#mermaid-svg-a5yBcHhz6n0IThqT .node polygon,#mermaid-svg-a5yBcHhz6n0IThqT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a5yBcHhz6n0IThqT .rough-node .label text,#mermaid-svg-a5yBcHhz6n0IThqT .node .label text,#mermaid-svg-a5yBcHhz6n0IThqT .image-shape .label,#mermaid-svg-a5yBcHhz6n0IThqT .icon-shape .label{text-anchor:middle;}#mermaid-svg-a5yBcHhz6n0IThqT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-a5yBcHhz6n0IThqT .rough-node .label,#mermaid-svg-a5yBcHhz6n0IThqT .node .label,#mermaid-svg-a5yBcHhz6n0IThqT .image-shape .label,#mermaid-svg-a5yBcHhz6n0IThqT .icon-shape .label{text-align:center;}#mermaid-svg-a5yBcHhz6n0IThqT .node.clickable{cursor:pointer;}#mermaid-svg-a5yBcHhz6n0IThqT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-a5yBcHhz6n0IThqT .arrowheadPath{fill:#333333;}#mermaid-svg-a5yBcHhz6n0IThqT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-a5yBcHhz6n0IThqT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-a5yBcHhz6n0IThqT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a5yBcHhz6n0IThqT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-a5yBcHhz6n0IThqT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a5yBcHhz6n0IThqT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-a5yBcHhz6n0IThqT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-a5yBcHhz6n0IThqT .cluster text{fill:#333;}#mermaid-svg-a5yBcHhz6n0IThqT .cluster span{color:#333;}#mermaid-svg-a5yBcHhz6n0IThqT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-a5yBcHhz6n0IThqT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a5yBcHhz6n0IThqT rect.text{fill:none;stroke-width:0;}#mermaid-svg-a5yBcHhz6n0IThqT .icon-shape,#mermaid-svg-a5yBcHhz6n0IThqT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a5yBcHhz6n0IThqT .icon-shape p,#mermaid-svg-a5yBcHhz6n0IThqT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-a5yBcHhz6n0IThqT .icon-shape .label rect,#mermaid-svg-a5yBcHhz6n0IThqT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a5yBcHhz6n0IThqT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-a5yBcHhz6n0IThqT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-a5yBcHhz6n0IThqT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 推送镜像
事件触发复制
拉取
拉取
保留策略:30 个 tag
保留策略:60 个 tag
CI/CD 流水线
主机房 Harbor

harbor-primary
备机房 Harbor

harbor-dr
K8s 集群(主)
K8s 集群(备)
自动清理旧镜像
自动清理旧镜像

7.2 主机房配置

yaml 复制代码
# harbor-primary 配置
# 1. 项目 prod-app:私有,自动扫描开启
# 2. 复制规则:推送模式,目标为备机房
# 3. 保留策略:保留 30 个 tag,prod 后缀永久保留

# 复制规则(推送模式)
name: push-to-dr
src_registry:
  type: local
dest_registry:
  id: 2
  type: harbor
  endpoint: https://harbor-dr.example.com
  credential:
    access_key: robot$sync
    access_secret: 'RobotToken456'
dest_namespace: prod-app
filters:
  - type: resource
    value: image
  - type: tag
    value: '*-prod'
trigger:
  type: event_based

7.3 备机房配置

yaml 复制代码
# harbor-dr 配置
# 1.
相关推荐
iCxhust1 小时前
51单片机电力载波应用开发(3)载波模块点对点收发
网络·嵌入式硬件·51单片机
j7~1 小时前
【Linux网络】四十六.《高级 IO (上篇)》-- 详解
linux·运维·网络
treesforest1 小时前
网络安全中的IP地址查询:从日志里的一条线索到溯源的关键证据
网络·web安全·php
liangshanbo12152 小时前
面试题:HTTP/2有哪些升级
网络·网络协议·http
努力努力再努力wz3 小时前
【边缘计算入门系列】从“在哪里算”到“怎么算”:一文建立边缘计算、算子、计算图与 Tensor 的底层心智模型
人工智能·docker·边缘计算
极客范儿3 小时前
华为HCIP网络工程师认证—DHCP、NAT和 PPPOE
网络·华为
江湖有缘3 小时前
3款开源IT工具箱整理合集,可Docker一键部署!
docker·容器·开源
z20348315203 小时前
计算机网络:网络层(二)——IPv4编址、CIDR与路由选择
网络·计算机网络
旋生万物3 小时前
用螺旋数重写 Transformer Attention:让大模型自带“相位记忆“的 PyTorch 实现
docker·云原生·kubernetes·螺旋生成论·螺旋相位