镜像仓库这东西,装起来容易,养起来难。Harbor 装完默认配置撑住几十个人的团队基本没问题,一旦推拉量上来、项目数破百、扫描任务开始排队,毛病就会一个接一个冒出来:垃圾回收卡在 99%、数据库连接被打满、扫描器超时、升级之后管理界面白屏。我这些年经手过几套 Harbor 环境,规模从单机几十 GB 到多实例几十 TB 都有,官方文档里写清楚的部分反而不是最容易出事的地方,真正让人熬夜的是那些“文档没写、但生产上一定会碰到”的边角。这份手册想做的事很直接:把 Harbor 日常运维、配置优化、漏洞扫描这三块拆开,讲清楚每个参数为什么这么调、每个动作背后的风险在哪、出问题先看哪里。适合已经装完 Harbor、正准备把它当生产设施来养的人,也适合刚接手一套历史环境、想知道从哪下手排查的同学。下面所有内容都基于 Harbor 2.x 的通用行为描述,不绑定某个具体版本号,但涉及版本差异的地方我会单独标出来。
1. 先摸清架构:每个组件坏了会表现成什么样
运维 Harbor 的第一课不是背命令,而是建立一张“组件—症状”的对应表。很多人排查问题慢,根本原因是没有把报错现象和具体容器对应上,于是在日志里到处翻。这一节先把架构讲透,后面所有的优化和排障都建立在这张图上。我个人的习惯是,接手一套环境先跑一遍docker compose ps(或者kubectl get pods),把每个组件的健康状态、重启次数、资源占用记下来,作为后续对比的基线。这个动作五分钟,能省下后面几个小时的猜测。
1.1 五个常驻角色和它们的故障边界
Harbor 的核心常驻组件大致分五类,理解它们的职责,等于掌握了排障的路由表。
harbor-core是整个系统的“大脑”,对外提供 REST API 和 Web 界面,对内负责项目、用户、配额、复制策略、保留策略这些业务逻辑,所有前端请求都先落到它身上。它挂了的表现非常直观:管理界面打不开、API 返回 502,但已经登录的客户端如果拿到过 token,短时间内拉镜像可能还是通的——因为镜像数据的实际读写不走 core。
registry是真正存镜像的组件,基于容器镜像分发规范的实现,负责 blob 和 manifest 的存取。它的故障表现是 push/pull 报错,但 Web 界面还能打开、项目列表还能看。这里有个经典误区:很多人看到推送失败就去翻 core 日志,其实应该先看 registry 容器的日志。
harbor-jobservice是异步任务执行器,垃圾回收、镜像复制、扫描触发、保留策略清理、Webhook 投递这些重活都由它调度。它出问题的表现很隐蔽:任务一直停在“Pending”或者“Running”,界面上进度条不动。任务队列积压十有八九是这里。
harbor-db是 PostgreSQL,存元数据。它一慢,整个系统都慢,因为几乎所有 API 都要查库。数据库连接数打满是 Harbor 最常见的性能故障之一,后文会专门讲。
harbor-portal是前端静态资源容器,用 Nginx 托管页面。它挂了就是白屏,但 API 依然正常,用curl打接口能验证这一点。另外从 2.x 中期版本开始,漏洞扫描能力由独立的trivy-adapter容器提供,它有自己的数据库缓存和更新周期,和主服务是解耦的。
我列一张表,把症状和组件对应起来,这张表可以直接贴到值班手册里:
| 现象 | 优先怀疑 | 次要怀疑 |
|---|---|---|
| 网页白屏、静态资源 404 | portal | 反向代理配置 |
| 接口 502、管理页打不开 | core | db 连接失败 |
| 推送失败、blob unknown | registry | 存储后端、GC 并发 |
| 任务卡住不动、队列堆积 | jobservice | db 锁、磁盘满 |
| 全站变慢、API 超时 | db | 磁盘 IO、连接池 |
| 扫描报告长期不更新 | trivy-adapter | 漏洞库同步失败 |
1.2 单机 compose 还是 K8s 集群,取决于你要扛的故障面
选型这件事没有标准答案,但有一个判断依据很实用:你能接受多长的不可用时间。单机 Docker Compose 部署的好处是简单、可控、排障路径短,所有日志都在本地目录里,出问题docker logs一把梭。缺点是它没有故障转移,宿主机的磁盘、内存、网络任何一环出问题,整个仓库就停摆。适合内部团队、CI 缓存、非关键路径的镜像中转。
K8s 上用 Helm 部署的好处是能横向扩展 core 和 jobservice、能做滚动升级、能借用集群的调度和存储能力。代价是复杂度上去了:你需要处理 PVC 的存储类、Service 的暴露方式、Ingress 的 body size 限制、Pod 的优雅退出,还有 CoreDNS 解析。我见过不少团队为了“看起来更专业”上了 K8s,结果半年内所有故障都出在存储卷挂载和 Ingress 配置上,反而不如单机稳。
一个折中的判断标准:日推送量在几千次以内、团队规模百人以下、没有跨机房容灾要求,单机加定期备份完全够用;一旦出现多团队共享、需要按项目计费或隔离、或者要对接多个集群做镜像分发,才值得上 K8s 多副本。别为了架构好看牺牲可维护性。
1.3 资源规划的一次真实估算
Harbor 的资源消耗主要来自三块:数据库、镜像存储、扫描器。前两个好算,第三个最容易被低估。我拿一套实际环境的数字做个示范。
假设 300 个活跃项目,每个项目平均保存 40 个镜像仓库,每个仓库保留 20 个 tag,平均镜像压缩后 350 MB。存储毛容量约为 300 × 40 × 20 × 0.35 GB ≈ 84 TB。这个数字看着吓人,但镜像层大量共享,实际去重后通常只有 30% 到 50%,也就是 25 到 42 TB。规划时按 40 TB 起步、留 50% 增长空间,磁盘给到 60 TB 是合理的。
数据库这边,元数据增长主要跟 tag 数和扫描报告数相关。经验值是每 10 万个 tag 大约占用 3 到 5 GB 数据库空间,加上扫描报告(每个镜像的漏洞列表以 JSON 存储),200 万条漏洞记录大概再吃掉 10 GB 左右。所以数据库盘给 100 GB、留出 WAL 和 vacuum 的空间,基本能撑很久。
扫描器的资源才是真的吃内存。Trivy 加载漏洞库时峰值内存可以到 2 GB 以上,并发扫描时每个 worker 都要占内存。如果 jobservice 的max_job_workers设成 10,扫描任务又和复制作业共用队列,高峰期内存很容易打满。我的建议是扫描器单独给 4 核 8 GB,并且把扫描任务的并发单独限制住,不要让复制作业和扫描抢同一个队列——后文 4.2 会给出具体做法。
2. 配置优化:把默认值调成能扛事的参数
Harbor 的默认配置是为“能用”设计的,不是为“扛量”设计的。这一节挑几个真正影响稳定性的参数展开,每个都说明为什么调、调到多少、调错了会怎样。所有参数都在harbor.yml里,改完之后必须重新执行./prepare再重启容器,直接改容器内的配置文件在下次重启时会被覆盖,这个坑每年都有人踩。
2.1 数据库连接池:最容易被忽视的瓶颈
harbor.yml里的database段有两个参数经常被忽略:
database: password: your_password max_idle_conns: 50 max_open_conns: 100 conn_max_lifetime: 2h conn_max_idle_time: 30m默认max_open_conns是 100,看起来够用,但实际上这个数字是每个 core 实例的连接上限。如果你跑三个 core 副本,理论上最坏情况会打到 300 个连接,而 PostgreSQL 默认的max_connections通常只有 100。结果就是连接被拒绝,API 开始随机报错,日志里出现too many clients already。
调参的思路是先算后改。假设 core 副本数为 N,每个副本期望的并发数据库操作数为 C,那么max_open_conns取 N × C,再留 20% 余量。同时把数据库的max_connections调到N × max_open_conns × 1.3,多出来的部分留给 jobservice 和运维工具。举个具体的:3 个 core 副本,每个 80 个连接,就是 240,数据库那边给到 320。
conn_max_lifetime这个参数值得单独说。它的作用是让连接定期重建,避免长时间存活的连接因为网络设备超时被静默切断,出现“连接还在但已经不可用”的假死状态。生产环境设成 2 小时比较稳妥,设成 0(永不超时)迟早会遇到半夜连接全部僵死的场景。
注意:改数据库参数前先确认数据库是内置的还是外置的。内置数据库改
harbor.yml后重启生效;外置数据库必须同时修改数据库服务端的max_connections和shared_buffers,只改 Harbor 这边没有任何意义。
2.2 Redis 缓存和会话
Redis 在 Harbor 里承担三件事:会话缓存、任务队列的部分状态、以及部分 API 的响应缓存。默认配置下 Redis 只给很小的内存,配置项在harbor.yml的redis段或者环境变量里。
关键参数是maxmemory和淘汰策略。对于纯缓存用途,建议设置maxmemory-policy为allkeys-lru,内存给到 512 MB 到 1 GB。这里要注意,如果 Redis 里同时存着会话,直接 LRU 淘汰会导致用户被强制登出,体验很差。我的做法是把会话有效期调短(比如 12 小时),让缓存的淘汰压力小一些。
还有一个隐蔽的问题:Redis 的持久化。如果开了 RDB 快照,在镜像仓库这种写入不算特别频繁但要求低延迟的场景里,快照期间的 fork 会带来瞬时延迟抖动,表现为 API 偶发的慢请求。在内存不大、对丢失不敏感的场景下,我一般建议关掉 RDB 和 AOF,把 Redis 当纯缓存用,重启后用户重新登录即可。当然这取决于你们对登录状态的容忍度,需要和业务方确认。
2.3 存储层:文件系统、对象存储与配额
存储驱动分两类:本地文件系统和对象存储。本地文件系统(filesystem)配置简单,性能好,但扩展性差,单盘容量就是上限。对象存储(S3 兼容、或各大云的对象存储)扩展性好,但每次拉取都要走网络,延迟比本地高,而且会额外产生流量费用。
选型的关键在于镜像拉取的频率和分布。如果镜像主要被同一个机房内的集群消费,本地文件系统加 RAID 的性价比最高;如果是多集群、跨地域分发,对象存储几乎是唯一选择。我遇到过一个反例:某团队为了“云原生”把镜像仓库后端换成了对象存储,结果 CI 流水线每次构建都要跨公网拉基础镜像,构建时间从 40 秒涨到 3 分钟,最后老老实实换回了本地盘加本地缓存。
配额这块,Harbor 支持项目级别的存储配额。开启之后,项目超出配额就无法推送新镜像。这个功能很好用,但有几个细节必须知道:配额统计的是逻辑大小,不是去重后的物理大小。也就是说同一个基础镜像被十个项目引用,每个项目都会被算一遍。如果按物理容量去规划配额,很容易出现“磁盘还剩一半但所有项目都推不上去”的尴尬。规划时按逻辑大小算,或者干脆给配额留足冗余。
配置示例:
storage_service: filesystem: rootdirectory: /storage # 或者对象存储 # s3: # accesskey: xxxx # secretkey: xxxx # region: us-west-1 # bucket: harbor-storage # endpoint: https://s3.example.internal改存储驱动是高风险操作,必须先做数据迁移演练。不要直接改配置重启就完事,那样只会导致所有镜像“消失”——元数据里的存储路径还是旧的。
2.4 日志、上传大小与超时
日志配置看起来无关紧要,实际上它直接决定你排障时能不能拿到有效信息。默认日志级别是info,保留 5 个文件、每个 200 MB 左右,在推拉量大的环境里,一天就能把日志写满一轮,等你第二天去查昨天的报错,文件已经被轮转掉了。
log: level: info local: rotate_count: 20 rotate_size: 200MB location: /var/log/harbor我的建议是把rotate_count调到 20 以上,磁盘空间紧张的话把单个文件大小降到 100 MB,用数量换时间跨度。debug级别不要长期开,日志量会翻好几倍,而且包含大量敏感路径信息。
另一个高频配置是反向代理的上传限制。Harbor 前端通常挂一层 Nginx 或 Ingress,默认的client_max_body_size往往只有 1 MB 到 10 MB,而镜像层动辄几百 MB。表现为推送大镜像时报413 Request Entity Too Large,小镜像完全正常,很有迷惑性。Nginx 侧要显式设置:
client_max_body_size 0; proxy_request_buffering off; proxy_read_timeout 900s; proxy_send_timeout 900s;client_max_body_size 0表示不限制,配合关闭请求缓冲,可以让大层直接流式转发,避免代理先把整个层缓存到本地磁盘再转发导致的磁盘打满。超时时间调到 900 秒是为了覆盖超大层在慢速网络下的传输时间,默认 60 秒对几百 MB 的层来说根本不够。
3. 日常运维动作:该手动做的一件都不能省
配置调好只是起点,真正决定一套 Harbor 能活多久的,是日常动作有没有形成节奏。这一节讲四个必须定期做的事,以及每件事做错时的后果。
3.1 项目、用户与权限的治理节奏
Harbor 的权限模型分系统级和项目级两层。系统级有管理员,项目级有项目管理员、开发者、访客、受限访客。问题往往不出在权限设计上,而出在权限只加不减。半年下来,一个项目里挂着二十个开发者,其中一半人早就调走了,镜像仓库变成了一个只进不出的权限黑洞。
我的做法是每月做一次权限盘点,重点看三类账号:超过 90 天没有登录的、加入了多个项目但只推过一次镜像的、以及所有系统管理员账号。系统管理员账号数量建议控制在三个以内,并且强制走 LDAP/OIDC 对接,本地账号只留一个应急账号并设置强密码。这样做的好处是,人员离职时只需要在目录服务里禁用账号,Harbor 侧自动失效,不用挨个环境去删。
项目命名也要有规矩。我见过太多环境后期变成test、test2、test-new、test-最终版这种命名,最后没人敢删。建议从第一天就按“业务线-用途”的格式约定,比如order-service-prod、order-service-ci,并且在项目描述里写清楚负责人和清理策略。这个规矩后期能省下大量清理成本。
3.2 垃圾回收和保留策略配合着用
这是 Harbor 运维里最需要理解原理的一块。删掉一个 tag 或者一个仓库,并不会释放磁盘空间,只是把元数据标记为删除,blob 还躺在存储里。真正的空间回收靠垃圾回收(GC)任务,它扫描存储、找出没有被任何 manifest 引用的 blob,然后删掉。
GC 有几个必须知道的特性。第一,执行期间仓库进入只读模式,也就是说这段时间内所有推送都会失败。所以 GC 一定要安排在有维护窗口的时段,或者至少提前通知。第二,GC 是重 IO 操作,会遍历整个存储,几十 TB 的环境跑几个小时很正常,期间存储的 IO 会被吃掉不少。第三,GC 默认不删除没有 tag 的 manifest,需要显式开启delete_untagged,否则那些被“删 tag”留下的悬空 manifest 依然占着空间。
通过 API 触发 GC 的示例:
curl -u admin:password -X POST \ "https://harbor.example.internal/api/v2.0/system/gc/schedule" \ -H "Content-Type: application/json" \ -d '{ "schedule": {"type": "Custom", "cron": "0 2 * * 6"}, "parameters": {"delete_untagged": true, "workers": 3} }'workers参数控制并发,不是越大越好。存储是机械盘的话给 2 到 3,SSD 可以给到 5。设太高会让存储 IO 打满,影响正常推拉。
和 GC 配套的是保留策略(Retention Policy)。它能按规则自动清理旧 tag,比如“每个仓库只保留最近 10 个 tag”或“保留最近 30 天内有拉取记录的 tag”。这两者要配合着用:保留策略负责日常清理,GC 负责定期回收空间。如果只做保留策略不跑 GC,磁盘会一直涨;如果只跑 GC 不设保留策略,tag 会无限堆积,GC 也删不掉正在被引用的东西。
注意:GC 和保留策略的执行时间要错开,不要设在同一时刻。保留策略删除 tag 之后需要隔一段时间再跑 GC,才能把对应的 blob 释放掉。我一般把保留策略设在每周五凌晨,GC 设在每周六凌晨。
3.3 备份要备份到什么粒度
Harbor 的备份分两部分:元数据和镜像数据。很多人只备份了数据库,恢复的时候发现所有镜像都指向空路径,因为镜像实际存在存储卷里。
完整的备份清单包括四样东西:PostgreSQL 的逻辑备份、存储目录(或者对象存储的桶)、harbor.yml配置文件、以及 core 的加密密钥文件。最后这个最容易被漏掉,它一般在common/config/core/secret这个路径下,用于加密敏感配置(比如复制的目标密码、Webhook 的密钥)。密钥丢了,即使数据完整恢复,这些加密字段也解不开,需要手动重新配置。
数据库备份用pg_dump就够了:
docker exec harbor-db pg_dump -U postgres -Fc registry > /backup/harbor-db-$(date +%F).dump-Fc是自定义格式,压缩率高,恢复时可以用pg_restore并行,比纯 SQL 文件快很多。备份频率看变更量,元数据每天一次,存储目录用增量同步(rsync或者对象存储的版本控制)每周一次足够了。
恢复演练必须做。我见过太多团队备份文件攒了一年,真出事的时候发现备份脚本早就因为密码变更而静默失败了。加一个检查:每次备份完成后校验文件大小和pg_restore -l能否正常列出内容,失败了就告警。
3.4 版本升级的顺序和回退
升级是风险最高的日常动作。Harbor 的版本升级有两条硬性约束:不能跨大版本跳跃,必须按小版本逐级升;升级前必须备份,而且备份要能独立恢复。
标准流程是这样的:先读发布说明里标红的破坏性变更(比如某个配置项被废弃、某个 API 被移除),然后停掉所有服务,备份数据库和配置,下载新版本的安装包,把旧的harbor.yml迁移到新包里(新版可能新增了必填项,./prepare会提示),执行./prepare生成新的 compose 文件,最后启动。
# 升级前 docker compose down docker exec harbor-db pg_dump -U postgres -Fc registry > /backup/pre-upgrade.dump cp harbor.yml /backup/harbor.yml.bak # 解压新版本,迁移配置 tar xzf harbor-offline-installer-vX.Y.Z.tgz cp /backup/harbor.yml ./harbor/harbor.yml cd harbor && ./prepare # 启动 docker compose up -d回退的关键在于数据库。Harbor 升级时会自动执行数据库迁移,迁移通常是单向的,旧版本无法识别新表结构。所以回退必须连数据库一起回退到升级前的备份。这意味着升级期间产生的数据会丢失,所以升级一定要安排在低峰期,并且提前通知用户。
我在实际环境里的做法是先在一套同配置的预发环境上完整演练一遍,把耗时、报错、需要的额外操作都记下来,生产升级时照着单子走。演练一次大概花两小时,能避免生产上几个小时的停机,很值。
4. 漏洞扫描:从“能扫”到“扫得准、修得动”
漏洞扫描这个功能,装起来最省事,用好最难。默认配置下它能给你一堆报告,但报告里哪些该修、哪些是误报、哪些根本修不了,需要一套判断标准。这一节讲接入、调度、分级和离线四件事。
4.1 扫描器接入和数据库同步
Harbor 的扫描能力来自外部的扫描器适配器,默认配套的是 Trivy。它在安装时通过--with-trivy参数启用,也可以后加。启用后 Harbor 会自动把它注册成一个 scanner,你可以在“系统管理-扫描器”页面看到它的健康状态。
# 安装时启用 ./install.sh --with-trivy # 或者后加 docker compose up -d trivy-adapter扫描器最重要的行为是漏洞库同步。Trivy 会定期从上游拉取漏洞数据库,默认间隔大概是六小时。这个数据库决定了扫描结果的时效性。如果同步失败(网络不通、磁盘满、上游限流),扫描出来的结果就是旧的,而且界面上不会明显报错,只是报告里少了新披露的漏洞。这是个很危险的静默失败。
排查同步状态要直接看扫描器日志:
docker logs trivy-adapter --tail 200 | grep -i "update\|download\|error"正常情况下能看到周期性的数据库更新时间戳。如果日志里全是重试失败,先确认网络出口,再确认磁盘空间。数据库文件通常在几百 MB 到 1 GB 之间,磁盘满的话更新会静默失败。
还有一个参数值得调:SCANNER_TRIVY_VULNERABILITY_DB_REFRESH_INTERVAL。默认值偏保守,如果你的环境对漏洞披露很敏感,可以缩短到 2 小时;如果只是内部合规检查,拉长到 24 小时能省不少资源。
4.2 触发时机、并发与资源占用
扫描的触发方式有三种:推送时自动扫描、手动触发、定时全量扫描。三者各有代价,需要组合使用。
推送时扫描的好处是“问题早发现”,坏处是拖慢推送流程。一个中等大小的镜像扫描要十几秒到一分钟,如果 CI 流水线同时推送几十个镜像,扫描任务会瞬间把队列塞满。我的建议是只在生产项目的仓库上开启推送扫描,开发项目用手动触发。因为开发镜像迭代快,扫描结果还没来得及看就被新版本覆盖了,纯属浪费资源。
定时全量扫描用于兜底,比如每周扫一次所有生产镜像,覆盖那些“上次扫描之后漏洞库又更新了”的情况。这个任务量很大,几百个仓库的环境可能要跑几个小时。
并发控制的关键在 jobservice 这边:
jobservice: max_job_workers: 10 max_job_duration_hours: 4 job_loggers: - STD_OUTPUTmax_job_workers是整个 jobservice 的并发上限,扫描、复制、GC 共用这个池子。设成 10 意味着最多同时跑 10 个任务。如果你发现扫描任务总是在排队,别急着调大这个数,先看看是不是复制作业占着 worker 不放。我一般的配置是:worker 总数 12,其中扫描任务的并发通过项目级别限制在 4 以内,剩下的留给复制和 GC。
内存方面,扫描器的峰值内存和镜像的层数、文件数正相关。一个包含几万个文件的大型镜像(比如带完整运行时的基础镜像),扫描时内存占用可能超过 1 GB。如果扫描器容器只给了 2 GB 内存,并发两个就可能 OOM。所以扫描器容器的内存限制要给足,我一般给 4 到 8 GB,并且用docker stats观察峰值。
4.3 CVE 分级、豁免与闭环
扫描报告出来之后,真正的难点是决策:哪些必须修、哪些可以接受、哪些根本修不了。我的做法是建立一个三级分类:
必须修:严重等级为 Critical 和高危,且存在官方修复版本的漏洞。这类漏洞有明确的升级路径,不修没有借口。
评估后接受:中危、低危,或者虽然高危但修复版本会破坏现有功能。这类需要记录评估结论和责任人,并且定期复核。
无法修复:基础镜像里带的、上游已经不维护的组件,或者误报。这类要进豁免清单。
Harbor 本身支持在项目级别设置 CVE 豁免(CVE Allowlist),把已知误报或者评估后接受的 CVE 加进去,这样扫描报告就不会一直飘红。用这个功能要克制:豁免必须带过期时间,最长不超过一个季度,到期自动失效,强制重新评估。否则豁免清单会变成垃圾桶,什么漏洞都往里扔。
闭环的关键是“扫描结果要能推送到开发那里”。Harbor 支持扫描完成后的 Webhook 通知,可以配置成把结果推到内部的消息系统或者工单系统。没有这个通道,扫描报告永远只有安全和运维在看,开发根本不知道自己的镜像有问题。
一个实用的做法是:在 CI 流水线里加一道检查,调用 Harbor 的 API 获取镜像的漏洞摘要,如果 Critical 数量超过阈值就阻断发布。
curl -s -u "robot\$ci:token" \ "https://harbor.example.internal/api/v2.0/projects/order-service/repositories/web/artifacts/sha256:xxxx/additions/vulnerabilities" \ | jq '[.[] | select(.severity == "Critical")] | length'返回数字大于 0 就让流水线失败。这个检查比任何流程文档都管用,因为它在阻止发布的位置上。
4.4 离线环境的扫描方案
很多内网环境没法直连上游漏洞库,这时候需要离线方案。Trivy 支持离线数据库文件,思路是在能联网的机器上下载数据库包,再传到内网让扫描器加载。
Harbor 的 Trivy 适配器提供了offline_scan相关配置。开启之后,扫描器不会主动去拉数据库,而是使用挂载进去的本地数据库文件。具体做法是把数据库文件放到一个持久化卷里,配置里指向这个路径,然后定期(比如每月)用外部机器下载最新的数据库包替换进去。
这里有三个细节会让人踩坑。第一,数据库文件有版本兼容性要求,扫描器版本和数据库格式必须匹配,升级扫描器版本时要同步更新数据库包。第二,替换数据库之后必须重启扫描器容器才会生效,热加载不一定可靠。第三,离线环境的数据库更新是最容易断的,因为没有网络失败告警,建议加一个监控:检查数据库文件的时间戳,超过 30 天就发告警。
5. 监控告警与故障速查
前面四节讲的是怎么把系统养好,这一节讲怎么在它出问题时第一时间知道、并且快速定位。监控不是装个面板就完事,关键指标选得对,才能提前预警而不是事后复盘。
5.1 必须盯住的几类指标
Harbor 从某个版本开始暴露 Prometheus 格式的指标,需要在配置里显式开启:
metrics: enabled: true port: 8001 path: /metrics指标很多,但真正值得设告警的只有几十个。我按优先级列一下:
可用性指标优先。core 的 HTTP 请求成功率和 P99 延迟、registry 的请求错误率。这两个决定了用户能不能正常用。
资源指标次之。数据库连接池的使用率(当前连接数 / 最大连接数),超过 80% 就要告警;磁盘使用率,存储卷超过 85% 就必须处理,因为 GC 期间会临时占用额外空间;扫描器容器的内存使用率。
队列指标容易被忽略但很关键。jobservice 的待处理任务数、任务平均等待时长。待处理任务持续超过 50 个或者等待时长超过 10 分钟,说明 worker 不够或者有任务卡死。
业务指标用于容量规划。每日新增镜像数、每日拉取次数、存储增长速率。存储增长率这个数字特别有用,能让你在磁盘满之前的两三个月就开始规划扩容。
告警阈值不要设得太紧,否则天天误报,最后所有人都把告警静音了。我一般的做法是:可用性告警立即发,资源类告警持续 5 分钟才发,业务类指标只做看板不发告警。
5.2 高频问题速查表
这张表是我这些年攒下来的,按现象查原因,能覆盖八成以上的日常故障。
| 现象 | 可能原因 | 排查动作 | 处理方式 |
|---|---|---|---|
| 推送报 413 | 反向代理 body 限制 | 查看代理配置 | 设置 body size 为 0 |
| 推送报 blob unknown | GC 并发执行或存储不一致 | 确认 GC 时间、检查存储挂载 | 等待 GC 结束、校验存储 |
| 登录后频繁掉线 | Redis 会话被淘汰或重启 | 检查 Redis 内存和重启次数 | 调整淘汰策略、关闭持久化 |
| 任务一直 Pending | jobservice worker 耗尽 | 查看任务队列和 worker 数 | 调大 worker、清理卡死任务 |
| 扫描报告不更新 | 漏洞库同步失败 | 查看扫描器日志和时间戳 | 修复网络、手动触发更新 |
| 管理页白屏 | portal 容器异常、静态资源 404 | 浏览器开发者工具看请求 | 重启 portal、清缓存 |
| API 偶发 502 | core 连接数据库失败 | 查连接池使用率、数据库日志 | 调整连接池、检查数据库负载 |
| 磁盘持续增长 | 未跑 GC 或保留策略失效 | 查看 GC 历史记录 | 配置保留策略加定时 GC |
| 镜像删除后空间不释放 | 未执行 GC | 确认 GC 是否运行 | 手动触发 GC |
| 复制任务失败 | 目标端凭证过期、网络不通 | 查看复制任务详情和日志 | 更新凭证、检查网络 |
5.3 几条我自己踩过坑才记住的经验
第一条,不要在生产环境直接改容器里的配置。Harbor 的容器启动脚本会用harbor.yml重新生成配置,你手动改的东西在下次重启时全部丢失,而且这种“临时改好了”的假象会让人误以为问题解决了,直到某次重启后故障复现,排查难度翻倍。所有改动都走harbor.yml加./prepare。
第二条,GC 的时间和保留策略的时间一定要错开,而且要和备份时间错开。我曾经把 GC 和备份设在同一个凌晨两点,结果 GC 把存储 IO 占满,备份脚本超时失败,第二天没人发现,一周后磁盘告警才暴露出来,那周的备份是空的。
第三条,升级前一定要确认扫描器版本和 Harbor 版本的兼容性。这两个组件的版本是独立的,某些组合下扫描器会注册失败但不报错,界面上显示绿色,实际扫描任务全部挂起。升级后要做一次真实扫描验证,随便找个镜像手动扫一下,确认能出报告。
第四条,给数据库留足磁盘。PostgreSQL 的 WAL 日志、自动 vacuum、以及扫描报告的增长,会让数据库磁盘消耗远超你的预期。我见过一套环境数据库盘只给了 20 GB,运行一年后因为一次大批量删除触发的 autovacuum 直接把盘写满,整个系统挂了两小时。数据库盘按元数据量的三倍规划,比什么都稳。
第五条,保留策略上线前先跑一遍模拟。Harbor 的保留策略在配置页面提供 dry-run 预览,能看到哪些 tag 会被删。一定要看这个列表,确认里面没有正在用的镜像。我见过有人把规则设成“保留最近 5 个 tag”,结果某个镜像半年没更新但一直在用,被清掉之后下游全部拉取失败,恢复只能靠备份。
最后分享一个小的习惯:给每套 Harbor 环境建一个运维台账,记录每次配置变更、升级、故障的时间点和处理过程。这东西平时看着没用,等半年后有人问“为什么这个参数是这个值”,你能三十秒内翻到当时的记录,而不是靠回忆。台账不用复杂,一个 Markdown 文件放在内网文档里就够了。