去年我把旧服务器淘汰的时候,第一反应就是把上面的Harbor私有仓库完整备份下来。那台机器上堆着我这三年攒的各种容器镜像,有编译缓存用的小基础镜像、不同版本的中间件、还有一堆标签写着"测试勿删"的自制包。不是说我有多爱折腾,而是很多镜像的构建环境和原始上下文早都丢了,重新拉一遍根本不可能复现原状。也是从那时候起,我认真意识到一件事:个人开发者同样值得拥有一个专属的Harbor私有仓库,它解决的问题不是"有没有",而是"稳不稳、快不快、安不安全"。
这篇就把我从零搭建Harbor私有镜像仓库的整套流程记录下来,从为什么选Harbor、内部组件怎么运作,到证书生成、配置踩坑、日常备份清理,基本都覆盖到了。如果你也打算给自己或者小团队搭一个专用的容器镜像仓库,照着这条路走,能省不少弯路。
1. 为什么个人也要搭一套Harbor私有仓库
1.1 公共镜像仓库的几个痛点,以及私有仓库能带来什么
我自己最开始也用公共仓库存镜像,用了一阵子之后发现几个实际槽点:一是公共仓库默认对公网开放,稍微涉及一点业务逻辑的镜像往上一推,等于把自己的项目结构暴露了;虽说可以用私有命名空间,但免费额度有限,镜像多了就得考虑付费。二是拉取速度受限于网络状况,很多时候一个几百兆的基础镜像要等半天,遇到网络波动更是让人头疼。三是公共仓库只解决存储和分发,权限、审计、清理这些运维能力都非常弱,想做个标签保留策略都得靠脚本自己写。
换成Harbor私有仓库就不一样了。首先镜像只存你自己服务器上,访问链路完全可控。其次是Harbor自带的Web管理界面很好用,项目、用户、角色、配额、清理策略都是点几下就能配置好的事情。再一个就是Harbor对Docker和OCI分发规范兼容得非常好,日常使用和公共仓库差别不大,几乎不需要改变已有工作流。个人场景下,一台2核4G的小服务器,配合一块够用的磁盘,Harbor就能跑得挺稳,日常使用完全足够。
1.2 同为私有仓库方案,Harbor和Registry、Nexus怎么选
我刚开始搭私有仓库时也纠结过,到底是用最基础的Docker Registry,还是上功能全套的Harbor,或者干脆用Nexus一锅端。这三者的定位差别其实非常明显。
Docker Registry是Docker官方提供的最小实现,就一个纯镜像存储和拉取服务,没有Web界面、没有权限体系、没有垃圾回收界面。适合的场景是"我只要一个能push能pull的端点就行"。问题在于一旦镜像多了,想查某个Tag存在哪、想清理几个不要的仓库,就会非常痛苦。
Nexus是通用制品仓库,Maven、npm、NuGet、PyPI什么都能存,自然也能存Docker镜像。如果你们团队是多语言、多制品的重度用户,用Nexus一个平台统一管理确实很香。但Nexus的Docker镜像功能在界面体验和镜像治理能力上,比Harbor还是弱一些,而且配置更重,个人用起来有点杀鸡用牛刀的感觉。
我的选择是Harbor。原因很直接:Harbor本来就是为了镜像仓库这个场景做的,项目隔离、机器人账号、镜像签名、漏洞扫描、复制同步、保留策略这些能力开箱即用。个人部署时我不用为用不上的通用制品功能买单,运维只需要维护一套Docker Compose栈,省心很多。
2. 部署思路梳理:先把Harbor的底子摸清楚
2.1 Harbor前后端组件在跑些什么,为什么安装包是那个样子
Harbor不是一个大单体程序,而是一组容器服务的组合。我第一次打开docker-compose.yml的时候,看到一大堆服务瞬间有点懵,但稍微捋一下就会发现它内部其实分得很清楚。
最外层是Nginx反向代理层,负责统一入口、TLS终止和请求路由。浏览器访问的Web界面走的Portal服务,是一个前后端分离的UI项目。真正存镜像的是底层的Registry服务,也就是Docker Registry的核心部分。Harbor自己还有一个核心API服务叫Core,负责登录鉴权、令牌签发、项目管理等业务逻辑。JobService负责执行异步任务,比如镜像复制、回收历史数据、垃圾回收这些脏活累活。如果开启漏洞扫描,还会跑一个Trivy组件去拉取漏洞库,对仓库里的镜像做CVE检测。
看到这里你大概明白了,Harbor是"用容器编排的方式来管理容器镜像仓库"。所以它的安装包里面自带了一套完整的编排模板,安装脚本的工作本质上是:检查环境、读取你配置的harbor.yml、生成对应的docker-compose.yml,然后用Docker Compose把这些服务拉起来。理解了这个逻辑,后面部署和排错就都有了方向。
2.2 个人部署的硬件要求和基础环境准备
Harbor运行起来之后主要吃两块资源:内存和磁盘。内存方面,Core、Registry、JobService再加上Nginx和PostgreSQL,整套组件空闲状态下大约占用1.5GB到2GB内存。如果开了漏洞扫描还要再多算。个人推荐至少2核4G的配置,云服务器3.6G内存其实也能压着跑,但Swap得预留出来。磁盘才是真正的大头,镜像仓库是存储密集型应用,一块多个Tag的镜像动辄几个GB很常见。我个人的建议是至少预留50GB,如果你是重度使用者,100GB起步更安心。磁盘尽量用SSD,镜像推拉都涉及大量随机读写,机械盘在高并发时会有明显延迟。
基础环境方面,系统推荐Ubuntu 20.04以上或者Debian 11以上,CentOS 7.9也能跑,但Docker版本要先提上去。需要提前装好Docker Engine和Docker Compose插件。Harbor新版本已经默认使用docker compose命令,老的docker-compose独立版本虽然兼容,但建议直接用插件版本。装Docker就按官方源装最新稳定版,没有特殊理由别用系统自带的旧版本,否则后面安装脚本的版本检查很容易卡住。
3. 手把手部署:从证书到安装脚本跑通
3.1 安装包选型:在线安装包和离线安装包怎么取舍
Harbor的每个发布版本都提供了在线安装包和离线安装包,文件名称区别主要在于harbor-online-installer和harbor-offline-installer。第一次部署的时候,我图省事下了在线包,结果安装脚本需要从网上拉取Harbor的镜像,网络一波动就报错,反复重试了三次才成功。后来我学乖了,直接改用离线包。
离线包体积通常会超过1.5GB,但它把Harbor的全部容器镜像一起打包了,安装过程中不需要额外从外部拉镜像,速度稳定,失败率低。个人部署强推离线包,下载一次,存服务器上,以后在新机器上装也不用愁网络。下载之后记得校验一下sha256,官方页面会提供对应的校验值,防止下载损坏。
3.2 自签名证书一步步生成,以及为什么推荐HTTPS
Harbor默认配置里同时预留了HTTP和HTTPS两套入口。个人在局域网或者云服务器上使用,我推荐一上来就配HTTPS,别用HTTP裸奔。原因不只是安全,还因为Docker客户端对非HTTPS仓库默认不信任,你要是不配HTTPS,就得在每台客户端上设置insecure-registries,有得麻烦。反倒是自签名证书,配合证书分发,一套流程下来一次搞定。
我自己是在服务器上建了一个cert目录,然后用OpenSSL生成CA根证书,再用这个CA去签发Harbor的服务器证书。注意这里的域名,后面会被写进harbor.yml的hostname,也会被写进Docker客户端的访问地址,所以提前规划好,比如我就用了一个私有的内网域名,而不是裸IP。
mkdir -p certs && cd certs # 1. 生成CA私钥 openssl genrsa -out ca.key 4096 # 2. 生成CA根证书,有效期10年 openssl req -x509 -new -nodes -sha256 -days 3650 \ -key ca.key -out ca.crt \ -subj "/CN=Harbor Private CA" # 3. 生成Harbor服务器私钥和证书签名请求 openssl req -newkey rsa:4096 -nodes \ -keyout server.key -out server.csr \ -subj "/CN=harbor.local" # 4. 生成ext.cnf,把域名写入SAN cat > ext.cnf <<EOF subjectAltName = DNS:harbor.local EOF # 5. 用CA签发服务器证书 openssl x509 -req -in server.csr \ -CA ca.crt -CAkey ca.key -CAcreateserial \ -out server.crt -days 3650 \ -extfile ext.cnf执行完会生成ca.crt、ca.key、server.crt、server.key四个文件。server.crt和server.key要交给Harbor使用,ca.crt要分发到各个需要登录这个仓库的机器上。证书有效期为10年,个人使用基本不用中途换。
3.3 核心配置文件harbor.yml逐项解析
Harbor的配置集中在harbor.yml里,默认带一份harbor.yml.tmpl模板。把模板复制一份再改。最核心的几项用我实际部署时的配置来说。
hostname: harbor.local http: port: 80 https: port: 443 certificate: /data/certs/server.crt private_key: /data/certs/server.key harbor_admin_password: Change_This_Password_After_Login database: password: root123 data_volume: /data/harbor trivy: ignore_unfixed: false skip_update: false offline_scan: false jobservice: max_job_workers: 10hostname这一行是访问入口,必须和证书SAN里的域名一致,不能乱写。如果你走HTTP模式,hostname也可以是IP,但如果你是走HTTPS,域名和证书必须严格匹配,否则Docker登录会报x509证书无效。
端口方面,默认80和443很容易和已有服务冲突。我之前一台机器上已经跑着Nginx了,Harbor安装时绑定443直接失败,后来把harbor.yml里的https端口改成8443才解决。harbor_admin_password是admin账号的初始密码,装完登录后第一件事就要改掉。database.password是内部PostgreSQL的密码,如果机器上还要跑别的应用,记好这个密码,将来备份和迁移都会用到。data_volume是Harbor所有持久化数据的主目录,包括数据库文件、镜像存储、证书、日志,全部汇总在这个目录下,后续备份只需要打包这个目录即可。
3.4 执行安装脚本与验证部署结果
配置改好之后,运行安装脚本就进入正题了。离线包解压之后,目录里有一个install.sh,执行时加个--with-trivy参数把漏洞扫描组件带上,个人部署多花点资源换安全能力还是值得的。
sudo ./install.sh --with-trivy安装脚本会先做环境检查,确认Docker和Docker Compose都能正常调用,然后读取harbor.yml,生成docker-compose.yml,最后把Harbor相关容器逐一启动。启动完成后脚本末尾会输出一个显示Harbor已安装的提示。别急着高兴,先用docker compose ps确认所有服务状态是healthy,再看一下Nginx容器有没有监听正确端口。
我的习惯是安装完成后立刻执行两组验证:一组是本机访问,浏览器打开https://harbor.local,能看到登录页就算成功;另一组是客户端信任验证,在一台已经配置好证书的客户端机器上执行docker login,登录成功后拉取一个测试镜像,跑通整个链路。这两步过了,部署才算真正完成。
4. 日常使用:登录、推拉镜像与权限设计
4.1 让Docker客户端信任Harbor证书的两种方式
Harbor装好之后不会自动被Docker客户端信任。如果你选的是HTTPS方式,需要让客户端信任Harbor的CA根证书。最简单的做法,把之前生成的ca.crt拷贝到客户机的/etc/docker/certs.d/harbor.local/目录下,命名为ca.crt,然后重启Docker,它就会对这个私有仓库按正常CA逻辑走。
sudo mkdir -p /etc/docker/certs.d/harbor.local sudo cp ca.crt /etc/docker/certs.d/harbor.local/ sudo systemctl restart docker另一种方式是在客户端的/etc/docker/daemon.json里配置insecure-registries,把harbor.local加进去。这种方式虽然更省事,但等于放弃了TLS校验,局域网个人使用可以接受,如果环境更复杂一点我不推荐这么图省事。证书方式一遍配好之后永久有效,后续不需要再操心。
4.2 项目、用户与角色的权限矩阵直接照抄
Harbor的权限模型核心是"项目-用户-角色"三个概念。每个项目内部可以设置仓库的可见性和权限,用户可以被分配到不同项目并拥有不同角色。个人使用不需要造太复杂,我实际用的是下面这套最小方案。
首先,默认的library项目保留,当作公共示例区域放一些无敏感信息的基础镜像。然后创建一个自定义项目,比如叫apps,这个项目设为私有,存放所有业务相关镜像。在系统管理的用户管理中,建两个用户,一个给自己的日常账号配"项目管理员",另一个给自动化流程用机器人账号。
角色有这么几个等级,从高到低依次是:项目管理员、开发者、访客。项目管理员能管项目配置、成员和镜像;开发者能推送镜像和读取镜像;访客只能拉取和查看。给不同来源的镜像分属不同项目,然后按实际协作需求分配角色,这样权限边界就清晰了。个人使用,一个小团队最多五个项目,权限管理也不会花太多时间。
4.3 一次完整的推送和拉取实操记录
部署完接着就是日常最频繁的操作:登录、打标签、推送、拉取。这一套流程我每次都会给新人演示,因为步骤看起来简单,但标签格式出错是最常见的坑。镜像路径必须严格写成"Harbor地址/项目名/镜像名:标签"这个格式。
# 登录Harbor docker login harbor.local # 给本地已有镜像重新打标签 docker tag myapp:1.0.0 harbor.local/apps/myapp:1.0.0 # 推送到Harbor docker push harbor.local/apps/myapp:1.0.0 # 在另一台机器上拉取 docker pull harbor.local/apps/myapp:1.0.0第一次推送的时候,如果遇到权限报错,先确认一下当前登录用户对目标项目是否有"开发者"以上角色,再检查项目名称是否拼写正确。如果报证书错误,回到证书信任那个环节排查。这套链路跑通过一次,之后就是日常复制粘贴的事情。我实际操作中还挺喜欢先在本地跑一个临时容器验证消化,确认没问题再push,免得把坏镜像丢到仓库里占地方。
5. 个人级运维清单:备份、清理和监控
5.1 备份方案不复杂,关键是动手做
个人私仓最容易忽略的就是备份。Harbor部署好以后,data_volume目录下面就是整个世界。配置、数据库、镜像存储全在里面。最省心的备份方案是先把Harbor停掉,再把/data/harbor整个目录打包,然后启动Harbor。这样在备份过程中不会出现数据库和镜像文件不一致的情况。
cd /data && docker compose -f /opt/harbor/docker-compose.yml down sudo tar -czvf harbor-backup-$(date +%Y%m%d).tar.gz /data/harbor cd /opt/harbor && docker compose up -d个人使用频率不高,一周打包一次就够。备份文件建议另存一份到独立磁盘或者对象存储,不要和Harbor在同一个目录里。恢复步骤也很简单,把tar包解压回原有data_volume路径,然后重新执行install.sh,或者直接docker compose up -d。实测下来,只要数据目录完整,恢复基本是无感的。
5.2 垃圾回收与存储空间处理
镜像越堆越多之后,磁盘会悄悄膨胀。镜像仓库的存储模型是这样的:推送新镜像会复用相同的镜像层,旧镜像虽然被新Tag覆盖了,但底层数据不会自动删除。必须手动触发垃圾回收,Harbor才能真正把废弃的镜像层从磁盘上清掉。
Harbor的Web界面里,在"系统管理-垃圾回收"页面,可以设置定时策略,建议每周跑一次。也可以直接在服务器上用docker compose执行清理任务。实际操作中我会关注两个数据:一个是磁盘分区剩余空间,另一个是仓库里的Tag数量。垃圾回收跑完后镜像存储目录会明显变小,但要注意回收期间Registry不接受推送操作,所以别在白天的业务高峰期跑回收。
如果想让磁盘长期更健康,更主动的方法是给项目配置保留策略。Harbor支持基于保留多少个最新镜像、保留最近多少天镜像等规则,设置好之后策略会定时清理旧镜像,等于把仓库存储空间当成了有上限的队列来管理。个人最常用的策略是每个项目保留最近10个Tag,比手动清理省事得多。
5.3 排查日志的关键入口
Harbor容器化部署之后,日志也全部落在容器里,查看日志本身就是排错的第一步。我一般分三层看:先用docker compose -f docker-compose.yml logs --tail=100,当前看哪几个容器在报错;再单独看core日志,这里记录了登录、令牌签发、项目操作的完整记录;最后是Registry日志,看镜像上传下载的详细记录,特别是返回4xx、5xx错误时,问题大概率出在这一层。
docker compose -f /opt/harbor/docker-compose.yml logs --tail=200 -f docker compose -f /opt/harbor/docker-compose.yml logs --tail=200 core常见的情况是,Web能登录但docker push报401,这个时候基本可以判断是令牌或者证书链路出问题,看core日志最直接。如果是upload超时,看nginx层日志能看出是流量走了哪个入口。Harbor的日志比较冗长,实在看不明白,就先看时间戳附近的ERROR级别内容,再对照Harbor文档,别从头到尾硬刚。
6. 高频故障速查与经验沉淀
6.1 个人部署最容易遇到的几个问题
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 浏览器打开提示证书无效 | 证书SAN里没有填访问域名 | 检查harbor.yml的hostname和证书SAN一致 |
| docker login报x509错误 | 客户端没有信任自签名CA | 把ca.crt放到docker客户端certs.d目录 |
| docker push报401 | 用户对项目角色不足 | 在项目成员里给用户加"开发者"角色 |
| 上传大镜像时长时间卡住 | 网络传输或磁盘写满 | 检查磁盘空间,用curl -v观察耗时卡点 |
| 安装脚本报docker compose命令不存在 | Docker Compose插件未安装 | 安装docker-compose-plugin,或者用docker-compose独立版本 |
| 安装完成后80端口占用 | 机器上已有Nginx等服务 | 修改harbor.yml里的http或者https端口 |
这些问题其实都不是Harbor本身多复杂,而是容器化部署环境差异带来的。我在部署过程中,遇到过至少三次因为端口冲突导致安装失败的情况,后来学乖了,装之前先执行ss -lntp查一下80和443端口有没有被占用,有就提前改配置。
6.2 磁盘满故障的一次真实排查过程
有一段时间我Harbor仓库推镜像老失败,提示信息很模糊,只写了"写入失败"。我先排查了网络,正常;再看Nginx日志,只显示转发到Registry服务后超时。最后用df -h检查了数据盘,才发现磁盘使用率已经到95%。清理了一波废弃镜像,触发垃圾回收之后,推送立刻恢复正常。
从那以后我养成了一个习惯:在服务器上写了一个简单的磁盘监控脚本,超过85%就往自己的消息通道发提醒。个人私仓用不到多复杂的监控系统,一条脚本足够。镜像仓库增长快是常态,真正危险的不是镜像数量多,而是管理者根本没意识到空间快满了。
6.3 迁移服务器时的两个关键细节
如果你和我一样,后面要把Harbor从旧机器迁到新机器,有两个细节必须注意。第一个,harbor.yml里的hostname和端口必须保持完全一致,否则迁移后客户端还要重新适配地址。第二个,数据库密码不要改,Harbor内部存储的加密数据与数据库密码有关联,乱改会导致服务起不来或者数据读不出来。
迁移操作其实就是一个打包、拷贝、解压、启动的过程,和备份恢复几乎一样。唯一需要注意的是新版Harbor升级场景,不建议直接用旧数据卷跑新版安装包。跨大版本升级之前先看官方升级文档,我一般会先把旧版本的数据目录完整备份,然后新版本重新安装,再把项目手动建一遍,镜像直接推一遍。虽然多花了点时间,但至少不会遇到数据库版本不兼容这种尴尬。
最后的个人体会
Harbor这个仓库我前后部署过不止一次,从最初在旧笔记本上折腾,到现在稳稳跑在独立服务器上,最大的感触是:搭建本身并不难,真正决定体验的是部署时的规划够不够细。证书选什么方式、数据卷放哪个盘、端口会不会冲突、备份怎么定策略,这些在harbor.yml里提前花十分钟想清楚,后续能省下不知道多少小时的排错时间。如果你也是个人开发者,想给镜像一个专属的"家",照着这条路走一遍,应该不会踩太多坑。等你用顺手之后,再回来看公共仓库,八成会觉得还是自建的香。