概述
凌晨两点,手机震了一下。磁盘告警——95%。
爬起来一看,Harbor 服务器 /data/registry 目录占了 200GB,里面堆了 3000 多个 tag。有去年测试时推的 app:v0.0.3-beta,有构建失败残留的 app:feature-branch-xxx,还有十个版本的 base image 副本——每个都 800MB 起步。
这不是个例。我在多个团队见过同样的剧本:CI/CD 跑起来之后,镜像只管推不管清,三个月后磁盘就炸。更麻烦的是,有些镜像带着已知 CVE 漏洞跑在生产上,权限管理形同虚设——谁都能推、谁都能拉,项目之间没有隔离。
这篇文章解决三个问题:镜像怎么管权限、怎么拦漏洞、怎么清垃圾。我会用 Harbor(CNCF 毕业项目,目前 GitHub 29k+ star)从安装到生产配置走一遍,每一步都给出可直接复制的配置。如果你刚开始搭制品仓库,或者已经有了但管得稀里糊涂,这篇能帮你少走弯路。
为什么需要制品仓库
先说清楚一个概念:制品仓库(Artifact Repository)不是 Docker Hub。
Docker Hub 是公共镜像托管平台,类似 npm 的 registry。你在上面拉官方镜像没问题,但拿它存公司业务镜像?有几个硬伤:
- 没有细粒度权限。Docker Hub 的 organization 只有 admin 和普通成员,做不到"测试团队能推 dev 项目、不能碰 prod 项目"
- 没有漏洞扫描。推上去就推上去了,CVE 该有还是有
- 没有保留策略。镜像只增不减,除非手动删
- 网络不稳定。国内拉 Docker Hub 限速、超时是家常便饭
Harbor 解决的就是这些问题。说白了,Harbor = Docker Registry + 权限管理 + 漏洞扫描 + 复制同步 + 审计日志。它像一个带安保、带分拣、带回收站的仓库,而不是一个露天堆场。
类比一下:Docker Hub 像公共停车场,谁都能进;Harbor 像企业内部车库,分区域(项目)、刷卡进(RBAC)、进门安检(漏洞扫描)、定期清退僵尸车(GC + 保留策略)。
Harbor 架构速览
不堆术语,直接看图:
┌─────────────┐
│ 客户端 │ docker push / pull
└──────┬──────┘
│
┌──────▼──────┐
│ Nginx │ 反向代理 + TLS
│ (Portal) │
└──────┬──────┘
│
┌────────────────┼────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Core API │ │ Registry │ │ JobService │
│ (API+UI) │ │ (Distribution)│ │ (扫描/复制/GC)│
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
┌──────▼──────┐ ┌─────▼───────┐ ┌─────▼───────┐
│ PostgreSQL │ │ Storage │ │ Trivy │
│ (元数据) │ │ (文件系统) │ │ (扫描引擎) │
└─────────────┘ └────────────┘ └─────────────┘
几个核心组件,一句话说清各自干嘛:
| 组件 | 作用 | 類比 |
|---|---|---|
| Core | 处理 API 请求、管理项目和用户 | 仓库前台 |
| Registry | 实际存镜像文件,基于 Docker Distribution | 货架 |
| JobService | 跑异步任务:扫描、复制、GC | 搬运工 |
| PostgreSQL | 存元数据:项目、用户、镜像 tag 信息 | 台账 |
| Trivy | 扫描镜像里的 CVE 漏洞 | 安检员 |
| Redis | 缓存会话和作业状态 | 便签纸 |
不需要记住每个组件的内部实现。你只要知道:推镜像走 Nginx → Core → Registry,扫描和 GC 走 JobService 异步执行。理解这个链路,后面排障就有方向了。
从零安装 Harbor
生产环境推荐用 Harbor 官方的 Helm Chart 部署到 K8s。但如果你刚开始验证,docker-compose 方式更快。下面两种方式都给出。
方式一:docker-compose(开发/测试环境)
# 下载 Harbor 离线安装包
wget https://github.com/goharbor/harbor/releases/download/v2.12.0/harbor-offline-installer-v2.12.0.tgz
tar xzf harbor-offline-installer-v2.12.0.tgz
cd harbor
# 复制配置模板
cp harbor.yml.tmpl harbor.yml
关键配置项(harbor.yml):
# HTTPS 配置——生产环境必须开 TLS
hostname: harbor.yourcompany.com
http:
port: 80
https:
port: 443
certificate: /data/cert/harbor.crt
private_key: /data/cert/harbor.key
# 管理员初始密码,首次登录后立即改
harbor_admin_password: Harbor12345 # 别用这个!
# 数据存储目录
data_volume: /data/harbor
# 数据库配置
database:
password: change_this_to_random_string
max_idle_conns: 50
max_open_conns: 100
# Trivy 扫描器
trivy:
ignore_unfixed: false # 不要忽略未修复的漏洞
skip_update: false # 每次启动更新漏洞库
offline_scan: false
# 垃圾回收
garbage_collection:
enabled: true
schedule: "0 3 * * *" # 每天凌晨 3 点执行
# 审计日志
audit_log:
enabled: true
安装和启动:
# 生成自签名证书(测试环境用,生产请用正式证书)
mkdir -p /data/cert
openssl req -x509 -newkey rsa:4096 -nodes \
-keyout /data/cert/harbor.key \
-out /data/cert/harbor.crt \
-days 365 \
-subj "/CN=harbor.yourcompany.com"
# 安装
./install.sh --with-trivy --with-chartmuseum
# 启动后验证
docker-compose ps # 所有组件应该是 healthy
方式二:Helm Chart(生产环境)
# 添加 Harbor Helm 仓库
helm repo add harbor https://helm.goharbor.io
helm repo update
# 创建 values.yaml 覆盖默认配置
cat > harbor-values.yaml << 'EOF'
expose:
type: ingress
tls:
enabled: true
certSource: secret
secret:
secretName: harbor-tls
externalURL: https://harbor.yourcompany.com
# 持久化存储——生产环境必须配
persistence:
persistentVolumeClaim:
registry:
size: 500Gi
storageClass: managed-nfs-storage
database:
size: 20Gi
storageClass: managed-nfs-storage
trivy:
size: 10Gi
storageClass: managed-nfs-storage
# Trivy 扫描配置
trivy:
enabled: true
skipUpdate: false
# 垃圾回收
garbageCollection:
enabled: true
schedule: "0 3 * * *"
# 资源限制
resources:
core:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
# 高可用(需要外置 PostgreSQL 和 Redis)
# 生产环境建议至少 2 个 Core 副本
core:
replicas: 2
EOF
# 安装
helm install harbor harbor/harbor \
-f harbor-values.yaml \
-n harbor \
--create-namespace
安装完成后,浏览器打开 https://harbor.yourcompany.com,用 admin 登录,第一件事改密码。
项目与权限分层
Harbor 的权限模型很直观:用户 → 项目 → 角色。项目是隔离的基本单位,类似 GitLab 的 Group 或 GitHub 的 Organization。
为什么要用项目隔离
我在某出行项目踩过坑。一开始图省事,所有镜像都推到一个叫 library 的默认项目里。结果:
- 测试同事手滑把
payment-service:v2tag 覆盖了生产镜像 - 没人分得清哪些是测试镜像、哪些是生产镜像
- 等保审计时被指出"缺乏访问控制",直接扣分
后来拆成 dev、staging、prod 三个项目,权限分层后才理顺。这个教训记住:镜像仓库的第一道防线不是漏洞扫描,是项目隔离。
四种角色和权限
Harbor 的项目角色就四种,别想复杂了:
| 角色 | 能干什么 | 类比 |
|---|---|---|
| 项目管理员 | 管项目设置、加人、删镜像 | 仓库主管 |
| 开发者 | 推镜像、拉镜像、删自己的 tag | 搬运工 |
| 访客 | 只能拉镜像 | 取货人 |
| 维护人员 | 扫描镜像、修改元数据 | 质检员 |
生产环境推荐这样分配:
prod 项目:
- SRE 团队 → 项目管理员
- CI/CD 服务账号 → 开发者(只能 push,不能删)
- 部署系统 → 访客(只能 pull)
dev 项目:
- 开发团队 → 开发者
- QA 团队 → 维护人员
用 API 批量管理权限
手动在 UI 上点几十个用户太累。Harbor 有完整的 REST API:
# 创建项目
curl -X POST "https://harbor.yourcompany.com/api/v2.0/projects" \
-u "admin:your_password" \
-H "Content-Type: application/json" \
-d '{
"project_name": "prod",
"metadata": {
"public": "false",
"auto_scan": "true",
"reuse_sys_cve_allowlist": "false"
}
}'
# 给项目添加成员
curl -X POST "https://harbor.yourcompany.com/api/v2.0/projects/1/members" \
-u "admin:your_password" \
-H "Content-Type: application/json" \
-d '{
"role_id": 2, # 2=开发者, 3=访客
"entity_type": "user",
"entity_id": 5
}'
CI/CD 流水线里推荐用机器人账号(Robot Account),而不是真实用户:
# 创建机器人账号(只有 push 权限)
curl -X POST "https://harbor.yourcompany.com/api/v2.0/projects/1/robots" \
-u "admin:your_password" \
-H "Content-Type: application/json" \
-d '{
"name": "ci-pusher",
"level": "project",
"duration": -1,
"permissions": [{
"access": [
{"action": "pull", "resource": "repository"},
{"action": "push", "resource": "repository"}
],
"kind": "project",
"namespace": "prod"
}]
}'
机器人账号的好处是:权限最小化、过期可控、不关联真人。流水线里用的就应该是这种账号,而不是某个人的 LDAP 账号——人离职了流水线就断了。
漏洞扫描:从检测到阻断
装了 Harbor 不开扫描,等于买了安检门不插电。
Trivy 集成原理
Harbor 从 v2.x 开始内置 Trivy 作为默认扫描引擎。Trivy 扫描的是镜像每一层(layer)里的软件包:apt 装的、pip 装的、npm 装的,全部查 CVE 数据库。
扫描分两种触发方式:
- 推送后自动扫描:镜像 push 完成后,JobService 拉起 Trivy 扫描
- 定时全量扫描:每天扫一遍所有已有镜像,因为漏洞库在更新,昨天的安全镜像今天可能就不安全了
配置自动扫描和阻断策略
在 Harbor UI 中:项目设置 → 勾选 Automatically scan images on push。
更关键的是 阻止有漏洞的镜像被部署。Harbor 叫 “Deployment Security” 策略:
# 通过 API 设置项目级安全策略
# prevent_vul 等于 "true" 时,有高危漏洞的镜像不能被 pull
curl -X PUT "https://harbor.yourcompany.com/api/v2.0/projects/1" \
-u "admin:your_password" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"auto_scan": "true",
"prevent_vul": "true",
"severity": "high"
}
}'
这段配置的含义:镜像存在 High 或 Critical 级别漏洞时,禁止 pull。severity 级别可选 none、low、medium、high、critical,我推荐生产项目设为 high。
这里有个踩坑点。
prevent_vul只对通过 Harbor 代理的 pull 请求生效。如果你的 K8s 节点直接配了 Docker daemon 的insecure-registries绕过 Harbor Core,阻断就不生效。解决办法:所有节点必须通过 Harbor 域名拉镜像,不走 IP 直连。
漏洞白名单
有些漏洞你暂时修不了——比如 base image 里的 glibc 版本,升了会炸。Harbor 支持配置 CVE 白名单:
# 全局 CVE 白名单
curl -X PUT "https://harbor.yourcompany.com/api/v2.0/system/CVEWhitelist" \
-u "admin:your_password" \
-H "Content-Type: application/json" \
-d '{
"items": [
{"cve_id": "CVE-2024-12345"},
{"cve_id": "CVE-2024-67890"}
],
"expires_at": 1767225600
}'
白名单必须有过期时间。不设过期时间的白名单等于永久放行,和没扫描一样。建议设 30 天,到期了要么修了漏洞要么续期——逼自己面对。
某次等保 2.0 审计,审计员专门查了白名单记录,发现有个 CVE 白名单开了 8 个月没动过,直接开了不符合项。教训:白名单是用来买时间的,不是用来逃避的。相关内容可以看相关文章:CI/CD 安全门禁的四层防御与误报治理,那篇专门讲了误报治理。
存储治理:保留策略与垃圾回收
这部分是开篇 200GB 磁盘爆炸的直接解法。
保留策略(Retention Policy)
保留策略解决一个问题:哪些 tag 该留、哪些该删。它按规则自动清理不需要的镜像 tag,不需要你手动删。
Harbor 支持的保留规则:
| 规则类型 | 含义 | 适用场景 |
|---|---|---|
| 保留最近 N 个 tag | 留最新的 N 个版本 | dev 项目,只需要能回滚的最近版本 |
| 保留 N 天内的 tag | 留近期推送的镜像 | feature 分支镜像,过期就删 |
| 保留匹配正则的 tag | 留 v* 格式的正式版 | prod 项目只保留发版镜像 |
| 保留最新 N 个 tag + 匹配正则 | 组合规则 | prod 项目:留最近 5 个 v1.x 版本 |
推荐配置:
dev 项目:
保留策略 → 保留最近 10 个 tag,其余删除
执行频率 → 每天一次
staging 项目:
保留策略 → 保留 7 天内的 tag
执行频率 → 每天一次
prod 项目:
保留策略 → 保留匹配 v* 的最近 20 个 tag
执行频率 → 每周一次
通过 API 配置:
# 给 dev 项目设置保留策略:保留最近 10 个 tag
curl -X POST "https://harbor.yourcompany.com/api/v2.0/retentions" \
-u "admin:your_password" \
-H "Content-Type: application/json" \
-d '{
"scope": {
"level": "project",
"ref": 1
},
"trigger": {
"kind": "Schedule",
"settings": {
"cron": "0 3 * * *"
}
},
"rules": [{
"action": "retain",
"template": "latestPulledNCount",
"params": {
"latestPulledNCount": 10
},
"tag_selectors": [{
"kind": "doublestar",
"decoration": "matches",
"pattern": "**"
}]
}]
}'
注意:保留策略删除的是 tag 引用,不删底层 blob 文件。真正的磁盘空间回收要靠垃圾回收。
垃圾回收(Garbage Collection)
保留策略删了 tag,但镜像的层(blob)还在磁盘上。就像你删了快捷方式但安装包还在。垃圾回收(GC)就是清理这些无主 blob 的过程。
Harbor 的 GC 基于 Docker Distribution 的 GC 机制:
# 手动触发 GC(建议先停 push 操作)
# Harbor UI → 系统 → 垃圾回收 → 立即执行
# 或通过 API
curl -X POST "https://harbor.yourcompany.com/api/v2.0/system/gc/schedule" \
-u "admin:your_password" \
-H "Content-Type: application/json" \
-d '{
"schedule": {
"type": "Daily",
"cron": "0 3 * * *"
}
}'
GC 执行时有个关键行为:会短暂阻塞 push 操作。因为 GC 需要将 Registry 切换到只读模式,扫描完所有 blob 标记无主的删除,再恢复读写。生产环境建议设在凌晨低峰期。
我推荐的开局操作(针对已经堆了很多镜像的老仓库):
# 1. 先配置保留策略,清理不需要的 tag
# 2. 等保留策略执行完,确认删除的 tag 确实不需要
# 3. 手动触发一次 GC
# 4. 查看 GC 日志确认释放了多少空间
# 查看 GC 执行结果
curl -s "https://harbor.yourcompany.com/api/v2.0/system/gc?page=1&page_size=5" \
-u "admin:your_password" | python3 -m json.tool | grep -E "status|space_released"
实测数据:在一个 200GB、3000+ tag 的仓库上,配置"保留最近 10 个 tag"策略 + 执行一次 GC,磁盘占用从 200GB 降到 45GB。177GB 的 blob 是无主垃圾——没人引用但占着磁盘。
存储配额
防止再次撑爆磁盘,给项目设配额:
# 给 dev 项目设 50GB 存储配额
curl -X PUT "https://harbor.yourcompany.com/api/v2.0/projects/1" \
-u "admin:your_password" \
-H "Content-Type: application/json" \
-d '{
"storage_limit": 53687091200 # 50GB in bytes
}'
配额不是替代保留策略的,而是兜底。即使保留策略漏配了,配额也能在 50GB 时拒绝新 push,不至于把磁盘撑爆。
生产环境注意事项
高可用部署
docker-compose 方式是单点,不适合生产。生产环境用 Helm Chart 部署到 K8s 时:
- Core 组件至少 2 副本,前面挂 Ingress
- 数据库用外置 PostgreSQL,不要用 Harbor 自带的容器版(单点、不可扩)
- Redis 用外置实例,避免重启丢会话
- 存储用 NFS/Ceph/对象存储,不要用 emptyDir
高可用方案的前提是外置数据库和 Redis。Harbor 的无状态组件(Core、JobService、Portal)可以多副本,但有状态组件(PostgreSQL、Redis)如果用内置的,就是单点。这个设计选择直接决定你的 Harbor 能不能扛住节点故障。
复制策略做跨机房容灾
如果有多机房需求,Harbor 的复制(Replication)功能可以做镜像同步:
主 Harbor (机房A) ──push──> 从 Harbor (机房B)
│
└── K8s 集群直接从机房B拉镜像
配置复制策略时注意三点:
- 过滤规则要精确。不要全量同步,按项目 + tag 前缀过滤,只同步
v*的生产镜像 - 触发模式选手动或定时。实时同步(event-based)在镜像量大时会有延迟堆积
- 带宽限制。跨机房同步带宽别拉满,留出业务流量空间。Harbor 支持配置
--bandwidth限制
在某次跨机房容灾方案设计中,我们用 Harbor 复制策略实现了 RPO<5min 的镜像同步。K8s 集群从本地 Harbor 拉镜像,网络延迟从跨机房的 30ms 降到本地 1ms,部署速度直接提升一截。这和相关文章:容器镜像优化里讲的镜像瘦身是同一思路——减少传输体积,提升部署效率。
监控指标
Harbor 暴露了 Prometheus 指标端点:
# harbor.yml 中启用 metrics
metric:
enabled: true
port: 9090
path: /metrics
关键告警规则:
# Prometheus 告警规则
groups:
- name: harbor
rules:
# 磁盘使用率 > 80%
- alert: HarborStorageHigh
expr: |
harbor_storage_usage_bytes / harbor_storage_capacity_bytes > 0.8
for: 10m
labels:
severity: warning
annotations:
summary: "Harbor 存储使用率超过 80%"
# 扫描失败率 > 10%
- alert: HarborScanFailureHigh
expr: |
rate(harbor_scan_total{result="error"}[5m])
/ rate(harbor_scan_total[5m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "Harbor 扫描失败率超过 10%"
# GC 执行失败
- alert: HarborGCFailed
expr: harbor_gc_status{status="failed"} == 1
for: 5m
labels:
severity: critical
annotations:
summary: "Harbor 垃圾回收执行失败"
这块可以结合相关文章:Prometheus 监控体系快速搭建来配置完整的监控告警链路。
备份策略
Harbor 的核心数据有两块:PostgreSQL 里的元数据 和 存储层的 blob 文件。备份策略:
| 数据类型 | 备份方式 | 恢复方式 | 频率 |
|---|---|---|---|
| PostgreSQL | pg_dump 定时备份 | 恢复 dump 文件 | 每天 |
| Blob 文件 | 存储层快照(NFS/Ceph snapshot) | 回滚快照 | 每天 |
| harbor.yml | Git 管理 | git checkout | 变更时 |
# PostgreSQL 备份脚本
#!/bin/bash
DATE=$(date +%Y%m%d)
pg_dump -h harbor-db.internal -U postgres harbor | gzip > /backup/harbor-db-${DATE}.sql.gz
# 保留 30 天
find /backup -name "harbor-db-*.sql.gz" -mtime +30 -delete
别只备数据库不备 blob。元数据没了你可以重建项目结构,但 blob 文件丢了就是镜像丢了。两样都得备。
一个完整的 CI/CD 集成示例
把前面讲的串起来,看看在流水线里怎么用 Harbor:
# .gitlab-ci.yml 示例
stages:
- build
- push
- scan
build_and_push:
stage: build
image: docker:24
variables:
HARBOR_URL: "harbor.yourcompany.com"
HARBOR_USER: "ci-pusher" # 机器人账号
HARBOR_PASS: "$HARBOR_CI_TOKEN" # 从 CI 变量注入
before_script:
# 登录 Harbor
- echo "$HARBOR_PASS" | docker login -u "$HARBOR_USER" --password-stdin "$HARBOR_URL"
script:
# 构建镜像,用多阶段构建减小体积
- docker build -t $HARBOR_URL/prod/app:$CI_COMMIT_TAG .
# 推送前先做本地 Trivy 扫描
- trivy image --severity HIGH,CRITICAL --exit-code 1 $HARBOR_URL/prod/app:$CI_COMMIT_TAG
# 扫描通过才推送
- docker push $HARBOR_URL/prod/app:$CI_COMMIT_TAG
# Harbor 会自动触发二次扫描
after_script:
- docker logout $HARBOR_URL
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/ # 只有打 tag 才推送
这里有两层扫描:CI 本地 Trivy 扫一遍 + Harbor 推送后再扫一遍。看起来重复,但价值不同——CI 本地扫描是阻断式的(有问题直接 fail),Harbor 扫描是审计式的(记录漏洞全貌,供后续查询)。
我推荐 CI/CD 流水线里做本地扫描阻断,Harbor 做全量扫描记录。两层策略互补:CI 拦新镜像,Harbor 查历史镜像。
总结
制品仓库不是镜像的网盘。它的核心价值在三个维度:
- 权限管控:项目隔离 + RBAC 角色 + 机器人账号,让谁该推、谁该拉清清楚楚
- 安全扫描:Trivy 自动扫描 + 漏洞阻断策略 + CVE 白名单管理,把漏洞镜像拦在生产之前
- 存储治理:保留策略自动清理无用 tag + GC 回收磁盘空间 + 配额兜底,防止磁盘爆炸
从我的实践经验看,三个维度里存储治理是最容易被忽略的。很多团队装了 Harbor、配了扫描、分了项目,就是没配保留策略和 GC。三个月后磁盘告警,半夜起来手动删镜像——就是开头那个场景。
配置建议一句话总结:
- dev 项目:保留最近 10 个 tag,每天 GC
- staging 项目:保留 7 天内 tag,每天 GC
- prod 项目:保留 v* 最近 20 个 tag,每周 GC
- 所有项目:设 50-100GB 存储配额兜底
- 生产项目:开 Trivy 自动扫描 + High 级别阻断
Harbor 本身不难装,难的是养成持续治理的习惯。镜像管理是 CI/CD 的最后一公里,这公里走不好,前面构建再快也是白搭。
参考资料与致谢
本文在撰写过程中参考了以下资料,感谢原作者的贡献:
- Harbor GitHub Repository — Harbor 官方团队,CNCF 毕业项目,提供镜像存储、签名、扫描的企业级 Registry
- Harbor 官方文档 - Garbage Collection — Harbor 官方文档,垃圾回收机制和存储管理说明
- Trivy 官方文档 — Aqua Security,Trivy 漏洞扫描引擎的配置和使用指南
- Harbor Helm Chart — Harbor 团队,Helm Chart 部署配置和参数说明
- CNCF Harbor 项目介绍 — CNCF 基金会,Harbor 项目概况和社区动态