7 min read

Harbor Registry 迁移到 MinIO:从本地存储到 S3 后端的完整踩坑记录

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/

于是直接 cpmv 或者在这个目录里改名,比如:

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 要对,同机传输不要绕公网。