一套业务系统跑了一两年,环境中几十上百个容器,镜像分散在各台机器甚至每个人电脑里,等到要统一发版、回滚版本的时候,才发现连一个准确的镜像清单都凑不出来。这种场景我想很多团队都经历过。所以当我的某位合作伙伴第一次提出“需要搭建一套私有镜像仓库”的时候,我第一个想到的方案就是 Harbor,而不是简单用 Registry 跑一个裸服务。这篇内容就围绕 Harbor 展开,从它能解决的问题、核心架构,到如何一步步部署落地,再讲一讲日常运维中我踩过的坑和排查思路,希望给正在考虑自建镜像仓库的团队一个可复用的参考。
Harbor 本质上是一个企业级的 Docker 镜像仓库解决方案,它把常用的镜像存储、权限控制、项目管理、复制同步、漏洞扫描等能力整合到了一个平台上。相比直接用 Docker Registry 镜像,Harbor 多了一层 Web 管理界面和更细粒度的权限模型,适合多团队、多项目的组织使用。那么它到底解决了什么痛点?我下面从实际使用场景慢慢拆。
1. 为什么需要私有镜像仓库
1.1 公共镜像仓库的痛点
很多人刚接触 Docker 时,都是从公共仓库拉镜像,比如 Docker Hub。开发阶段这么干没毛病,但到了生产环境,问题就暴露了。首先是网络稳定性。公共仓库的服务节点在国外,拉取大镜像经常慢得让人怀疑人生。也许你会说“我配置了镜像加速器”,但加速器也有不灵的时候,而且加速器本质还是把镜像缓存在云端,企业内部网络到公网出口一旦抖动,发布流程就卡住了。
其次是安全与合规风险。你没法保证公共仓库里某个镜像的每一层内容都符合公司安全要求。尽管有扫描工具,但公共仓库的扫描大多是“事后”的,拉下来的镜像如果有漏洞,已经进入运行时环境了。对于企业来说,镜像供应链的安全是必须做的一道闸门。私有仓库可以让你在镜像入库时就用漏洞扫描插件过一遍,甚至阻断有高危漏洞的镜像被拉取。
再就是多版本管理与分发控制。公共仓库里镜像的命名空间是全局的,组织内部想要管理项目维度、团队成员维度、权限维度,几乎是做不到的。比如 A 团队发布的镜像,B 团队能不能拉取?开发环境允许推送,生产环境是否只允许特定账号拉取?这些需求裸的 Registry 很难优雅实现,Harbor 则天生设计好了这一套规则。
1.2 Harbor 与其他私有仓库方案对比
市面上私有镜像仓库的选择不少,轻量级的是官方 Registry,复杂一点的还有云厂商的镜像服务,比如某些云平台自带的容器镜像服务。Harbor 是开源项目,可以部署在自己的服务器上,这点对很多私有化部署的项目来说很关键。
我把常见选择做了个简单梳理:
| 方案 | 优点 | 不足 |
|---|---|---|
| 官方 Registry | 轻量,部署极简 | 无 Web UI,无权限模型,适合单机测试 |
| Harbor | 功能完整,界面友好,项目级权限,复制和扫描 | 组件较多,部署和维护成本略高 |
| 公有云镜像服务 | 免运维,开通即用 | 依赖特定云厂商,内网环境受限 |
从我的实践经验看,如果团队刚起步、只有三四个人,用官方 Registry 跑起来确实省事。但一旦到了需要区分环境、划分项目、按角色控制权限的阶段,迁移成本会越来越大,不如直接上 Harbor。它基于 Docker Registry 内核,但把权限、审计、复制这些企业能力都补齐了,相当于一辆改装好的整车,而不是给你一堆零件自己组装。
2. Harbor 的核心概念与架构
2.1 Harbor 由哪些组件组成
Harbor 不是一个单一进程,而是一组服务的组合。最初部署的时候我盯着 docker-compose.yaml 看了一会儿,里面有好几个服务,比如 nginx、core、portal、registry、registryctl、redis、postgresql、jobservice 等。了解各个组件的分工,对后面排查问题特别重要。
- nginx 是统一入口。所有外部访问都先经过它,它负责 TLS 终止、路由转发。Harbor 的 HTTPS 配置本质就是给 nginx 配上证书。
- core 是核心 API 服务。登录、鉴权、项目管理、用户管理、镜像复制触发等业务逻辑都在这里。
- portal 是前端页面。就是你在浏览器里操作的那个图形界面。
- registry 是真正存储镜像的服务。Harbor 复用的是 Docker 官方的 Registry 实现,它处理镜像的分层上传下载。
- jobservice 是后台任务服务。镜像复制、垃圾回收、漏洞扫描这些异步任务都靠它调度。
- redis 承担缓存和队列。某些同步任务的状态、临时数据会放在里面。
- postgresql 保存用户、项目、权限、审计日志这些元数据。
理清楚这些组件,你就知道出了问题该去哪个日志里翻。比如镜像推不上去,先看 registry 的日志;界面登录失败,核心在 core;复制任务没执行,去看 jobservice。
2.2 项目、用户与权限模型
Harbor 把权限管理的粒度放在了“项目”这一层。每个项目可以理解为一个独立的命名空间,对应一个团队、一个应用或一条产品线。项目内部的镜像路径通常是项目名/镜像名:标签。
权限角色分为几种:项目管理员、开发者、访客。开发者可以推送和拉取镜像,访客只能拉取,项目管理员还能管理成员和配置。在 Harbor 1.8 之后的版本里,还引入了机器人账号机制,这个很实用。比如 CI/CD 流水线需要推送镜像,没必要创建一个真实用户,而是可以创建一个只具有指定项目推送权限的机器人账号,密钥是加密令牌。这样即使密钥在配置里泄露,也不会影响全局账号体系,回收也方便。
权限校验的规则要补充一点:同一用户可以加入多个项目,在不同项目里角色可以不同。这个设计很灵活,比如一个测试同学在 A 项目是访客,在 B 项目是项目管理员。权限判断发生在 core 服务,实际推送镜像时,registry 也会通过 token 服务做二次校验,防止绕过 API 直接操作。
2.3 复制与多机房同步设计
Harbor 的镜像复制功能,我理解是它最值钱的企业能力之一。你可以配置一个复制规则,把某个项目下的镜像自动同步到另一个 Harbor 实例,或者同步到另一个命名空间。复制的模式支持基于推送和基于拉取。基于推送更常见,主节点上有新镜像入库,jobservice 会触发任务,把镜像推到备节点。
这个机制解决了两类典型问题。一是容灾。主节点挂掉或网络波动时,备节点还能提供拉取服务,业务发布不至于中断。二是就近分发。如果多个机房需要相同镜像,可以通过复制规则推送到各机房的 Harbor,各机房客户端从本地仓库拉取,速度更快。实际配置时要注意目录结构和凭证问题。跨 Harbor 复制需要填写目标仓库地址、访问凭证,而且项目名如果不存在,要勾选自动创建目标项目。复制任务失败后,可以去任务详情页看错误信息,大多是凭证失效或者网络不通。
3. 部署前的规划与准备
3.1 硬件与版本选择
Harbor 的部署方式官方推荐用 Docker Compose,也有 Helm Chart 方式用于 Kubernetes。如果只是内部测试,2 核 4GB 内存的机器就够了。但如果是正式环境,我建议至少 4 核 8GB,磁盘采用 SSD。镜像仓库是 IO 密集应用,磁盘速度直接决定推送和拉取的体验。
版本选择上,Harbor 目前仍在迭代,新版本会加入更多功能,但未必稳定。我个人习惯选择最新发布周期中已经发布了一个多月的小版本,而不是追着 RC 版本跑。另一个需要注意的点是 Harbor 的版本和 Docker 版本兼容性。官方文档会写明支持的最低 Docker 版本,低于对应版本时,镜像推送可能会因为 API 不兼容而报错。部署之前先把docker version看一下。
3.2 HTTPS 证书与端口规划
Harbor 强烈建议使用 HTTPS。原因不只是安全,还有一个很实际的问题:Docker 客户端默认只信任自己的证书仓库中的 CA,如果没有配置 insecure-registries,默认要求仓库地址是 HTTPS,否则docker login会被拒绝。虽然有办法把仓库地址加入/etc/docker/daemon.json的insecure-registries来做豁免,但在浏览器端仍然会有证书警告,而且这种方式在生产环境有中间人攻击风险,我并不推荐。
最稳妥的方式是给 Harbor 配置一个正式签发的证书,可以是公共 CA 签发的域名证书,也可以是公司内部 CA 签发的证书。如果是内部 CA,需要在所有 Docker 客户端的系统信任区里导入 CA 根证书,客户端才能信任该 HTTPS 端口。端口规划上,默认 Harbor 的入口端口是 80 和 443。如果主机上 443 被其他服务占用,可以在 harbor.yml 里修改端口,比如改成 8443。改完之后需要注意,Docker 客户端在docker login时如果指定了非默认端口,地址必须写全,比如docker login harbor.example.com:8443。
3.3 存储与数据库选型
Harbor 的数据分为元数据和镜像数据。元数据存在 PostgreSQL 里,镜像数据存在文件系统或对象存储中。默认安装时,镜像数据落在 Harbor 数据目录下的registry目录中。为了后期备份和扩容方便,我建议把数据目录挂载到独立的数据盘上,而不是放在系统盘。
遇上镜像量大的场景,可以考虑把存储后端换成对象存储,比如 MinIO 或者云上的对象存储服务。Harbor 支持配置多种存储驱动,具体在 harbor.yml 的 storage 字段中调整。这个能力在集群化部署时很重要,因为直接跑 Docker Compose 的 Harbor 是单节点的,如果节点挂了,镜像数据如果没有冗余,就存在丢失风险。通过对象存储做后端,相当于给镜像数据做了一层持久化,后面即便重启容器,数据也不会丢。数据库方面,单机版默认使用内置 PostgreSQL,数据卷会持久化。但更稳妥的方式是外接一个独立数据库实例,并开启定期备份,这样即使整个 Harbor 节点损坏,也能通过重新部署并指向原有数据库来恢复大部分状态。
4. 一步步部署 Harbor
4.1 下载与配置 harbor.yml
部署 Harbor 最常用的是离线安装包。这个包体积比较大,因为它包含了所有组件的镜像。到 GitHub 的 Releases 页面下载和版本对应的离线包,然后解压到目标目录。解压后你会看到 install.sh、docker-compose.yml、harbor.yml.tmpl 这些文件。首先把harbor.yml.tmpl复制一份成harbor.yml,因为安装脚本默认读取的是后者。
harbor.yml 是最核心的配置文件。至少要改这几项:
- hostname:对外提供服务的地址,必须和证书里的域名一致。
- 证书相关配置:
certificate和private_key指向证书文件路径。 - harbor_admin_password:管理员初始密码。默认是 Harbor12345,建议改成强密码。这个密码只在首次初始化时生效。
- data_volume:数据目录,比如
/data/harbor。
还有一些进阶配置可以根据实际需要调整,比如database、redis、max_job_workers等。第一次部署不建议过多修改,保持默认即可。修改完配置,可以直接执行./install.sh。脚本会先检查 Docker 和 Docker Compose 环境,然后自动生成一些编排配置并启动所有服务。启动的过程输出会比较多,可以等到最终出现Harbor has been installed and started successfully这样的提示。
4.2 启动后验证组件状态
安装完成后的第一件事是用docker ps查看所有容器状态。正常情况下应该有一组容器在运行,名字都以harbor-开头。如果某个容器反复重启或处于异常状态,可以用docker logs查看日志。这里贴一个基础的验证命令:
docker ps --filter "name=harbor" --format "table {{.Names}}\t{{.Status}}"同时还要确认主机端口是否监听。如果你配置的是 443,就执行ss -lntp | grep 443。随后在浏览器输入https://<hostname>,应该能打开 Harbor 登录页面。用 admin 账号和初始密码登录,进入界面后会看到系统管理的各项入口。
这里有一个容易出错的点:安装脚本会读取 harbor.yml 并生成 docker-compose.yml,但如果我把 harbor.yml 里的 hostname 写成了 IP,而实际上前端通过域名访问,浏览器会报证书不匹配。所以如果证书是给域名签的,hostname 一定要写域名,不要偷懒写 IP。
4.3 配置 Docker 客户端访问
部署好了 Harbor,下一步就是让客户端能登录。如果用了内部 CA 证书,先把 CA 证书加入客户端系统信任区。以 Ubuntu 为例:
sudo cp ca.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates然后重启 Docker 服务,使证书加载生效。这时执行:
docker login harbor.example.com输入用户名和密码,如果返回Login Succeeded,说明访问链路已经打通。如果走的是自签名证书且暂时不想配置 CA,可以在/etc/docker/daemon.json中加:
{ "insecure-registries": ["harbor.example.com"] }然后重启 Docker。这个配置等于跳过了证书验证,只适合内网测试环境。我反复提醒,生产环境不要这么干,哪怕是内网,也要用 CA 签发的证书走 HTTPS,否则镜像在传输过程中存在被篡改的风险,而这恰恰是依赖镜像仓库的公司最不应该妥协的部分。
4.4 创建项目并推送镜像
登录 Harbor 前端界面,在“项目”菜单下点击“新建项目”。填写项目名称,比如demo-app。“访问级别”可以选择“私有”或“公开”。私有项目只有被授权用户才能访问;公开项目所有人都可以拉取。镜像推送之前,先要标记本地镜像,让它和 Harbor 的项目名匹配:
docker tag myapp:v1.0 harbor.example.com/demo-app/myapp:v1.0 docker push harbor.example.com/demo-app/myapp:v1.0推送完成后,前端界面上就能看到这个镜像。之后其他机器拉取时执行:
docker pull harbor.example.com/demo-app/myapp:v1.0这里要注意,镜像标签的“项目名”部分是路径中的第一段,不能随便写。如果项目不存在,push 会直接报错。项目创建时如果设置了“公开”,拉取时不需要登录,但推送仍然需要权限。
5. 日常运维与常见问题排查
5.1 镜像清理与垃圾回收
镜像仓库跑久了,最占空间的其实是各种过期的 tag 和不再被引用的层。删除一个 tag 时,Harbor 并不会立即释放磁盘空间,它只是删除了引用关系,镜像层还躺在文件系统里。只有执行垃圾回收,才能真正把无引用的镜像层清除。
在 Harbor 界面里,垃圾回收功能在系统管理的“垃圾回收”页面,可以手动执行,也可以设置定时策略。配置定时策略时,我一般选择每天凌晨执行一次,避开业务高峰期。执行之后,可以到日志里看回收了多少层,确认效果。如果发现磁盘空间还是很大,常见原因是有很多 tag 指向同一个镜像,删除单个 tag 后镜像仍被其他 tag 引用,垃圾回收不会清理它。这种情况下需要把整个镜像的全部 tag 删除后再回收。
5.2 常见故障排查实录
我遇到过几个典型问题,这里整理成一张速查表,大家可以先收藏。
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| docker login 提示证书错误 | 证书不受客户端信任 | 检查 CA 证书导入,检查 hostname 与证书域名 |
| push 镜像报 405 或 denied | 项目不存在或权限不足 | 确认项目名,确认账号角色为开发者 |
| 推送大镜像超时 | nginx 的 client_max_body_size 过小 | 修改 harbor 相关 nginx 配置并重启 |
| Harbor 页面可以访问,但 docker login 失败 | 客户端网络无法解析域名或访问非默认端口 | 检查 hosts 映射,检查端口是否开放 |
| 复制任务失败 | 目标仓库凭证错误 | 到 jobservice 日志中查看具体错误码 |
| 垃圾回收后空间未释放 | 镜像仍有其他 tag 引用 | 删除全部 tag 后重新执行回收 |
有一次我排查了一个很隐蔽的问题:某个项目下所有用户都拉取一个镜像,但有一个客户端反复报摘要不一致。后来发现是在复制规则里,目标仓库的镜像被覆盖更新,但某些客户端本地缓存了旧的 tag 索引。解决方法是让客户端先删除本地镜像再拉取,同时调整复制策略,避免同一 tag 被频繁覆盖。生产环境我更推荐固定不可变 tag,比如加上 commit 号,而不是一直用latest。
5.3 备份与恢复方案
Harbor 的数据备份,我最推荐的方式是直接备份数据目录。默认数据目录下有 database、registry、secret 等子目录。其中 database 是 PostgreSQL 的数据文件,registry 是镜像的分层数据。如果整个数据目录都备份了,恢复时直接在新节点上安装相同版本 Harbor,然后把数据目录替换过去,重新启动即可。
这个操作有一个重要前提:Harbor 的版本要一致,否则数据库迁移可能会有兼容性问题。除了文件备份,还可以用 PostgreSQL 的pg_dump单独备份元数据。但要注意,镜像层的文件是海量小文件,逐级备份速度会比较慢。建议结合 rsync 做增量同步。升级 Harbor 之前,一定先做一次全量备份,这是我在多次升级后总结出来的铁律。有一次我跳版本升级,因为跳过了中间版本导致数据库初始化报错,最后靠备份回滚才避免了长时间不可用。
5.4 基于策略的镜像保留
镜像仓库的容量管理,光靠人工删除 tag 是远远不够的。Harbor 提供了镜像保留策略,可以给项目设置规则,比如“保留最近 10 个版本”“保留最近 30 天内推入的镜像”。一旦规则生效,jobservice 会定期扫描并自动清理不符合规则的镜像。这比手动删除要稳妥得多,尤其当团队发布频繁、每天都会产生大量 tag 的时候。
配置保留策略时,需要注意规则是基于“被保留的镜像”,而不是“被删除的镜像”。也就是说,你要反过来思考:什么条件下镜像应该保留。这个逻辑关系和某些人的直觉相反,我之前就在这里绕了一下。另外,保留策略执行后,也会产生垃圾数据,仍然需要配合垃圾回收把磁盘空间真正释放。所以运维上的标准动作是:定时保留清理 -> 定时垃圾回收 -> 查看回收报告。
6. 我的实践心得与建议
我从最初的裸 Registry 用起,到后来部署 Harbor,中间有一段并行过渡期。当时最明显的感受是,Harbor 带来的不只是功能上的丰富,更是流程上的规范。项目成员不再需要手动翻找镜像地址,因为港口页面里每个项目的镜像列表一目了然。权限收紧以后,再无某个前辈跟我说“你可以直接推到那个公共仓库”这样的操作了,所有变更都有了记录。
关于部署形态,我最后再给一点建议。如果公司已经上了 Kubernetes,Harbor 可以以 Helm Chart 方式部署进去,和集群的鉴权、存储、Ingress 做整合,灵活度更高。如果只是传统虚拟机环境,用 Docker Compose 方式最直接,维护成本也不高。但无论哪种方式,都要把证书、备份、访问控制这三件事放在最前面,不要等出了问题再补救。
另外,团队使用 Harbor 时,最好约定一套明确的镜像命名规范。比如项目名/应用名:环境-版本号,同时禁止随意使用latest标签。这个小习惯能避免大量混乱。我在实际项目中就因为这个约定受益很大,回滚时只要找到对应的版本号就能准确拉起历史镜像。可以说,Harbor 不是终点,而是整个镜像管理流程的基础设施,把它用好了,后续的持续交付会顺畅很多。