镜像仓库这件事,很多团队一开始都是凑合着用。开发机上随手跑一个私有Registry,能push能pull就觉得够了。等镜像多起来、人多了,权限、垃圾回收、磁盘爆掉、镜像覆盖这些问题一个接一个冒出来,才发现仓库管理不是“能存镜像”就完了。我去年帮某公司内部搭了一套Harbor v1.10.6,从环境准备到落地用了大概半天,中间踩了几个坑,也把整个思路捋清楚了。这篇文章就把这整套实操过程完整记录下来,适合正在给团队做容器化落地、被镜像管理逼到头秃的运维和开发同学参考。
1. 为什么选Harbor v1.10.6而不是直接docker registry
1.1 企业级镜像管理到底缺什么
一个裸的Docker Registry只能做到最基础的事情:你把镜像push上去,它帮你存着,你pull的时候它给你。权限?没有。界面?没有。审计日志?别想了。镜像复制到异地机房?那是另一个故事。但企业里跑容器化,这些功能每一项都是刚需。
我见过一个真实场景:某团队五六个开发直接把账号密码写在Jenkins脚本里,所有人都能push、能删tag,结果一次误操作把生产环境的镜像tag覆盖了,线上回滚的时候发现镜像没了。这种锅一旦甩出来,整个副本里没有一个岗位能接得住。Harbor解决的就是这个问题——它有完整的RBAC权限体系,能限制谁可以push、谁只读、谁只能看某个项目;有操作日志,谁在什么时间删了什么镜像都查得到;还有镜像复制能力,可以在不同机房间同步镜像,配合多集群部署非常有用。
除此之外,Harbor还带了漏洞扫描、镜像签名、垃圾回收这些能力。v1.10.6这个版本虽然是2020年的老版本,但它稳定、文档多、踩坑案例丰富,很多公司至今在生产环境跑这个版本。相比后来的版本没有那些大幅度的API变更,周边工具链兼容性反而更好。
1.2 版本选型的真实考量
说句实话,现在网上写Harbor新版本的文章一大把,但我还是选了1.10.6,原因有三。
第一是稳定压倒一切。这个版本的组件组合固定,底层依赖的是Docker Registry 2.7.1、PostgreSQL 9.6、Redis 5.0,这些组件在Docker Hub上都有官方镜像,拉取不折腾。后来的版本把PostgreSQL升级到13、14,还引入了OCI兼容层,灵活性上升的同时,对老Kubernetes集群的兼容性反而下降。
第二是资源占用。1.10.6整套跑起来大概占用1.5GB内存,2核CPU的虚拟机就能带得动。我帮某公司搭的时候用的是4核8G的机器,跑完Harbor之后还能在上面跑一个Jenkins。新版Harbor多了Trivy扫描和Nydus加速这些周组件,内存吃紧的生产机器未必有这么多余量。
第三是操作习惯。团队里如果已经有人用过旧版本,迁移成本最低。1.10.6的管理界面虽然是老版Angular风格,但项目、成员、仓库、机器人账号这些功能布局跟新版本一脉相承,用得顺手就不折腾了。
这里也留个提醒:如果你是从0开始、且Kubernetes集群版本比较新(比如1.24以上),建议直接用Harbor 2.x甚至2.8+。1.10.6的API和配置模型偏老,在新环境里做自动化集成反而要改脚本。技术选型永远是看存量、看团队、看依赖,没有一刀切的标准答案。
2. 技术架构和核心组件构成
2.1 Harbor v1.10.6的组件全家桶
安装完Harbor之后,你执行docker ps会看到这么几个核心容器在跑。我先把它们的作用捋清楚,后面排查问题的时候能迅速定位到是哪个环节出了故障。
| 容器名 | 作用 | 关键端口 |
|---|---|---|
| nginx | 反向代理层,所有的对外HTTP/HTTPS入口都在这里 | 80/443 |
| harbor-core | 主业务逻辑,处理Web UI、API、权限校验 | 8080(内部) |
| harbor-registry | 真正的镜像存储服务,兼容Docker Registry API | 5000(内部) |
| harbor-jobservice | 异步任务,负责镜像复制、GC垃圾回收、扫描等 | 8080(内部) |
| harbor-db | PostgreSQL数据库,存元数据 | 5432(内部) |
| harbor-redis | Redis缓存,存放会话和任务状态 | 6379(内部) |
| harbor-portal | Harbor Web管理界面前端 | 8080(内部) |
数据流向大概是这样的:用户通过浏览器访问HTTPS,请求先打到nginx,nginx把静态页面交给harbor-portal,把API请求转发给harbor-core。用户执行docker login或docker push时,Docker客户端同样是访问HTTPS地址,nginx识别出这是registry的请求,转发给harbor-registry容器。
值得多说一句的是,harbor-core在用户执行docker login时做了token认证,基于JWT生成一个带权限的token传给harbor-registry。这意味着registry本身并不知道用户是谁,它只认token中携带的权限范围。理解了这条链路,以后遇到“我明明登录了但push报unauthorized”这种问题,就能顺着去查权限配置。
2.2 存储层和元数据层的关键细节
镜像本身存在harbor-registry容器里,默认的存储目录是宿主机的/data/registry。这个目录下是一个标准的Docker Registry目录布局,按repository和tag组织,存的是镜像的layer层。
元数据存在PostgreSQL里,包括项目信息、成员关系、镜像的tag列表、操作日志、垃圾回收的blob记录等。Redis缓存了session、用户登录状态、job任务的临时数据。
实际上在1.10.6里,Harbor用的不是纯docker-compose独立管理数据库,而是直接在harbor-db容器里内置了PostgreSQL。数据库的数据落在宿主机的/data/database目录。所以备份Harbor,核心是备份整个/data目录,而不是只备份镜像目录。很多人在这个地方吃过亏,以为把/var/lib/docker备份了就完事,结果机器挂了之后从备份恢复,Harbor直接起不来,因为数据库是空的。
2.3 设计要点小结
做一个总结:Harbor的设计核心是“元数据与数据分离”——镜像归档是数据和业务逻辑分开的两层,数据库只管索引和权限,Registry只管存block。这种设计的妙处在于,当你需要做容灾或迁移时,只要保证/data目录的完整性和一致性,整个仓库就能无损搬迁。后面我会在备份章节详细讲。
3. 实操部署:从环境准备到docker login全通
这一节是全文的核心,我按照从头到尾的操作顺序来写。每一步命令、每一个配置文件、每一个坑点都会提到。以下实操环境是CentOS 7.9,Docker CE 19.03,Docker Compose 1.29.2,Harbor离线安装包v1.10.6。
3.1 前置环境配置
Harbor依赖Docker和Docker Compose。没装Docker的机器先装Docker,我直接贴一遍常用的安装方式:
yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y docker-ce docker-ce-cli containerd.io systemctl start docker systemctl enable dockerHarbor v1.10.6离线安装包下载下来之后,你会得到一个harbor-offline-installer-v1.10.6.tgz压缩包。解压之后目录里最重要的就是harbor.yml和install.sh。记得把解压后的目录放到一个持久化路径,例如/opt/harbor,不要放在/tmp下,避免误清理。
解压命令:
cd /opt tar xzf harbor-offline-installer-v1.10.6.tgz cd harbor然后编辑harbor.yml。1.10.6的配置模板是带注释的,你只需要关注几个关键项。
3.2 harbor.yml的核心配置项
hostname: 192.168.10.20 http: port: 80 harbor_admin_password: Harbor12345 database: password: root123 data_volume: /datahostname这里我填的是内网IP。如果你有域名且已经做好DNS解析,填域名更好,因为后面配置Docker的insecure-registry会简单一些。填IP的代价是你必须保证所有人的机器都能ping通这个IP,且后续换IP会导致所有客户端的配置跟着变。
http.port默认80。生产中如果80端口被nginx或其他Web占用,可以改到8080、5000这类端口。但改完之后所有docker login都得带端口号,使用成本上去了。
harbor_admin_password是控制台的初始管理员密码,建议第一次登录后立刻改成强密码,并且不要用默认密码跑生产环境。
database.password是Harbor内部PostgreSQL的密码。这个密码只在安装时写入配置,安装后如果要改,得操作数据库内部用户,非常麻烦。所以安装前就把它定好,别装完了再反悔。
data_volume是存放Harbor数据的主目录。建议单独挂一块数据盘,不要跟系统盘混在一起。某公司的环境就是数据盘直接挂到/data,这样后面清理、扩容都方便。
改完配置后,直接执行安装脚本:
./install.shinstall.sh会自动检测环境、启动所有容器、初始化数据库。正常情况下一两分钟就能完成,最后你会看到类似 "Harbor has been installed and started successfully" 的提示。接着用docker ps确认一下容器状态,所有的容器都应该处于Up状态。
3.3 配置Docker客户端访问Harbor
如果你用的是HTTP方式访问Harbor,Docker默认会拒绝这种不安全的仓库。需要在每台客户端机器上编辑/etc/docker/daemon.json,加一段insecure-registries:
{ "insecure-registries": ["192.168.10.20"] }改完后重启Docker:
systemctl restart docker这里有个很常见的坑:改了daemon.json之后,如果你之前拉过其他hub.docker.com的镜像,Docker重启时会重新载入配置,内存里的镜像标签会短暂“消失”,但数据还在。不用慌,等Docker启动完成后再执行docker images就能看到。
然后测试登录:
docker login 192.168.10.20 -u admin输入密码,如果一切正常会提示Login Succeeded。然后可以试着创建一个项目、push一个镜像。
docker pull nginx:1.19 docker tag nginx:1.19 192.168.10.20/library/nginx:1.19 docker push 192.168.10.20/library/nginx:1.19注意,Harbor默认自带一个library项目,属于公开项目。如果不想让所有人看到默认项目,可以在配置里修改、或者新建自己的项目并设置成私有。
3.4 开启HTTPS的方式
如果你要在公网或者跨部门使用Harbor,HTTP明文传输就会成为问题。镜像内容被拦截或篡改的话,整个软件供应链的安全性就废了。
启用HTTPS实际上只需要三步:准备好证书、改harbor.yml、重装。
第一步,如果有内网CA,就申请一个内网证书;如果没有,可以自签一个。自签证书的命令:
openssl req -newkey rsa:4096 -nodes -keyout harbor.key -x509 -days 3650 -out harbor.crt -subj "/CN=192.168.10.20"第二步,改harbor.yml。先把http段落注释掉,然后加入https段落:
https: port: 443 certificate: /opt/harbor/certs/harbor.crt private_key: /opt/harbor/certs/harbor.key第三步,因为改了配置文件,需要重新运行install.sh。注意Harbor的install.sh不是幂等的,如果你需要重装,要先执行docker-compose down,把现有容器和网络清理干净,再执行./install.sh。实测下来,直接再次运行install.sh有时会提示"Please set configuration... "之类的错误。
客户端这边,需要把自签CA证书放到/etc/docker/certs.d/192.168.10.20/目录下,Docker才会信任。这个目录结构是Docker专门为私有仓库预留的证书目录:
mkdir -p /etc/docker/certs.d/192.168.10.20 cp harbor.crt /etc/docker/certs.d/192.168.10.20/ca.crt systemctl restart dockerHTTPS配置完成后再docker login,就不会再有insecure-registry的警告了。
4. 生产环境使用:项目权限、复制与垃圾回收
4.1 项目权限模型配置要点
Harbor的项目是权限控制的最小单元。我一般建议每个业务线建一个独立项目,而不是所有人都在library里挤着。建项目很简单,在Web控制台点“新建项目”,填项目名称,选择私有即可。
权限角色这块要理解清楚。Harbor默认的角色分几档:项目管理员、开发人员、维护人员、访客。开发人员可以push镜像、pull镜像,但不能删除tag;维护人员可以删tag;项目管理员可以改项目设置。实际使用中,我给开发人员的权限只到“开发人员”,给负责发布的运维同学配“维护人员”,管理员账号收在基础设施组手里。这样即使某个开发账号泄露了,坏人至多只能往这个项目里塞东西,不能把项目清空。
一个操作上的坑点是:新建私有项目后,默认连你自己要拉镜像都得先docker login。Kubernetes节点拉私有镜像有两种方式,一种是把docker login生成的config.json做成imagePullSecret,另一种是给节点账号配置Harbor的机器人账号。机器人账号是Harbor 1.8以后引入的功能,可以针对项目生成独立的访问令牌,不给密码。我建议你在Kubernetes里用机器人账号,把令牌放到imagePullSecret里,这样即使secret泄露,也只是部分项目的拉取权限被暴露,不受影响面有限。
4.2 跨机房镜像复制
Harbor的复制功能在这套体系里很实用。假设你有两个机房,A机房是主,B机房是灾备,或者B机房需要独立部署一套服务。你不希望人肉把镜像一个个导过去,用Harbor的复制规则就能自动同步。
在Web控制台里配置复制管理,创建一个新的复制规则,填对端Harbor的地址、用户名、密码,然后选择要复制的项目或镜像名。触发方式可以设成“手动触发”或者“每次push后自动触发”。我强烈建议生产环境开“事件驱动”——每次主机房push了tag,Harbor自动推送到备份机房,这样容灾切换时镜像早就同步好了,不会出现临时抱佛脚下载镜像等到超时的问题。
复制任务跑在harbor-jobservice容器里。日志在Harbor Web的“任务”页面能看到,也可以在主机上执行docker logs harbor-jobservice来观察。我遇到过复制任务一直Pending的情况,常见原因是两个机房之间网络不通、防火墙拦了5000端口,或者对端Harbor的磁盘满了。用docker logs看jobservice日志,错误信息通常会直接告诉你卡在哪一步。
4.3 垃圾回收:你删的镜像为什么还在
Harbor的删除机制跟Docker Registry的逻辑一致:你在界面上删掉一个tag,其实只是删除了指向镜像层的引用,实际的layer数据还留在存储里。只有执行垃圾回收(GC),才会真正把没被引用的镜像层清理掉。
垃圾回收的触发方式在Web 控制台的“系统管理-垃圾回收”里,可以立即执行,也可以开启定时任务。我建议每周跑一次,时间放在凌晨流量低峰期。GC执行时registry会短暂进入只读状态,正在pull镜像的用户可能请求失败。所以操作前要评估一下当前是否有人正在拉取大镜像。
有个小坑要提醒:v1.10.6的GC任务偶尔会卡住,原因是数据库里有些blob引用未清理干净。遇到这种情况,重启harbor-registry容器往往能解决,但重启之前确认没有重要任务正在跑。
5. 日常运维和常见故障排查实录
5.1 Harbor容器起不来、一直Restarting
第一优先要查的是磁盘空间。Harbor一整套跑起来,日志、数据库、registry数据加起来很占空间。如果/data满了,PostgreSQL会直接拒绝写入,core容器也会因为健康检查失败反复重启。
快速定位命令:
df -h /data docker ps -a | grep harbor docker logs harbor-core如果日志显示像 "no space left on device",那就别犹豫了,先清理磁盘。注意不能乱删/var/lib/docker下的文件,要用docker system prune清理无用容器、镜像缓存,或者加快GC频率。
另一种常见原因是内存不足。Harbor整套容器加起来正常占用1.5G内存,如果你的机器只有2G内存,很容易出现OOM。查看系统日志:
dmesg | grep -i oom如果是OOM,就升级机器配置或者减少同时运行的插件组件。
5.2 docker login成功,但push报unauthorized
这个问题通常在配置了HTTPS并重启Docker后出现。Docker缓存了旧凭证,但服务端重启后token失效,重新登录一次就能解决。如果重新登录还报错,去Harbor Web里检查一下账号角色是否为访客。
更隐蔽的情况是:Kubernetes节点使用imagePullSecret时,secret里的.dockerconfigjson没有更新,里面的令牌过期了。更新secret的办法是重新执行docker login后,把~/.docker/config.json里面的内容重新base64编码填进secret里,或者直接用kubectl create secret重新创建。
5.3 Web界面能打开但镜像列表显示为空
镜像列表是数据库驱动+API查询的。如果你确认镜像已经push成功,但界面上看不到,大概率是浏览器缓存。强制刷新或换个无痕窗口试一下,如果还是空的,那就要怀疑数据库表被意外清掉了。所以我再次强调,数据备份的重点是整个/data目录,不是单靠拉镜像文件。
5.4 GC后磁盘容量不变
这是Harbor的经典坑。GC跑完后你去执行df -h,发现磁盘可用空间没变大。原因是Docker存储驱动的特性,删除的layer文件虽然从逻辑上被释放,但底层文件系统可能还没有回收,需要执行:
systemctl restart docker或者
docker run --rm -v /var/lib/docker:/var/lib/docker alpine:latest sh -c "rm -rf /var/lib/docker/tmp/*"严格来说,如果用的是overlay2驱动,镜像层被删除后空间不归还到宿主机是正常现象。需要确保垃圾回收的时机是低峰期,并定期重启一次Docker来回收空间。
5.5 备份恢复的最省心方案
Harbor没有内置一键备份恢复工具,但在v1.10.6时代,最稳妥的备份方案就是直接备份宿主机上的/data目录。操作前先停止所有容器,保证数据一致:
docker-compose down tar czf harbor-backup-$(date +%F).tar.gz /data docker-compose up -d恢复时,在准备好了相同环境的前提下,把备份文件解压到/data目录,再启动所有容器即可。注意机器IP要跟备份时一致,否则Harbor配置文件里的hostname会导致UI访问异常。
这个方案是某个生产环境实际使用过的,从备份到恢复完整验证过两次,没有一次出问题。比用什么数据库逻辑备份都省心。
6. 实操后的几点感受
Harbor这套系统整体的学习曲线很平滑,一两个下午就能从装好到用顺手。我最有成就感的瞬间,是看到所有开发统一了“先打tag再push、只访问一个仓库地址、权限清清楚楚”的流程之后,再也没有出现过“这镜像到底是哪个版本拍的”这种争论。容器镜像管理是软件供应链上最不起眼但最容易出乱子的环节,把Harbor搭好、规则定清楚之后,团队协作的顺畅度是肉眼可见的。
最后留一个建议:不要装完就扔在那里。第一周每天看一眼GC跑没跑、磁盘用了多少、复制任务是否正常;第二周折腾一遍备份恢复演练;第三周再考虑接入Kubernetes。这样一套下来,Harbor就从“装了个能用的镜像仓库”变成了“自己心里有底的基础设施”。