在内网环境里搭一套企业级镜像仓库,Harbor基本是绕不开的那个名字。我最近给一台离线部署机做环境交付,目标就是用docker-compose部署最新版Harbor v2.3.4,全程不碰图形安装向导,尽量把每一步的输入输出都吃透,出问题能在十分钟内定位原因。这篇文章不是官方文档的复读,而是把harbor.yml配置、prepare渲染、docker-compose启动、验证排错这一整条链路从头到尾过一遍。适合准备部署Harbor,或者已经部署了但遇到一些诡异问题的运维和开发同学。
很多人习惯拿到安装包直接跑install.sh,觉得快。但当你需要批量交付环境、需要把部署步骤固化成文档、需要定制某些服务参数时,那个一键脚本反而成了最大的黑盒。所以这次我选择把install.sh拆开:手动执行prepare,再用docker-compose up -d拉起全部服务。这样部署动作可复用,故障边界也更清晰。下面直接进入正题。
1. 为什么不用install.sh,也要懂docker-compose部署
1.1 install.sh帮你做了什么,又没帮你做什么
Harbor官方离线安装包解压之后,目录里最重要的就是install.sh和prepare脚本。install.sh的执行逻辑很简单,检查docker和docker-compose版本,检测80/443端口占用,然后调用prepare去渲染配置,最后执行docker-compose up -d启动。听起来是标准一条龙,但问题是脚本把每个阶段的日志混在一起输出,一旦harbor.yml里某个字段配错,它抛出的错误往往很笼统,比如"please check your input",没有任何上下文。我第一次部署时,就被这种提示带着绕了半小时,明明只是hostname写成了localhost。
后来我手动把install.sh拆开跑,才发现prepare阶段输出的日志非常具体,哪个配置项非法、哪个证书文件路径不存在,都会直接打印出来。所以我很建议所有人,哪怕你最后还是会用install.sh,也先用手动方式跑一遍prepare,熟悉一下它生成了哪些文件,这对后续排错有决定性帮助。
1.2 手动执行prepare+docker-compose up,到底强在哪
手动执行部署,流程就两行命令:
./prepare docker-compose up -d但这两行背后的可控性远高于install.sh。一方面,prepare生成的docker-compose.yml和common目录下所有nginx、core、registry的配置文件都是明文的,你可以看到Harbor的真实组件结构,而不是只知道"装好了";另一方面,当你需要调整某个组件时,可以直接改配置后重新prepare,不必再从头跑安装器。我在生产中比较看重的一点是:手动部署下,配置生成与容器启动是分离的。这意味着我可以先prepare,把生成的配置拿去走一遍测试,没问题再进行启动;如果是install.sh,它会一口气执行到底,中途没有暂停的机会。对要交付的客户环境或项目环境来说,这种"先渲染、后启动"的模式,部署节奏完全可控,出问题也好回滚。
2. 部署前的最后检查:环境、安装包、harbor.yml里的三处硬配置
2.1 Docker版本、Compose插件和磁盘内存的底线
Harbor v2.3.4对Docker版本的最低要求是17.06.0-ce,这是它文档里写的底线。但实际部署中我建议直接用20.10及以上的稳定版,原因很简单,老版本Docker在网络和存储驱动上的行为差异比较大,一旦碰到问题很难判断是Harbor配置问题还是Docker本身的问题。Compose方面,官方要求1.18.0以上,我沿用了一个经过验证的组合:Docker 20.10.7配Docker Compose v1.29.2。如果你用的是新版Docker自带的compose插件,v2.3.4也可以跑,但最好先做一次测试验证,不同版本对depends_on和健康检查的处理时机略有差别。
资源上,Harbor这套容器全部起来后,内存占用大约2GB左右,我建议部署机至少4GB内存,把系统本身的开销留出来。磁盘方面,data_volume规划100GB起步,但更关键的是日志目录。Harbor默认把日志写到/var/log/harbor,由log容器统一收集,如果/var/log所在分区很小,日志增长会悄悄占满根分区。我一般会提前在/var下建立一个独立的harbor日志分区,或者直接把/var/log/harbor软链到数据盘。这个细节很多人忽略,等磁盘告警才回头排查,很被动。
2.2 离线包还是在线包,证书方案怎么选
安装包分online和offline两种。online包体积小,但执行安装过程会实时从镜像仓库拉取全部镜像,外网环境倒是没关系,内网环境几乎不可用。offline包把v2.3.4各个组件镜像都打包成tar文件,解压后通过docker load导入本地镜像库,然后再用docker-compose启动。我这台机器虽然是内网,但之前测试时发现online包在镜像拉取阶段会反复超时,果断换成offline包。唯一的缺点是包体将近1.6GB,但相比部署时的稳定,这点体积完全可以接受。
证书方案是另一个容易被忽略的点。如果走HTTPS,harbor.yml里需要填两个路径:certificate对应证书文件,private_key对应私钥。注意必须是完整证书链,也就是包含中间CA的那份文件,否则nginx容器启动时报证书解析失败,最典型的现象是所有容器都起来了,但443端口没监听。如果公司有正规CA签发的证书最好,没有的话,我建议先跑HTTP模式,配合客户端侧insecure-registries配置,把业务跑通再考虑HTTPS。不要让证书问题卡住整个项目的交付节奏,这是实战里最常见的阻塞点。
2.3 三处最容易写错的配置项
打开harbor.yml模板,几十行配置里真正部署阶段必须改的就是hostname、harbor_admin_password、data_volume。hostname是最容易出错的,写成localhost或127.0.0.1会导致本机可以访问UI,但其他机器docker login时解析不了。正确做法是写部署机内网IP,或者规划好的内网域名,同时保证客户端能解析这个域名。注意hostname决定的是镜像地址的前缀,一旦配错,后面所有docker tag和push都要跟着错,返工成本很高。
harbor_admin_password默认是Harbor12345,不修改等于把管理员账号挂在明处。这个值至少8位且包含大小写字母和数字,如果只写简单密码,prepare阶段会直接用exit code告诉你配置不合法。data_volume建议放在独立数据盘,比如/data/harbor,目录属主最好配成非root可写,因为Harbor容器默认以非root用户运行,权限过严会导致数据库初始化和registry启动失败。我遇到过有人把data_volume放到用户家目录,结果目录权限是755且属主为root,容器一直在启动和崩溃之间循环,排障排了半天才发现是权限问题。
3. prepare之后发生了什么:读懂生成的docker-compose.yml
3.1 prepare不是简单的配置校验
官方文档把prepare称为"准备环境配置",听起来轻描淡写,实际上它承担的是一套模板渲染工作。脚本会读取harbor.yml,检查必填项,然后基于内置模板生成common目录下的nginx配置、core环境变量、数据库初始化参数,最后在根目录生成docker-compose.yml。多做几次部署就会发现,它本质上和web框架里的模板渲染是一回事。
正因为prepare是渲染过程,所以有一个重要的隐含约束:它每次生成的配置都是覆盖式的。如果你手动改过生成的docker-compose.yml,或者改过common目录下的nginx.conf,再次执行prepare后这些改动会被直接覆盖。这就意味着任何定制需求都要改模板或改harbor.yml,不要直接改生成物。很多人在compose文件里手动加了自定义环境变量,升级或重新部署后发现失效,原因就在这里,不是Harborbug,而是渲染机制决定的。
3.2 核心容器的职责与依赖关系
v2.3.4默认生成的docker-compose.yml包含的服务数量接近10个。用表格呈现会清楚很多:
| 服务名 | 职责 |
|---|---|
| nginx | 统一入口,代理UI、API、registry流量 |
| portal | 前端页面,提供Web管理界面 |
| core | 核心API,处理登录、项目、镜像元数据等业务 |
| registry | 实际存储镜像层的registry服务 |
| registryctl | registry控制面,管理GC和配置 |
| postgresql | 元数据数据库,存项目、用户、权限等 |
| redis | 会话缓存和任务队列 |
| jobservice | 异步任务,负责复制、扫描等操作 |
| log | 日志聚合,统一管理各组件日志 |
| trivy-adapter | 镜像漏洞扫描器 |
| exporter | Prometheus监控指标输出 |
这些服务的依赖主线很清晰:nginx在最前面接收外部请求,转发给core和portal;core要连postgresql和redis,registry的读写检查要经过registryctl;jobservice从数据库取任务,跑完再写回状态。docker-compose的depends_on决定的是启动顺序,但服务真正可用要靠各自的健康检查。我见过有人在nginx还没通过健康检查时就急着访问页面,结果看到502就误以为部署失败,其实等十几秒再看就正常了。理解这条依赖链,排障时不会手忙脚乱。
3.3 数据卷与持久化:哪些目录不能丢
compose文件里所有持久化数据都映射到data_volume的各个子目录,比如/data/harbor/database存放PostgreSQL数据文件,/data/harbor/registry存放镜像层数据,/data/harbor/secret存放密钥证书,/data/harbor/jobservice存放异步任务数据。这里我要特别强调:Harbor的数据库和registry数据是两个独立的持久化单元,迁移或备份时两个目录都不能丢。丢registry,镜像全没;丢database,元数据全没,即使registry里还有镜像,Harbor也无法识别。
换机迁移时,我的做法是先在测试环境跑同版本Harbor,再停掉旧环境的容器,整个拷贝/data/harbor目录到新机器,最后在新机器上prepare并启动。只要版本一致,这套迁移基本无损。还有个细节:docker-compose down不会删除bind mount目录里的数据,但docker-compose down -v会把compose定义里所有卷一并清理。Harbor的compose文件用的是bind mount,正常情况下不会触发卷删除,但如果有人改成named volume,这条命令就要格外小心。
4. 启动、验证、排错:从docker-compose up -d到docker login
4.1 三维验证法确认Harbor真正健康
执行docker-compose up -d后,我从三个维度确认Harbor真的健康,缺一不可。第一维看容器状态,docker-compose ps输出里所有服务都应该是Up,其中带健康检查的服务最好显示healthy。第二维看页面访问,浏览器打开hostname地址,能看到登录页说明nginx和portal链路通了。第三维看命令行交互,在客户端机器上执行docker login hostname,然后用一个最小的busybox镜像打个tag推上去,能推能拉才说明registry、core、数据库整条业务链路是通的。
为什么一定强调三种方式都用?因为只打开管理页面不能代表仓库可用。我实际遇到过UI正常、docker login也正常,但推送镜像时一直报connection refused的情况,最后定位到是registry容器在反复重启。所以仅看页面那一维是不够的,docker push的成功才是一条最完整的链路验证。另外,在客户端机器测试之前,先确认防火墙和安全组对Harbor的80/443端口放行,否则docker login会一直卡在超时。
4.2 启动失败的三大高频原因
先看端口占用。Harbor默认监听80和443,如果服务器上已经有nginx或其他web服务,docker-compose up -d会直接失败,日志里出现bind: address already in use。解决思路有两个:停掉占用的服务,或者改harbor.yml里的http.port和https.port。改端口后记得客户端docker login时也要带端口号,同时docker daemon的insecure-registries列表要包含新的端口地址。这里我提醒一句,Harbor的UI访问地址和执行docker login的地址必须一致,很多人UI能打开但login失败,就是因为浏览器访问用了hostname的默认端口,而docker命令写的地址没有带自定义端口。
第二高发的是数据卷权限问题。表现是postgresql或registry容器反复重启,状态一直在Restarting和Up之间跳。排查方法很直接,看对应容器日志:
docker-compose logs postgresql docker-compose logs registry如果提示Permission denied,那就是data_volume子目录属主不对。Harbor容器里跑的是非root用户,数据目录需要对这个UID可写。常规做法是把整个data_volume的属主改成1000:1000,因为Harbor镜像内部默认用户UID是1000。改完权限再docker-compose restart对应服务即可。这个坑在全新机器上最容易踩,因为新系统里手动创建的目录属主往往是root。
第三是证书引起的x509错误。启用HTTPS但证书是自签名时,客户端docker login会报x509: certificate signed by unknown authority。最规范的办法是把自签CA证书加入客户端的系统信任链,但这需要每台客户端都配置;如果只是内网测试环境,更省事的办法是在客户端docker daemon配置insecure-registries,跳过该registry的证书校验。需要明确的是,insecure-registries只适合测试或隔离内网,真实生产环境还是应该用统一CA签发的证书。
4.3 配置变更与版本升级注意事项
修改harbor.yml之后,不要直接docker-compose restart完事,必须重新执行./prepare再docker-compose up -d。因为prepare要重新生成nginx配置、数据库连接参数等一堆文件,只重启容器是加载不到新配置的。我在一次调整端口时跳过prepare,结果页面一直停留在旧端口,算是踩得很实在的一个坑。每次改配置,流程都是一样的:改harbor.yml,跑prepare,再docker-compose up -d,缺一不可。
升级场景更要多一份谨慎。Harbor从v2.3.4升到更高版本时,数据库有迁移逻辑,官方建议先备份整个data_volume,再在测试环境验证升级链路,最后轮到生产。升级本质上也是prepare加docker-compose up -d,但旧版数据文件与新版本程序之间可能存在不兼容,所以我会在升级前做一次完整的数据目录快照,恢复也只需要把快照放回去再启动旧版本容器。还有一个小细节:离线包里的镜像都已经load到本地,但docker-compose up -d依然会检查远端镜像是否存在同名tag,为避免意外拉到一个不同的镜像,建议部署前确认本地镜像和compose文件里期望的镜像tag完全一致。
写到这里,基本上把用docker-compose部署Harbor v2.3.4的关键链路都过了一遍。我个人最大的体会是:不要怕拆开脚本,把配置渲染和容器启动分开来理解,很多问题都不再是玄学。最后再分享一个实用小习惯:每次部署或升级完,把harbor.yml、执行过的prepare命令、docker-compose ps的完整输出保存到运维记录里,下次无论是排障还是重建,照着操作一遍就能复现环境,比依赖个人记忆可靠得多。希望这篇记录能帮你少走弯路。