半夜被存储告警叫起来,翻到监控大盘上那一根直冲 90% 的曲线,然后想起来上个月清理镜像的计划还没排期——这种场景,做过 Harbor 日常运维的人应该都不陌生。镜像仓库这个组件很特殊,它不像业务服务那样天天有人盯着,平时安静得像不存在,可一旦出问题,整条 CI/CD 流水线立刻断掉,测试环境拉不到镜像,生产发布卡在最后一步,所有人都来找你。我前后经手过几套不同规模的 Harbor 实例,从单机跑在测试机上当共享仓库的小打小闹,到给一整个研发中心做多项目隔离、带复制和扫描的中等规模集群,配置文件改过无数遍,也踩过不少坑。
这篇内容围绕 Harbor 的日常运维手册、配置优化指南和漏洞扫描最佳实践展开,重点不是把官方文档翻译一遍,而是把实际跑起来之后那些"文档里没写清楚、出问题才知道"的东西整理出来。适合谁看?刚开始接手 Harbor、正在做运维交接的同学可以当作巡检和故障排查的参照;已经跑了一段时间、准备做配置调优和安全加固的,可以直接拿里面的参数和策略去对照;做研发效能或者平台工程的,可以看扫描闭环那部分怎么和流水线打通。全文基于 Harbor 2.x 版本的通用实践,具体参数以你手上的版本为准,但思路和取舍逻辑是通用的。
1. 先摸清家底:组件地图决定运维边界
很多人接手 Harbor 的第一反应是打开 Web 界面点点看,这没什么问题,但只停留在界面上,后面一定会吃亏。Harbor 不是一个单体进程,它是十来个容器拼起来的组合体,每个容器有自己的职责、自己的日志、自己的故障模式。你不清楚哪个功能落在哪个组件上,出了问题就只能瞎猜。我在第一次做故障定位时,就是因为不知道扫描任务是由独立的适配器容器承担的,盯着 core 的日志翻了半天,最后发现扫描器容器早就因为内存不够被系统杀掉了。
1.1 九个容器各管什么
一套标准部署的 Harbor 跑起来,容器数量通常在九个左右,具体取决于你启用了哪些可选组件。把这些组件的职责理清楚,是后面所有运维动作的基础。
| 组件 | 职责 | 出问题时的典型表现 |
|---|---|---|
| proxy | 入口反向代理,统一转发请求 | 整个界面打不开,502/504 |
| core | 主服务,处理 API、认证、项目与权限逻辑 | 登录失败、接口报错、权限异常 |
| portal | 前端静态资源 | 页面白屏但接口正常 |
| registry | 实际存储和分发镜像的服务 | push/pull 失败,报 500 或 blob 不存在 |
| registryctl | registry 的控制接口,负责 GC 等操作 | 垃圾回收任务卡住或失败 |
| jobservice | 异步任务队列的执行器,扫描、复制、GC 都靠它 | 任务一直排队、状态不更新 |
| db | PostgreSQL,存元数据、用户、项目、任务状态 | 大量接口超时,任务状态错乱 |
| redis | 缓存与会话,jobservice 的状态协调 | 登录态异常、任务重复执行 |
| trivy | 漏洞扫描适配器(可选) | 扫描任务报错或长时间无结果 |
除此之外,早期版本还有用于 Chart 存储的组件,较新版本已经把它移除,改用 OCI 制品来承载 Helm Chart,这个变化在做升级规划时必须注意,否则升级完之后发现原来的 Chart 仓库访问不了,会手忙脚乱。
这张表的价值在于建立映射关系。比如"任务一直排队"这个现象,可能的层次有三层:jobservice 容器本身挂了、jobservice 和 redis/db 之间的连接有问题、或者任务确实太多而 worker 数量不够。有了映射,你至少知道该先看哪几个容器的日志,而不是从 proxy 一路往下翻。我个人习惯是把这九个容器的日志路径和关键错误关键字提前整理成一张小卡片,贴在运维文档最前面,半夜被叫起来的时候不用现查。
1.2 三种部署形态,选错了后面全是债
部署形态的选择基本决定了后面两三年的运维体验,而这个决定往往是在项目刚起步、大家对容量还没概念的时候做出的,很容易埋雷。
第一种是单机一体化部署,所有容器跑在一台机器上,数据落在本地磁盘。优点是简单、快、不依赖外部服务,适合测试环境、小团队内部共享、或者做 POC 验证。缺点是它没有冗余,机器挂了就全挂,而且磁盘扩容很麻烦——你总不能天天给虚拟机加盘。我见过有团队拿这种形态跑了大半年生产,最后磁盘满了,只能停机迁移,一停就是四个小时。
第二种是拆分部署,把数据库、缓存、对象存储都换成外部独立服务,Harbor 的容器本身可以跑多副本。这是绝大多数生产环境应该选的形态。数据库用独立的 PostgreSQL 实例,缓存用独立的 Redis,镜像数据放对象存储,这样 Harbor 的 core 和 jobservice 都可以横向扩到多个实例,任何一台机器挂掉都不影响整体可用性。代价是依赖变多,任何一个外部依赖出问题都会传导过来,所以监控要覆盖到这些依赖。
第三种是跨站点部署,通过复制功能在多个位置各放一套 Harbor,之间做镜像同步。这种形态主要解决两个问题:一是异地拉取速度,二是单点风险。但复制是有成本的,带宽、存储都是双份,而且复制任务的失败排查相当琐碎。我的建议是只有确实存在跨地域拉取慢、或者有明确的容灾要求时才上,不要因为"看起来更专业"就盲目铺开。
这里有个容易被忽略的点:不管你选哪种形态,external_url这个配置一定要设置成用户实际访问的地址,而不是容器内部的主机名。很多通知邮件里的链接点不开、复制的回调地址不对,根源都在这里。
1.3 上线前必须定下来的几个参数
有几个参数一旦定下来,后面改起来成本很高,建议在部署时就一次性想清楚。
数据目录位置。默认落在一个固定路径下,这个路径所在的分区要有足够空间,并且最好是独立挂载的卷。镜像数据的增长曲线通常不是线性的,某个团队开始往仓库里推大模型权重或者基础镜像,容量可能在一周内翻倍。
对外访问地址与协议。是用 HTTP 还是 HTTPS,域名是什么,证书怎么续。生产环境强烈建议 HTTPS,不是出于安全洁癖,而是很多客户端工具在 HTTP 环境下需要额外配置 insecure-registries,一旦漏配就是一堆莫名其妙的报错。
存储后端。本地文件系统还是对象存储,这个决定影响备份策略和扩容方式,后面第 3 章会详细展开。
管理员初始密码。默认密码是公开的,部署完不修改等于把仓库大门敞开。这个不需要多解释,但确实有实例跑了大半年还是默认密码。
项目配额策略。默认是不限制,意味着任何一个项目都可以把磁盘吃满。建议在部署时就设置一个全局的每项目配额上限,哪怕设得宽松一点,也比完全没有强。
2. 日常运维手册:把动作固化成例程
Harbor 的运维有个特点:平时不出事,出事就是大事。所以真正有效的做法不是等告警,而是把检查动作固化成日、周、月的例程,让问题在变成事故之前被发现。我经手的第二套实例,就是靠一套十五分钟的日常巡检,提前两周发现了数据库连接数持续上涨的趋势,最后定位到一个客户端没有正确关闭连接,避免了后面的大面积超时。
2.1 每天十五分钟:巡检清单怎么定
日常巡检不需要面面俱到,抓几个关键指标就够了。我自己的清单是这样的:
- 磁盘水位。看数据目录所在分区的使用率,超过 75% 就要开始规划清理,超过 85% 进入紧急处理流程。注意这里要看的是实际物理分区,而不是容器内的视角,容器里看到的可能是一个 overlay 层,数字对不上。
- 容器状态与重启次数。重点看 jobservice 和 trivy 这两个容器的重启计数,它们是最容易因为内存不足被系统杀掉的。重启次数在悄悄增长,说明资源配置不够,别等到彻底起不来才处理。
- 任务队列积压。看 jobservice 的待处理任务数量,正常情况下应该是零或者个位数。持续积压说明 worker 不够或者有任务卡死。
- 接口响应时间。随便调一个轻量 API,比如系统的健康检查接口,看响应时间是否正常。这个能给数据库和缓存的健康度提供一个粗略信号。
- 最近 24 小时的错误日志。用关键字过一遍 core 和 registry 的日志,重点看 500、timeout、connection refused 这类词。
这套动作熟练之后确实十几分钟能完成,但前提是你提前把命令准备好了。比如查磁盘,与其进容器翻,不如直接在宿主机上看挂载点;查任务积压,直接调 API 比点界面快得多。下面这条命令是我常用的,把待处理任务数和几个关键状态一次取出来:
# 需要先拿到管理员账号的会话或者使用 basic auth curl -s -u "admin:你的密码" \ "https://你的域名/api/v2.0/jobservice/pools" | head -c 2000 # 查询最近执行失败的异步任务,便于快速定位 curl -s -u "admin:你的密码" \ "https://你的域名/api/v2.0/projects?page_size=100" > /dev/null提示:把密码写在命令行里会留在 shell 历史中,生产环境建议用环境变量或者配置文件传入,并且定期轮换。
巡检清单不是越全越好,条目太多执行不下去,最后就变成走过场。我见过一份四十几项的巡检表,实际上没人真的每天做完,反而失去了意义。宁可只留五条,但每天雷打不动地执行。
2.2 每周的硬活:垃圾回收到底该怎么做
镜像仓库的磁盘增长有两个来源:一是新镜像,二是删除镜像之后没有被真正释放的空间。Harbor 的删除是逻辑删除,删掉一个制品只是移除了引用,底层的 blob 还在磁盘上躺着,必须靠垃圾回收任务才能真正释放。这一步做不好,就会出现"我明明删了镜像,磁盘怎么还是满的"这种经典困惑。
垃圾回收有几个关键点必须搞清楚:
第一,GC 期间 registry 会进入只读状态,这时候的 push 操作会失败。所以绝对不能在工作时间跑,建议放在凌晨或者周末的固定窗口,并且提前通知到研发团队。
第二,GC 有两种模式:一种只清理没有任何 manifest 引用的 blob,另一种会额外清理那些没有打标签的制品。第二种清理得更彻底,但风险也更高——如果有团队依赖无标签的镜像摘要来部署,清理之后就会拉不到。我个人的建议是先在测试环境验证一遍,确认没有这种用法之后再开启。
第三,并行执行是个大坑。GC 任务在跑的时候如果同时有推送操作,可能出现状态不一致。虽然新版本在这一点上做了改进,但稳妥的做法还是在执行 GC 前把写入操作暂停,或者至少确认当时的推送活动很低。
第四,GC 不是万能的。有些 blob 因为历史原因被多个 manifest 引用,或者因为之前的异常中断留下了悬空状态,GC 可能清不掉。遇到这种情况,需要结合 registry 自身的一些检查手段来定位,必要时手工处理,但一定要提前做好备份。
除了 GC,每周还应该看一下各项目的存储占用排名。这个数据在界面上能看到,也可以直接查 API。发现某个项目异常膨胀,先别急着删,去问一下负责人,很多时候是有意为之,比如在做镜像归档,你手一抖删了人家的东西,后面很难解释。
2.3 每月的必修课:备份、恢复演练与升级窗口
备份这件事,做了不代表有效,只有真正恢复过一次才算数。我遇到过不止一个团队,备份脚本跑了两年,真出事的时候发现备份文件是空的——因为脚本里的路径写错了,而从来没人检查过。
一份完整的 Harbor 备份至少包含四部分:
- 数据库。这是元数据的核心,用户、项目、权限、镜像的标签和关联关系全在里面。用 PostgreSQL 自带的导出工具做逻辑备份即可,注意要包含数据之外还要能重建结构。
- 密钥文件。Harbor 用这个文件加密敏感信息,比如远程复制的密码、存储后端凭证。这个文件丢了,即使数据库恢复了,这些加密字段也解不开,需要全部重配。
- 配置文件。包括主配置文件和生成的组件配置目录,恢复时要用到。
- 镜像存储。如果用的是本地文件系统,就是数据目录下的 registry 子目录;如果用的是对象存储,需要依赖对象存储自身的版本或快照能力,这部分不能用常规的文件备份方式处理。
# 数据库逻辑备份示例,库名与用户名按实际环境调整 docker exec harbor-db pg_dump -U postgres -d registry \ -Fc -f /tmp/harbor_$(date +%Y%m%d).dump # 把备份文件拷出来 docker cp harbor-db:/tmp/harbor_$(date +%Y%m%d).dump ./backup/ # 密钥与配置文件 tar czf harbor_conf_$(date +%Y%m%d).tar.gz \ /path/to/harbor/harbor.yml \ /path/to/harbor/common/config \ /data/secretkey恢复的顺序很关键,写反了会出问题:先停掉全部容器,然后恢复数据库,接着恢复密钥和配置,再恢复镜像存储,最后按顺序启动。启动之后不要急着宣布完成,要实际验证一遍:登录、查看项目列表、拉一个镜像、推一个镜像、跑一次扫描任务。这五项都正常,才算恢复成功。
至于升级,我的原则是"不追新、不跨跳"。跨多个大版本升级必须按官方的升级路径逐跳进行,不能一步到位。升级前必须做的三件事是:完整备份、在测试环境演练一遍、确认新版本有没有移除你正在用的功能。前面提到的 Chart 存储组件被移除,就是典型的升级踩坑点——升级前以为只是版本号变了,升级完发现业务方的 Chart 拉不下来了。
2.4 项目、用户与配额的日常治理
这部分工作技术含量不高,但特别容易积累成隐患。最常见的三个问题是:项目数量无限膨胀、机器人账号权限过大、配额形同虚设。
项目治理的核心是建立归属关系。每个项目都应该有明确的所有者和用途说明,定期清理长期无推送、无拉取的项目。我见过一个实例上有几百个项目,其中一多半是某次实验留下的空壳,既占着名字又干扰检索。清理的时候要注意,删除项目会连带删除里面的所有镜像,动手前必须确认清楚,最好先改成非公开再做观察。
权限方面,遵循最小权限原则。开发和运维人员给开发者权限就够了,管理员权限只留给真正需要的人。机器人账号(用于 CI/CD 流水线)要单独创建,只授予特定项目的推送或拉取权限,并且设置合理的过期时间。有些团队图省事,流水线直接用管理员账号,这等于把仓库的最高权限散布在各个流水线配置里,一旦某个配置泄露,后果很严重。
配额治理是控制磁盘增长的最后一环。给每个项目设置存储上限,超过后推送会被拒绝。这个策略刚开始可能会引起一些抱怨,但长远看是必须的。设置的时候不要一刀切,基础镜像仓库、业务镜像仓库、临时测试仓库的需求差别很大,按用途分组设阈值更合理。同时要留出一定的弹性,比如设成预估容量的 1.5 倍,避免过于频繁地触发拒绝。
3. 配置优化指南:从默认值到生产可用
Harbor 的默认配置是为了"能跑起来",不是为了"跑得好"。很多性能问题和稳定性问题,根源都在于默认值没有针对实际场景调整。这一章挑几个最值得改的地方展开,每个改动我都会说明背后的原因,而不是简单给一个数字让你抄。
3.1 主配置文件里最该动手的几项
主配置文件是整个部署的核心,改动之后需要重新生成组件配置并重启才生效。下面这几项是我每次部署都会过一遍的:
# 数据落盘位置,务必放在独立挂载的大容量分区 data_volume: /data # 对外访问地址,通知链接、复制回调都依赖它 external_url: https://registry.example.internal # 日志策略,避免日志把磁盘吃掉 log: level: info local: rotate_count: 50 rotate_size: 200M # 数据库连接池,按并发量调整 database: max_idle_conns: 100 max_open_conns: 900 # 异步任务并发数 jobservice: max_job_workers: 20 job_loggers: - STD_OUTPUT - FILE # 扫描器配置 trivy: ignore_unfixed: false skip_update: false offline_scan: false insecure: false timeout: 5m0s日志那一项值得多说两句。默认的日志轮转策略在小规模下够用,但只要开启自动扫描、复制这些功能,日志量会上升得很快。我遇到过一次磁盘告警,查下来是 jobservice 的任务日志没有合理轮转,几个月积累了上百 GB。所以轮转份数和单文件大小都要设,并且把任务日志的输出方式也控制一下,不需要长期保留的就别写文件。
数据库连接池的调整要看实际并发。这两个参数的含义是:最大空闲连接数和最大打开连接数。设置过小会导致请求排队等连接,表现为接口偶发超时;设置过大则会耗尽数据库的连接资源,导致整个服务不可用。一个粗略的估算方法是:单实例的 core 数量乘以单实例的并发处理能力,再留 30% 余量。比如三个 core 实例,每个大概需要 200 个连接,那么总上限设到 900 左右是比较安全的,同时要注意数据库服务端自身的连接上限不能低于这个值。
任务并发的调整要结合机器资源。扫描任务是 CPU 和内存密集型,复制任务是 IO 密集型,GC 也是 IO 密集型。把 worker 数量调太高,多个任务同时抢资源,反而会拖慢整体。我的经验是先在默认值基础上翻倍观察,看 CPU 和内存的水位,再决定是否继续加。
3.2 存储后端:本地盘和对象存储怎么选
这是架构层面影响最大的一个决定。
本地文件系统的优势是延迟低、实现简单、不依赖外部服务,适合单机部署或者对拉取速度极其敏感的场景。缺点是扩容麻烦,备份只能靠文件级复制或者卷快照,多实例共享更是做不到。如果你的 Harbor 是单机形态,本地盘基本是唯一选择。
对象存储的优势是可扩展、天然支持多实例共享、备份和容灾可以交给对象存储自身的能力。代价是每次拉取多了一跳网络,首次拉取的延迟会略高(有缓存的情况下影响不大),而且要把访问凭证管理好。多副本部署基本必须用对象存储,否则多个 core 实例之间没法共享同一份数据。
如果把对象存储用在自建环境里,有几个参数要注意:分片大小决定了上传大镜像时的请求粒度,默认值偏小会导致大镜像上传时请求数暴增;根目录前缀可以让你在同一存储桶里隔离多套环境;加密传输要按实际环境开启,内部网络可以视情况关闭以换取一点性能,但公网访问必须开。
还有一个容易被忽略的细节:对象存储的凭证轮换。凭证写死在配置里,一旦需要更换(比如安全审计要求定期轮换),就要改配置、重新生成、重启全部容器。建议提前把轮换流程写进运维文档,并且验证过至少一次。
3.3 数据库与缓存:看不见的性能瓶颈
数据库和缓存平时最没存在感,但它们一旦成为瓶颈,症状会非常分散:接口偶发超时、任务状态不更新、登录莫名其妙失败。等你顺着日志查到数据库,可能已经排查了很久。
数据库侧要关注的几个点:连接数上限(前面提过)、慢查询、以及磁盘空间。Harbor 的数据量增长主要来自任务记录和审计日志,如果开启了详细的审计,表会涨得比较快。建议定期清理过期的任务记录和审计记录,保留周期按合规要求定,一般三到六个月。清理的时候要用批量删除,别一条 SQL 删几百万行,那会把数据库锁住。
缓存侧的关键是内存。缓存承担了会话和任务协调的职责,内存不够会被系统回收,表现为登录态丢失或者任务重复执行。给缓存分配内存时,按"最大并发用户数 × 单会话大小"粗略估算,再留一倍余量。如果缓存和数据库部署在同一台机器上,要注意两者的内存争抢,我在一个资源紧张的环境里见过因为缓存触发内存回收导致 core 反复重启的情况。
注意:修改数据库或缓存的连接参数后,一定要重启全部相关容器,而不是只重启 core。因为配置是生成到每个组件的配置文件里的,只重启一个组件会留下配置不一致的状态。
3.4 反向代理与大镜像上传的那些坑
反向代理层的配置问题,表现出来的症状通常很迷惑人:小镜像推拉正常,大镜像就失败;或者上传进度卡在 99% 然后超时。这类问题八成是代理层的限制导致的。
需要检查的几个参数:
- 请求体大小限制。默认可能有一个上限,超过就直接拒绝。镜像层动辄几百 MB,这个值要么设得足够大,要么直接设为不限制。
- 读取和发送超时。大镜像上传耗时长,超时设短了会在传输中途断开。建议把相关超时设到十几分钟以上。
- 请求缓冲。开启缓冲会让代理先把整个请求体收完再转发,对大文件上传很不友好,既慢又占内存。上传路径应该关闭缓冲。
- 连接复用。开启之后能减少握手开销,对高频拉取有明显帮助。
还有一个经常被忽略的点:如果你的 Harbor 前面还有一层负载均衡或者 CDN,那两层都要检查类似的限制。我遇到过一次排查了两小时的问题,最后发现是最外层负载均衡的请求体限制没改,而 Harbor 自己的代理配置完全正常。
3.5 日志与可观测性:别等出事才想起监控
日志策略前面提过轮转,这里说说监控指标。Harbor 提供了指标接口,可以对接常规的监控体系。值得采集的指标包括:请求量、请求耗时分布、各状态码的比例、存储使用量、任务队列长度、扫描任务的成功率。
其中我觉得最有用的是"镜像拉取耗时的分位数"。这个指标能提前反映存储和网络的问题,比如 P99 从 200ms 涨到 2s,通常意味着存储层出现了瓶颈。另一个是"扫描任务失败率",突然升高往往意味着漏洞库更新出了问题或者扫描器资源不足。
日志采集方面,建议把关键组件的日志统一收集,而不是留在各个容器里。原因很简单:容器重建之后日志就没了,而出问题的时候往往需要看历史日志。收集的时候注意脱敏,日志里可能包含访问凭证和内部地址。
4. 漏洞扫描最佳实践:从能扫到能用
漏洞扫描是 Harbor 里最容易被做成"摆设"的功能。很多团队开了自动扫描,界面上也确实能看到报告,但没有人看报告,也没有任何流程去处理报告里的问题。这种情况下,扫描只是消耗了计算资源和存储空间,没有产生实际价值。要让扫描真正起作用,需要解决三个问题:扫得准、看得懂、有人管。
4.1 扫描器怎么接:几种部署姿势的取舍
Harbor 的扫描能力是通过可插拔的扫描器提供的,最常见的是 Trivy 适配器,部署在独立容器里。它的工作方式是:Harbor 把镜像摘要和访问凭证传给扫描器,扫描器自己去拉取镜像、分析、生成报告,然后把结果回写给 Harbor。
部署上有几种选择:
内置适配器。随 Harbor 一起部署,配置最简单,适合大多数场景。缺点是它和 Harbor 共享同一台机器的资源,扫描任务重的时候会影响到核心服务。
独立部署适配器。把扫描器单独部署在另一台机器上,通过接口注册到 Harbor。这种方式能把扫描负载从核心服务上剥离,适合镜像量大、扫描频繁的环境。注册的时候要注意网络连通性——扫描器需要能访问 Harbor 的 registry 接口去拉镜像,Harbor 也需要能访问扫描器的接口,两个方向都要通。
多扫描器并行。Harbor 支持注册多个扫描器,理论上可以按项目分配不同的扫描器,实现负载分担。但实际用下来,多扫描器的收益不如预期,因为报告格式和管理复杂度都会上升。除非你的镜像量真的很大,否则一个扫描器足够了。
资源分配是这一节的关键。扫描任务是 CPU 密集型,同时也要占用不少内存,因为要解析镜像层。给扫描器容器分配资源时,建议 CPU 至少给到两核,内存至少两 GB,镜像层数多、体积大的环境还要往上加。如果发现扫描任务频繁失败或者容器被系统杀掉,第一反应就应该是加内存。
4.2 扫描策略:自动、阈值和阻断
策略配置决定了扫描结果怎么影响实际流程,这部分在项目级别设置。
推送时自动扫描。这个几乎应该无条件开启。镜像推上来就自动扫,成本很低,收益是你能随时知道仓库里有什么问题。唯一的代价是每次推送都会触发一次扫描任务,镜像量大的时候会占用不少资源,可以通过调整任务并发来控制。
严重程度阈值。报告里的漏洞分级通常是危急、高、中、低、可忽略、未知这几档。阈值的作用是决定"多严重才算严重"。我的建议是从"高"开始,也就是高危及以上才触发后续动作。把阈值设成"低"会导致大量报告被标记,团队很快就麻木了。等流程跑顺了再逐步收紧。
阻断拉取。这个选项开启后,带高危漏洞的镜像在拉取时会被拒绝。听起来很美好,但实际使用要非常谨慎,因为它会直接打断业务流程。我的建议是分三步走:先在测试环境的项目上开启,观察一周,看看有多少镜像会被拦住、这些镜像是不是有必要拦;然后在非核心业务的生产项目上开;最后才在核心业务上开。开启之前一定要把例外白名单机制建好,否则某个关键镜像因为一个实际上不可利用的漏洞被拦住,业务方会直接炸锅。
还有一点要提醒:在 Kubernetes 环境里,仅仅在 Harbor 侧开启阻断是不够的,因为节点上可能已经缓存了旧镜像,绕过仓库直接使用本地副本。要做到真正的阻断,还需要在集群侧配置准入控制,在部署时校验镜像的扫描状态。这两层配合起来才算完整。
4.3 离线环境下的漏洞库更新
这是内网环境下最实际的问题。扫描器需要定期更新漏洞数据库才能发现新披露的漏洞,但内网环境没法直接访问外网,默认的自动更新会一直失败,导致报告里全是"未知"或者停留在几个月前的状态。
解决方案是搭建一条离线更新链路,大致流程是:在一台能访问外网的机器上下载漏洞库文件,通过内部的文件传输方式送进内网,再导入到扫描器容器里。
# 在有外网访问的机器上下载漏洞库(需要先安装扫描器命令行工具) trivy image --download-db-only --cache-dir ./trivy-cache # 或直接拉取漏洞库的 OCI 制品形式 # 下载完成后打包 tar czf trivy-db-$(date +%Y%m%d).tar.gz -C ./trivy-cache db # 在内网机器上导入到扫描器容器 docker cp trivy-db-$(date +%Y%m%d).tar.gz harbor-trivy:/tmp/ docker exec -it harbor-trivy sh -c \ "tar xzf /tmp/trivy-db-*.tar.gz -C /home/scanner/.cache/trivy/" docker restart harbor-trivy配置上要把自动更新关掉,改成离线模式,同时把跳过更新的选项打开,否则容器启动时会尝试联网然后卡住。这一步经常被漏掉,表现为扫描器容器反复重启、扫描任务一直报错。
更新频率上,建议每周至少一次,有重大漏洞披露时临时加更。为了让这条链路可靠,我建议把它做成半自动的:有一个脚本负责下载和打包,有一个检查步骤验证文件完整性和时间戳,导入之前要确认目标容器的路径和权限。我见过因为文件路径写错导致漏洞库一直没更新成功、而所有扫描任务都正常完成的情况——报告一直在出,只是数据是旧的,这种"假正常"最难发现。
提示:导入完成后,随便挑一个镜像手动触发一次扫描,确认报告里的漏洞数据时间是新的。这个验证步骤不能省。
4.4 从报告到闭环:分级、豁免与修复
扫描报告的价值在于闭环,而闭环的关键是把"一堆 CVE 编号"翻译成"团队能执行的动作"。
第一步是分级。不要把所有漏洞平等对待,按可利用性和影响范围排优先级。基础镜像里的高危漏洞优先处理,因为一处修复能覆盖大量基于它的业务镜像;应用层依赖里的漏洞,交给对应的研发团队处理;标记为"未修复"的漏洞单独列一类,因为很多根本没有可用的修复版本,需要评估实际风险而不是干等。
第二步是豁免机制。任何一个真实环境都需要例外,问题是怎么管。建议的规则是:豁免必须有理由、有负责人、有过期时间。比如某个漏洞在实际使用场景中不可达,可以豁免,但要在工单里写清楚为什么,并且设一个三个月后的复查提醒。禁止的是那种"全选、一键加白"的操作,那是自欺欺人。
第三步是修复的落地。我建议把扫描结果接到流水线里,作为构建的一个门禁。具体做法是在流水线里调用 Harbor 的接口,获取指定镜像的报告,按严重程度过滤,超过阈值就让构建失败。这样问题在进入仓库之前就被拦住了,而不是等积压了一堆之后再集中清理。实现上要注意区分新引入的漏洞和存量漏洞,否则一开这个门禁,所有老项目都构建失败,推不动。
# 查询某个镜像的漏洞报告摘要(示例路径,参数按实际填写) curl -s -u "user:pass" \ "https://registry.example.internal/api/v2.0/projects/myproj/repositories/myapp/artifacts/latest/additions/vulnerabilities" \ | jq '.severity // .application_vulnerability_summary'还有一个实践是定期做基线镜像治理。维护一套经过审核的基础镜像,定期扫描并更新,业务镜像必须基于基线构建。这样漏洞收敛的效率比逐个业务镜像去改要高得多。做这件事的时候要和研发团队提前沟通,因为他们可能需要适配基础镜像的版本变化。
5. 常见问题与排查实录
理论讲完,说说实际操作中最常遇到的问题。这部分内容大部分是从事故里总结出来的,希望你能少走一遍。
5.1 故障速查表
| 现象 | 大概率原因 | 排查与处理 |
|---|---|---|
| 界面打不开,502 | 代理层配置错误或 core 未正常启动 | 看 proxy 日志转发目标,检查 core 容器状态和端口 |
| 登录成功但页面空白 | 前端资源加载失败 | 检查前端容器的静态文件与代理的头信息配置 |
| push 报 500 | registry 存储不可写或认证问题 | 看 registry 日志,检查磁盘空间和权限 |
| 删除镜像后磁盘不降 | 未执行垃圾回收 | 按计划执行 GC,注意清理无标签制品的选项 |
| 扫描任务一直排队 | worker 不足或扫描器不可达 | 加并发,检查扫描器容器状态与网络连通 |
| 扫描报告无数据 | 漏洞库过旧或离线模式配置错误 | 检查漏洞库时间戳,确认更新链路 |
| 任务状态不更新 | 缓存或数据库连接异常 | 检查缓存内存与数据库连接数 |
| 大镜像上传超时 | 代理层请求体限制或超时太短 | 调整代理的大小限制和超时,关闭请求缓冲 |
| 回收站里的镜像占空间 | 逻辑删除未物理释放 | 执行 GC,确认回收站已清空 |
| 复制任务持续失败 | 目标端不可达或凭证过期 | 检查网络、证书和目标端权限 |
这张表覆盖不了全部情况,但能解决日常遇到的八成问题。遇到表里没有的,我的排查顺序是:先定位是哪个组件的行为异常(对照第 1 章的表),再看这个组件的日志,最后才去看配置。反过来从配置开始查往往会浪费很多时间。
5.2 几个印象深刻的坑
坑一:GC 和推送撞车。有一次赶在凌晨跑 GC,结果正好有个夜间构建任务在推送镜像,最后导致部分镜像的层数据不一致,拉取时报 blob 不存在。事后复盘发现是 GC 进入只读状态时正在进行的推送被中断了。教训是:GC 窗口必须和构建窗口错开,不能只看"凌晨人少",要看"凌晨有没有自动化任务"。
坑二:磁盘满了但容器还在跑。磁盘写满的时候,容器的表现不一定是直接崩溃,更常见的是各种奇怪的写入失败。数据库写不进数据,任务状态卡住,日志写不出来。这种状态下服务看起来还活着,但实际已经不可用了。所以磁盘监控必须设置在容器层面之上,用宿主机的指标,并且阈值要留足余量。
坑三:升级后复制任务全部失效。一次版本升级之后,跨站点的复制任务全部失败,日志里是一串证书相关的错误。查下来是新版本对传输安全的要求变严了,而目标端用的还是旧的自签证书。升级前如果做一遍完整的功能验证,这个问题本可以在测试环境暴露。后来我把"复制任务验证"加进了升级检查清单。
坑四:扫描器内存不足引发的连锁反应。扫描器容器被系统杀掉之后,容器编排会不断重启它,每次重启都会重新加载漏洞库,占满 CPU,反过来影响到同一台机器上的 core 服务。表现是"整个 Hornor 变慢了"。解决办法是给扫描器设置合理的内存限制并做好监控,发现 OOM 就及时扩容,而不是等它拖垮邻居。另外在资源规划上,扫描器最好不要和 core 部署在同一台机器上,尤其是镜像量大的环境。
坑五:配额设置引发的争议。给项目设了配额之后,某天一个团队发布失败,跑来问为什么推不上去。查下来是配额满了,但他们觉得自己"没有推很多东西"。实际原因是每次构建都会推一个带新标签的同名镜像,标签越积越多,存储持续增长。这件事之后我们加了镜像保留策略,每个仓库只保留最近若干个标签,从机制上控制增长,比事后一句句解释有效得多。
坑六:机器人账号权限失控。有个流水线用的机器人账号权限给到了系统管理员,后来这个账号的凭证被写进了一个公开的代码仓库配置里。发现时还算及时,但这个教训说明权限分级不是形式主义。后续我们做了两件事:所有机器人账号按最小权限创建,并且加了定期审计的机制,每季度检查一次哪些账号还有效、权限是否合理。
6. 一些实际体会
运维这套东西,经验比技术更重要,但经验往往来自踩坑。我现在做任何 Harbor 相关的变更,都会先问三个问题:备份做了没有、回滚方案是什么、影响范围有哪些人。这三个问题问完,大部分冲动操作都会被拦住。
还有一点体会是关于文档的。我最初不太在意写运维文档,觉得配置都在文件里,看文件就行。后来吃过几次亏才明白,配置里只有"是什么",没有"为什么"。为什么这个参数设成这个值、为什么这个项目不能删、为什么扫描器的内存要给这么多,这些都必须在文档里写清楚,否则接手的人会按照自己的理解重来一遍,然后踩同样的坑。我现在维护的运维文档里,每个非默认的参数都有一行注释说明原因,每个手工操作过的步骤都有一个时间戳和操作人,故障处理记录则写清楚现象、排查路径和最终方案。这份文档的价值在交接的时候体现得最明显。
另外一个实用的小技巧:把常用操作做成脚本,但不要做成全自动。比如清理镜像、导出报告这些高频动作,脚本化能省很多时间;但删除项目、修改配额这类操作,留着人工确认的环节。全自动的好处是快,坏处是快,一秒钟删掉一堆东西,想恢复就难了。