我最近在一家公司的内网机房,用一台代号为 docker5 的服务器把一套全功能 Harbor 跑了起来。这篇文章就是整个折腾过程的完整记录。Harbor 是目前企业自建容器镜像仓库时最常用的开源项目,很多人以为它就是一个带 Web 界面的 Docker Registry,实际上全功能版本把镜像权限、漏洞扫描、签名校验、跨机房复制这些能力都包进来了。它解决的问题很具体:开发机、测试集群、正式环境都需要一套统一、可控、可追溯的镜像分发底座。如果你正在负责搭建内部镜像仓库,或者刚接触 Harbor 想一步到位把全功能开起来,这一篇可以直接当作业手册参考。
1. 全功能 Harbor 到底解决了什么问题
1.1 为什么要抛开简单的 Registry 方案
早期很多团队偷懒,直接用裸的 Docker Registry 起一个镜像仓库。它能 push、能 pull,也能撑住一段时间的日常使用。但跑一段时间就会遇到几个绕不开的坎:多个项目之间权限怎么隔离?谁删了哪个镜像有没有记录?镜像里藏着高危漏洞有没有人发现?生产集群从哪个仓库拉东西才是可信来源?还有最要命的——两台机房之间的镜像怎么互相同步?
这些问题单独看都不大,叠在一起就会把所有维护精力吞掉。全功能的 Harbor 是在传统 Docker Registry 之上加了一层企业级能力:Web 门户负责可视化操作,core 服务负责 API 和认证,jobservice 负责扫描、复制这种异步任务,PostgreSQL 存元数据,Redis 扛缓存,Trivy 负责漏洞扫描,Notary 对应镜像签名。把这些模块全装起来,镜像仓库就不再是一个被动存储的地方,而是一个有完整准入流程的镜像管理平台。
1.2 “全功能”指的是哪些模块
我这次部署时没有做减法,把官方脚本支持的可选组件全部开启了。一张表看清楚每个模块的作用:
| 模块 | 作用 | 部署形态 |
|---|---|---|
| registry | 镜像存储与分发,OCI 规范 | 容器 |
| core | Harbor 的 API、鉴权、配置中心 | 容器 |
| portal | 浏览器端管理界面 | 容器 |
| jobservice | 扫描、复制、清理等异步任务队列 | 容器 |
| PostgreSQL | 保存项目、用户、规则、任务等元数据 | 容器 |
| Redis | 缓存任务数据,降低数据库压力 | 容器 |
| Nginx | 统一入口网关,负责 TLS 证书终止与流量转发 | 容器 |
| Trivy | 镜像漏洞扫描引擎 | 容器 |
| Notary / Notation | 镜像签名与可信验证 | 容器 |
之所以叫全功能,核心其实是把 Trivy 扫描和 Notary 签名这两个可选模块一并开启。没有扫描的 Harbor 只能算带界面的私仓;没有签名的 Harbor 就缺少对镜像完整性的约束。后面我会具体展开这两个模块的配置和验证。
2. 部署前的准备:服务器、证书与安装包
2.1 服务器规格与目录规划
这台 docker5 服务器的用途比较纯粹,就是专门跑 Harbor。硬件规格是 8 核 16 线程、32GB 内存,系统盘 200GB,另外挂了一块 2TB 的独立数据盘。很多人在这一步容易拍脑袋,觉得镜像仓库随便拿台低配机器就行。实测下来,如果只是存几个镜像,8GB 内存的机器也能勉强跑,但 Trivy 扫描大镜像、复制任务并发执行时,内存和磁盘 IO 会立刻吃紧。我建议内网生产环境至少 16GB 内存起步,数据盘独立于系统盘挂载。
系统盘与数据盘分家的原因很容易理解:镜像数据、数据库文件、日志都在持续写入,如果和操作系统抢同一块盘,Docker 本身都会跟着遭殃。我在 docker5 上把数据盘挂载到了 /data,ext4 文件系统,并在 /etc/fstab 里配置了开机自动挂载。数据目录规划如下:
- /data/harbor:Harbor 数据卷,镜像内容、数据库文件都在这里
- /data/cert:HTTPS 证书和私钥
- /backup:预留的备份目录
Docker 环境我习惯统称为 docker5,其实对应的是 Docker Engine 24.x 加 Compose v2 的组合。Harbor 安装脚本依赖 docker compose 命令,所以部署前先检查:
docker version docker compose version如果哪台机器上还在用旧的 docker-compose,务必先补齐 Compose v2,否则 install.sh 会在准备阶段直接报错。
2.2 域名、证书与内部 DNS
无论 Harbor 部署在内网还是公网,我强烈建议一上来就走 HTTPS。原因不只是密码会被明文截获这种老生常谈,更重要的是 Docker 客户端、Harbor 复制任务、镜像签名验证都依赖可靠的 TLS 链路。没有 HTTPS,后面配置扫描器、复制任务、登录认证时会遇到各种想不到的信任问题。
我先规划了一个内部域名:harbor.example.org。这个域名只在内网 DNS 解析,不在公网发布。如果内网没有集中 DNS,至少要在所有需要用镜像的机器上统一写入 /etc/hosts,否则每台机器单独改配置会是场灾难。
证书方面,企业里有内部 CA 的直接向 CA 申请;没有 CA 的可以自己搭一套内部 CA。我这次为了演示,用 OpenSSL 自建了一个内部 CA,再给 harbor.example.org 签发证书:
# 生成内部 CA openssl genrsa -out ca.key 4096 openssl req -x509 -new -nodes -sha256 -days 3650 \ -subj "/CN=harbor-internal-ca" -key ca.key -out ca.crt # 生成 Harbor 服务端私钥和证书签发请求 openssl genrsa -out harbor.key 2048 openssl req -new -key harbor.key -out harbor.csr \ -subj "/CN=harbor.example.org" # 使用 SAN 文件签发证书 cat > ext.cnf <<EOF subjectAltName=DNS:harbor.example.org,IP:192.168.10.20 EOF openssl x509 -req -in harbor.csr -CA ca.crt -CAkey ca.key \ -CAcreateserial -out harbor.crt -days 825 -extfile ext.cnf这里有一个容易忽略的细节:证书的第一项 DNS 名称必须和 harbor.yml 里的 hostname 完全一致,否则浏览器和 Docker 客户端都会报证书不匹配。我是把内部 CA 证书同时分发到了所有开发机的系统信任区,并在 Docker 客户端的 /etc/docker/certs.d/harbor.example.org/ 目录下放了一份 ca.crt。
注意:如果直接把 harbor 服务器上用的 ca.crt 复制到了 /etc/docker/certs.d/harbor.example.org/ca.crt,一定要重启 Docker daemon 或至少重新加载配置,否则 x509 错误不会消失。
2.3 在线包和离线包怎么选
Harbor 官方提供在线安装包和离线安装包。在线包体积小,安装时会去拉取需要的镜像;离线包把所有组件的镜像都压缩在一个 tar.gz 里,适合没有公网连接或者下载速度不理想的内网环境。我这次在内网部署,选的是离线安装包,下载后第一步做完整性校验:
sha256sum harbor-offline-installer-v2.x.x.tgz校验值通过后,把安装包装放到 /opt,解压出 harbor 目录。Harbor 的安装脚本会在启动时把配置和数据目录铺好,所以解压目录最好放在一个固定位置,方便后续升级维护。
3. 完整安装流程与全功能模块配置
3.1 修改 harbor.yml 的关键参数
解压后第一件事就是改配置文件。Harbor 的配置中心是一个 harbor.yml,初次使用前先复制模板:
cp harbor.yml.tmpl harbor.yml vim harbor.yml我实际使用的关键配置如下:
hostname: harbor.example.org http: port: 80 https: port: 443 certificate: /data/cert/harbor.crt private_key: /data/cert/harbor.key harbor_admin_password: "YourVeryStrongPassword" database: password: "DBPasswordHere" max_idle_conns: 100 max_open_conns: 200 data_volume: /data/harbor trivy: ignore_unfixed: false skip_update: false timeout: 300s notary: enabled: true ca_file: /data/cert/ca.crt log: local: rotate_count: 5 rotate_size: 100M level: info几个参数值得展开讲讲。
第一,hostname 千万别当摆设。很多教程里随便填个 IP 就完事,结果后面配置镜像复制和签发证书时处处对不上。在 Harbor 里,hostname 会被写进各类资源的 URL,如果不和证书域名统一,跨实例复制和 Docker 登录都会很痛苦。
第二,数据库密码不要留空。Harbor 默认会在首次启动时生成随机密码,虽然安全,但后续做备份、对接监控、排查数据库问题时会很难受。我习惯在一开始就显式配置数据库密码,并把同样的密码记录到公司内部的密码管理库里。
第三,日志轮转配置我格外看重。默认不限制日志大小的 Harbor 跑几个月后,会把数据盘塞满。rotate_count 和 rotate_size 这两项是给日志清理工作上的保险,后面 5.2 节的磁盘告警案例就是从这里引出来的。
3.2 执行 install.sh 启动全功能模块
配置改完后,执行带全功能参数的安装脚本:
sudo ./install.sh --with-trivy --with-notaryinstall.sh 会先做环境检查,包括 Docker 版本、docker compose 版本、端口占用情况,然后生成一份最终的 docker-compose.yml,并拉取或加载离线包里的镜像。第一次执行过程比较长,主要集中在 Trivy 和 Notary 相关镜像的加载上,耐心等即可。
启动完成后,我按顺序验证服务和端口:
docker compose -f /data/harbor/docker-compose.yml ps ss -lntp | grep -E '80|443' curl -k https://harbor.example.org/api/v2.0/pingcurl 返回 PONG 说明 API 正常,浏览器里打开 https://harbor.example.org,用管理员账号登录,看到门户页面上出现了“系统管理-扫描”“系统管理-签名”等菜单,就说明全功能模块已经被正确加载。
注意:不要在安装启动之后再手动去改 docker-compose.yml。Harbor 的 docker-compose.yml 是由 harbor.yml 通过 prepare 生成的,手工改动会在下次 prepare 时被覆盖。正确的改配置方式永远只有一种:修改 harbor.yml,重新执行 prepare 和 docker compose up -d。
3.3 Docker 客户端的信任配置
Harbor 起来之后,接下来就是让各台开发机能够登录。这一步不处理好,后面所有 push、pull 都会卡在证书信任上。我在每台开发机上把内部 CA 放到系统信任目录:
sudo cp ca.crt /usr/local/share/ca-certificates/harbor-ca.crt sudo update-ca-certificates同时,为了让 Docker daemon 自己也信任 Harbor 的证书,在每台机器上创建:
sudo mkdir -p /etc/docker/certs.d/harbor.example.org sudo cp ca.crt /etc/docker/certs.d/harbor.example.org/ca.crt然后重启 Docker:
sudo systemctl restart docker之后执行:
docker login harbor.example.org输入管理员账号密码,登录成功。这一步通过,说明 TLS 链路、DNS 解析、证书信任全链路都是通的。
4. 全功能模块实测与使用验证
4.1 项目、用户与机器人账户
Harbor 的权限模型是以项目为中心的。我先创建了一个项目 team-web,然后把团队相关账号加进来,分配不同角色。角色权限大致是:项目管理员能管理成员和配置,开发人员能推送镜像,访客只能拉取。生产环境强烈建议用这个模型,而不是所有人都用 admin 操作。
对于 CI/CD 流水线,我不建议把真实用户的账号埋在脚本里,而是使用机器人账户。Harbor 的机器人账户支持按项目授权,权限可以收敛到最小。我在 team-web 项目下创建了一个 robot-ci 账户,只给它推送权限,然后把这个账号的 token 放到构建服务器的环境变量里:
docker login harbor.example.org -u robot$team-web+robot-ci -p <token> docker tag nginx:1.24 harbor.example.org/team-web/nginx:1.24 docker push harbor.example.org/team-web/nginx:1.24这里有个小细节,机器人账户的用户名格式通常是 robot$项目名+机器人名,其中的 $ 符号在某些 shell 里会被当成变量解析,传参时记得加上单引号,不然 token 会莫名对不上。
4.2 漏洞扫描策略与 Trivy 实战
全功能 Harbor 的“全”,很大程度上体现在扫描模块。Trivy 在 Harbor 里以异步任务方式工作,扫描结果会展示在镜像详情页里。我在管理界面的“系统管理 -> 扫描”里配置了全局扫描策略,按每小时一次的频率扫描新推送的镜像。实际使用的验证流程如下:
- 推送一个带有已知问题的旧版本镜像到 team-web 项目
- 在镜像列表里触发“扫描”按钮,观察任务状态从 Queued 变成 Running
- 刷新页面查看漏洞总数、高危数量、修复建议
第一次在真实环境里跑扫描时,我遇到了 Trivy 更新漏洞数据库的任务一直在重试的问题。原因是内网环境无法直接访问更新服务器。解决办法是把 Trivy 的 skip_update 参数设为 true,然后手动导入离线漏洞数据库文件。离线数据包要从官方 Release 下载,然后放到配置的扫描器数据目录里,再重启 Trivy 容器。这一整套操作下来,扫描器就开始正常工作。
提示:漏洞扫描不是部署完就一劳永逸。漏洞库是持续更新的,生产内网一定要规划漏洞库的同步机制,哪怕一个月同步一次,也比长期不更新强。
4.3 镜像签名与可信验证
镜像签名这部分是很多同学最容易打折的环节。Harbor 全功能里默认集成了 Notary,而新的签名生态更多推荐 Notation 或 Cosign。我在 docker5 上把 Notary 开着,同时演示了 Cosign 验签的过程。先安装 Cosign,然后生成密钥对:
cosign generate-key-pair签名时指向 Harbor 里的镜像地址:
cosign sign --key cosign.key harbor.example.org/team-web/nginx:1.24验签时使用公钥:
cosign verify --key cosign.pub harbor.example.org/team-web/nginx:1.24签名验证的意义在于保证从拉取到运行的整条链路上,镜像没有被任何人篡改。配合 Harbor 的签名管理,可以做到:只允许已经签名且校验通过的镜像被部署到生产集群。实际落地时要注意,Notary 和 Notation 的信任机制是不互通的,先把团队采用的签名工具定下来,再统一配置策略,不要混着用。
4.4 跨实例复制规则
我这边有两套 Harbor,一套在测试机房,另一套在正式机房。测试验证通过之后,镜像需要同步到正式环境。Harbor 的复制功能就是干这件事的。
配置流程是:在“系统管理 -> 复制管理”里先添加目标端点,填正式机房的 Harbor 地址和账号。为了安全,我在正式机房也新建了机器人账户,专门用于接收复制。然后创建复制规则:
- 方向:Push based(推模式)
- 触发方式:事件驱动,也就是源 Harbor 上推送新镜像后自动触发
- 过滤器:team-web/ 项目下的镜像
配置完成之后,我在测试环境重新推了一个新版本镜像,很快就在复制任务列表里看到状态变成 Succeeded。同时,正式机房的 team-web 项目下也出现了对应的镜像。
这里要提个醒:复制任务走的是两端 Harbor 之间的网络链路,在大镜像频繁推送时要规划好带宽。内部机房之间一般没问题,但如果链路带宽有限,建议把触发方式改成定时批量复制,而不是事件驱动,避免并发任务把网络打满。
5. 日常运维、备份与问题排查实录
5.1 备份与恢复的完整动作
Harbor 的高可用方案成熟之前,单机部署最需要上心的就是备份。我把备份拆成三部分:配置、数据库、数据目录。配置指的是 /opt/harbor 目录里的 harbor.yml 和证书;数据库保存了用户、项目、规则等元数据;数据目录里则是镜像实体和扫描结果。
手动备份命令如下:
# 1. 备份配置和证书 sudo tar czf /backup/harbor-conf-$(date +%F).tar.gz \ /opt/harbor/harbor.yml /data/cert # 2. 导出数据库 docker compose -f /data/harbor/docker-compose.yml \ exec -T postgresql pg_dump -U postgres -d harbor \ > /backup/harbor-db-$(date +%F).sql # 3. 备份镜像数据目录 sudo tar czf /backup/harbor-data-$(date +%F).tar.gz /data/harbor恢复场景下,顺序是先装好同版本的 Harbor,停止服务,把配置、证书、数据库导入、数据目录放回原位置,再重新执行 docker compose up -d。我做过一次从零恢复到完成的过程,总共花了不到半小时。有一个比较大的坑是:Harbor 版本升级后数据库结构可能变化,恢复备份时尽量保持版本一致,跨版本恢复要慎之又慎。
5.2 常见问题速查表
我在这次部署和后面两个月的使用里,遇到了不少问题,整理成一个速查表:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| Docker 登录时报 x509 signed by unknown authority | 客户端没有信任 Harbor 的 CA 证书 | 按 3.3 节把 ca.crt 放入 /etc/docker/certs.d 并重启 Docker |
| 登录报 401 Unauthorized | 密码错误或账号被锁定 | 重置管理员密码,检查机器人账号 token 是否过期 |
| Trivy 扫描一直无法完成 | 内网无法更新漏洞库 | 设置 skip_update: true,离线导入漏洞库数据文件 |
| 磁盘使用率持续上涨,GC 后空间不释放 | 有长期未清理的镜像或日志 | 配置镜像保留策略,在系统管理里手动触发垃圾回收 |
| 复制任务一直在 Pending | 目标端点凭证失效或网络不通 | 检查机器人账号权限,用命令行测试两端 Harbor 连通性 |
| Web 页面 502/504 | 后端服务内存不足或数据库连接耗尽 | 查看 core 容器日志,检查内存和数据库连接数 |
其中磁盘满这个问题最隐蔽。我遇到过一次镜像仓库明明没放多少内容,数据盘却快满了,原因是日志没有限制大小。后来把 harbor.yml 里 log 部分加上了 rotate 配置,同时定期手动触发垃圾回收,数据盘才回归正常。
5.3 运维心得与避坑建议
跑了一段时间的全功能 Harbor,我有几条心得体会,属于常规文档里不会细讲的东西:
第一,数据目录一定单独挂盘。系统盘如果和镜像数据混用,日志一膨胀就会把 Docker 自身的运行空间挤掉,到时不是重启服务那么简单,整台机器都可能卡死。
第二,配置变更只走 harbor.yml 和 prepare 流程。我一开始偷懒直接改 docker-compose.yml,结果一次配置重灌后全被覆盖,后来才理解了 Harbor 这套配置生成逻辑的意义。千万别绕过它。
第三,密码和密钥管理要严肃对待。数据库密码、管理员密码、签名私钥,任何一个泄露都可能让内网镜像体系失控。建议把密钥放进公司现有的密码管理平台,并做好访问审计。
第四,全功能不代表把模块全开了事。开启 Notary、Trivy 这些模块后,它们自身也需要维护。如果你的团队目前只缺一个私仓,可以先部署基础版;等确定了扫描和签名策略,再把对应的可选模块打开,一步一个脚印比一次性全开要稳得多。
落地的下一步想法
这套全功能 Harbor 现在已经稳定服务了公司内部的多个开发团队。接入 Kubernetes 集群时,只要在集群节点的 containerd 或 Docker 配置里预埋好 harbor.example.org 的证书信任,配合镜像拉取凭据,就能无缝对接。后续我计划把 Harbor 的监控指标接到内部监控平台,把镜像仓库的容量、任务队列长度、扫描成功率这些数据纳入整体观测体系。另一个可以做的优化是把复制功能扩到第三个机房,做成多活镜像分发网络。
最后再分享一个小技巧:Harbor 的 harbor.yml 值得纳入版本管理。每次变更前先提交一个可回滚的版本,再执行 apply 和重启,这样改坏了也能快速还原。毕竟镜像仓库是生产环境的基础设施,稳字当头,每一次变更都应该像发布业务版本一样严肃对待。