Harbor / 镜像仓库
本文范围
这篇讲企业私有镜像仓库怎么落地,主线是 Harbor:安装、证书、项目、用户、机器人账号、docker login/tag/push/pull、镜像 tag/digest 治理、扫描、保留策略、垃圾回收、复制、备份恢复、内网和离线镜像交付。
不展开 Dockerfile 怎么写,不展开 K8S Deployment 怎么拉镜像,也不把 Harbor 高可用、对象存储和 Helm 部署作为第一主线。对于大多数团队,先把单套 Harbor 的权限、证书、磁盘、备份和发布接入做扎实,比一开始堆复杂架构更重要。
前置条件
落地前先准备这些东西:
| 项目 | 要求 |
|---|---|
| 服务器 | 建议 4 核 8GB 起步,数据盘单独挂载;镜像多、团队多时单独做容量规划 |
| 域名和证书 | 使用固定域名;生产必须 HTTPS;自签 CA 要分发给 Docker、containerd、K8S 节点和 CI 机器 |
| 运行时 | 已安装 Docker Engine 与 Docker Compose v2,或使用官方支持的安装方式 |
| 权限 | 有 Harbor 管理员账号、系统级备份权限、DNS/证书变更权限 |
| 网络 | 构建机、发布机、K8S 节点、离线导入机都能访问 Harbor;公网不稳定时要准备代理或同步策略 |
| 版本 | 以上线当天 Harbor 官方 release 和安装文档为准,升级前必须读迁移说明和回滚要求 |
镜像仓库是发布链路的基础设施。它一旦不可用,影响的不是一个服务,而是构建、发布、扩容、回滚和应急修复整条线。
先看要解决什么问题
公司里只要开始上容器,迟早会遇到一个问题:镜像到底放哪里?
开发同学本地构建了一个镜像,测试环境要用,预发环境要用,生产环境也要用。如果每台机器都手工 docker save、scp、docker load,刚开始还能忍,服务一多就会变成灾难:版本不知道谁打的,旧镜像没人清,生产拉镜像还依赖外网,出了漏洞也不知道哪些服务受影响。
Harbor 要解决的不是“找个地方存镜像”这么简单,而是把镜像从构建、推送、扫描、分发、拉取、保留、清理、审计这一整条链路管起来。
这里先把边界说清楚:
- 这篇讲镜像仓库治理,Harbor 是主线工具。
- 不展开 Docker 基础,不从零讲 Dockerfile。
- 不展开 K8S 镜像拉取策略,只讲镜像仓库和部署系统的连接点。
- 不把 Helm 高可用部署作为主线,单机 Docker Compose 部署更适合先把企业私有仓库跑通。
- 命令以 Harbor 2.15.x、Docker Engine 20.10 以上、Docker Compose v2 为基线。
一般我会建议先按下面这个顺序落地:
准备域名和证书 -> 安装 Harbor -> 创建项目和账号 -> 打 tag -> push -> pull
-> 配置扫描 -> 配置保留策略 -> 配置 GC -> 做复制/离线交付 -> 接入发布流水线不要一开始就追求“大而全”。镜像仓库是发布链路基础设施,第一步先保证能稳定 push、pull、追版本、控权限、清磁盘。
把 Registry、Repository、Tag 讲明白
先看一个完整镜像名:
registry.example.com/backend/order-service:1.4.2-20260709-a1b2c3d可以把它拆成几段:
| 部分 | 示例 | 说明 |
|---|---|---|
| Registry | registry.example.com | 镜像仓库服务地址 |
| Project / Namespace | backend | Harbor 里的项目,也可以理解为团队或环境空间 |
| Repository | order-service | 具体镜像仓库,一般对应一个服务 |
| Tag | 1.4.2-20260709-a1b2c3d | 镜像标签,常拿来当版本号 |
| Digest | sha256:... | 内容摘要,真正指向不可变镜像内容 |
这跟 Maven 私服有点像。Registry 像 Maven 仓库地址,Project 像组或命名空间,Repository 像某个组件,Tag 像版本号。但镜像比 jar 更容易出问题,因为 tag 可以被覆盖。
这里要注意:tag 不是镜像内容本身,它只是一个引用。今天 backend/order-service:latest 可以指向 A 镜像,明天也可以被覆盖成 B 镜像。生产排障时如果只记录 latest,不记录 digest,最后经常会变成一句话:当时跑的到底是哪一个?
用命令看一下 digest:
docker image inspect registry.example.com/backend/order-service:1.4.2-20260709-a1b2c3d \
--format '{{json .RepoDigests}}'正常情况下会看到类似这样的输出:
["registry.example.com/backend/order-service@sha256:xxxxxxxx..."]看到 @sha256:,才说明你拿到了内容级标识。发布记录里建议同时保存 tag 和 digest,tag 方便人看,digest 方便机器和审计追踪。
Harbor 的核心对象不要只停在“仓库里有镜像”。设计评审时要把权限、推送、扫描、保留、复制、GC 和 K8S 拉取边界一起画出来,否则后面很容易出现“能 push 但 K8S 拉不动”“删了 tag 但磁盘不降”“流水线账号权限过大”这类问题。
用这张图做上线检查时,至少要确认四件事:流水线使用 Harbor robot 而不是个人密码,项目 RBAC 只给到 push 或 pull 所需权限,发布记录保存 tag 和 digest,保留策略和 GC 有 dry run、低峰执行和审计记录。K8S 侧还要把 imagePullSecret、节点 containerd 信任链和 kubectl describe pod 里的拉取错误放进同一个检查闭环。
为什么不是直接用一个 Docker Registry
开源 Docker Registry 可以存镜像,也可以 push、pull。但企业里一般还需要这些能力:
- Web 页面能看镜像、tag、大小、扫描结果。
- 按项目隔离权限,不同团队互不干扰。
- CI/CD 使用机器人账号,不用个人密码。
- 旧镜像能按规则保留和清理。
- 镜像能做漏洞扫描。
- 多机房、多环境能做复制。
- 中国大陆或内网环境能缓存外部镜像。
- 谁 push、谁 delete、谁改策略要有审计。
Harbor 就是把这些能力放在一起。它不是只解决“存储”,而是解决“镜像治理”。
不过也不是所有团队都必须自建 Harbor。小团队如果没有专人维护证书、备份、升级、磁盘、漏洞库,可以先用云厂商的企业镜像仓库,比如 ACR、TCR、SWR。自建 Harbor 的前提是:你愿意把它当成生产基础设施维护,而不是装完就扔在那里。
安装前先检查机器
Harbor 可以用 Docker Compose 部署,也可以用 Helm 部署到 K8S。这里以 Docker Compose 单机部署为主,因为最容易在企业内网、测试环境、客户现场跑起来。
官方给的基础要求可以先记住这几个数:
| 项目 | 最小值 | 推荐值 |
|---|---|---|
| CPU | 2 核 | 4 核以上 |
| 内存 | 4 GB | 8 GB以上 |
| 磁盘 | 40 GB | 160 GB以上 |
| Docker Engine | 大于 20.10 | 使用维护中的稳定版本 |
| Docker Compose | 大于 2.3 | 使用 v2 插件 |
| 端口 | 80 / 443 | 生产优先 443 |
安装前先看机器状态:
hostname -f
ip addr
df -h
free -h
docker version
docker compose version
ss -lntp | grep -E ':80|:443' || true这些命令分别解决几个问题:
hostname -f看机器域名,后面 Harbor 的hostname要用到。ip addr看 IP 是否和 DNS 规划一致。df -h看磁盘是否够,镜像仓库最容易先爆磁盘。free -h看内存,Trivy 扫描和并发任务会吃内存。docker version看 Docker daemon 是否可用。docker compose version看 Compose v2 是否可用。ss -lntp看 80、443 是否已经被 Nginx 或其他服务占用。
如果 443 已经被占用,安装时就要先决定:是 Harbor 直接监听 443,还是前面放统一 Nginx / LB 做 TLS 终止。不要等 install.sh 报端口冲突再临时改。
生产环境一般还需要提前准备:
registry.example.com Harbor 域名
/data/harbor 数据目录
/etc/harbor/certs 证书目录
admin 初始密码 第一次登录后马上修改
企业 CA 分发方式 所有 Docker 客户端都要信任
备份目录和恢复演练方式 不要只备份配置文件下载安装包
Harbor 安装包有 online installer 和 offline installer。
在线包体积小,但安装时要拉取 Harbor 自己的镜像。中国大陆公网、企业内网、客户现场、离线机房都不建议把在线包作为唯一方案。
离线包体积大,因为里面已经带了 Harbor 组件镜像。生产和交付环境建议优先准备离线包,并保留下载来源、版本号、校验值。
假设已经拿到离线安装包,解压:
tar xf harbor-offline-installer-v2.15.2.tgz
cd harbor
cp harbor.yml.tmpl harbor.yml执行完以后,目录里一般会看到这些文件:
ls -lh重点看:
harbor.yml
install.sh
prepare
common.sh
harbor.v2.15.2.tar.gz如果没有 harbor.v2.15.2.tar.gz 这种大文件,多半不是离线安装包。离线场景下不要走到安装时才发现它还要访问外网。
这里要注意:Harbor 2.15.0 以后官方 release artifact 支持 Cosign 签名校验。生产环境至少要做摘要或签名校验,别从聊天窗口、网盘、历史目录里随手拿一个安装包就装。
修改 harbor.yml
Harbor 的核心配置在 harbor.yml。第一次不要改太多,先把域名、HTTPS、密码、数据目录、扫描和日志配好。
一个生产化起步配置大概长这样:
hostname: registry.example.com
http:
port: 80
https:
port: 443
certificate: /etc/harbor/certs/registry.example.com.crt
private_key: /etc/harbor/certs/registry.example.com.key
harbor_admin_password: "CHANGE_ME_ON_FIRST_BOOT"
database:
password: "CHANGE_ME_DB_PASSWORD"
max_idle_conns: 100
max_open_conns: 900
data_volume: /data/harbor
trivy:
ignore_unfixed: false
security_check: vuln
skip_update: false
insecure: false
jobservice:
max_job_workers: 10
log:
level: info
local:
rotate_count: 50
rotate_size: 200M
location: /var/log/harbor
proxy:
http_proxy:
https_proxy:
no_proxy: 127.0.0.1,localhost,core,registry,portal,jobservice,redis,postgresql,trivy-adapter
metric:
enabled: true
port: 9090
path: /metrics改完以后先自己检查一遍:
grep -nE '^(hostname|data_volume|harbor_admin_password):|^https:|^trivy:|^metric:' harbor.yml这里看结果不是为了“好看”,是为了确认几个关键点:
hostname写的是客户端能访问到的域名或 IP,不能写localhost。- 生产环境开启 HTTPS。
harbor_admin_password不要用默认弱密码。database.password要修改。data_volume指到独立磁盘或可靠存储。- 内网环境如果 Trivy 要访问外部漏洞库,
proxy要提前规划。 metric.enabled建议开启,后续方便接入 Prometheus。
如果只是临时实验,可以先用 HTTP。但生产里不要把 HTTP 当常态,否则 Docker 客户端、代理、K8S 节点、安全审计都会不停踩坑。
启动 Harbor
不启用扫描时:
sudo ./install.sh启用 Trivy 扫描时:
sudo ./install.sh --with-trivy安装脚本会生成 Compose 配置并启动一组容器。执行完不要急着登录页面,先看容器:
docker compose ps正常应该看到 core、portal、registry、jobservice、redis、postgresql、nginx 等服务处于 running 或 healthy 状态。如果启用了 Trivy,还会看到 trivy-adapter。
再看最近日志:
docker compose logs --tail=100如果日志里有数据库连接失败、证书文件找不到、端口占用、权限不足,先处理这些基础问题,不要直接打开浏览器反复刷新。
再检查健康接口:
curl -k https://registry.example.com/api/v2.0/health正常会返回各组件健康状态。这里用了 -k 是为了在自签证书或企业 CA 还没完整信任时先看服务本身是否起来,生产验证最终还是要让客户端正确信任证书。
最后浏览器访问:
https://registry.example.com访问后应该看到 Harbor 登录页。第一次用 admin 和 harbor.yml 里的初始密码登录。登录后马上做三件事:
- 修改
admin密码。 - 新建项目,例如
demo、base、backend。 - 创建普通用户或机器人账号,不要让流水线长期使用
admin。
证书要让 Docker 客户端也信任
这里是 Harbor 最常见的坑:浏览器能打开,不代表 docker login 能成功。
Docker daemon 校验证书有自己的信任目录。如果 Harbor 使用企业 CA 或自签 CA,需要把 CA 证书放到每台 Docker 客户端:
sudo mkdir -p /etc/docker/certs.d/registry.example.com
sudo cp ca.crt /etc/docker/certs.d/registry.example.com/ca.crt
sudo systemctl restart docker如果 Harbor 不是 443 端口,例如 8443,目录必须带端口:
sudo mkdir -p /etc/docker/certs.d/registry.example.com:8443
sudo cp ca.crt /etc/docker/certs.d/registry.example.com:8443/ca.crt
sudo systemctl restart docker然后验证:
docker login registry.example.com成功时会看到:
Login Succeeded如果报:
x509: certificate signed by unknown authority先检查 CA 文件是否放对目录:
ls -l /etc/docker/certs.d/registry.example.com/
openssl s_client -connect registry.example.com:443 -servername registry.example.com </dev/null如果你只能临时用 HTTP,需要在每台客户端配置:
{
"insecure-registries": [
"registry.example.local"
]
}重启 Docker 后检查:
sudo systemctl daemon-reload
sudo systemctl restart docker
docker info | grep -A 5 "Insecure Registries"看到 registry 地址出现在 Insecure Registries 下面,说明客户端知道这是 HTTP 或不安全 registry。这里要注意,HTTP 方案只能作为受控内网里的过渡,不要把生产仓库长期放在明文链路上。
创建项目、用户和机器人账号
Harbor 权限是从 Project 开始的。不要把所有镜像都塞进 library,也不要让所有人共用一个管理员账号。
一般可以这样划分项目:
| 项目 | 放什么 |
|---|---|
base | JDK、Nginx、Alpine、Ubuntu 等基础镜像 |
middleware | Redis、MySQL、PostgreSQL、RabbitMQ 等中间件 |
backend | 后端业务服务 |
frontend | 前端静态资源镜像 |
ops | 运维工具镜像 |
third-party | 外部同步进来的镜像 |
release | 生产发布镜像,权限最严格 |
角色可以按这个思路给:
| 角色 | 典型用途 |
|---|---|
| System Admin | 平台管理员,管全局配置、扫描器、配额、GC |
| Project Admin | 项目负责人,管成员、策略、保留规则 |
| Maintainer | 维护项目镜像,可 push、delete、scan |
| Developer | 开发或流水线账号,可 push / pull |
| Guest | 只读拉取 |
| Limited Guest | 更受限的只读访问 |
流水线推荐用项目级机器人账号。命名要能看出来用途:
robot-ci-order-service
robot-pull-prod-cluster
robot-replication-shanghaiCI 登录不要这样写:
docker login registry.example.com -u robot-ci-order-service -p 'plain-text-token'这样密码可能进入 shell history、流水线日志或进程参数。推荐这样写:
echo "$HARBOR_ROBOT_TOKEN" | docker login registry.example.com \
--username "$HARBOR_ROBOT_USER" \
--password-stdin验证方式很简单:机器人账号能登录,能推目标项目,不能推别的项目,才算权限刚好。
docker login registry.example.com
docker push registry.example.com/backend/test-permission:1.0.0
docker push registry.example.com/release/test-permission:1.0.0如果前一个成功、后一个失败,而你的策略就是“CI 只能推 backend,不能推 release”,说明权限边界是有效的。
生产建议:
- 开发项目允许开发流水线 push。
- 生产项目只允许发布流水线 push。
- 运行环境只给 pull 权限。
- 删除镜像、修改保留策略只给项目管理员。
- 机器人 token 定期轮换。
- 用户离职、流水线下线、项目归档时要清账号。
跑通 docker login、tag、push、pull
先在 Harbor 控制台新建一个项目,例如 demo。如果没有这个项目,后面 push 会因为权限或路径不存在失败。
第一步,登录:
docker login registry.example.com看到:
Login Succeeded说明认证通过。如果失败,先看是证书问题、账号密码问题,还是项目权限问题。
第二步,准备一个测试镜像。有公网环境可以临时拉一个小镜像:
docker pull alpine:3.20中国大陆公网或内网环境里,如果外部拉取不稳定,就不要纠结 Docker Hub。可以从企业已有基础镜像仓库拉,或者先离线导入一个测试镜像。
检查本地镜像:
docker images | grep alpine应该看到 alpine 和 3.20。
第三步,给镜像打 Harbor 地址:
docker tag alpine:3.20 registry.example.com/demo/alpine:3.20docker tag 不会复制一份镜像数据,它只是给同一个镜像内容增加一个新名字。可以这样验证:
docker images | grep alpine一般会看到两行,IMAGE ID 一样:
alpine 3.20 <same-image-id>
registry.example.com/demo/alpine 3.20 <same-image-id>第四步,推送:
docker push registry.example.com/demo/alpine:3.20推送时会看到一层一层上传,类似:
The push refers to repository [registry.example.com/demo/alpine]
...
3.20: digest: sha256:xxxx size: xxx看到 digest: sha256:,说明 registry 已经接受了这个镜像。
第五步,换一台机器拉取:
docker login registry.example.com
docker pull registry.example.com/demo/alpine:3.20
docker image inspect registry.example.com/demo/alpine:3.20 \
--format '{{json .RepoDigests}}'如果能 pull,而且 digest 能对上,最小闭环就通了。
这里最常见的错误是漏掉 project:
docker push registry.example.com/alpine:3.20这个名字里没有 demo,Harbor 不知道你要推到哪个项目。正确写法是:
docker push registry.example.com/demo/alpine:3.20记住这个格式:
Harbor地址 / 项目 / 镜像仓库 : 标签镜像版本不要只靠 latest
生产环境里最忌讳的版本名是:
latest
prod
stable
v1不是说这些 tag 不能出现,而是不能作为唯一发布依据。latest 看起来方便,排障时非常折磨人:今天拉到的是哪个版本?昨天覆盖过没有?线上节点是不是都一样?
推荐版本里至少带上业务版本、构建时间和 Git 短提交:
1.4.2-20260709-1530-a1b2c3d构建脚本可以这样写:
set -euo pipefail
HARBOR_HOST=registry.example.com
PROJECT=backend
APP=order-service
VERSION=1.4.2
BUILD_TIME=$(date +%Y%m%d-%H%M)
GIT_SHA=$(git rev-parse --short=7 HEAD)
IMAGE="${HARBOR_HOST}/${PROJECT}/${APP}:${VERSION}-${BUILD_TIME}-${GIT_SHA}"
docker build -t "$IMAGE" .
docker push "$IMAGE"
docker image inspect "$IMAGE" --format '{{index .RepoDigests 0}}'执行后要记录这几个字段:
| 字段 | 示例 |
|---|---|
| 应用 | order-service |
| 环境 | prod |
| 镜像 | registry.example.com/backend/order-service:1.4.2-20260709-1530-a1b2c3d |
| Digest | registry.example.com/backend/order-service@sha256:... |
| Git commit | a1b2c3d |
| 构建号 | build-20260709-1530 |
| 扫描结果 | 通过 / 豁免 / 阻断 |
镜像里也建议写 OCI Label:
LABEL org.opencontainers.image.title="order-service"
LABEL org.opencontainers.image.version="1.4.2"
LABEL org.opencontainers.image.revision="a1b2c3d"
LABEL org.opencontainers.image.created="2026-07-09T15:30:00+08:00"验证:
docker image inspect "$IMAGE" \
--format '{{json .Config.Labels}}'如果团队确实要保留 prod 这种环境别名,可以额外打一个辅助 tag:
docker tag "$IMAGE" registry.example.com/backend/order-service:prod
docker push registry.example.com/backend/order-service:prod但发布记录和回滚不要依赖 prod,要依赖不可变版本 tag 和 digest。Harbor 里生产项目建议配置 Tag Immutability,禁止覆盖已经发布过的版本。
用 Overlay2 和 CoW 解释镜像为什么会占磁盘
镜像仓库治理离不开磁盘。很多人看到 Harbor 磁盘涨,就只想删 tag,但删完发现空间没下来。这就要理解镜像层。
Docker 默认常用 Overlay2 存储驱动。镜像不是一个大文件,而是一层一层叠起来的只读层。容器启动后会多一层可写层,应用写进去的临时文件、日志、缓存一般在这一层。
CoW,也就是 Copy-on-Write,意思是:多个容器可以共享同一批镜像只读层,只有发生写入时,才把要修改的文件复制到自己的可写层里再改。这样能省磁盘,但也带来一个结果:同一个 blob 可能被多个 tag、多个镜像引用。
先看 Docker 本地磁盘:
docker system df -v这个命令能看到镜像、容器、卷、构建缓存占用。你会发现一个镜像表面上很大,但共享层并不会重复下载和重复存储。
再看本地存储驱动:
docker info | grep -E 'Storage Driver|Docker Root Dir'如果看到:
Storage Driver: overlay2
Docker Root Dir: /var/lib/docker说明本机 Docker 镜像层和容器可写层主要在 Docker Root Dir 下。这里要注意,不要手工删除 /var/lib/docker/overlay2 里的目录,容易把 Docker 元数据搞坏。要用 Docker 命令或 Registry / Harbor 自己的清理机制。
这跟 Harbor 的 GC 有什么关系?
Harbor 里删除一个 tag,只是删除了“引用”。真正的 blob 如果还被其他 tag 或其他镜像引用,GC 不能删。就算不再引用,也要执行垃圾回收,空间才会释放。
所以镜像磁盘治理要分清三件事:
- Docker 主机本地镜像清理:用
docker image prune、docker system prune。 - Harbor 仓库镜像清理:先删 tag / artifact,再执行 GC。
- 应用持久化数据:不要放容器可写层,要放 volume、宿主机目录、对象存储或数据库。
发布流水线怎么接 Harbor
完整 CI/CD 不在这里展开,但镜像仓库这一步要写清楚。一个实用脚本如下:
set -euo pipefail
HARBOR_HOST=registry.example.com
PROJECT=backend
APP=order-service
VERSION="${VERSION:?missing VERSION}"
GIT_SHA="$(git rev-parse --short=7 HEAD)"
BUILD_TIME="$(date +%Y%m%d-%H%M)"
IMAGE="${HARBOR_HOST}/${PROJECT}/${APP}:${VERSION}-${BUILD_TIME}-${GIT_SHA}"
echo "$HARBOR_ROBOT_TOKEN" | docker login "$HARBOR_HOST" \
--username "$HARBOR_ROBOT_USER" \
--password-stdin
docker build -t "$IMAGE" .
docker push "$IMAGE"
DIGEST="$(docker image inspect "$IMAGE" --format '{{index .RepoDigests 0}}')"
cat > image-release.txt <<EOF
app=$APP
image=$IMAGE
digest=$DIGEST
git_sha=$GIT_SHA
build_time=$BUILD_TIME
EOF
cat image-release.txt执行后重点看两处:
docker push最后有没有输出 digest。image-release.txt是否记录了完整镜像名和 digest。
如果流水线只打印了“构建成功”,没有记录镜像 digest,这条流水线还不能算生产可追踪。
生产建议:
- CI 账号只给目标项目 push 权限。
- 生产项目不允许开发人员手工 push。
- 扫描失败要阻断,或者进入明确的豁免流程。
- 回滚使用上一版镜像,不重新构建一个“同版本”镜像。
- 发布系统保存 tag、digest、Git commit、构建号、扫描结果。
中国大陆、内网和 Docker Hub 不稳定怎么处理
中国大陆环境里,不要把 Docker Hub、GitHub、国外软件源、公开加速器当成稳定生产依赖。很多旧镜像源会失效、限流、停止同步,或者只适合个人开发。
企业里更稳的做法是:
- 自建 Harbor 做内部统一镜像入口。
- 使用云厂商企业镜像仓库,比如阿里云 ACR、腾讯云 TCR、华为云 SWR。
- 对基础镜像做白名单,同步到内部仓库。
- 需要外部缓存时,用 Harbor Proxy Cache 或云厂商海外源订阅能力。
- 客户现场和离线环境用
docker save/load加校验清单。 - 外网访问走企业代理或全球加速,但要经过安全和合规评审。
不要在生产部署脚本里写:
docker pull redis:latest
docker pull mysql:latest更稳的是先把基础镜像同步进内部仓库:
docker pull redis:7.4
docker tag redis:7.4 registry.example.com/base/redis:7.4
docker push registry.example.com/base/redis:7.4业务部署只拉内部地址:
docker pull registry.example.com/base/redis:7.4这里要注意,基础镜像不是同步越多越好。同步越多,扫描、存储、保留、漏洞响应成本越高。建议先列白名单:
nginx:1.27
redis:7.4
mysql:8.4
postgres:17
eclipse-temurin:17-jre
eclipse-temurin:21-jre
alpine:3.20
busybox:1.36每个基础镜像都要记录:
来源 registry
原始 tag
原始 digest
同步时间
同步人或流水线
内部镜像地址
用途
扫描结果离线镜像交付
离线交付不是把 tar 包拷过去就完事。真正要做到的是:导入后能证明镜像版本正确、来源可信、可回滚。
在线环境先准备镜像:
docker pull registry.example.com/backend/order-service:1.4.2-20260709-a1b2c3d
docker image inspect registry.example.com/backend/order-service:1.4.2-20260709-a1b2c3d \
--format '{{json .RepoDigests}}'
docker save registry.example.com/backend/order-service:1.4.2-20260709-a1b2c3d \
-o order-service-1.4.2.tar
sha256sum order-service-1.4.2.tar > order-service-1.4.2.tar.sha256这几步分别做了什么:
docker pull确认交付镜像本地可取得。docker image inspect记录 digest。docker save把镜像层和 tag 保存成 tar。sha256sum生成文件级校验值,防止传输过程损坏或被替换。
离线环境先校验:
sha256sum -c order-service-1.4.2.tar.sha256看到:
order-service-1.4.2.tar: OK再导入:
docker load -i order-service-1.4.2.tar
docker images | grep order-service如果离线环境也有 Harbor,就重新打内网 tag 并推送:
docker tag registry.example.com/backend/order-service:1.4.2-20260709-a1b2c3d \
harbor.offline.local/backend/order-service:1.4.2-20260709-a1b2c3d
docker login harbor.offline.local
docker push harbor.offline.local/backend/order-service:1.4.2-20260709-a1b2c3d验证:
docker pull harbor.offline.local/backend/order-service:1.4.2-20260709-a1b2c3d
docker image inspect harbor.offline.local/backend/order-service:1.4.2-20260709-a1b2c3d \
--format '{{json .RepoDigests}}'离线交付包建议至少包含:
images/
order-service-1.4.2.tar
redis-7.4.tar
checksums/
SHA256SUMS
manifests/
image-list.txt
image-digest.txt
docs/
import.sh
verify.sh这里要注意,不建议用 docker export/import 做镜像迁移。export 导出的是容器文件系统快照,容易丢镜像历史、tag、metadata。镜像交付一般用 docker save/load。
镜像复制和跨环境分发
Harbor 支持复制策略,可以在两个 registry 之间同步镜像。常见场景有:
- 总部 Harbor 同步到分公司 Harbor。
- 研发环境同步到测试环境。
- 生产发布镜像同步到灾备机房。
- 从外部 registry 拉取白名单镜像到内部 registry。
创建复制策略时,一般按这个顺序做:
- 在 Harbor 控制台添加目标 registry endpoint。
- 测试 endpoint 连接。
- 创建复制规则,选择 push 或 pull 模式。
- 限定项目、仓库、tag,不要一上来全量同步。
- 手工触发一次。
- 查看任务日志和目标仓库。
验证可以从目标端拉镜像:
docker login registry-backup.example.com
docker pull registry-backup.example.com/backend/order-service:1.4.2-20260709-a1b2c3d如果拉不到,先在 Harbor 里看复制任务日志,再查网络:
curl -vk https://registry-backup.example.com/v2/这里要注意:复制的是制品,不是 Harbor 用户、项目成员、权限策略的完整迁移。权限、机器人账号、保留策略、扫描策略要在目标 Harbor 单独规划。
生产建议:
- 复制规则要小范围开始,避免第一次把历史镜像全同步爆磁盘。
- 跨机房复制要考虑带宽、失败重试、窗口期。
- 不同 Harbor 大版本之间复制前要看兼容性说明。
- 生产发布镜像建议复制到灾备仓库。
Proxy Cache 怎么用
Proxy Cache 可以把外部 registry 的镜像缓存到 Harbor。第一次 pull 会去上游拉,后面相同镜像可以从 Harbor 缓存返回。
它适合这些场景:
- 团队经常拉固定基础镜像。
- Docker Hub 不稳定,但又不能马上把所有镜像手工同步。
- 想减少外部 registry 拉取次数和限流影响。
但它不是万能的:
- Proxy Cache 项目不能 push。
- 第一次拉取仍依赖上游可用。
- 上游 tag 如果变了,需要理解缓存刷新策略。
- 生产发布镜像不建议只依赖 proxy cache,最好同步成内部正式镜像。
使用时一般在 Harbor 控制台:
Registries -> New Endpoint -> 选择 Docker Hub / Docker Registry / GHCR / Quay 等
Projects -> New Project -> 勾选 Proxy Cache -> 选择 Endpoint验证拉取:
docker pull registry.example.com/dockerhub/library/nginx:1.27拉完后到 Harbor 项目里应该能看到缓存下来的 repository 和 tag。第二次拉同一个镜像速度一般会明显更稳定。
镜像保留策略
镜像仓库不做保留策略,最后一定会变成“只进不出”。保留策略要按项目设计,不要全局一刀切。
常见策略:
| 项目 | 建议 |
|---|---|
base | 基础镜像少而精,长期保留指定版本 |
backend-dev | 保留最近 20 个 tag,或最近 30 天 |
backend-test | 保留最近 30-50 个 tag |
release | 生产版本长期保留,至少覆盖回滚窗口 |
third-party | 按白名单和使用频率保留 |
Harbor 控制台里可以在项目中配置 Tag Retention。第一次配置建议先 dry run,看看会删哪些 tag。
一个比较常见的规则是:
Repository 匹配:**
Tag 匹配:**
保留:最近 push 的 30 个 artifacts如果是生产项目,可以更谨慎:
保留所有 release-* tag
保留最近 90 天内 push 的版本
保留最近 20 个可回滚版本这里要注意,保留策略不是马上释放磁盘。它只是删掉符合条件的 tag / artifact 引用。真正释放 blob 空间,还要执行 GC。
生产建议:
- 新策略先 dry run。
- 先从 dev/test 项目开始。
- 生产项目必须和发布回滚窗口对齐。
- 不要删除仍在运行环境使用的镜像。
- 删除前先确认 digest 是否还被其他 tag 引用。
删除和垃圾回收
Harbor 删除镜像后,磁盘不一定马上下降。这不是 Harbor 坏了,而是 registry blob 可能还被引用,或者还没执行 GC。
先看磁盘:
df -h
du -sh /data/harbor再看 Harbor 任务日志:
docker compose logs --tail=200 jobservice
docker compose logs --tail=200 registry正确的治理顺序是:
设置保留策略 -> dry run -> 确认删除列表 -> 执行策略 -> 低峰执行 GC -> 验证磁盘Harbor 控制台里 GC 建议先点 dry run。dry run 不会真正释放空间,它只是告诉你大概能清哪些 blob。确认没问题,再在低峰期执行 GC。
这里要注意几个坑:
- GC 有保护窗口,刚删除的 blob 不一定立刻清。
- blob 被其他 tag 引用时不会释放。
- 对象存储场景可能存在回收延迟。
- GC 会消耗 I/O,不要在发布高峰期跑。
- 不要手工删除 Harbor 数据目录里的 registry 文件。
执行后验证:
df -h
du -sh /data/harbor
docker compose logs --tail=200 jobservice | grep -i gc如果空间没有明显下降,不要急着扩大删除范围。先确认是不是共享层仍被引用,或者 GC 只是 dry run。
漏洞扫描和安全准入
Harbor 默认可以集成 Trivy 扫描。安装时启用:
sudo ./install.sh --with-trivy安装后看组件:
docker compose ps | grep trivy
docker compose logs --tail=100 trivy-adapter如果 trivy-adapter 正常,Harbor 控制台里就可以对镜像手工扫描,也可以配置项目扫描策略。
扫描结果要这样用:
- 开发项目:给出结果,提醒修复。
- 测试项目:高危漏洞要有处理意见。
- 生产项目:严重漏洞默认阻断,确需上线走豁免。
- 基础镜像:定期重扫,因为漏洞库会更新。
内网或离线环境里,Trivy 最常见的问题是漏洞库下载失败:
docker compose logs --tail=200 trivy-adapter
docker compose logs --tail=200 jobservice如果看到类似:
failed to download vulnerability DB一般按这个顺序排查:
- Harbor 是否配置了企业代理。
no_proxy是否把内部组件排除掉。- 企业 CA 是否被 Trivy 相关容器信任。
- 离线环境是否准备了漏洞库导入方案。
- 扫描任务是否太多,jobservice 并发是否过高。
这里要注意,扫描不是“扫一次就永久安全”。镜像内容没变,漏洞库会变。一个月前通过的镜像,一个月后可能扫出高危 CVE。所以生产项目要配置周期性重扫。
安全治理最低要求:
生产 tag 不可覆盖
生产镜像必须记录 digest
生产项目开启扫描
严重漏洞有阻断或豁免流程
机器人账号最小权限
删除和策略修改有审计
基础镜像定期重扫镜像签名、不可变和审计
如果团队刚开始落地 Harbor,不一定第一天就把签名链路做满。但有三件事建议尽早做:
- 生产 tag 不允许覆盖。
- 发布记录保存 digest。
- 审计日志要能追到谁 push、谁 delete、谁改策略。
Harbor 支持内容信任和签名能力,可以结合 Cosign、Notation 等工具做制品签名。这里不展开签名体系,但要先把基础动作做稳。
审计重点看这些事件:
push artifact
pull artifact
delete artifact
create project
delete repository
create robot
change configuration如果公司有 SIEM 或集中日志平台,建议把 Harbor 日志和审计日志接进去。供应链问题发生后再想补审计,通常已经来不及。
备份、恢复和升级
Harbor 备份不要只备份镜像目录。至少要覆盖:
harbor.yml
HTTPS 证书和私钥
data_volume 指向的数据目录
PostgreSQL 数据
Redis / 缓存相关数据
外部对象存储配置
机器人账号和权限配置记录
安装包和版本信息单机 Compose 部署可以先用一个简化停机备份理解流程:
cd harbor
docker compose down
tar czf harbor-config-$(date +%F).tar.gz harbor.yml common/
tar czf harbor-data-$(date +%F).tar.gz /data/harbor
docker compose up -d
docker compose ps这只是简化示例。生产环境数据量大时,要按数据库、对象存储、文件系统快照、一致性窗口来设计。备份一定要做恢复演练,否则只是“看起来有备份”。
升级前先检查:
docker compose ps
docker compose logs --tail=100
df -h升级原则:
- 先读目标版本升级文档。
- 确认支持的升级路径。
- 备份配置和数据。
- 在测试环境演练。
- 低峰期执行。
- 升级后验证登录、push、pull、扫描、复制、GC。
这里要注意,Harbor 是发布入口,不是普通工具站。升级后只看 Web 页面能打开是不够的,一定要完整跑一次镜像推送拉取闭环。
常见故障排查
镜像仓库故障最怕一上来就重启 Harbor。先把错误拆成“能不能连到 registry”“能不能认证授权”“镜像内容是否存在”“是否被策略挡住”“节点运行时是否信任仓库”。下面这张图适合放在值班手册里,按症状从外到内排。
这张排查图的价值在于把命令和现象对上:x509 优先查 CA 和证书链,unauthorized 优先查 docker login、robot token 和 imagePullSecret,manifest unknown 优先查镜像名、tag 和 digest,K8S 节点单独失败时再查 containerd 信任配置。每一步修复后都要重新跑一次 docker pull 或 crictl pull,不要只看 Web 页面能打开。
docker login 提示 x509
现象:
x509: certificate signed by unknown authority一般原因:
- Harbor 使用自签证书。
- 企业 CA 没有分发到 Docker 客户端。
- 证书链不完整。
- Harbor 域名和证书 CN / SAN 不匹配。
先查证书:
openssl s_client -connect registry.example.com:443 -servername registry.example.com </dev/null
ls -l /etc/docker/certs.d/registry.example.com/再修复:
sudo mkdir -p /etc/docker/certs.d/registry.example.com
sudo cp ca.crt /etc/docker/certs.d/registry.example.com/ca.crt
sudo systemctl restart docker
docker login registry.example.com验证看到 Login Succeeded 才算处理完。
server gave HTTP response to HTTPS client
现象:
http: server gave HTTP response to HTTPS client这个报错翻译一下就是:客户端按 HTTPS 去访问,但服务端返回的是 HTTP。
先确认 Harbor 到底是什么协议:
curl -vk https://registry.example.local/v2/
curl -v http://registry.example.local/v2/如果 Harbor 确实是 HTTP,临时内网可以配置:
{
"insecure-registries": [
"registry.example.local"
]
}然后:
sudo systemctl restart docker
docker info | grep -A 5 "Insecure Registries"
docker login registry.example.local生产建议还是改 HTTPS,不要长期靠 insecure registry。
push denied
现象:
denied: requested access to the resource is denied按这个顺序查:
docker login registry.example.com
docker tag app:1.0.0 registry.example.com/backend/app:1.0.0
docker push registry.example.com/backend/app:1.0.0如果还是失败,重点看:
backend项目是否存在。- 当前账号是否有 Developer、Maintainer 或 Project Admin 权限。
- 镜像名是否写了 project。
- 是否推到了 Proxy Cache 项目。
- 是否被不可变 tag 规则挡住。
- 项目配额是否已满。
修复后重新 push,看到 digest 输出才算成功。
pull manifest unknown
现象:
manifest unknown一般是 tag 不存在,或者你拉错了项目。
先查镜像名:
docker pull registry.example.com/backend/app:1.0.0
docker manifest inspect registry.example.com/backend/app:1.0.0再到 Harbor UI 里确认:
Project -> Repository -> Artifact -> Tag如果 Harbor 里没有这个 tag,就不是客户端问题。回到构建流水线看当时 push 的镜像名和 tag。
删除镜像后磁盘不下降
先不要急着删更多。按顺序看:
df -h
du -sh /data/harbor
docker compose logs --tail=200 jobservice常见原因:
- 只删 tag,没有执行 GC。
- blob 仍被其他 tag 引用。
- 执行的是 GC dry run,不是真清理。
- 删除发生在 GC 保护窗口内。
- 对象存储回收有延迟。
处理方式:
- 检查保留策略。
- 执行 GC dry run。
- 低峰执行正式 GC。
- 查看 GC history 和 jobservice 日志。
- 再看
df -h和du -sh /data/harbor。
Trivy 扫描失败
现象:
scan failed
failed to download vulnerability DB先看组件:
docker compose ps
docker compose logs --tail=200 trivy-adapter
docker compose logs --tail=200 jobservice常见原因:
- 漏洞库地址不可达。
- 企业代理没配置。
no_proxy配错,内部组件也走代理。- 企业 CA 没信任。
- 离线环境没有漏洞库导入方案。
- 扫描任务太多。
修复方向:
- 配置 Harbor
proxy。 - 正确配置
no_proxy。 - 准备离线 Trivy DB。
- 降低扫描并发或错峰扫描。
- 修复后重新触发扫描。
push 大镜像失败
现象:
unexpected EOF
client disconnected
413 Request Entity Too Large
blob upload invalid先看基础资源:
df -h
docker info
docker compose logs --tail=200 registry
docker compose logs --tail=200 nginx常见原因:
- 前置 Nginx / LB 限制上传大小。
- 代理超时太短。
- 网络抖动。
- Harbor 磁盘不足。
- Docker daemon 并发上传在弱网下不稳定。
如果是弱网,可以在 Docker daemon 降低并发上传:
{
"max-concurrent-uploads": 2
}重启后验证:
sudo systemctl restart docker
docker info | grep -i "Concurrent"
docker push registry.example.com/backend/big-image:1.0.0复制任务失败
先在 Harbor UI 里看复制任务日志。命令侧再查连通性:
curl -vk https://target-registry.example.com/v2/
docker login target-registry.example.com常见原因:
- 目标 registry 证书不被信任。
- 目标账号没有 push 权限。
- 网络或代理不通。
- 目标项目不存在。
- tag 被不可变规则拦截。
- 目标仓库空间或配额不足。
修复后手工触发一次复制,再到目标 Harbor 拉取镜像验证。
落地工程深水区
Harbor 的深水区集中在“镜像能不能长期可信地分发”。
容量和 GC:删除 tag 不等于磁盘立刻下降,Harbor 需要垃圾回收清理不再被 manifest 引用的 blob。先 dry run,再正式 GC;低峰执行,并保留执行记录:
df -h
du -sh /data/harbor
curl -k https://registry.example.com/api/v2.0/health如果业务大量使用多架构镜像、频繁构建基础镜像或短分支 tag,保留策略要按项目拆开,不要全局一刀切。
权限和凭证:流水线不要用 admin。每个项目使用机器人账号,按 push/pull 最小授权,token 有轮换周期。镜像仓库凭证一旦泄露,攻击者可以覆盖 tag 或拉取内部镜像,影响比普通制品更直接。
证书和客户端信任:Harbor 证书不只浏览器要信任,Docker daemon、containerd、K8S 节点、Jenkins agent、离线导入机都要信任。出现 x509 时,先确认客户端信任链,不要为了省事把生产仓库改成 insecure registry。
tag、digest 和回滚:生产发布记录必须同时保留 tag 和 digest。tag 方便人读,digest 才代表不可变内容。不可变 tag 策略可以挡住“同名覆盖”,但也要给紧急修复和回滚预留流程。
复制和离线:跨机房、跨环境复制要限制范围,只同步已准入镜像。离线交付时保存镜像清单、tar 包、sha256、导入脚本和验证命令;不要只给一堆 *.tar,现场没人知道顺序和来源。
安全准入:扫描结果不能只停在“看一眼”。高危漏洞、过期基础镜像、未知来源镜像要有阻断、豁免和复核流程。否则 Harbor 只是仓库,不是治理入口。
常用命令速查
登录:
docker login registry.example.com安全登录:
echo "$HARBOR_ROBOT_TOKEN" | docker login registry.example.com \
--username "$HARBOR_ROBOT_USER" \
--password-stdin打 tag:
docker tag app:1.0.0 registry.example.com/backend/app:1.0.0推送:
docker push registry.example.com/backend/app:1.0.0拉取:
docker pull registry.example.com/backend/app:1.0.0查看 digest:
docker image inspect registry.example.com/backend/app:1.0.0 \
--format '{{json .RepoDigests}}'保存镜像:
docker save registry.example.com/backend/app:1.0.0 -o app-1.0.0.tar
sha256sum app-1.0.0.tar > app-1.0.0.tar.sha256加载镜像:
sha256sum -c app-1.0.0.tar.sha256
docker load -i app-1.0.0.tar查看 Harbor 容器:
cd harbor
docker compose ps
docker compose logs --tail=100看磁盘:
df -h
du -sh /data/harbor
docker system df -v看证书:
openssl s_client -connect registry.example.com:443 -servername registry.example.com </dev/null健康检查:
curl -k https://registry.example.com/api/v2.0/health生产落地清单
上线前按这个清单过一遍:
- Harbor 域名固定,DNS 可解析。
- 生产使用 HTTPS,Docker 客户端已信任 CA。
data_volume使用可靠存储,并监控容量。admin密码已修改,流水线不用管理员账号。- 项目按团队、环境或用途拆分。
- 机器人账号最小权限,token 有轮换机制。
- 版本 tag 包含业务版本、时间、Git commit。
- 发布记录保存 tag 和 digest。
- 生产项目启用不可变 tag。
- 扫描策略已配置,高危漏洞有阻断或豁免流程。
- 基础镜像来自内部仓库或可信同步流程。
- 中国大陆和内网环境不依赖外部公共仓库实时拉取。
- 离线交付有
save/load、校验值和镜像清单。 - 保留策略先 dry run,再正式执行。
- GC 安排在低峰期,有执行记录。
- 复制策略限制范围,目标仓库可拉取验证。
- 备份覆盖配置、证书、数据库、镜像数据。
- 升级前有测试环境演练和回滚方案。
镜像仓库做得好,开发同学感受不到它的存在:构建能推,发布能拉,回滚能找,磁盘不爆,漏洞能追。做得不好,它会在最忙的时候提醒你:证书不信任、外网拉不动、tag 被覆盖、磁盘满了。
官方文档入口
落地前建议重新对照官方入口校准版本、参数和限制:
- Harbor 安装和配置文档:
https://goharbor.io/docs/ - Harbor GitHub Releases:
https://github.com/goharbor/harbor/releases - Docker CLI
login:https://docs.docker.com/reference/cli/docker/login/ - Docker CLI
tag:https://docs.docker.com/reference/cli/docker/image/tag/ - Docker CLI
push:https://docs.docker.com/reference/cli/docker/image/push/ - Docker CLI
pull:https://docs.docker.com/reference/cli/docker/image/pull/ - Docker CLI
save/load:https://docs.docker.com/reference/cli/docker/image/save/ - Docker daemon 配置:
https://docs.docker.com/reference/cli/dockerd/ - 阿里云 ACR:
https://help.aliyun.com/product/60716.html - 腾讯云 TCR:
https://cloud.tencent.com/document/product/1141 - 华为云 SWR:
https://support.huaweicloud.com/swr/
