做过容器化的朋友大概都有这么一段经历:镜像在本地 build 得好好的,一到多机部署就开始满世界传 tar 包,scp 来 scp 去,版本号靠文件名区分,谁也说不清生产上跑的那个 latest 到底是哪天打的包。等到团队从十几个人扩到上百人、集群从一个机房扩到两地,这套手工流程基本就崩了。企业级镜像仓库要干的事,说白了就是把这个散乱的环节收拢成一条有权限、有审计、有复制、有漏洞扫描的供应链。这篇就把我这些年搭建 Harbor 仓库时量过的参数、踩过的坑和最后沉淀下来的配置摊开讲一遍——Harbor 的企业化部署不是把官方安装包解压、跑个 install.sh 就完事,真正花时间的是存储后端、认证对接、多站点复制和后面日复一日的垃圾回收。适合已经把容器跑起来、正准备把镜像管理正规化的团队看,也适合接手了一台"前人留下的、谁都不敢重启"的仓库机器、想重构的同学参考。
1. 先想清楚:企业级镜像仓库到底要解决什么问题
1.1 从"能跑"到"敢用"的四个硬指标
很多人对镜像仓库的期待停留在"能 docker push 上去、能 pull 下来"。这在三五个人的小团队里确实够用,用 distribution 官方那个 registry 镜像,加个 basic auth 密码文件就上线了。但它撑不起企业场景,原因不在功能多寡,而在于一旦仓库挂了、镜像被覆盖了、某个历史版本带着高危漏洞被推到生产,你没有办法回溯和止损。
我判断一套仓库是否达到"企业级",一般看四个硬指标。第一是身份的单一来源,账号不能在仓库里独立维护一套,必须和公司统一的目录服务打通,人员离职当天权限自动失效,而不是靠管理员手动删人。第二是权限到项目粒度,A 产品线的开发者不能读 B 产品线的镜像,这条靠命名空间加角色绑定实现,而不是靠口头约定。第三是内容可追溯,每个 tag 谁推的、什么时候推的、推上去的镜像扫描出什么漏洞,都要留痕。第四是故障可恢复,仓库所在主机整机报废时,数据和配置能在另一处重建出来,恢复时间是可预期的,而不是"看运气"。
这四条听起来朴素,实际落地时每一条都会牵出一堆细节。比如统一认证,接了目录服务之后还有个坑:目录里的组和仓库里的项目怎么映射?人员调动后组变了,仓库权限是否跟着变?再比如可追溯,扫描出来的漏洞数是要拦发布还是只做提醒?这些问题不提前定,后面就是无穷的扯皮。
还有一点容易被忽略:仓库是所有部署流水线的上游依赖。CI 里的构建、CD 里的发布、集群的扩容,全部要拉镜像。仓库一挂,整条链路停摆。所以它需要的可用性等级,实际上和数据库、配置中心是一个级别,不能按"内部工具"来对待。我在第一次搭的时候就是按内部工具标准来的,单机、本地盘、没备份,结果一次磁盘写满导致新镜像全部推不上去,整个发布停了两天,从那以后我就再也不把它当小工具看了。
1.2 为什么最后落在 Harbor 上而不是别的
私有仓库的选项其实不少,我列个实际比对过的表,这些都是我在真实项目里跑过或评估过的:
| 方案 | 优势 | 短板 | 适用场景 |
|---|---|---|---|
| distribution(官方 registry) | 极轻,单容器即可跑 | 无 UI、无 RBAC、无复制、无扫描 | 临时测试、边缘节点本地缓存 |
| Harbor | 项目隔离、RBAC、复制、扫描、配额、保留策略齐全 | 组件多,运维面比单容器大 | 有合规和审计要求的团队 |
| 通用制品库(支持 Docker 协议) | 一个库管多种制品(npm、maven、镜像) | 镜像层性能与 GC 不如专用仓库 | 制品类型杂、追求统一入口 |
| 公有云托管仓库 | 零运维 | 数据出域、跨云迁移成本高、内网拉取要走公网 | 纯云上、无数据驻留要求 |
我最后选 Harbor,核心原因有两个。一是它的项目模型和 RBAC 跟企业的组织架构天然对齐,项目即命名空间,成员角色分维护者、开发者、访客、受限访客,配合目录服务的组绑定,人员流动时权限能自动跟着走。二是复制能力,两地机房各自有一套集群,但希望镜像只推一次、自动同步到另一处,Harbor 的复制规则可以按项目、按 tag 过滤、可限速,这套机制在我们做异地多活的时候省了大量手工操作。
至于"企业版"这个说法,需要拆开看。Harbor 本身有开源发行版和商业发行版,两者的核心仓库能力是一致的,商业版多的是官方支持、更细的审计报表、部分高级策略能力和一些企业级集成。我个人的判断是:如果你有能力自己运维一套多组件的系统,开源版足够撑起生产;真正需要付费买的是"出问题有人兜底"这件事,而不是功能。所以后面讲的所有内容,都是按生产可用标准来搭,不依赖任何商业特性。
2. 部署形态选型与资源规划
2.1 单机 compose 还是容器平台编排
Harbor 官方提供两条主路径:离线安装包(内部用 compose 编排一整套容器)和 Helm Chart(部署到容器平台)。这两条路我都在生产用过,选哪条不看时髦,看你的运维底座。
单机 compose 的优点是简单直接:一份 harbor.yml 描述全部配置,prepare 脚本生成 compose 文件,一条命令拉起,出问题看容器日志就行,没有编排层这个中间变量。缺点是它天生是单点,主机挂了就全挂,扩容只能靠"再起一套 + 复制"或者前面挂负载均衡跑多实例(需要外置数据库和 Redis)。我一般把它用在两类场景:一是中小规模、日拉取量在万级以下的团队,二是异地灾备站点的边缘仓库。
容器平台编排(Helm)的优点是和现有运维体系融合:滚动升级、健康检查、资源限制、节点亲和都由平台管,多个副本天然负载均衡。但它引入了新的复杂度:镜像层存储必须走共享存储或对象存储,不能落在本地盘;数据库和 Redis 强烈建议外置,否则 Pod 漂移后数据对不上;还有 Chart 版本和 Harbor 版本的兼容矩阵要盯。我见过最典型的事故是:数据库用了 Chart 内置的,节点重建后 PVC 没绑对,起来是个空库,UI 能登录但所有项目消失,一群人以为被攻击了。
我的建议很直接:如果你没有成熟的容器平台运维经验,老老实实用单机 compose + 外部数据库 + 对象存储,把状态全部外置之后,应用层就是个无状态容器,重建成本很低。反过来,如果你平台侧已经有稳定的有状态服务运维能力,Helm 那条路体验更好。
2.2 组件拆解与资源账本
Harbor 跑起来是一组容器,不理解各自职责,排障时就是一团乱麻。我按实际职责拆一遍:
| 组件 | 职责 | 是否可外置 | 我的资源参考(中等规模) |
|---|---|---|---|
| nginx(反向代理) | 统一入口,转发 UI、API、镜像分发流量 | 否 | 0.5 核 / 256MB |
| core | 主 API,处理项目、权限、策略 | 否 | 2 核 / 2GB |
| portal | Web 前端静态资源 | 否 | 0.2 核 / 128MB |
| jobservice | 异步任务:复制、扫描、GC、保留策略 | 否 | 2 核 / 1GB |
| registry | 真正的镜像层读写 | 否 | 2 核 / 1GB |
| registryctl | 管理 registry 配置与 GC 触发 | 否 | 0.2 核 / 128MB |
| 数据库 | 元数据:项目、用户、tag 清单、扫描结果 | 强烈建议外置 | 2 核 / 4GB |
| Redis | 缓存 + jobservice 任务队列 | 强烈建议外置 | 1 核 / 1GB |
| 扫描器(Trivy) | 镜像漏洞扫描 | 否(可配外部扫描服务) | 2 核 / 2GB |
资源账不能只看单实例。举个实测例子:一次 CI 高峰,20 个流水线同时推镜像,每个镜像 3 层、单层 200MB,瞬时写入带宽会跑到 500Mbps 以上,registry 容器 CPU 会打到 1.5 核。这时候如果给 registry 只分 0.5 核,表现就是 push 卡在某个百分比不动,客户端超时重试,越重试越堵。我后来的做法是给 registry 和 jobservice 都留足余量,反而给 portal 这种静态服务压到最低。
磁盘容量的估算我给个可复用的算法。假设有 300 个镜像仓库,每个仓库平均保留 5 个有效 tag,单 tag 有效数据 400MB(层之间共享率高,实际去重后通常远小于各 tag 之和),那么核心数据约300 × 5 × 400MB ≈ 600GB。再乘一个 1.5 的"临时层与未 GC 残留"系数,加上 20% 的增长预留,起步盘就是 1TB 左右,并且必须能在线扩容。这个数字我很在意,因为磁盘写满是 Harbor 最常见、也最容易演变成事故的故障——不是服务挂,是所有人都推不上去,流水线全红。
2.3 存储后端怎么选:本地盘、共享存储、对象存储
镜像层的存放位置是整个部署里最关键的一个决策,选错了后面迁移成本极高。
本地盘最简单,性能也最好,适合单机部署。致命点是绑死主机——主机报废,元数据能从数据库恢复,但镜像层全丢,恢复意味着所有镜像重新推一遍。而且它没法支撑多实例,因为两个副本看不到彼此的层文件。
共享文件系统(网络文件系统之类)能让多实例共享层文件,看起来解决了问题。但镜像层是"一次写入、多次读取、偶尔大量删除"的负载,共享文件系统在小文件元数据操作和 GC 阶段的大量删除上性能衰减明显,而且它自身又成了一个单点和高可用难题。我不太推荐把它当作主力方案。
对象存储是我现在的首选。Harbor 支持 S3 兼容协议,接上之后 registry 就变成了几乎无状态的读写层,多副本横向扩展非常自然,容量按需增长,不用再算磁盘。代价是延迟比本地盘高,小文件多的场景下 push 速度会慢一些,所以对象存储和 Harbor 之间的网络质量很关键——同机房内网直连没问题,跨公网那就是自找麻烦。
还有一个折中方案,我在灾备站点用过:主站用对象存储,边缘站点用本地盘 + 只读复制。边缘站点只需要 pull,不需要承担写入压力,本地盘性能最好,通过复制规则从主站同步过来,容量按"只需保留最近若干版本"的保留策略控制。这个组合性价比很高。
3. 落地实操:从零搭一套生产可用的仓库
3.1 基础环境准备:系统参数与目录规划
系统层面的准备,我列一份实际会执行的清单。这些调优不是玄学,每一条都对应一类具体故障。
# 文件句柄数:每个镜像层上传都是若干并发连接,默认 1024 在高峰期会不够 cat >> /etc/security/limits.conf <<'EOF' * soft nofile 655360 * hard nofile 655360 EOF # 内核网络参数:应对高并发短连接和大量 TIME_WAIT cat >> /etc/sysctl.d/99-harbor.conf <<'EOF' net.core.somaxconn = 32768 net.ipv4.tcp_max_syn_backlog = 16384 net.ipv4.ip_local_port_range = 10240 65000 net.ipv4.tcp_fin_timeout = 15 vm.swappiness = 10 fs.file-max = 1000000 EOF sysctl --system # 时钟同步,这条最容易被忽视,见下面的说明时钟这条我要单独强调。Harbor 的 token 认证里有时间戳校验,容器之间、客户端和服务器之间时间漂移超过容忍范围,表现是一批莫名其妙的 401,日志里还看不出明确原因。我遇到过一台机器因为长期没重启、时钟漂了十几分钟,导致只有从那台机器发起的 push 会失败,其他机器全正常,排查花了很久。所有节点必须配好时间同步服务,这是硬要求。
目录规划上,我习惯把数据和程序分开:程序装在/opt/harbor,数据卷挂在独立的数据盘/data/harbor,并且/data单独分区。这样即使程序升级出问题,数据不受影响;盘满了也只影响 Harbor,不拖垮系统盘上的其他服务。另外把容器运行时的数据目录也挪到数据盘,否则镜像层会在系统盘上悄悄吃掉几十 GB。
防火墙这边,通常只需要放通 443(或自定义的入口端口)。如果前面还有一层负载均衡,那么 Harbor 所在主机只需要对负载均衡的地址开放,不对业务网段直接暴露。我见过把 80/443 全开的配置,结果被扫描器扫到默认管理员账号,虽然只是内网,但这事写进报告里很难看。
3.2 harbor.yml 关键参数逐项配置
配置文件是整套部署的核心,我按"改哪些、为什么改"的方式过一遍。下面是一份我实际在用的精简版,注释里写了取值理由:
# 对外访问的域名,必须和证书 CN/SAN 完全一致 # 写成 IP 会导致证书校验失败,客户端必须加信任配置,不推荐 hostname: registry.lab.internal https: port: 443 certificate: /data/harbor/cert/registry.crt private_key: /data/harbor/cert/registry.key # 只启用 TLS 1.2 及以上,关掉老协议 # 具体协议版本由前置 nginx 配置控制,见 3.3 # 初始管理员密码,只在首次初始化时生效,上线后立刻改 harbor_admin_password: <首次登录后立即修改> database: # 密码用复杂串,且不要和镜像仓库的访问密码复用 password: <强密码> # 连接池:多实例部署时这几个数要一起算,见下方说明 max_idle_conns: 50 max_open_conns: 100 data_volume: /data/harbor storage_service: # 走对象存储,filesystem 是本地盘方案 s3: accesskey: <访问密钥> secretkey: <密钥> region: us-east-1 regionendpoint: http://object-store.lab.internal:9000 bucket: harbor-registry # 关键:开启分片上传,大层上传更稳 secure: false v4auth: true chunksize: 5242880 rootdirectory: / trivy: # 内网环境必须关掉在线更新,改用离线漏洞库 skip_update: true # 只报有修复版本的漏洞,减少噪音,这个取舍见 4.3 ignore_unfixed: false offline_scan: false jobservice: # 异步任务并发数,复制和扫描都受它限制 max_job_workers: 20 # 任务日志保留时长,排障时很有用,别设太短 job_loggers: - STD_OUTPUT - FILE log: level: info # 本地日志轮转,防止日志把盘吃满 local: rotate_count: 20 rotate_size: 200M连接池那段值得单独算一遍。max_open_conns是每个 core 实例的连接上限,不是全局的。假设你跑 3 个 core 副本,每人 100,那数据库侧至少要能承受 300 个来自 Harbor 的连接,再叠加运维工具、监控采集、备份的连接,数据库的max_connections得开到 450 以上才安全。我见过一套部署,core 扩到 5 副本但数据库没调,结果高峰期报"too many connections",Harbor 表现为间歇性 500,非常难定位。稳妥的算法是:实例数 × max_open_conns × 1.5 ≤ 数据库 max_connections。
max_job_workers也是个容易踩的点。它控制 jobservice 同时处理多少个异步任务,复制、扫描、GC 全在这条队列里排队。设得太小,一次大批量复制会排到天荒地老;设得太大,会同时发起几十个对外连接,把源站和目标站都打满。20 这个值是我在 4 核 8G 的 jobservice 上实测比较平衡的,超过 30 之后 CPU 上下文切换的损耗开始明显。
3.3 HTTPS 证书配置与信任分发
证书这块是新人最容易翻车的地方,我把完整的处理链条写清楚。
生产环境不要用自签证书,走企业内部 CA 签发。签发时注意两点:证书的 SAN 里必须包含访问用的完整域名,只写 CN 是不够的,现代客户端校验收紧之后只看 SAN;另外证书链要完整,服务端返回的证书文件里应该包含"服务器证书 + 中间证书",顺序是服务器证书在前。链不完整的典型表现是:浏览器访问正常(浏览器会自己去补中间证书),但命令行工具和容器运行时拉取时报x509: certificate signed by unknown authority,非常迷惑。
客户端这边的信任分发有三种做法,按推荐度排序:
- 把内部 CA 根证书预置到基础镜像和宿主机,位置是
/etc/docker/certs.d/registry.lab.internal/ca.crt(容器运行时同理,有对应的信任目录)。这是最干净的做法,一次性配置,全局生效。 - 在 CI 流水线里显式挂载 CA 文件,适合流水线容器频繁重建、镜像不便定制的场景。
- 跳过校验,也就是给客户端加"不安全仓库"白名单。这只应该出现在临时测试里,生产上这么干等于放弃了中间人防护,我不建议,也不接受在交付物里看到这个配置。
还有一个细节:证书续期。如果证书有效期只有一年,一定要在到期前留足提前量做好替换,并且把续期这件事写进值班检查项。我经历过一次证书过期,凌晨所有节点拉镜像全部失败,集群滚动更新卡住,恢复过程就是换证书 + 重启前置代理,十分钟的事,但代价是半小时的业务抖动。后来我们改成证书剩余 30 天自动告警。
3.4 启动、验证与首轮冒烟测试
配置改完,执行 prepare 生成编排文件、再拉起容器。这里有个经验:prepare 会重新生成 compose 文件,任何手工改过 compose 的内容都会被覆盖。所以所有定制必须落在 harbor.yml 里,或者放在容器外挂的配置文件目录里,不要手改生成物。
cd /opt/harbor # 生成配置,注意执行前确认 harbor.yml 语法正确 ./prepare # 拉起全部组件 docker compose up -d # 看整体健康状态,重点确认 core 和 registry 是 healthy docker compose ps # 跟踪 core 日志,启动阶段的问题都会打在这里 docker compose logs -f --tail=200 core启动后按顺序做冒烟测试:登录 Web 界面并立刻修改初始管理员密码、创建第一个项目、给项目配置成员、用命令行登录并推送一个测试镜像、拉取回来、在界面上看到 tag 和扫描状态。这几步看着基础,但能一次性验证认证、权限、存储、扫描四条链路。
推送验证的命令大概是这样,注意先把项目建出来,否则会报项目不存在的错:
# 登录,输入账号密码 docker login registry.lab.internal # 打 tag 并推送 docker tag myapp:1.0.0 registry.lab.internal/demo/myapp:1.0.0 docker push registry.lab.internal/demo/myapp:1.0.0 # 拉取验证 docker rmi registry.lab.internal/demo/myapp:1.0.0 docker pull registry.lab.internal/demo/myapp:1.0.0提示:第一次推送大镜像(超过 1GB)时如果卡住,先看前置代理的请求体大小限制和请求缓冲配置。分片上传场景下,代理把请求缓冲到磁盘再转发会导致明显的卡顿,正确做法是关闭请求缓冲、把客户端上限设为不限制。这个配置在 Harbor 自带的反向代理模板里默认是对的,但如果外面又套了一层代理,就要把那层也同步改掉。
3.5 把状态外置:外部数据库、Redis 与对象存储
前面反复强调外置,这里说清楚怎么改。核心思路是 harbor.yml 里的database和redis段落从"本地容器"模式切换成"外部地址"模式,然后重新 prepare、重启。
database: # 类型声明为外部,Harbor 就不会再启动内置数据库容器 type: external external: host: pg-primary.lab.internal port: 5432 username: harbor password: <强密码> sslmode: require coreDatabase: registry # 连接池参数同上,按副本数算总账 max_idle_conns: 50 max_open_conns: 100 redis: type: external external: addr: redis-primary.lab.internal:6379 password: <强密码> db_index: 0改造过程中有几个必须注意的点。数据库要在 Harbor 首次启动之前就准备好空库和可用账号,并且字符集和排序规则要按官方要求来,否则初始化脚本会中途失败,留下一个半成品库,后面只能删库重来。数据库必须开 TLS,sslmode: require是底线,镜像仓库的元数据里虽然不含镜像内容,但包含完整的项目结构、成员和扫描结果,明文传输不合适。
Redis 这边要配访问密码,并且只允许 Harbor 网段访问。Redis 的未授权访问是老生常谈的问题,一旦暴露,攻击者能直接篡改任务队列,往里面塞任意任务。我把这条列为"必须确认项",交付前一定会去核对一次监听地址和访问控制。
状态外置之后,应用层就变成了可以随时重建的无状态组件。这时候再在前面挂一层四层或七层负载均衡,多副本就能跑起来。有个细节:多个 core 副本之间不需要会话粘滞,API 是无状态的,token 由客户端携带;但 portal 是静态资源,多副本之间内容一致,也不需要粘滞。所以负载均衡用最普通的轮询就行,配置越简单越不容易出错。
4. 企业级能力落地:认证、权限、复制、扫描
4.1 统一认证对接与权限模型设计
目录服务对接是企业化的第一步。配置位置在管理界面的认证设置里,填上目录服务地址、查询账号、用户和组的搜索基准,然后保存。这里最容易出问题的是查询账号的权限和绑定格式,不同目录服务的 schema 差异很大,绑定 DN 写错一个字段就会报"认证失败",而界面上给的错误信息往往很模糊。
我的排查顺序是固定的:先用查询账号在目录服务侧手工验证一次能否查到用户;再确认 Harbor 填写的基准路径是否和实际组织结构一致;最后确认组属性字段名对不对。这三步能覆盖九成以上的对接问题。
组映射这块值得单独说。我一般会建一个专门的组,比如"容器平台使用者",把需要访问仓库的人统一加进去,然后在 Harbor 里把这个组绑定到项目上,赋予开发者角色。好处是人员变动全部在目录服务侧完成,Harbor 这边不用动。但有个前提:登录时会同步组信息,所以人员移出组之后需要重新登录才生效,这点要提前跟使用者讲清楚,否则会出现"我都把某人移出去了,他怎么还能推"的疑问。
项目模型的设计我踩过坑,说个具体的。早期我是按团队建项目,一个团队一个大项目,所有仓库全塞在下面。结果半年后项目里有 400 多个仓库,权限完全没法细分,想在项目里再按产品线隔离根本做不到,只能推倒重来。后来改成按产品线建项目、按仓库名划模块,一个项目对应的是一组生命周期一致、权限一致的镜像,比如"订单服务"、"网关服务"。判断依据很简单:如果两个镜像永远由同一批人维护、遵循同样的保留策略,就放同一个项目;否则拆开。
角色分配上,我的默认规则是:绝大多数人是开发者(可推可拉),CI 流水线用机器人账号也只给开发者,只有少数几个负责发布的人给维护者(可删 tag、可改配置),访客角色给需要拉取但不应推送的环境,受限访客给只读部分仓库的场景。管理员账号严格限制,并且不允许在流水线里使用管理员账号,这条是红线——流水线的凭证泄露概率远高于人工操作。
4.2 多站点镜像复制与带宽控制
复制是 Harbor 最有价值的能力之一,配置逻辑是:在源端登记一个远端仓库地址作为复制端点,然后建一条复制规则,指定哪些项目、哪些 tag 需要同步、触发方式是什么。
触发方式有三种。手动触发适合临时同步;定时触发适合按批次同步,比如每天凌晨同步一次全量;事件驱动适合"推上来就同步"的实时场景。我一般混用:核心基础镜像用事件驱动,保证异地集群随时能拉到最新;业务镜像用定时触发,避开白天的带宽高峰。
带宽限制这里有个实际的计算。假设两个站点之间是 1Gbps 专线,这条线还要跑数据库主从和其他业务同步。我通常给复制任务限到 300Mbps,留出足够余量。300Mbps ÷ 8 ≈ 37MB/s,一个 500MB 的镜像层大约 14 秒传完,看起来慢,但对比"把专线打满导致数据库同步延迟报警",这个取舍很值。限速参数在复制规则里直接填,单位是 Mbps。
标签过滤是另一个实用点。全量同步会把所有历史 tag 都搬过去,包括那些早就没人用的旧版本。用 tag 过滤规则只同步形如v*或release-*的 tag,能大幅减少传输量和目标端存储占用。我一般再加一条保留策略在目标端,只留最近 10 个版本,形成"源头全量、异地精简"的格局。
复制失败是常态,不要指望它 100% 成功。失败原因按频率排序大概是:网络抖动超时、目标端存储配额打满、证书过期或不被信任、目标端项目不存在且没开自动创建。复制任务不会自动重试到成功为止,所以必须配监控——我一般是定期查复制任务的状态,失败超过一定次数就告警,人工介入。指望它自己恢复,通常结果是两地的镜像版本悄悄出现了偏差,等到异地切换时才发现拉不到需要的版本,那就晚了。
4.3 漏洞扫描与准入门槛
镜像扫描默认集成,推上去之后自动扫,结果按严重程度分级展示。这块的配置有几个现实取舍。
第一是在线更新还是离线库。内网环境访问不了外部漏洞源,必须用离线方式导入漏洞库,并且定期更新。离线库的更新频率取决于你的合规要求,一般一周一次足够。更新动作要写进例行运维,否则库会停留在几个月前,扫描结果形同虚设。
第二是**ignore_unfixed开不开**。打开之后只报"有官方修复版本"的漏洞,噪音大幅降低;关掉则包含"尚无修复方案"的漏洞,更全面但数量会翻几倍。我的做法是生产项目关掉这个选项(要看到全貌),开发项目打开(减少干扰),用项目级别的策略区分。
第三是要不要做准入拦截。Harbor 支持配置漏洞阈值,超过阈值的镜像不允许被拉取。这个能力很诱人,但我建议分阶段上:先只报警不拦截,跑一个月看看误报率和团队反馈,再在核心项目上开启拦截,最后才全面推开。一上来就全面拦截的后果通常是:某个基础镜像爆出一个影响面很广的高危漏洞,而官方还没有修复版本,结果所有依赖它的业务全部无法部署,团队会直接把拦截关掉,后面再想推就难了。
注意:扫描结果依赖漏洞库的时效性,而漏洞库更新存在滞后。所以"扫描通过"不等于"没有漏洞",它只是当时的快照。定期重扫历史镜像这件事,我是通过保留策略配合定期手动触发扫描来做的,重点重扫那些仍在生产运行中的版本。
4.4 配额、保留策略与垃圾回收
这三件事是一套组合拳,缺一个都会导致存储失控。
配额按项目设置存储上限,超过之后拒绝推送。设置时要留余量,我一般按项目预估峰值的 1.3 倍给。太紧会导致正常的发布被拒,太松就失去了约束意义。配额触发时的报错信息比较直白,但团队第一次遇到会慌,所以我会在项目说明里写清楚"配额满了找谁、怎么申请"。
保留策略按规则自动清理旧 tag,支持按"保留最近 N 个"或"保留最近 N 天"两种维度,还可以按 tag 正则排除(比如永远保留latest和所有release-*)。配置要点是先跑 dry-run 看会删哪些,确认无误再启用。我见过一条规则写错正则,把某个产品的所有历史版本一次清空的,虽然镜像还能重新构建,但当时正在回滚的版本没了,很尴尬。
垃圾回收是真正释放磁盘的动作。删除 tag 只是把元数据标记删掉,镜像层文件还在磁盘上,必须跑 GC 才会真正回收。GC 分两种模式:离线 GC 需要先把仓库置为只读,期间不能推送;在线 GC 可以在运行中执行,但资源占用会高一些。我的习惯是每月一次离线 GC,放在业务低峰期,提前通知。
GC 有个经典陷阱:正在被引用的镜像层不会被删,即使它的 tag 已经被删了。判断依据是层的引用计数,只要有任意一个 tag 还引用着这层,它就保留。所以"删了 tag 但磁盘没降"往往是正常的,等所有引用它的 tag 都删掉再跑 GC 才会释放。这一点如果不理解,会反复怀疑 GC 没生效。
还有一个实操细节:GC 期间不要做大批量推送,两者都会大量操作存储,叠加起来容易超时。我在一次 GC 期间正好赶上流水线高峰,结果 GC 任务超时中断,留下一堆临时文件,后来还得手工清理。
5. 运维日常:备份、升级、监控与故障排查
5.1 备份策略与恢复演练
备份要覆盖两样东西:数据库和镜像层存储。两者缺一不可——只有数据库没有层文件,恢复出来是个空壳,界面能看到所有项目但拉不到任何镜像;只有层文件没有数据库,那堆文件就是无法索引的二进制垃圾。
数据库备份我用逻辑备份,简单可靠:
# 每日全量逻辑备份,压缩后落到独立的备份盘 pg_dump -h pg-primary.lab.internal -U harbor -Fc registry \ > /backup/harbor/registry-$(date +%F).dump # 保留最近 14 天 find /backup/harbor -name 'registry-*.dump' -mtime +14 -delete对象存储这边,如果用的是兼容 S3 的服务,优先用存储侧的版本控制或跨区域复制能力;退而求其次用同步工具做增量镜像。备份的关键不是备份动作本身,而是恢复演练。我的要求是每季度做一次完整恢复:在一套隔离环境里,用备份的数据库和存储重建出仓库,验证能登录、能看到项目、能拉取指定镜像。不做演练的备份,等于没有备份——我见过备份任务每天绿油油地跑着,真出事时才发现备份文件是 0 字节,因为磁盘早就满了,脚本一直静默失败。
备份文件本身也要保护。数据库备份里有完整的项目结构、成员关系和访问凭证的哈希,属于敏感数据,要加密存储、限制访问,并且和源系统放在不同的故障域。
5.2 监控指标与告警项设计
监控分三层:主机层、容器层、应用层。前两层用常规方案就行,应用层需要单独开启指标暴露。Harbor 可以在配置里打开指标端口,然后把 Prometheus 指过去。
我实际配的告警项按优先级排列:
| 告警项 | 阈值建议 | 说明 |
|---|---|---|
| 存储使用率 | > 80% 预警,> 90% 紧急 | 最关键的指标,写满即全站不可推 |
| 接口 5xx 比例 | 5 分钟内 > 1% | 反映认证、数据库、内部服务异常 |
| 接口 P99 延迟 | > 3 秒 | 镜像拉取变慢会拖垮集群扩容 |
| 核心服务存活 | 连续 3 次探活失败 | 容器崩溃或僵死 |
| 数据库连接数 | > 最大连接数的 80% | 连接耗尽会导致间歇性 500 |
| 复制任务失败 | 连续失败 2 次 | 两地镜像版本可能已偏斜 |
| 证书剩余有效期 | < 30 天 | 提前换证,避免凌晨故障 |
| 定时任务堆积 | 队列长度持续增长 | 异步任务处理能力不足 |
这八项里,我要特别强调存储和证书。这两个属于"平时完全无感、一出事就是全站级"的类型,也是最容易被漏配的。其余几项属于性能和质量类,早发现能避免小问题演变成大故障。
指标之外,日志聚合也很重要。多副本部署时,出问题的那次请求可能落在任意一个副本上,逐个登录去看日志效率极低。把所有容器的日志集中收集,按请求 ID 串联,排障效率能提升一个数量级。我在一次认证异常排查里就是靠这个定位到某个副本的时间同步有问题,否则只能靠猜。
5.3 常见故障速查表与排查路径
我把这几年的高频问题整理成表,遇到时可以直接对照。
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| 推送报 413 | 前置代理请求体限制 | 检查每一层代理的请求体上限,关闭请求缓冲 |
| 推送报 401 且账号密码正确 | 时间不同步 / token 服务异常 | 比对服务器与客户端时间,查核心服务日志 |
| 拉取报证书错误 | CA 未分发 / 证书链不全 | 检查客户端信任目录,用工具查验服务端返回的证书链 |
| 界面能开但操作报 500 | 数据库连接耗尽或连接中断 | 查数据库连接数、网络连通性、连接池配置 |
| 上传卡在固定百分比 | 存储写入超时 / 后端不可用 | 查对象存储连通性、分片上传配置、网络带宽 |
| 任务长时间 pending | 异步任务队列堵塞或工作进程不足 | 查队列长度、工作进程数、Redis 状态 |
| 删除后磁盘不降 | 层仍被其他 tag 引用 / 未跑 GC | 查引用关系,安排 GC 窗口 |
| 启动后立刻退出 | 配置语法错 / 数据目录权限 | 查启动日志首屏,核对目录属主 |
| 复制一直失败 | 端点证书 / 目标配额 / 项目不存在 | 逐个验证端点连通性、配额、自动创建开关 |
| 升级后页面 502 | 数据库迁移未完成 / 版本不兼容 | 查核心服务日志的迁移进度,确认版本升级路径 |
排障有个通用顺序我一直用:先看容器状态,再看日志首屏,再看依赖连通性,最后才怀疑业务逻辑。绝大多数问题卡在前两步就能定位。日志首屏尤其关键,启动失败的真正原因几乎总是打在最初几十行,而不是最后那行"启动失败"。
升级这件事要单独提醒。Harbor 的版本升级需要沿着小版本逐步走,不能跨大版本跳跃,而且升级前必须备份,因为数据库迁移是不可逆的。升级流程是:备份、停服、替换程序目录、重新 prepare(注意保留原 harbor.yml)、拉起、观察迁移日志。整个过程中最危险的是 prepare 覆盖了手工改过的配置,所以我的习惯是把 harbor.yml 单独版本管理,升级前先 diff 一遍。
5.4 几条写在交付文档里的经验
最后分享几条我觉得最有价值、但一般文档里不会写的经验。
第一条:仓库的容量规划要按"最坏情况"而不是"平均值"。平均每天新增 20GB,不代表磁盘能撑 50 天,因为一次大版本发布可能一天推 300GB。我一般按峰值日增量乘以 15 天来定盘,再配合配额和保留策略兜底。
第二条:所有自动化账号的权限要单独审计。流水线用的机器人账号往往权限过大,因为它要推多个项目的镜像。我的做法是给每个流水线建独立账号,只授权它需要的项目,并且定期拉一次账号列表和最近登录时间,半年没登录的直接清理。这件事做一次很枯燥,但能避免很多"这个账号是谁的、能不能删"的扯皮。
第三条:变更要能回滚,包括配置变更。Harbor 的配置文件、证书、复制规则,全部纳入版本管理。我经历过一次改复制规则时手滑把过滤条件删了,触发全量同步,把专线打满。如果有版本管理,回滚就是一条命令的事。
第四条:不要在没有验证的情况下开启任何"自动清理"类功能。保留策略、GC、配额,这三个都是"配错了很难挽回"的类型。上线前一定用 dry-run 或者影子项目验证一遍,确认删除范围符合预期。
第五条:把仓库当成有状态的核心服务来对待。它不是一个可以随便重启的辅助工具。每次重启、每次升级、每次改配置,都要按变更流程走,有备份、有回滚方案、有观察窗口。我在早期把它当普通服务随手重启,吃过一次正在上传的镜像全部失败的亏,那批镜像里有几个是当天要发布的版本,只能重新构建,硬生生拖了两个小时。这个教训之后,我对仓库类服务的操作习惯彻底变了——先看当前有没有活跃的上传和复制任务,再决定动不动手。
这套东西搭下来,前期大概要花两三天,其中包括环境准备、配置调试、冒烟测试和文档整理。后面就是日常的巡检、扩容和策略调整。真正省下的时间体现在别处:新同事入职当天就能拉到所有镜像,异地扩容不用再手工搬镜像,出了漏洞能一分钟内查出哪些镜像受影响。这些都是把仓库正经搭起来之后才有的东西。