Docker 私有镜像仓库:Registry 与 Harbor 实战指南

在 Docker 的日常使用中,Docker Hub 是最常用的公共镜像仓库。然而,在企业生产环境或特定开发场景下,搭建私有的 Docker 镜像仓库往往是必不可少的。本文将探讨为什么需要私有仓库,介绍主流的解决方案,并重点实战部署和使用 Docker Registry 和 VMware Harbor。

一、 为什么要使用私有镜像仓库?

搭建和使用私有镜像仓库主要有以下几个关键原因:

  1. 网络与速度 (Network & Speed):

    • 加速镜像拉取: 将仓库部署在内网或地理位置相近的网络中,可以显著减少网络延迟,大幅提升镜像拉取(pull)和推送(push)的速度,尤其是在网络环境不佳或需要频繁部署的情况下。
    • 带宽控制: 避免大量内部服务器同时从公网拉取镜像,节省公网带宽。
  2. 安全与合规 (Security & Compliance):

    • 知识产权保护: 对于包含商业逻辑或专有技术的应用程序镜像,存储在私有仓库中可以防止源码或敏感信息泄露给竞争对手或未经授权的第三方。
    • 访问控制: 可以精确控制哪些用户或团队有权限访问、推送或拉取特定的镜像。
    • 合规要求: 某些行业或公司可能有数据存储位置或安全审计的合规要求,私有仓库更容易满足这些规定。
  3. 稳定与可控 (Stability & Control):

    • 避免单点故障: 依赖公共仓库可能因其服务中断或网络问题影响自身业务。私有仓库提高了对镜像存储服务的掌控力。
    • 版本管理: 更方便地管理内部使用的特定镜像版本,避免公共仓库可能发生的镜像更新或删除导致的问题。
    • 集成 CI/CD: 私有仓库可以更好地与内部的持续集成/持续部署(CI/CD)流程集成。

二、 私有镜像仓库解决方案概览

市面上有多种搭建私有 Docker 镜像仓库的方案:

  1. Docker Hub (私有仓库功能): Docker Hub 本身提供私有仓库托管服务,但需要付费,且数据仍存储在 Docker Hub 的服务器上。
  2. Docker Registry: Docker 官方提供的开源基础镜像仓库实现。
    • 优点: 轻量级(容器仅几十MB),部署简单。
    • 缺点: 功能非常基础,缺少 Web UI,用户管理、权限控制、镜像搜索、垃圾回收等操作相对复杂,不适合大规模企业级应用
  3. VMware Harbor: 由 VMware 发起并捐赠给 CNCF 的开源企业级镜像仓库项目。
    • 优点: 功能全面,提供友好的 Web UI,支持基于角色的访问控制 (RBAC)、镜像漏洞扫描 (集成 Trivy/Clair)、镜像签名 (Notary)、镜像复制、Helm Chart 仓库、审计日志等丰富功能,强烈推荐用于企业生产环境
    • 缺点: 相对于 Registry,部署稍复杂,资源占用更高(因为它包含了数据库、缓存、UI、扫描器等多个组件)。
  4. 云厂商提供的仓库服务: 如阿里云 ACR、腾讯云 TCR、华为云 SWR、AWS ECR、Google GCR 等。
    • 优点: 开箱即用,与云平台深度集成,通常提供高可用性和弹性。
    • 缺点: 可能存在厂商锁定风险,成本考量,数据存储在云端。
  5. 其他第三方方案: 如 JFrog Artifactory, Sonatype Nexus Repository 等,它们通常是更通用的软件制品库,也支持 Docker 镜像存储。

本文将重点实战部署 Docker RegistryHarbor

三、 实战:部署和使用 Docker Registry (基础版)

Docker Registry 提供了一个基础的镜像存储服务。虽然功能有限,但了解其工作方式有助于理解更复杂的系统。

1. 部署 Docker Registry 服务

在一台 Docker 主机(例如 10.0.0.101)上运行 Registry 容器:

# 运行 Registry 容器,将容器 5000 端口映射到宿主机 5000 端口
# --restart=always 保证容器异常退出后自动重启
# -v /var/lib/registry 将宿主机目录挂载到容器内,用于持久化存储镜像数据
# registry 是官方提供的 Registry 镜像名称
root@docker101:~# docker run -dp 5000:5000 --restart=always --name oldboyedu-registry -v /var/lib/registry:/var/lib/registry registry:2

注意:官方推荐使用 /var/lib/registry 作为容器内的数据存储路径,并挂载出来。

2. 验证 Registry API

Registry 提供了一个 HTTP API。可以通过访问 _catalog 端点查看仓库中已有的 repositories(项目/镜像组):

# 访问 API 端点 (初始为空)
curl http://10.0.0.101:5000/v2/_catalog
# 预期输出: {"repositories":[]}

3. 配置 Docker 客户端信任不安全的 Registry

默认情况下,Docker 客户端期望通过 HTTPS 与 Registry 通信。由于我们部署的 Registry 是 HTTP 的,需要在所有需要访问此 Registry 的 Docker 客户端上进行配置,将其标记为 "insecure-registry"。

在客户端机器(例如 10.0.0.102)上编辑或创建 Docker 守护进程配置文件 /etc/docker/daemon.json

# root@docker102:~# cat /etc/docker/daemon.json
{
  "insecure-registries": ["10.0.0.101:5000"]
  # 如果已有其他配置,请确保 JSON 格式正确,例如:
  # "registry-mirrors": ["https://your-mirror.com"],
  # "insecure-registries": ["10.0.0.101:5000"]
}

修改配置后,必须重启 Docker 服务

root@docker102:~# systemctl restart docker

验证配置是否生效:

root@docker102:~# docker info | grep "Insecure Registries" -A 2
 Insecure Registries:
  10.0.0.101:5000
  127.0.0.0/8 # 本地地址默认被信任

常见错误: 如果未配置 insecure-registries,尝试 push/pull 时会遇到类似 Get "https://10.0.0.101:5000/v2/": http: server gave HTTP response to HTTPS client 的错误。

4. 推送镜像到 Registry

  • 标记 (Tag) 镜像: 将本地已有的镜像(例如 mysql:8.3.0-oracle)标记为指向私有 Registry 的格式。格式通常为:<registry_host>:<port>/<project_name>/<image_name>:<tag><registry_host>:<port>/<image_name>:<tag>

    # 将 mysql:8.3.0-oracle 标记为私有仓库中的 oldboyedu-db/mysql:8.3.0-oracle
    root@docker102:~# docker tag mysql:8.3.0-oracle 10.0.0.101:5000/oldboyedu-db/mysql:8.3.0-oracle
    
    • 10.0.0.101:5000: 私有仓库地址和端口。
    • oldboyedu-db: 项目名称(或称为 namespace/organization),有助于组织镜像。
    • mysql: 镜像名称。
    • 8.3.0-oracle: 镜像标签。
  • 推送 (Push) 镜像: 将标记好的镜像推送到私有 Registry。

    root@docker102:~# docker push 10.0.0.101:5000/oldboyedu-db/mysql:8.3.0-oracle
    # ... (推送过程输出,显示各层 layer 被推送) ...
    8.3.0-oracle: digest: sha256:ed17... size: 2411
    
  • 再次验证 API:

    curl http://10.0.0.101:5000/v2/_catalog
    # 预期输出: {"repositories":["oldboyedu-db/mysql"]}
    

5. 从 Registry 拉取镜像

已配置好 insecure-registries 的客户端机器(可以是 Registry 宿主机或其他机器)上拉取镜像:

# 确保客户端配置了 insecure-registries 并重启了 Docker 服务
root@docker101:~/haha# cat /etc/docker/daemon.json
{
	"insecure-registries": ["10.0.0.101:5000"]
}
root@docker101:~/haha# systemctl restart docker

# 拉取镜像
root@docker101:~/haha# docker pull 10.0.0.101:5000/oldboyedu-db/mysql:8.3.0-oracle
# ... (拉取过程输出) ...
Status: Downloaded newer image for 10.0.0.101:5000/oldboyedu-db/mysql:8.3.0-oracle
10.0.0.101:5000/oldboyedu-db/mysql:8.3.0-oracle

# 查看本地镜像
root@docker101:~/haha# docker image ls 10.0.0.101:5000/oldboyedu-db/mysql:8.3.0-oracle
REPOSITORY                           TAG            IMAGE ID       CREATED        SIZE
10.0.0.101:5000/oldboyedu-db/mysql   8.3.0-oracle   a88c3e85e887   6 months ago   632MB

Registry 的局限性: 正如提示所说,用户需要确切知道镜像的完整名称 (<registry>/<project>/<image>:<tag>) 才能拉取,没有方便的搜索或浏览功能。

6. 删除 Registry 中的镜像 (复杂)

Docker Registry 本身不直接提供删除镜像的 API(出于安全和简单性考虑)。删除操作相对繁琐,且需要手动触发垃圾回收 (Garbage Collection, GC) 来释放磁盘空间。

  • 步骤 1: 删除镜像元数据 (Manifests 和 Tags)
    虽然 Registry API v2 规范包含删除 manifest 的端点,但默认配置下可能未启用删除功能。更直接(但不推荐直接操作存储)的方式是进入 Registry 容器内部,手动删除存储后端中的元数据。

    # 进入 Registry 容器
    root@docker101:~/haha# docker exec -it oldboyedu-registry sh
    
    # 进入存储目录
    / # cd /var/lib/registry/docker/registry/v2/repositories/
    
    # 找到对应的 repository 并删除 (这将移除所有 tag 和 manifest 引用)
    /var/lib/registry/docker/registry/v2/repositories # ls
    oldboyedu-db
    /var/lib/registry/docker/registry/v2/repositories # rm -rf oldboyedu-db/
    
    # 此时通过 _catalog API 查看,该 repository 已消失
    # curl http://10.0.0.101:5000/v2/_catalog -> {"repositories":[]}
    
    # 注意:此时镜像的 blob 数据(layer 文件)仍然存在于 blobs 目录中!
    /var/lib/registry/docker/registry/v2 # du -sh ../../../*
    175.0M	/var/lib/registry/docker/registry/v2/blobs
    4.0K	/var/lib/registry/docker/registry/v2/repositories
    
  • 步骤 2: 运行垃圾回收 (Garbage Collect)
    垃圾回收命令会扫描 blobs 目录,删除所有未被任何 manifest 引用的 blob 文件。

    # 在 Registry 容器内执行垃圾回收命令
    # 需要指定 Registry 的配置文件路径
    # 官方 registry:2 镜像默认可能没有 /etc/docker/registry/config.yml
    # 你可能需要先挂载一个配置文件或使用环境变量配置存储选项
    # 假设配置文件存在 (如果不存在,GC 可能无法正确执行)
    / # registry garbage-collect /etc/docker/registry/config.yml
    # ... (GC 输出,显示哪些 blob 被删除) ...
    # 12 blobs and 0 manifests eligible for deletion
    # INFO[0000] Deleting blob: ...
    # ...
    
    # 再次查看 blobs 目录大小,应已减小
    /var/lib/registry/docker/registry/v2 # du -sh ../../../*
    56.0K	/var/lib/registry/docker/registry/v2/blobs  # 大小显著减小
    4.0K	/var/lib/registry/docker/registry/v2/repositories
    / # exit
    

    重要: 默认情况下,Registry 容器可能没有包含 config.yml。为了能运行 GC,你可能需要在启动容器时挂载一个配置文件,或者确保 Registry 配置了 delete: enabled: true (如果通过 API 删除 manifest)。手动删除 repositories 目录后运行 GC 是回收空间的一种方式,但直接操作存储层有风险。

Registry 小结: Docker Registry 轻量易部署,适合非常基础的私有存储需求。但其功能匮乏,尤其是缺乏 UI 和便捷的管理功能(如删除、搜索、权限控制),使其难以满足团队或企业级应用的需求。


四、 实战:部署和使用 VMware Harbor (企业级推荐)

Harbor 提供了功能丰富的企业级镜像仓库解决方案,是目前私有化部署的主流选择。

1. 准备工作

  • 下载 Harbor Offline Installer: 获取离线安装包,其中包含了 Harbor 运行所需的所有 Docker 镜像和编排文件。

    # 在目标部署主机上 (例如 10.0.0.102)
    root@docker102:~# wget http://192.168.16.253/Linux92/Docker/day13-/softwares/harbor-offline-installer-v2.7.4.tgz
    
  • 解压安装包:

    root@docker102:~# tar xf harbor-offline-installer-v2.7.4.tgz -C /oldboyedu/softwares/
    
  • 创建数据存储目录: Harbor 需要持久化存储其数据(镜像、数据库、配置等)。

    root@docker102:~# mkdir -pv /oldboyedu/data/harbor
    

2. 配置 Harbor (harbor.yml)

进入解压后的 Harbor 目录,复制模板文件并进行修改。

root@docker102:~# cd /oldboyedu/softwares/harbor/
root@docker102:/oldboyedu/softwares/harbor# cp harbor.yml{.tmpl,}
root@docker102:/oldboyedu/softwares/harbor# vim harbor.yml

关键配置项修改:

  • hostname: 设置 Harbor 的访问地址(域名或 IP)。客户端将使用此地址访问 Harbor。务必确保客户端能解析此地址。

    hostname: harbor.oldboyedu.com
    
  • http / https: 配置访问协议。默认启用 HTTPS,如果暂时不配置 HTTPS 证书,需要注释掉 https 部分,启用 HTTP(默认端口 80)。生产环境强烈建议使用 HTTPS

    # 注释掉 https 相关配置
    # https:
    #   port: 443
    #   certificate: /your/certificate/path
    #   private_key: /your/private/key/path
    # http: # (可选,如果需要修改默认 80 端口)
    #   port: 80
    
  • harbor_admin_password: 设置 Harbor 初始 admin 用户的密码。务必修改为强密码。

    harbor_admin_password: YourStrongPassword123 # 示例中使用了 '1', 生产环境绝不能用弱密码
    
  • data_volume: 指定 Harbor 持久化数据的存储路径,使用之前创建的目录。

    data_volume: /oldboyedu/data/harbor
    

其他配置项可根据需要调整(如数据库、日志、扫描器等)。

3. 安装 Harbor

执行安装脚本。Harbor 使用 Docker Compose 进行编排。

root@docker102:/oldboyedu/softwares/harbor# ./install.sh --with-notary --with-trivy --with-chartmuseum
# --with-notary: 启用 Notary 服务 (镜像签名)
# --with-trivy: 启用 Trivy 漏洞扫描服务 (推荐)
# --with-chartmuseum: 启用 ChartMuseum (Helm Chart 仓库)
# 如果不需要这些可选组件,可以省略对应的 --with-* 参数

安装脚本会执行以下操作:

  • 检查 Docker 和 Docker Compose 环境。
  • 加载 Harbor 所需的 Docker 镜像到本地。
  • 根据 harbor.yml 生成 Docker Compose 配置文件 (docker-compose.yml) 和相关服务的配置文件。
  • 使用 docker-compose up -d 启动所有 Harbor 服务容器。

4. 访问 Harbor Web UI

安装成功后,通过浏览器访问配置的 hostname(例如 http://harbor.oldboyedu.comhttp://10.0.0.102,取决于你的 hostname 配置和网络解析)。

  • 用户名: admin
  • 密码: 你在 harbor.yml 中设置的 harbor_admin_password

登录后,你会看到 Harbor 的管理界面。

5. 推送镜像到 Harbor

  • 步骤 1: 配置客户端 DNS 或 hosts 解析
    确保推送镜像的客户端机器能够将 Harbor 的 hostname (harbor.oldboyedu.com) 解析到正确的 IP 地址 (10.0.0.102)。如果没有配置内部 DNS,可以在客户端机器上修改 /etc/hosts 文件:

    # 在客户端机器 (例如 10.0.0.101) 上
    root@docker101:~# echo "10.0.0.102 harbor.oldboyedu.com" >> /etc/hosts
    

    常见错误: 如果不配置解析,会遇到 dial tcp: lookup harbor.oldboyedu.com ...: no such host 错误。

  • 步骤 2: 配置 Docker 客户端信任不安全的 Harbor (如果使用 HTTP)
    如果 Harbor 配置为 HTTP 访问,需要在客户端的 /etc/docker/daemon.json 中添加 harbor.oldboyedu.cominsecure-registries 列表,并重启 Docker 服务。

    # root@docker101:~# vim /etc/docker/daemon.json
    {
      "registry-mirrors": [ ... ],
      "insecure-registries": ["10.0.0.101:5000", "harbor.oldboyedu.com"] # 添加 Harbor 地址
    }
    
    root@docker101:~# systemctl restart docker
    
  • 步骤 3: 在 Harbor UI 中创建项目 (Project)
    Harbor 中的镜像是按项目组织的。推送前需要先在 Web UI 中创建一个项目,例如 oldboyedu-games。可以设置项目为公开 (Public) 或私有 (Private)。私有项目需要用户权限才能访问。

  • 步骤 4: 标记 (Tag) 镜像
    将本地镜像标记为指向 Harbor 项目的格式:<harbor_hostname>/<project_name>/<image_name>:<tag>

    root@docker101:~# docker tag linux92-games:motuo-v0.1 harbor.oldboyedu.com/oldboyedu-games/linux92-games:motuo-v0.1
    
  • 步骤 5: 登录 Harbor
    推送私有项目或需要认证的仓库时,必须先登录。

    # 交互式登录 (推荐)
    root@docker101:~# docker login harbor.oldboyedu.com
    Username: admin
    Password: # 输入 Harbor admin 密码 (或其他有权限用户的密码)
    Login Succeeded
    
    # 或者使用用户名密码参数 (不安全,密码会出现在历史记录)
    # docker login -u admin -p YourStrongPassword123 harbor.oldboyedu.com
    
    # 或者通过 stdin 传递密码 (更安全)
    # echo "YourStrongPassword123" | docker login -u admin --password-stdin harbor.oldboyedu.com
    

    安全警告: Docker 会将认证信息(通常是 Base64 编码的 username:password)存储在 ~/.docker/config.json 中。虽然编码过,但很容易解码。建议使用 Docker Credential Helpers 来更安全地存储凭据。
    常见错误: 如果未登录或凭据错误,推送时会遇到 unauthorized: authentication required 或类似的错误。

  • 步骤 6: 推送 (Push) 镜像

    root@docker101:~# docker push harbor.oldboyedu.com/oldboyedu-games/linux92-games:motuo-v0.1
    # ... (推送过程) ...
    motuo-v0.1: digest: sha256:7d82... size: 1569
    
  • 步骤 7: 在 Harbor UI 中查看镜像
    刷新 Harbor Web UI,你应该能在 oldboyedu-games 项目下看到刚推送的 linux92-games 镜像及其 motuo-v0.1 标签。可以查看镜像详情、层信息,如果配置了扫描器,还可以触发漏洞扫描。

  • 步骤 8: 退出登录 (好习惯!)
    完成推送后,尤其是在共享机器上,记得退出登录以移除 ~/.docker/config.json 中的凭据。

    root@docker101:~# docker logout harbor.oldboyedu.com
    Removing login credentials for harbor.oldboyedu.com
    root@docker101:~# cat ~/.docker/config.json
    {
    	"auths": {} # 认证信息已移除
    }
    

6. 从 Harbor 拉取镜像

  • 确保客户端配置了正确的 DNS/hosts 解析和 insecure-registries (如果需要)。

  • 如果项目是私有的,需要先 docker login harbor.oldboyedu.com

  • 使用完整镜像名称拉取:

    # 在需要拉取的机器上 (例如 10.0.0.102)
    # (确保已配置好 hosts 和 insecure-registries)
    root@docker102:/oldboyedu/softwares/harbor# docker pull harbor.oldboyedu.com/oldboyedu-games/linux92-games:motuo-v0.1
    # ... (拉取过程) ...
    Status: Downloaded newer image for harbor.oldboyedu.com/oldboyedu-games/linux92-games:motuo-v0.1
    harbor.oldboyedu.com/oldboyedu-games/linux92-games:motuo-v0.1
    

7. 删除 Harbor 中的镜像 (简单)

Harbor 提供了便捷的方式来删除镜像:

  • 通过 Web UI: 在项目 -> 仓库 -> 镜像详情页面,可以勾选要删除的 tag,然后点击“删除”按钮。
  • 通过 API: Harbor 提供 RESTful API,可以通过 API 调用删除镜像 tag。

删除 tag 后,镜像的 manifest 会被标记为删除。Harbor 有后台的垃圾回收 (Garbage Collection) 机制(可以在管理界面配置定时任务或手动触发),它会定期清理那些不再被任何 tag 引用的 blob(镜像层),从而真正释放磁盘空间。这比 Docker Registry 的手动 GC 方便得多。

Harbor 小结: Harbor 是功能完善、界面友好、安全可控的企业级镜像仓库解决方案。虽然部署比 Registry 稍复杂,但其提供的价值(UI、RBAC、扫描、GC 等)远超其复杂性,是大多数企业私有化部署的首选。


五、 总结:Registry vs. Harbor

特性 Docker Registry VMware Harbor
核心功能 基础镜像存储 API 企业级镜像仓库 + 制品库
Web UI 无 (仅非常基础的 API 端点) ✅ 提供功能丰富的管理界面
用户/权限管理 无内置 (需外部方案) ✅ RBAC, LDAP/AD/OIDC 集成
镜像搜索/浏览 困难 (需精确知道名称) ✅ UI 内方便搜索和浏览
镜像删除/GC 复杂 (需手动删元数据 + 手动 GC) ✅ UI/API 删除 + 自动/手动 GC
安全扫描 ✅ 集成 Trivy/Clair 漏洞扫描
镜像签名 ✅ 集成 Notary
镜像复制 ✅ 支持多实例间复制
Helm Chart 不支持 ✅ 可作为 Helm Chart 仓库
部署复杂度 非常简单 中等 (依赖 Docker Compose)
资源占用 非常低 较高 (包含多个服务组件)
适用场景 个人测试、极简环境 团队、企业生产环境

最终建议: 对于任何需要协作、安全控制或易用性的场景,强烈推荐使用 Harbor。Docker Registry 只适用于对功能要求极低、追求极致轻量的个人或测试环境。

通过搭建和使用私有镜像仓库,无论是选择基础的 Registry 还是功能强大的 Harbor,都能更好地掌控你的 Docker 镜像资源,提升开发部署效率和安全性。

posted on 2025-04-11 10:06  Leo-Yide  阅读(1907)  评论(0)    收藏  举报