去年团队从三台测试机扩到十几个节点的时候,镜像分发彻底乱套了。有人把镜像导出成 tar 包靠网盘转,有人直接把镜像 tag 改成别的环境的名字再推,直到有人误覆盖了一个生产镜像,我才下定决心把私有镜像仓库这件事正经做完。当时对比了一圈方案,最后选了 Harbor,一直用到现在。这篇就把我从零搭建 Harbor 的完整过程、选型依据、配置细节和后来踩过的坑一次性写清楚。
Harbor 是什么,一句话能说清楚:它是一个基于 Docker Registry 的企业级镜像仓库服务,核心解决的不是"存镜像",而是镜像的权限管理、安全扫描、复制同步、审计日志这些团队协作里真正麻烦的事情。如果你只是自己在笔记本上跑两个容器,那 Docker Hub 或者裸跑一个 Registry 都够用;但只要是多人协作、多环境部署、有私有化交付需求,直接上 Harbor 基本是唯一少走弯路的选择。
1. 为什么我不建议裸跑 Registry:几个真实痛点的对比
很多人刚开始想省事,直接docker run -d -p 5000:5000 registry就把仓库起了。我自己在正式搭 Harbor 之前也这么干过,那段时间的痛苦体验让我现在还会劝别人:如果团队超过三个人用同一个仓库,裸 Registry 就是给自己埋雷。
1.1 最要命的权限问题
裸 Registry 没有用户概念,谁能 push 全靠 Docker daemon 的访问控制,要么全开要么全关。我当时的做法是映射端口后放在内网,说服自己"内网是安全的",结果某次一台测试机被扫到 5000 端口,被人塞了一堆奇怪的镜像,还删掉了好几个正在用的 tag。从那次之后我就明白,仓库这种基础设施,访问控制必须做到项目级别。
Harbor 在这块是天然的优势:用户体系独立于操作系统,有管理员、开发、访客多种角色,每个项目可以单独设置公开或私有,还能精确控制某个人对某个项目的 push / pull 权限。你不需要教团队什么复杂的 RBAC 概念,在 Web 界面里点几下就能完成授权。
1.2 镜像多了之后,Registry 的"无法搜索"很难受
裸 Registry 提供了 HTTP API,但默认没有像样的人机交互界面。你要查某个服务的镜像有哪些 tag,得curl调 API 或者干脆docker pull试错。Harbor 自带一个挺完整的 Web UI,项目列表、镜像列表、tag 详情、镜像大小、push 历史一应俱全。排查问题的时候,比如"这个镜像到底是不是最新的",登录 Web 界面一眼就能看到,省太多事。
1.3 还有个容易被忽略的:垃圾回收
镜像多了之后裸 Registry 那套registry:2会有一堆未标记的层占磁盘。裸 Registry 虽然支持/v2/_cancatalog那套 API,但没有专门的 GC 机制。Harbor 在 Registry 之上做了一个中文界面的 GC 功能,可以标记要清理的仓库、模拟运行查看要释放的空间,再真正执行。这个我后面专门讲,是生产环境很重要的一个按钮。
可以看这么一张对比表,方便你决定自己到底要不要上 Harbor:
| 能力项 | 裸 Registry | Harbor |
|---|---|---|
| 镜像存储 | 支持 | 支持 |
| 用户与权限 | 不支持 | 项目级 RBAC |
| 图形界面 | 无 | 完整 Web UI |
| 镜像复制 | 需另配工具 | 内置复制规则 |
| 垃圾回收 | 需手工 API | 可视化 GC |
| 审计日志 | 无 | 有操作记录 |
| 安全扫描 | 无 | 支持漏洞扫描 |
我见过太多的团队一开始图省事用裸 Registry,最后因为权限和排查问题耗时过长,又痛苦地迁到 Harbor。迁移本身不麻烦,麻烦的是过程中容易丢 tag。所以如果你还在选型阶段,听我一句劝:直接上 Harbor,省下一个月后的迁移成本。
2. 部署前准备:版本选型、资源规划与域名证书
Harbor 部署方式有好几种,最推荐的是离线安装包,因为在线安装依赖网络情况,而且离线包把需要的镜像全部打包好了,安装过程中几乎不用外网。你只需要准备一台装了 Docker 的服务器,再按下面的规划把目录和证书想清楚就行。
2.1 版本怎么选
Harbor 的版本节奏挺规律的,推荐选偶数版本或者是稳定发布一段时间的版本。以 Harbor 2.x 为例,2.6、2.8、2.9 这些长期稳定版本都没有硬伤,新特性也不会在短时间内大改。不要追新版本,尤其是刚发布的 .0 版本,我见过有人在 2.0 刚出的时候直接上,结果 Helm Chart 卷版本不兼容,折腾了一整天。
选版本的时候还要注意配套的 Docker 和 Docker Compose 版本。Harbor 的 install.sh 脚本默认调用 docker-compose 命令,新版本已经逐步迁移到docker compose插件式命令,但生产环境建议用 docker-compose 2.x 独立版,兼容性最稳。
2.2 服务器资源的最低下限
Harbor 本身不算重,但别把它想得太轻。部署后它要同时跑 Postgres、Redis、Registry、Core 服务、Portal 服务、JobService,还有可选的 Trivy 漏洞扫描组件,整套起来内存占用在 2GB 左右,如果开了扫描还要更多。我的建议是:
- 最低配置:2 核 CPU,4GB 内存,100GB 可用磁盘
- 推荐配置:4 核 CPU,8GB 内存,磁盘按镜像增长速度预留,200GB 起
磁盘那块要格外注意,镜像仓库比代码仓库更吃空间,因为一个镜像动不动就是几百 MB 到几个 GB,而且每层都是持久化的。我当时顺手用df -h检查时发现数据目录所在的盘又快满了,这才意识到要提前做空间规划。
2.3 域名和 HTTPS 证书:这一步千万别跳
很多人第一次搭 Harbor 直接 IP 访问,还用 HTTP,图省事。但 Docker 客户端默认对非 localhost 的 HTTP 仓库极其不友好,你需要给每个 Docker 节点配置 insecure-registries 才能绕过去。在测试环境可以接受,生产环境我强烈不建议这么做,因为镜像传输过程中是明文,任何能抓包的人都能拿到你所有的镜像内容,相当于把核心资产裸奔在内网。
正确做法是准备一个域名,比如harbor.internal.example.com,然后给这个域名签证书。分两种情况:
- 内网环境,自己用 OpenSSL 签自签证书,然后把 CA 证书分发到所有需要访问的 Docker 节点;
- 有公网域名,直接上 Let's Encrypt,申请和续期都简单。
自签证书这条我下面会给出具体命令。提前说一句:ip直连方式虽然能在 harbor.yml 里配置不启用 HTTPS,但团队大了之后谁也说不清哪个节点少了 insecure-registries 配置,为了不给自己添麻烦,一开始就把证书搞定。
在这里顺便澄清一个误区:Harbor 页面打开如果不安全,很多人的第一反应是"算了不管了",但这会直接影响 Docker 客户端登录。你通过在节点上装 CA 证书,能同时解决 Web 端和 Docker 客户端的信任问题,一举两得。
3. 一步一步搭起来:docker-compose 部署 Harbor 全流程
部署过程本身不复杂,核心是把harbor.yml改对,然后跑一遍install.sh。但有几个细节容易在改了配置之后翻车,我把完整流程和踩坑点拆开讲。
3.1 下载与解压
先到 Harbor 官方的 GitHub Release 页面下载离线安装包,注意文件名里有offline字样,一般长这样:
$ wget https://github.com/goharbor/harbor/releases/download/v2.9.0/harbor-offline-installer-v2.9.0.tgz $ tar -zxvf harbor-offline-installer-v2.9.0.tgz $ cd harbor解压后目录里最重要的就是harbor.yml.tmpl模板文件和一个install.sh脚本。先把模板文件复制成harbor.yml:
$ cp harbor.yml.tmpl harbor.yml这里有个新手容易犯的错:直接改模板文件,等到后续升级或者重新安装时发现模板被污染了,又得重新下载。养成复制一份改的习惯,后面维护干净很多。
3.2 harbor.yml 的关键配置项
用编辑器打开harbor.yml,重点关注这几块:
hostname: harbor.internal.example.com http: port: 80 https: port: 443 certificate: /data/certs/harbor.internal.example.com.crt private_key: /data/certs/harbor.internal.example.com.key harbor_admin_password: YourStrongPassword database: password: root123 data_volume: /data/harbor逐项解释一下:
hostname:必须是你规划的域名,不能写 IP(除非你真的想只用 IP 访问,但我不建议);http.port:默认 80,如果服务器上已经占用 80 端口,改成 8080 之类的同时要保证后续访问方式顺手;https.certificate和private_key:指向证书文件路径,提前把证书放到指定位置;harbor_admin_password:这是初始管理员 admin 的密码,如果留空或太简单,安装完成之后登录 Web UI 时还要回头改,麻烦;database.password:Harbor 内置 Postgres 的 root 密码,最好也改成强密码;data_volume:所有镜像和数据库数据的存放目录,一定要放到大分区。
我见过有人把data_volume放在根目录/data/harbor,结果根分区才 50GB,镜像一多整个服务器直接报警。这个路径务必规划好,最好用单独的挂载盘,比如/mnt/harbor-data。
3.3 生成自签证书
如果走 HTTPS 且没有机构签发的证书,用 OpenSSL 生成自签证书。注意必须包含subjectAltName,否则现在新版 Docker 和浏览器都会报域名不匹配。
$ mkdir -p /data/certs && cd /data/certs $ openssl req -newkey rsa:4096 -nodes -sha256 -keyout harbor.internal.example.com.key \ -x509 -days 3650 -out harbor.internal.example.com.crt \ -subj "/CN=harbor.internal.example.com" \ -addext "subjectAltName = DNS:harbor.internal.example.com,IP:192.168.1.100"证书有效期我给了 3650 天,内网自签证书这个时长合理,省得频繁换。然后还需要一个 CA 证书文件ca.crt,其实就是把刚生成的 crt 复制一份改名即可:
$ cp harbor.internal.example.com.crt ca.crt这个ca.crt后面要分发给所有 Docker 节点。
3.4 执行安装
配置没问题就可以执行安装了:
$ sudo ./install.shinstall.sh 会自动检测 docker 和 docker-compose 版本、加载离线镜像、生成 docker-compose.yml、启动所有容器。整个过程大概几分钟,日志里看到Harbor has been installed and started successfully就完成了。
验证是否正常,用:
$ docker ps | grep harbor $ curl -I https://harbor.internal.example.com前者能看到十几个相关容器在跑,后者返回 HTTP 200 就说明页面已经起来了。
这里说一个我首次安装时遇到的情况:服务器上本来装了别的服务占了 443 端口,结果安装完成之后 Harbor 页面一直打不开,排查了半天我才意识到是端口冲突。所以端口占用这一类基础检查真的要在安装前做掉,别嫌麻烦。
3.5 让 Docker 客户端信任仓库
这一步目标很简单:让这台机器和其他所有要拉取镜像的机器都能docker login成功。
内网自签证书模式下,推荐把ca.crt复制到/etc/docker/certs.d/harbor.internal.example.com/目录,并改名为ca.crt。Docker 会依据这个目录里的证书自动信任对应域名的仓库。
$ mkdir -p /etc/docker/certs.d/harbor.internal.example.com $ cp /data/certs/ca.crt /etc/docker/certs.d/harbor.internal.example.com/ca.crt $ systemctl restart docker之后用管理员账号测试登录:
$ docker login harbor.internal.example.com Username: admin Password: Login Succeeded登录成功后就可以打 tag 推送了:
$ docker tag nginx:latest harbor.internal.example.com/library/nginx:v1 $ docker push harbor.internal.example.com/library/nginx:v1看到 push 完成,Harbor 的 Web 界面里刷新项目library,镜像就会出现。完成了这一步,整个基础链路算是通了。
4. 安全加固与日常使用配置:认证、项目权限、Robots 账户
基础通了之后大部分人就停下来了,但生产环境不是能访问就算完。这一节讲我在实际使用中觉得最值得做的几项加固,尤其适合多团队共用一个仓库的场景。
4.1 项目权限模型:别什么镜像都往 library 塞
Harbor 默认有一个叫library的公开项目,一开始所有人都可以往里面推。如果你们是多个小组共用,很快就乱成一锅粥:服务 A 的镜像和小组的镜像混在一起,tag 还可能被覆盖。
强烈建议每个业务线建一个独立项目,并且设置私有模式。比如:
| 项目名 | 模式 | 说明 |
|---|---|---|
| base-images | 私有 | 公共基础镜像,仅 Ops 可写 |
| service-a | 私有 | A 服务镜像 |
| service-b | 私有 | B 服务镜像 |
| public-cache | 公开 | 只读公开镜像,便于各节点拉取 |
在 Web 界面创建项目之后,在"成员"标签页里给对应的人分配角色。角色分为项目管理员、开发人员、访客等,开发人员有 push 权限,访客只能 pull。这样哪怕某个人误操作,影响面也能限制在一个项目内部。
4.2 Robots 账户:给 CI/CD 机器人用的专用凭证
Harbor 2.x 引入了一个特别贴心的功能,叫 Robots 账户。它在用户体系之外单独创建,专门给 CI/CD 流水线使用。这样你不需要在 Jenkins 或者 GitLab CI 里存管理员密码,也不怕某个人的账号离职之后导致流水线不可用。
创建路径:在 Web UI 里"系统管理 -> Robot Accounts",新建账号时填名字、过期时间,并勾选权限。我通常给每个项目建一个只具备该项目 push 权限的最小化机器人账户,GitLab CI 里用它登录再推送镜像,安全性和可追溯性都很好。
# GitLab CI 部分示例 push: stage: deploy script: - docker login harbor.internal.example.com -u robot$service-a -p xxx - docker push harbor.internal.example.com/service-a:latest设置成项目级别的最小权限后,即使机器人账号泄露,攻击者最多祸害一个项目,比裸管理员的杀伤力小得多。
4.3 强制 HTTPS 与禁用 HTTP 明文流量
在harbor.yml里同时配置了 HTTP 80 和 HTTPS 443 时,Harbor 允许两种协议并存。生产环境建议你在系统配置里把"仅允许 HTTPS"打开,具体在 Web 界面系统设置里勾选。这样即使有人想用 http:// 访问,也会被重定向或拒绝,少一个明文传输的隐患。
另外,如果你用的网络环境里有多层代理,要注意 Harbor 处理的是来自客户端直连的流量,如果你在前面挂了 Nginx,要记得把X-Forwarded-Proto头正确传给 Harbor,否则强制 HTTPS 策略会让部分客户端请求莫名卡住。
4.4 审计日志与操作追踪
Harbor 默认开启了日志功能,在系统管理 -> 日志里可以查看用户登录记录和操作。我做过的几次重大误删事故复盘,都是靠这个日志找到是哪个账号在什么时间删了什么 tag。定期把日志导出归档一次,成本极低,但关键时刻能救命。
5. 镜像复制和垃圾回收:生产环境真正离不开的两个功能
镜像复制和 GC 是 Harbor 相对裸 Registry 最明显的生产力提升。很多人搭好 Harbor 之后只用了 push/pull,等于买了个旗舰手机只用了打电话功能,太可惜了。
5.1 跨机房镜像复制规则怎么设计
镜像复制解决的是多机房或容灾环境下的镜像分发问题。最典型的需求是:总部一套 Harbor,分机房一套 Harbor,主环境推送的镜像要自动同步到分机房。
具体做法是在分机房的 Harbor 里配置复制规则,选择基于"拉取"的模式。源就是总部的 Harbor 地址,目标选择本地仓库,触发器设为"事件驱动",即总部出现新镜像时自动拉取过来。
规则配置项参考: - 名称: sync-from-main-site - 复制模式: 拉取 (Pull) - 源仓库: https://harbor-master.internal.example.com - 过滤器: 项目 service-a, 名称匹配 /.*/ - 触发模式: 事件驱动注意几个易错点:
- 拉取模式与推送模式方向要搞清楚。分机房拉总部是拉取模式;总部主动往分机房推是推送模式。
- 跨机房网络带宽不够时,复制会很慢,建议按项目拆成多条规则,而不是一条规则全量同步。
- 复制规则执行失败后 Harbor 会自动重试,但要注意看 JobService 日志,如果远程仓库凭证过期,会一直失败。
我实际遇到过复制规则全配好了,但总部远程仓库的密码过期,导致新镜像同步不过去,而分机房的镜像还是旧的,部署到那边直接出问题。后来养成了习惯:每次改了远程仓库密码,第一件事就是去复制规则页面手动执行一次,确认同步成功。
5.2 垃圾回收的正确打开方式
镜像每推一次,Registry 会保留旧层的引用,但当某个 tag 被删除或覆盖后,对应的一些层就会变成"孤儿层",不占引用但占磁盘。所谓垃圾回收就是把这些孤儿层删掉。
Harbor 的 GC 功能在"系统管理 -> 垃圾回收",操作前有个非常关键的建议:先在配置里勾选"跳过当前正在被拉取的镜像",避免 GC 过程中删到正在使用的层。真正执行的时候建议在业务低峰期进行,因为 GC 过程中 Registry 的读写会受影响。
我个人的执行节奏: 1. 先勾选 "在进行垃圾回收前停止仓库服务" 2. 在 GC 页面点击 "立即执行垃圾回收" 3. 执行完成后,检查磁盘释放情况 4. 全团队发通知:接下来推送新镜像需要重试一两次至于 GC 周期,一个月跑一次就够,除非你们团队删镜像特别频繁。我见过有人天天跑 GC,结果某天有容器还在拉镜像就触发 GC,导致部分镜像层短暂不可用,虽然影响不持久,但也惊出冷汗。
5.3 镜像保留策略:防止磁盘被暴涨的 tag 填满
生产环境经常有人给每次构建都打latest,一段时间后同一个项目下 tag 数几百个,磁盘暴涨。Harbor 支持"保留规则",通过正则筛选镜像和时间,自动清理超期 tag。
比如可以给service-a项目建一条规则:保留最近 7 天以内、或者标签匹配release-*的镜像,其余清理。这样既保持历史可回溯,又不会无限膨胀。
6. 运维排坑实录:这些年我踩过的 Harbor 坑
说实话,Harbor 稳定性在同类开源项目里算很能打的,但长期跑下来总会遇到一些零碎问题。我把印象最深的几个记录下来,帮你提前避雷。
6.1 磁盘占满后整个 Harbor 直接挂掉
这是我第一次被 Harbor"吓到"的故障:数据盘满了之后,Registry 的写入直接失败,但 Harbor 的 Core 容器还活着,只有docker push一直报错。我以为是 Harbor 的配置坏了,排查半天才发现是磁盘 100% 占用。
排查链路:
- 先
df -h,确认数据盘是否写满 - 如果写满,找出大文件目录,常用
du -sh /data/harbor/* | sort -rh - 优先清理 /data/harbor 下的临时文件,再考虑 GC
- 清理出来空间后,重启 harbor 容器组
把磁盘监控加好,尤其是数据盘,比任何配置优化都重要。
6.2 自签证书更新后,客户端还是报 x509 错误
换过证书之后,明明所有客户端ca.crt都更新了,还是会报x509: certificate signed by unknown authority。排查到最后发现,有一台机器上的 Docker 没重启,daemon 还在用旧证书缓存。
正确做法是更新 ca.crt 后一定执行systemctl restart docker,并且把 Docker 客户端缓存也清掉:
$ docker logout harbor.internal.example.com $ systemctl restart docker $ docker login harbor.internal.example.com这个顺序问题很多人会漏掉logout这一步,导致新的登录请求被 daemon 缓存住了。
6.3 Harbor 升级时不要直接覆盖数据目录
Harbor 升级基本靠着重新下载安装包、修改harbor.yml、跑./install.sh,听起来简单,但有个致命细节:新版本的数据库结构可能变化,如果直接覆盖旧数据目录很可能导致 migration 失败。
常规升级路径是:
- 备份整个
/data/harbor目录和数据库 - 下载新版本离线包
- 用旧的
harbor.yml作为基础,对比新版本的配置项做调整 - 执行
./install.sh,Harbor 会自动跑 migration 脚本
如果 migration 失败,建议先看/var/log/harbor的日志,大部分情况是权限问题或数据库存储空间不足。注意升级前一定要备份,我第一次升级 2.0.0 的时候没备份,migration 过程中磁盘不够直接卡住,最后回滚差点丢数据,吓出一身汗。
6.4 慢的不是 Harbor,是你的网络或 DNS
有时候docker pull有时从 Harbor 拉镜像特别慢,第一反应都是"仓库出问题了"。实际上很多情况是 Docker 客户端的 DNS 解析太慢,尤其是用了内网域名但 /etc/resolv.conf 配置了公网 DNS,域名解析绕了一大圈。
解决方式是把仓库域名直接写进/etc/hosts:
192.168.1.100 harbor.internal.example.com如果用了 Nginx 反代,还要注意client_max_body_size设置。镜像一个 layer 可能是几百 MB,Nginx 默认 1MB 限制会直接把 push 截断。我第一次给 Harbor 套 Nginx 反代时就碰到这个问题,在配置里加上:
client_max_body_size 0;意思是限制大小不设上限。
6.5 定时备份:Harbor 的配置与数据库比想象中更重要
Harbor 绝大多数配置都存在数据库里,项目、用户权限、复制规则、保留规则全在 Postgres 中。如果服务器宕机或数据盘损坏,恢复成本不低。我的建议是定期备份两个东西:data_volume下的database目录,以及harbor.yml。
可以用简单的 cron 脚本定期打包/data/harbor/database,或者更推荐用 Harbor 自带的 API 配置备份功能。不过我自己更习惯对 data_volume 做基于文件的 rsync 到另一台存储机器,配合数据库逻辑备份,恢复起来最快。
# 简单的每日备份示例 tar -czf /backup/harbor-db-$(date +%F).tar.gz /data/harbor/database根据我个人的经验,Harbor 配置和数据备份策略值得在搭建完成后立刻写进运维手册,别等真出了事故再补。
最后再分享一个自己在日常运维中的小习惯:每次改完harbor.yml或者换证书,我都习惯先执行一次docker-compose down -v再重新install.sh。这里注意,-v会删掉容器但不会删数据卷对应的宿主机目录,只要data_volume里的数据还在,Harbor 重启后所有项目和镜像都还在。当然,这个操作依然要谨慎,尤其是生产环境,一定要先确认data_volume路径没有配错。
Harbor 这套东西本质上不复杂,但作为团队镜像流转的核心,每一步配置都值得认真对待。这个项目后续还可以继续扩展,比如接入 OIDC 统一登录、配置镜像签名、对接外部对象存储来扩大容量。先把基础打好,后面的路自然顺。