说到企业级容器镜像仓库,Harbor基本是当前开源体系里最成熟、最绕不开的选择。把它部署到Kubernetes上,意味着你不再需要单独维护一台虚拟机跑Docker Compose,而是能直接复用集群已有的调度、存储、网络和监控能力,把镜像仓库变成基础设施的一部分。这篇文章我从零开始,按“跑起来、配置好、生产化、踩坑排查”这条线,把我自己从Helm chart一路搭建到生产环境的完整过程写清楚,包括前置规划、关键参数、外部依赖改造,以及不少文档里不会明说但实际必然遇到的坑。
如果你正在搭建私有镜像仓库、做私有化交付,或者单纯想把团队的基础设施规范化一点,这篇应该能帮你少走好几周弯路。我不写理论上的花架子,只写我实际操作过、验证过的内容。
1. 先想清楚:Harbor到底解决了什么
1.1 为什么不能继续用裸Registry
很多团队最开始都用Docker Registry或者某云厂商的基础镜像仓库,业务小的时候确实够用。但一旦团队超过两三个,或者业务进入私有化交付阶段,裸仓库的短板会非常明显。
首先是权限模型基本等于没有。Docker Registry默认不区分用户,即使加了认证,也只能做到“能拉”“能推”这种粗粒度控制。但企业环境里,通常至少有“开发组A只能访问应用A的镜像”“测试组只能拉取不能推送”“CI机器人只能访问某个项目”这类需求。没有RBAC,就只能靠运维手工管理账号,项目一多就失控。
其次是审计和联动能力缺失。谁在什么时间推送了什么镜像、哪个镜像存在高危漏洞、某个项目容量还剩多少,这些问题裸仓库全都回答不了。Harbor补上的正是这一层:项目级隔离、用户与用户组、机器人账号、操作审计、CVE扫描、配额管理、复制规则、定时GC,这些都是企业级仓库的标配。
我打过一个比方:Docker Registry是你给所有人发了一把仓库钥匙,谁都能随便进出;Harbor是一栋带门禁和监控的大楼,每一层都能单独控制权限,每个动作都有记录。企业化诉求一上来,你基本没有第二个开源选择能和Harbor在这个成熟度上对标。
1.2 Harbor和Kubernetes的适配点在哪
Harbor本身不依赖Kubernetes,直接Docker Compose也能跑,但放进Kubernetes集群后才真正发挥出“平台级”价值。核心原因有三个:
第一,网络链路统一。部署在Kubernetes后,Harbor的Service地址可以直接被集群内的Pod访问,kubelet拉取镜像不需要绕公网,也方便在私有网络或离线环境里工作。配合Ingress对外提供HTTPS入口,内部流量和外部流量天然隔离。
第二,运维交付归一。所有组件都用Helm管理,升级回滚和Kubernetes生态一致,不再单独维护一套Compose脚本。监控告警也可以直接接Prometheus,不需要额外给Harbor单机配监控。
第三,很多边缘场景天然就是Kubernetes形态。比如私有化交付时,目标环境往往已经装好了一整套Kubernetes,Harbor作为一个Helm Chart打进去是最省事的;如果另外用一台虚拟机跑Compose,反而多出了一套运维负担。
所以我的结论很直接:如果环境允许,优先把Harbor放进已有Kubernetes集群;只有当你确实没有一个可用的集群,或者只是想在笔记本上快速体验,才考虑Compose方式。后面所有操作都基于“Kubernetes + Helm”这条路线。
2. 部署前必须搞定的三件事
2.1 部署方式选型:Compose、Operator还是Helm
Harbor的部署方式主要有三种,我简单做个对比。
| 方式 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| Docker Compose | 部署快,单命令启动 | 高可用难,升级维护成本高,存储绑定单机 | 测试体验、小规模单机 |
| Operator | 自动化程度高,能处理复杂生命周期 | 生态相对小众,自定义能力受限于CRD | 大规模集中管理Harbor实例 |
| Helm Chart | 官方维护,参数化灵活,与Kubernetes生态无缝 | 需要理解values体系 | 绝大多数生产环境 |
我选择Helm的原因很实际:首先它是Harbor项目官方提供的安装方式,版本演进跟得上;其次values文件可以完整放进Git做版本管理,部署、升级、回滚都是标准化的Helm动作,不用额外发明一套流程。Operator看起来更高级,但对于Harbor这种有状态、依赖外部数据库和存储的组件,Helm反而更容易掌控细节。
2.2 证书、域名和Ingress入口
生产环境必须启用HTTPS,这一点没有商量。Harbor对三种证书来源都支持:cert-manager自动签发、外部已有的secret、以及自签名证书。我的建议是优先用现有方式接入:如果你有统一证书管理平台,就按已有secret的方式挂进来;没有的话,直接接cert-manager最省心。
这里有一个特别容易踩的坑:ExternalURL必须和用户访问Harbor的域名完全一致。比如你希望通过https://registry.internal.example.net访问Harbor,那ExternalURL就必须填这个地址,不能写内网Service地址,也不能写带/IP的临时域名。否则登录后的Session、镜像拉取token、复制任务的引用地址都会对不上,表现形式很怪异:页面能打开,但docker login总报错。
Ingress实现上,我以最常见的Nginx Ingress为例。Harbor Chart会自动生成Ingress规则,但你需要保证集群的Ingress Controller正常,并且IngressClass配置正确。如果你用的不是默认IngressClass,记得在values里指定,不然资源创建成功但一直没人处理,页面就是一直404。
2.3 存储规划别等部署后再想
Harbor的数据分两块:元数据存在PostgreSQL里,回执任务依赖Redis,镜像层文件则由Registry组件负责写入。生产环境的存储规划在部署前就要确定,不然后面迁移会很痛苦。
元数据数据库和Redis,我的建议是直接用外部服务,这一点后面第4章详细展开。这里先提一下镜像数据的存储选型:你可以选Kubernetes的PVC,也可以选S3或MinIO这类对象存储。如果只是测试环境,PVC够用;但如果打算长期跑生产,对象存储一定是更优解。原因很简单:PVC在多副本场景下没法共享,Registry组件一旦扩容到2个实例,每个实例写入不同的PVC,你最后会得到两个内容不一致的仓库。对象存储天然支持多实例并发读写,这个问题直接不存在。
3. 实战第一步:用Helm把Harbor跑起来
3.1 最小可用values配置
在讲完整生产配置之前,我先给一份“能跑起来”的最小配置。这个配置适合测试环境和功能验证,PVC用默认StorageClass,数据库和Redis先使用内置的。
# values-min.yaml expose: type: ingress tls: enabled: true certSource: secret secret: secretName: harbor-tls ingress: hosts: core: registry.example.net registry: registry.example.net notary: notary.registry.example.net className: nginx externalURL: https://registry.example.net harborAdminPassword: "ChangeMe-2025" persistence: enabled: true persistentVolumeClaim: registry: storageClass: "" size: 100Gi database: storageClass: "" size: 10Gi redis: storageClass: "" size: 5Gi chartmuseum: enabled: false notary: enabled: false trivy: enabled: true几个关键点说一下。harborAdminPassword一定要改,默认密码是公开的,生产环境登进去第一件事就是改这个。registry.example.net这个域名要替换成你自己的,并且需要能和Ingress Controller的对外地址解析上。chartmuseum和notary我默认先关掉,前者是因为现在Helm Chart的使用场景在减少,后者是为了避免引入镜像签名后带来的客户端兼容性问题,等基础跑通再按需开启。
3.2 安装命令与启动验证
把上面的配置保存好之后,按这套命令执行:
# 添加官方Helm仓库 helm repo add harbor https://helm.goharbor.io helm repo update # 创建命名空间并安装 kubectl create ns harbor helm upgrade --install harbor harbor/harbor \ -n harbor \ -f values-min.yaml \ --version 1.18.0安装完成后不要急着登页面,先等Pod全部Ready。Harbor组件的Pod比较多,core、portal、registry、jobservice、trivy、database、redis,加起来十多个,第一次拉镜像会比较慢。
kubectl wait --for=condition=Ready pod --all -n harbor --timeout=600s kubectl get pods -n harbor所有Pod都处于Running且Ready状态后,用两行命令验证服务是否正常。第一行验证API,第二行用docker登录:
curl -k https://registry.example.net/api/v2.0/ping docker login registry.example.net -u admin如果curl返回的是一个JSON结构而不是错误页面,说明Core服务已经正常。docker login输入密码后提示Login Succeeded,就说明完整的认证链路已经打通了。这时Harbor已经可用,接下来要做初始化配置。
3.3 初始化配置:项目、用户和机器人账号
Harbor安装完是空的,需要做四件初始化动作。
第一步,修改Harbor系统配置。在UI的“管理配置”里,把项目创建权限设置为“仅管理员”,避免任何注册用户随手建项目。同时建议开启“局域网内部镜像拉取”相关的访问控制选项,具体按你们团队规范来。
第二步,创建项目。我习惯按“应用名-环境”的格式建项目,比如nginx-prod、gateway-test,项目默认设为私有。私有项目意味着没有权限的账号连拉取都会被拒绝,这对生产镜像来说太重要了。
第三步,创建用户。给研发、测试、运维分别建账号,通过“成员”功能把用户加入项目并分配角色。角色有项目管理员、开发人员、维护人员、访客几档,开发人员能推送,访客只能拉取,测试集群给访客就够了。
第四步,创建机器人账号,这是CI/CD最需要的。Harbor的机器人账号不依赖真实用户,令牌可以设置过期时间,可以按项目授权,而且可以配置成只能推送或只能拉取。流水线里用它来替代admin密码,安全得多。
初始化完成后,就可以往仓库里推第一个镜像了:
docker tag nginx:1.27 registry.example.net/nginx-prod/nginx:1.27 docker push registry.example.net/nginx-prod/nginx:1.27看到latest: digest: ...的输出,说明推送链路完全正常。到这里,一个单机模式、内部依赖的Harbor已经能服务开发测试了。但如果你要上生产,下面这章才是关键。
4. 生产化改造:外部依赖与高可用
4.1 把内置数据库和Redis换成外部服务
内置数据库和Redis虽然安装省事,但长期跑生产有几个硬伤。
首先是单点问题。内置PostgreSQL和Redis都是单实例,一旦所在节点故障,整个Harbor不可用。其次是数据备份麻烦,你需要给PVC做快照,或者手动进Pod执行pg_dump,这和现有的数据库备份体系是脱节的。最后是状态管理,Kubernetes里的有状态服务运维起来,远远比直接用托管数据库方案复杂。
我的改造建议很明确:数据库用外部PostgreSQL,Redis用外部哨兵集群,或者至少用带持久化的Redis主从节点。
外部PostgreSQL的values配置大致是这样的:
database: type: external external: host: postgres-ha.example.net port: 5432 username: harbor password: "YourStrongPassword" database: harbor改成外部数据库前,需要先确认外部PostgreSQL的空库已经创建好,并把字符集设置为UTF8。如果使用内置数据库安装过一段时间,再切外部库,需要做一次数据迁移,过程相对麻烦。所以我的建议是,从一开始就用外部数据库,不要先跑内置再迁。
Redis同理,外部化配置如下:
redis: type: external external: addr: redis-ha.example.net:6379 password: "YourRedisPassword"如果你用的是哨兵模式,配置上要区分哨兵地址和主节点集合名,这类参数在Helm Chart里有完整说明,这里不展开。核心思路是:生产环境一定不要让Harbor自己管理Redis和PostgreSQL,用已有的数据库中间件体系统一管理,监控、备份、扩容都不是额外负担。
4.2 镜像数据落盘:从PVC迁移到对象存储
Registry的存储是Harbor生产化改造里最影响长期演进的一项。前面提到,PVC在多副本场景下没法共享。实际生产中,应用层一般会给core、portal、jobservice配2个副本,如果registry也配2个副本,那它的存储必须是共享的。
对象存储是这里最标准的选择。S3兼容的存储都行,比如MinIO,或者你企业内部已有的对象存储产品。只要Harbor版本支持,配置方式如下:
persistence: imageChartStorage: type: s3 s3: bucket: harbor-registry-prod region: us-east-1 endpoint: http://minio.internal.example.net:9000 accesskey: "YourAccessKey" secretkey: "YourSecretKey" secure: false这段配置的目的是让Harbor的Registry组件把所有镜像层写入对象存储桶。端点指向内网MinIO,secure字段根据你们对象存储是否启用TLS来定,如果MinIO前面有反代加证书,也可以填true。
迁移到对象存储之后,最直观的变化是扩容不再有存储焦虑。Registry实例从1扩到3,每个实例都通过S3协议访问同一个桶,拉取镜像时还可以利用对象存储的分片并发读,大镜像的拉取速度往往比单机磁盘还好。备份也从“给PVC做快照”变成了“给桶开快照或异地复制”,容灾能力完全不是一个量级。
4.3 副本数与GC策略:高可用不只是多开几个Pod
生产环境里,我常看到有人把Harbor的副本都改成2,然后就不管了。这其实不够,真正的关键在于无状态组件能多副本,有状态的数据层已经完全外部化。
我的生产级副本数配置大致这样:
- core:2副本
- portal:2副本
- registry:2副本,存储指向对象存储
- jobservice:2副本,开启多个任务执行器
- trivy:按扫描压力调整,一般2副本
需要特别提醒的是jobservice。jobservice负责复制、GC、扫描这类异步任务,用外部Redis做任务队列后,多副本才能安全共享任务。如果不换外部Redis,两个jobservice会同时消费同一个本地队列,轻则任务重复执行,重则GC把正在使用的镜像层删掉。这一步一定不能省。
GC策略也要提前规划。Harbor删除镜像Tag后,Registry里的镜像层并不会立刻释放,必须由管理员触发“垃圾回收”才能清理无用层。垃圾回收是有风险操作的,我建议固定一个低峰窗口,每周执行一次;并且不要手动删除项目底下的所有镜像Tag再立刻GC,先确认没有活跃副本在引用这些镜像。GC的配置就在系统管理页面里,可以设置cron表达式,打开后让jobservice按时执行即可。
4.4 保生产质量的四个功能开关
基础架构改造完,再打开几个保障生产质量的功能,它们都不复杂,但能让Harbor从“存储库”变成真正合规的软件供应链节点。
第一个是漏洞扫描。Harbor默认集成了Trivy扫描器,可以在项目级别配置扫描策略,比如推送后自动扫描。扫描能力依赖漏洞库的更新,在线环境没问题,但离线环境就需要手动导入漏洞库的离线包。这块我放在第5章踩坑部分细讲,因为它非常影响离线交付体验。
第二个是配额管理。给每个项目设置存储配额和镜像数量上限,可以防止某个团队无意中把仓库塞满。我在生产环境为仓库配的是“项目级配额 + 团队维度的回收提醒”,测试项目配小一点,生产项目按需求放开。
第三个是复制规则。多环境之间同步镜像,是Harbor复制规则最常见的用途。比如测试环境推送的新镜像自动同步到预发环境,或者总部Harbor把基础镜像复制到分公司Harbor。复制规则可以按项目、按Tag过滤,也支持覆盖已有镜像,配置好后由jobservice异步执行,前面做了外部Redis和对象存储的改造,复制规则的执行稳定性和数据一致性都很高。
第四个是审计日志和操作留痕。Harbor的审计日志记录了用户登录、镜像推送、项目变更、权限调整这些关键动作。生产环境建议把日志接入已有的日志采集平台,方便事后追踪。这个动作不复杂,但很多人忽略。
5. 常见问题排查与避坑实录
5.1 高频问题速查表
我把实际运维中遇到过、也和身边同行确认过的高频问题整理成了一张表,按“现象—原因—解决”三个维度列出来,方便你直接对照排查。
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| docker push 报 413 Request Entity Too Large | Ingress对请求体大小有默认限制 | Ingress Controller配置proxy-body-size: 0或合理大小 |
| 页面访问404 | IngressClass指定错误或Ingress Controller未处理 | 检查ingressClassName配置,确认对应的Controller正常运行 |
| docker login 提示证书错误 | 自签证书未加入客户端信任链 | 把CA证书放到/etc/docker/certs.d/registry.example.net/ca.crt,或配置daemon.json的insecure-registries |
| 复制任务一直失败 | jobservice无法连接Redis队列,或外部Redis密码有误 | 检查jobservice日志,核对外部Redis连接参数 |
| GC执行后镜像空间没释放 | 还有Tag正在引用镜像层,或GC在非空闲窗口执行 | 确认被清理的镜像Tag确实已删除,再触发GC |
| 数据库连接缓慢导致core 503 | 外部PostgreSQL连接池耗尽 | 检查数据库连接池上限,调大Harbor的core连接配置 |
| 拉取镜像突然全失败 | 证书过期或ExternalURL被改 | 检查TLS证书有效期和ExternalURL配置 |
这里最诡异的是第一个问题,现象是docker push到一半报错,实际上和Harbor本身无关,纯粹是Ingress把大体积镜像请求挡了。Nginx Ingress默认对请求体大小有限制,Harbor这个镜像仓库天然就是大请求体场景,所以必须在Ingress配置里放开。我是在部署Harbor之前就把这条Ingress注解加上了。
ingress: annotations: nginx.ingress.kubernetes.io/proxy-body-size: "0"0在这里表示不限制。如果你的Ingress Controller不是Nginx的,对应的参数名不同,但思路一样:必须允许大体积Body。
5.2 离线环境部署的几个隐藏坑
离线环境部署Harbor是私有化交付里最常遇到的场景。表面看,你只需要把Helm Chart和镜像包带到内网装一遍就行,实际上有三个特别容易被低估的坑。
第一个坑是容器镜像私有化。Harbor本身有十多个镜像,分布在不同的镜像仓库地址上。离线环境如果没有任何镜像源,你要先把所有Harbor组件镜像tar保存下来,再在目标环境中import进去。我建议在制作离线包时,把helm pull拿到的Chart和docker save导出的镜像文件放在同一个目录,校验SHA256后一起拷进内网。
第二个坑是Trivy漏洞库。Trivy默认需要联网下载漏洞库,离线环境如果没配置离线库,扫描任务会一直报错或显示“数据库未初始化”。解决方法是在有网环境中提前下载指定版本的漏洞库压缩包,在内网中挂到一个HTTP服务上,然后在values里把trivy的数据库更新地址指到内网地址。这个过程我反复试过,关键是要保证漏洞库版本和Trivy版本完全一致,否则会报schema不匹配。
第三个坑是对象存储端点。内网的对象存储地址往往是短域名或IP,证书可能也只是服务端单向认证。这时候如果Harbor的Registry组件和对象存储之间还有一层代理策略,那配置里要特别小心endpoint是否能从集群内访问。我在某个交付环境就出现过这个奇怪问题:管理页面一切正常,但一旦推镜像就报写入超时,排查了很久才定位到内网DNS在部分节点没有配置MinIO的解析记录。
5.3 备份与恢复:别等丢数据才后悔
最后聊一下备份。镜像仓库的数据分两类:元数据在PostgreSQL,镜像层在存储后端。生产环境备份要覆盖这两块。
元数据备份,直接用外部PostgreSQL的备份机制就行。如果还是内置数据库,那么备份就复杂一些,需要进数据库Pod执行pg_dump,再导出到外部存储。这也是我坚持把数据库外部化的原因,备份体系能直接复用。
镜像层数据的备份,对象存储场景下走桶快照或异步复制。如果你还在用PVC,那就只能做PVC快照,但快照一致性得不到保障,恢复时容易出现数据库记录和镜像层对不上的情况。
配置层面的备份,记住把values.yaml、证书secret和所有自定义配置保存到Git里。Harbor本身的恢复并不复杂:重装Helm Chart,导入values配置,恢复PostgreSQL数据,把镜像同步或快照恢复回来,就完成了。关键是这个过程一定要演练,不要等真出事才第一次试。我会在每个季度做一次恢复演练,确保备份是能用的。
6. 部署完成之后的一点个人体会
整套流程走完,我最大的感受是:Harbor的技术难点不在安装,而在把存储、数据库、Redis这些底座想清楚。很多人一开始嫌麻烦,先用内置依赖跑起来,结果后面每次升级、扩容、备份都要为当时的省事买单。如果你是从零起步,我建议你第一步就按生产标准来做,也许多花半天时间,但后面一整年都会很省心。
还有一个很容易被忽略的小建议:Harbor部署完成后,给整个命名空间打上监控标签,接入集群的监控体系。镜像仓库的磁盘使用率、API延迟、任务队列堆积情况都是运维的盲区,等用户报问题再发现就太迟了。我是吃了这个亏之后才补上监控告警的,现在Harbor的任何异常,我基本都能比用户更早发现。
当然,Harbor本身还有很多可以继续折腾的方向,比如镜像签名策略、多级复制拓扑、基于机器人账号的细粒度权限自动化,这些都可以在企业落地过程中逐步展开。先把基础做扎实,后面的一切都是水到渠成。