Harbor Registry 迁移到 MinIO:从本地存储到 S3 后端的完整踩坑记录
最近把 Harbor 的镜像存储从本地磁盘迁移到了 MinIO。原本以为只是简单地把 registry 目录同步到 MinIO Bucket,然后修改 Harbor 的 storage_service.s3 配置即可,实际过程中踩了几个典型坑:路径前缀、rootdirectory、MinIO 底层目录直接拷贝、大文件上传失败、以及同机服务绕公网访问等。
这篇记录一下完整迁移过程和最终稳定方案。
一、原始环境
Harbor 和 MinIO 都部署在同一台机器上,但不在同一个 Docker 网络里。
MinIO 的 docker-compose.yml 类似这样:
services:
minio:
image: quay.io/minio/minio:RELEASE.2025-04-22T22-12-26Z
restart: always
privileged: true
ports:
- 9000:9000
- 9001:9001
volumes:
- ./minio/config:/root/.minio
- ./minio/data:/data
...
宿主机实际路径是:
/data/docker/minio/data
Harbor 原来的 registry 数据目录是:
/data/docker/harbor/data/registry/docker/registry/v2.origin/
目标是迁移到 MinIO 的 harbor bucket 下:
harbor/docker/registry/v2/
也就是说,最终 MinIO 里的对象路径应该是:
harbor/docker/registry/v2/blobs/...
harbor/docker/registry/v2/repositories/...
二、先确认 Harbor 本地目录结构
Docker Registry 的正常目录结构大概是:
v2/
blobs/
sha256/
xx/
<digest>/
data
repositories/
<project>/
<repo>/
_manifests/
_layers/
_uploads/
可以用下面命令确认本地 registry 数据是否完整:
cd /data/docker/harbor/data/registry/docker/registry/v2.origin
find . -type f -name data | head
如果能看到类似:
./blobs/sha256/bc/bc3d715a65266231d160449965bd11afcb2d40fd5ea72292a786c89e466aca79/data
./blobs/sha256/78/78c89655c96a950260b665a4f022baba4154aa41e5da9f9ceb9ecff59bd9d37/data
说明 Harbor 的 blob 文件是正常的。
三、不要直接操作 MinIO 底层 data 目录
一开始很容易误以为 MinIO 的 bucket 就是宿主机目录:
/data/docker/minio/data/harbor/
于是直接 cp、mv 或者在这个目录里改名,比如:
mv v2.origin v2
这不推荐。
MinIO 是对象存储,虽然底层看起来像目录,但正确操作应该通过 S3 API,也就是使用 mc 工具来做:
mc mirror ...
这样 MinIO 才能正确维护对象元数据、ETag、multipart 信息等。
四、配置 mc alias:同机一定不要绕公网域名
如果 Harbor 和 MinIO 在同一台机器上,或者 mc 就在 MinIO 宿主机上执行,不要这样配置:
mc alias set minio https://s3.vlb.cn MINIO_ROOT_USER MINIO_ROOT_PASSWORD
这会导致链路变成:
本机 -> s3.vlb.cn -> Caddy / 反代 -> MinIO
明明是同机迁移,却绕了一圈公网域名、HTTPS、反向代理,容易出现大文件上传中断、Content-Length 不匹配等问题。
正确做法是直接走本机 MinIO API:
mc alias set minio http://127.0.0.1:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD
验证:
mc ls minio
mc ls minio/harbor
五、迁移命令
源目录:
/data/docker/harbor/data/registry/docker/registry/v2.origin/
目标路径:
minio/harbor/docker/registry/v2/
推荐命令:
mc mirror --overwrite --retry --max-workers 1 --disable-multipart \
/data/docker/harbor/data/registry/docker/registry/v2.origin/ \
minio/harbor/docker/registry/v2/
几个参数的作用:
--overwrite 覆盖目标端已有但不一致的对象
--retry 失败时重试
--max-workers 1 降低并发,避免大文件传输时磁盘或网络抖动
--disable-multipart 禁用 multipart 上传,避免大 blob 分片上传异常
如果 registry 里有超过 5GiB 的单个 blob,需要谨慎使用 --disable-multipart。迁移前可以检查一下:
find /data/docker/harbor/data/registry/docker/registry/v2.origin \
-type f -name data -size +4900M -print
如果没有输出,基本可以放心使用 --disable-multipart。
六、遇到的大文件上传错误
迁移过程中遇到过这个错误:
You did not provide the number of bytes specified by the Content-Length HTTP header.
典型场景是上传 900MiB、1GiB、1.8GiB 这类大 blob 时,进度条已经到 100%,但最后报错。
原因通常不是 Harbor 数据坏了,而是上传链路中断或请求体不完整。常见触发因素:
1. mc alias 使用了 https://s3.vlb.cn,绕了 Caddy / 反代
2. 大文件 multipart 上传过程中连接中断
3. 并发太高,磁盘 IO 或 MinIO 压力过大
4. 源目录和目标目录都在同一块盘上,读写压力大
最终稳定方案是:
mc alias set minio http://127.0.0.1:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD
mc mirror --overwrite --retry --max-workers 1 --disable-multipart \
/data/docker/harbor/data/registry/docker/registry/v2.origin/ \
minio/harbor/docker/registry/v2/
七、迁移后验证
先看目标路径:
mc ls minio/harbor/docker/registry/v2/
正常应该看到:
blobs/
repositories/
查看对象大小:
mc du minio/harbor/docker/registry/v2/
检查某个 blob 是否存在:
mc stat minio/harbor/docker/registry/v2/blobs/sha256/ff/ff0d34418e9b0eb4d1f27a17df8df9f53f0f55356481b701483b10379821aa61/data
如果返回对象大小、ETag、时间等信息,说明这个 blob 已经正确导入。
也可以对比源文件数量:
find /data/docker/harbor/data/registry/docker/registry/v2.origin -type f | wc -l
mc ls --recursive minio/harbor/docker/registry/v2/ | wc -l
数量不一定完全一致,但不应该差太多。
八、Harbor 的 S3 配置
迁移后,Harbor 的 harbor.yml 需要指向 MinIO。
如果 MinIO bucket 是:
harbor
对象路径是:
docker/registry/v2/...
那么 Harbor 配置应该是:
storage_service:
s3:
accesskey: harbor_user
secretkey: harbor_password
region: us-east-1
regionendpoint: http://minio:9000
bucket: harbor
secure: false
v4auth: true
forcepathstyle: true
chunksize: 10485760
multipartcopychunksize: 33554432
multipartcopymaxconcurrency: 20
multipartcopythresholdsize: 33554432
rootdirectory: ""
redirect:
deactivate: true
这里最关键的是:
bucket: harbor
rootdirectory: ""
不要写成:
rootdirectory: /
也不要写成:
rootdirectory: /registry
否则 Harbor 会去错误路径找 blob。
比如:
rootdirectory: /registry
Harbor 会去找:
harbor/registry/docker/registry/v2/...
但实际数据在:
harbor/docker/registry/v2/...
这样就会出现镜像元数据存在,但 blob 找不到的问题。
九、Harbor 和 MinIO 同机时,不要绕公网访问
如果 Harbor 和 MinIO 都在同一台机器上,Harbor 不应该配置:
regionendpoint: https://s3.vlb.cn
secure: true
这样会绕公网域名和反向代理。
更推荐把 MinIO 加入 Harbor 的 Docker 网络,然后 Harbor 直接访问:
regionendpoint: http://minio:9000
secure: false
如果不想改 Docker 网络,也可以通过宿主机网关访问:
regionendpoint: http://minio.internal:9000
secure: false
并在 Harbor 的 docker-compose.override.yml 里给 registry 加:
services:
registry:
extra_hosts:
- "minio.internal:host-gateway"
但最干净的方式还是让 MinIO 加入 Harbor 的 Docker 网络,并使用 Docker 内部 DNS:
registry 容器 -> http://minio:9000
十、重新生成 Harbor 配置
修改 harbor.yml 后,需要重新生成 Harbor 配置:
cd /path/to/harbor
./prepare
docker compose up -d
查看 registry 日志:
docker compose logs -f registry
测试登录:
docker login hub.vlb.cn
测试拉旧镜像:
docker pull hub.vlb.cn/your-project/your-image:tag
测试推新镜像:
docker pull alpine:3.20
docker tag alpine:3.20 hub.vlb.cn/test/alpine:3.20
docker push hub.vlb.cn/test/alpine:3.20
docker pull hub.vlb.cn/test/alpine:3.20
十一、最终总结
这次迁移的关键经验:
1. Harbor registry 的本地结构是 docker/registry/v2,不要迁错层级
2. MinIO 底层 data 目录不要直接 mv/cp,当对象存储就应该走 S3 API
3. mc alias 同机迁移必须用 http://127.0.0.1:9000,不要绕 s3.vlb.cn
4. 大文件上传失败时,用 --max-workers 1 --disable-multipart --retry
5. Harbor 的 rootdirectory 要和 MinIO 里的实际对象前缀完全一致
6. Harbor 和 MinIO 同机部署时,Harbor 访问 MinIO 应走 Docker 内网或宿主机内网
7. redirect.deactivate 建议开启,避免 Docker 客户端被重定向到内部 MinIO 地址
最终稳定迁移命令:
mc alias set minio http://127.0.0.1:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD
mc mirror --overwrite --retry --max-workers 1 --disable-multipart \
/data/docker/harbor/data/registry/docker/registry/v2.origin/ \
minio/harbor/docker/registry/v2/
最终 Harbor S3 关键配置:
storage_service:
s3:
regionendpoint: http://minio:9000
bucket: harbor
rootdirectory: ""
secure: false
forcepathstyle: true
redirect:
deactivate: true
一句话总结:Harbor 迁移到 MinIO,不难,难的是路径前缀和传输链路;路径要对,rootdirectory 要对,同机传输不要绕公网。