ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Harbor私有镜像仓库实战:从部署、HTTPS证书到故障排查

Harbor私有镜像仓库实战:从部署、HTTPS证书到故障排查

不少团队走到私有镜像仓库这一步,其实是环境倒逼出来的。项目从两三个服务膨胀到十几二十个,镜像开始散落在各个开发机和构建机上,版本一多就乱了:有人推上去再也不管,有人面对一堆旧 tag 不知道该拉哪个。想用官方 Docker Registry 临时顶一顶,用两周就会发现它只解决了"存储"问题,没解决"协作"问题。Harbor 正是在这个位置补位的——一套开源的企业级镜像仓库,底层依旧是成熟的 Registry 存储引擎,外面则包上了 Web 界面、项目级权限、镜像复制、漏洞扫描、审计日志这些真正让团队能一起干活的能力。

这篇文章结合我自己从零部署到日常维护的完整经验,把在 Linux 上搭建 Harbor 私有仓库、配好 HTTPS、跑通推送拉取,以及后面遇到的三类典型故障,一次讲清楚。适合准备自建镜像仓库的运维同学,也适合已经装了 Harbor 但还没把证书、权限、清理这些细节理顺的开发同学。

1. 为什么不用裸Registry:Harbor补上的四块短板

1.1 权限管理:裸Registry没有"人"的概念

Docker 官方开源的 Registry 镜像,本质上就是一套存储加 API 服务,默认部署完没有任何账号体系,谁能 push、谁能 pull 基本没法按人控制。你可以拿 Nginx 在前面做一层 basic auth,但那是"团队共用一个密码"的模式,日志里只看到一个账号,出了事根本说不清是谁推的。更麻烦的是没法做项目隔离,开发环境、测试环境、生产环境的镜像堆在同一个库里,误操作成本极高。

Harbor 的权限模型是推到项目维度的。一个用户可以关联多个项目,在每个项目里单独分配角色:项目管理员能管成员和配置,开发人员能推送镜像,访客只能拉取,还有更严格的限制访客。实际用的时候,我给测试组只开测试项目的访客权限,给构建机只开对应项目的推送权限,生产项目就留给两个核心同学。这套模型直接对应团队分工,不用额外设计一套权限体系。

1.2 删除不是真的删除:镜像生命周期管理

裸 Registry 支持通过 API 删除镜像,但实际操作比较绕:先拿到 digest,再调接口,而且删了之后底层存储未必立刻释放。多人反复推送同一个 tag、或者中断的推送,都会在 registry 里留下大量无引用的层,时间一长 /var/lib/registry 只涨不跌。

Harbor 把这件事做成了界面操作。项目管理员可以设置保留策略,比如"保留最近 20 个 tag"、"只保留被某条流水线标记过的镜像",到时间自动清理;同时提供手动垃圾回收按钮,把无主的 blob 真正从磁盘上清掉。对要长期运维的人来说,这个功能比在服务器上敲一堆 curl 值钱得多。

1.3 跨机房复制:给镜像搬家

当你有第二个机房,或者想在开发环境同步一份生产镜像时,裸 Registry 基本没有现成方案。Harbor 内置复制管理,可以配置基于拉取模式的复制规则,由目标实例按计划从源实例拉镜像,而且保留项目结构、权限和签名校验。

我当时把构建放在西边机房,东边机房通过复制规则定时同步,业务容器部署时直接从本地仓库拉,速度和稳定性都好很多。这个能力不只是多机房需要,从生产同步到测试、从主仓库同步到灾备仓库,思路都是一样的。

1.4 审计日志与漏洞扫描

Harbor 的审计日志会记录谁在什么时间登录、push、pull 了哪个镜像,配合 UI 的访问日志,排查"这个镜像到底谁推上去的"这类问题非常快。它默认还提供漏洞扫描能力,镜像入库时基于 CVE 库做检查,扫描结果直接显示在镜像列表上。生产拉镜像前先扫一遍,很多低版本基础镜像的明显漏洞能在源头拦下来。

简单总结一下裸 Registry 和 Harbor 的差距:

维度裸 RegistryHarbor
账户体系无,仅基础认证完整 RBAC,按项目分配角色
镜像清理API 删除,不自动释放空间保留策略 + 手动垃圾回收
跨实例同步需要自己写脚本内置复制规则
审计与扫描无审计日志 + 漏洞扫描
Web 管理界面无完整 UI,管理和运维都在浏览器里完成

2. 动手前先定四件事:版本、依赖、证书、存储

2.1 版本选型与硬件底线

Harbor 目前主力分支是 2.x。官方文档给的最小配置是 2 核 CPU、4G 内存、40G 磁盘,但那是"能跑起来"的标准。我个人经验是,开了漏洞扫描之后,扫描任务会把 CPU 和内存顶上去,尤其是第一次全量扫描的时候,所以生产环境直接按 4 核 8G 起步更稳妥,系统盘和数据盘也必须分开。

版本选择上,没有特殊要求就用当前最新稳定版,拿到离线安装包。离线包的好处是整个安装过程不需要从公网拉取任何镜像,所有组件镜像都打包在安装包内,内网环境部署和后面的离线升级都很方便。

2.2 Docker 与 Compose 依赖检查

Harbor 的安装机需要 Docker 和 Docker Compose。2.x 官方要求 Docker 17.06 以上、Compose 1.18 以上,实际建议直接用当前 Docker 稳定版。装完先确认版本,省得后面报一些莫名其妙的兼容问题:

docker --version docker compose version

如果机器上只有老的 docker-compose 命令,没有 docker compose 插件,install.sh 也能识别,但注意后面所有日常管理命令要和安装时用的命令保持一致,避免一会儿 docker compose、一会儿 docker-compose 把环境搞乱。

2.3 域名与证书方案:越早定越省事

Harbor 的对外地址会写进镜像的 tag 里,比如 harbor.example.com/app-a/web:v1,一旦上线之后再改 hostname,所有旧 tag 都要重新打一遍。所以第一步就是把规划的地址定下来,别用 127.0.0.1,也别拿临时的内网 IP 当正式地址。

证书我建议直接走"内部 CA + 服务端证书"的方案:自己维护一个根 CA,用它给 Harbor 签一张证书,后面所有要拉镜像的节点只要信任这个根 CA 就行。这个方法比每台机器都配 insecure-registries 要规范、安全得多。第 4 节我会把整条生成链路完整写出来,照着做就行。

2.4 数据目录与备份规划

harbor.yml 里的 data_volume 参数决定所有数据落在哪里。Harbor 的数据库、Redis、镜像存储、日志都在这一个目录下面,默认是 /data。最怕的是把它装在系统盘上,跑几个月镜像把根分区占满,整个机器跟着出问题。

我建议把 data_volume 指向独立数据盘的挂载点,比如 /data/harbor,同时在部署前就想好备份策略。后面不管是升级还是出故障,这个目录就是最重要的恢复源。

3. 部署全过程:离线包、harbor.yml、install.sh

3.1 下载解压与目录规划

去 Harbor 项目官方发布页面下载最新稳定版的离线安装包,文件名形如 harbor-offline-installer-v2.11.0.tgz。传到服务器后解压:

tar -xzvf harbor-offline-installer-v2.11.0.tgz cd harbor

解压出来的目录里有 harbor.yml.tmpl 模板、install.sh、prepare、common.sh,还有一个打包了全部组件镜像的压缩文件。建议把目录固定放在 /opt/harbor,以后升级时解开新版本替换进来,路径保持一致能减少很多环境变量上的烦恼。

3.2 harbor.yml 逐项配置

先复制模板再修改:

cp harbor.yml.tmpl harbor.yml

核心配置项如下,完整字段可以参考模板文件里的注释:

hostname: harbor.example.com http: port: 80 https: port: 443 certificate: /data/certs/harbor.example.com.crt private_key: /data/certs/harbor.example.com.key harbor_admin_password: YourStrongPassword database: password: YourDbPassword data_volume: /data/harbor trivy: enabled: true

逐项说:hostname 必改,别留模板默认值;如果纯走 HTTPS,可以把 http 段整个注释掉,避免 80 端口被机器上其他服务占用;harbor_admin_password 是 admin 的初始密码,至少 8 位,不要用模板里的默认弱密码;database.password 会写入内置数据库的初始化配置,记好别丢;trivy.enabled 打开漏洞扫描能力,如果你暂时不看扫描结果,可以先关掉减少资源占用。

3.3 执行 install.sh 与启动校验

配置文件改好之后直接执行:

./install.sh

install.sh 会依次做三件事:加载离线镜像包,调用 prepare 根据 harbor.yml 生成最终的 docker-compose.yml 和 nginx 配置,最后 docker compose up -d 启动全套组件。加载离线包可能需要几分钟,耐心等它跑完。

启动后用下面两条命令确认状态:

docker compose ps docker compose logs -f

看到 core、nginx、registry、数据库、Redis 这些容器都是 running 状态,就可以在浏览器里访问 https://harbor.example.com,用 admin 登录了。到这里 Harbor 就能正常提供镜像仓库服务,但还没完,后面几步才是让它真正好用的关键。

4. HTTPS 证书链路:私有仓库第一大坑的完整解法

4.1 用 OpenSSL 搭一个内部 CA

如果你已经有企业内部的 CA 平台,直接跳到下一步签服务端证书就行。没有的话,用 OpenSSL 建一个根 CA 成本非常低。先准备工作目录:

mkdir -p /data/certs && cd /data/certs openssl genrsa -out ca.key 4096 openssl req -x509 -new -nodes -sha512 -days 3650 \ -subj "/CN=Harbor Internal CA" \ -key ca.key -out ca.crt

这一步会得到 ca.key 和 ca.crt。ca.key 一定要收好,后续给其他服务签发证书时才用得到;ca.crt 则是可以放心分发给所有节点的信任凭证。

4.2 给 Harbor 签发服务端证书

服务端证书有两个容易出错的地方:有效期和 SAN。先生成私钥和 CSR,再通过扩展文件把域名和可能用到的 IP 都写进 SAN:

openssl genrsa -out harbor.example.com.key 4096 openssl req -new -sha512 \ -subj "/CN=harbor.example.com" \ -key harbor.example.com.key \ -out harbor.example.com.csr cat > v3.ext <<EOF subjectAltName = DNS:harbor.example.com, IP:192.168.100.10 EOF openssl x509 -req -sha512 -days 3650 \ -extfile v3.ext \ -CA ca.crt -CAkey ca.key -CAcreateserial \ -in harbor.example.com.csr \ -out harbor.example.com.crt

CAcreateserial 会在当前目录生成 ca.srl 序列号文件,不要删,后面再签其他服务证书时还要用它。把生成的 crt 和 key 路径填到 harbor.yml 的 https 段,证书部分就完成了。SAN 里的 IP 和 DNS 都要覆盖,否则你用 IP 访问时会出现证书校验失败。

4.3 让每台 Docker 守护进程信任 CA

Harbor 所在机器,以及所有要访问它的节点,Docker 守护进程都必须信任刚才的根 CA。正规做法是建 Docker 的证书目录,把 ca.crt 放进去,目录名必须和访问地址完全一致:

mkdir -p /etc/docker/certs.d/harbor.example.com cp ca.crt /etc/docker/certs.d/harbor.example.com/ca.crt systemctl restart docker

这里有个特别容易忽略的点:Harbor 所在机器自己也要 push/pull,所以这台机器同样要执行这一步,否则你在本机登录自己的仓库照样报证书错误。分发 ca.crt 可以用一条 scp 推送,或者配合配置管理工具批量下发,规则就是每台需要访问的节点都得有。

如果实在不想分发证书,还有一个短平快的方案,在 /etc/docker/daemon.json 里声明:

{ "insecure-registries": ["harbor.example.com"] }

insecure 方案能跑,但镜像和登录信息都走明文 HTTP,内网临时测试可以接受,正经环境我不推荐,后面会越来越难改。

4.4 配置变更后必须重新 prepare

Harbor 不会自动重读 harbor.yml。不管是补了证书,还是改了数据库密码,都要按这个顺序来一遍:

docker compose down ./prepare docker compose up -d

prepare 会重新生成 docker-compose.yml、nginx 和各个组件的配置文件,然后再用 up -d 把服务拉起来。我习惯每次变更之前先把当前 data_volume 备份一下再动文件,成本很低,但保险系数非常高。

5. 上线后的日常:项目、用户、推送、拉取

5.1 项目的权限模型怎么落地

登录 UI 后第一件事是建项目。项目名会成为镜像仓库路径的第一段,比如建一个项目 app-a,之后镜像地址就是 harbor.example.com/app-a/xxx:v1。项目分公开、私有两种,公开项目默认允许所有人拉取,私有项目必须登录并获得权限。

用户管理里,除了普通用户还有机器人账户,这是给 CI/CD 用的。比如构建机上不想存管理员密码,可以给一条流水线单独建一个机器人账户,只给某个项目的推送权限,拿到的 token 直接当登录密码用。这个功能实际用下来,比把管理员密码交给流水线安全太多,而且随时可以撤销。

5.2 推送镜像的完整命令

推送前先登录,再打 tag,最后推送:

docker login harbor.example.com docker tag myapp:latest harbor.example.com/app-a/myapp:latest docker push harbor.example.com/app-a/myapp:latest

tag 的仓库名部分必须和项目对应,推错项目名会直接报权限错误。docker login 的凭证默认缓存在 ~/.docker/config.json 里,CI 机器上可以先手动登录一次,或者把 user 和 auth 字段直接写进 config.json,这样流水线不用每次弹登录交互。

拉取端只要提前做过 4.3 里的 CA 信任配置,直接拉就行:

docker pull harbor.example.com/app-a/myapp:v1

5.3 复制规则与垃圾回收

跨机房或者多环境同步,在 UI 的复制管理里配置规则。选"基于 pull 模式的复制",源实例填本 Harbor,目标实例填对端地址,然后指定具体项目。目标实例会按计划从源拉镜像,权限和签名校验都保留,完全不用自己写脚本。

垃圾回收要单独提醒一句:Harbor 不会自动执行 GC,删除镜像后磁盘空间不会立刻释放。UI 的垃圾回收页面可以手动触发,建议先在测试环境跑一次,确认不影响业务再在生产环境排周期执行。GC 期间 registry 的写入性能会受影响,尽量避开发布窗口。

5.4 升级备份的基本流程

Harbor 升级的套路是固定的:先 docker compose down,备份 data_volume,解压新版本安装包,把旧 harbor.yml 拷贝过去,再执行 ./install.sh 完成升级。三个关键点:一是备份一定要有,data_volume 里的数据库目录是恢复的核心;二是配置别重新手写,直接用归档的旧配置改;三是升级前看一眼版本变更说明,组件行为有变化时好提前准备。

6. 三例真实故障的排查链路

6.1 x509 证书校验失败

最典型的现象:某台机器 docker push 时报 x509: certificate signed by unknown authority,或者报 server gave HTTP response to HTTPS client。两个错误其实都不难定位。

先确认地址能访问,curl https://harbor.example.com/v2/ 看看返回。再检查 Docker 证书目录:/etc/docker/certs.d/ 下面必须有一个和访问地址完全同名的目录,目录里放着 ca.crt。改完记得 systemctl restart docker,让守护进程重新加载证书。如果报的是 HTTP response,说明 Harbor 这端没开 HTTPS,而 Docker 默认用 HTTPS 访问,要么补上 HTTPS,要么明确在 daemon.json 里把该地址列为 insecure。

6.2 登录界面 502 Bad Gateway

Harbor 的 UI 和 API 都经过 nginx 反代到 harbor-core,出现 502 基本是后面服务没起来。第一步看容器状态:

docker compose ps docker compose logs harbor-core

我遇到过的两次 502,一次是 harbor.yml 里的数据库密码和实际内置库不一致,core 连不上数据库起不来;另一次是磁盘写满,日志组件起不来。排查顺序建议是:先 df -h 排除磁盘问题,再看 compose logs 里的具体报错。Harbor 的日志文件本身也在 data_volume 下面,必要时直接翻 common/logs 里的 nginx 和 core 日志。

6.3 磁盘占用异常与孤儿层

现象是数据盘快满了,但 UI 里看镜像没多少。原因一般是反复推送相同 tag、或者推送中断留下的孤儿层,它们没有被 tag 引用,却占着磁盘。处理手段就是触发垃圾回收。

GC 之后如果空间下降不明显,先看是不是还有旧 tag 引用着这些层,把无用 tag 删除后再跑一次 GC。另外 registry 的存储目录是分层结构,直接 du 刷目录大小会有误导,以 GC 前后 df 的差异为准。我建议给数据盘配一个磁盘水位告警,别等满了才处理。

最后说一个自己的习惯。每次在 Harbor 上做配置变更,我习惯先 docker compose down 再动文件,改完验证好再起来;升级前一定把 data_volume 完整 rsync 一份到别的磁盘。这套流程不能防止所有故障,但至少保证凌晨两点处理环境问题的时候还有回滚的余地。Harbor 的搭建本身一两个小时就能跑通,真正决定后面省不省心的,是证书链路、权限设计和清理这些细节有没有一开始就做对。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进