将 HARBOR 迁移到新的数据库和现有 PVC 存储

当使用 StorageClass 动态创建的文件系统 PVC 部署的 HARBOR 实例需要迁移到另一个命名空间或集群时——例如在操作系统替换或集群重建期间——你需要使用备份/恢复工具来复制其无状态工作负载。HARBOR 的 core / registry / jobservice / portal / trivy 工作负载都是无状态的。用于保留项目和镜像所需的持久化状态位于两个外部位置:

  • PostgreSQL:所有元数据(项目、仓库、标签→blob 引用、用户、robot 账户、复制规则、配额)。
  • Registry 文件系统 PVC:镜像 blob。

Jobservice 日志和 Trivy 数据属于辅助持久化数据;如果这些数据必须保留,也应一并迁移其 PVC。Redis 保存缓存和任务队列数据,在目标端会重新创建。

因此,迁移流程是:使源 HARBOR 静默,使用各自的方式迁移 PostgreSQL 和 HARBOR PVC 数据,准备目标命名空间中的 Secret,然后使用相同的 HARBOR 名称重新部署 HARBOR,并指向已迁移的数据库和已存在的 PVC

INFO

如果你希望备份工具本身来复制卷数据,请改用 使用 Velero 进行备份和恢复

术语

术语说明
源实例迁移前的 HARBOR 实例
目标实例迁移后的 HARBOR 实例
源命名空间源实例所在的命名空间
目标命名空间目标实例所在的命名空间
HARBOR CR Resource描述 HARBOR 部署配置的自定义资源

先决条件

  1. 安装 kubectl:请参阅 Kubernetes 官方文档
  2. PostgreSQL 迁移方案:HARBOR 的数据库必须迁移到目标端,或者在目标端可访问,并且必须保持相同的主版本号。如果由 PostgreSQL Operator 托管,跨集群热备切换流程可以将停机时间缩短到切换阶段。
  3. HARBOR PVC 迁移方案:registry PVC 必须迁移到目标命名空间,或者在目标命名空间中预先创建;如果 jobservice 和 Trivy PVC 的数据需要保留,也应一并迁移。
  4. Secret 输入值:源 secretKey、源 admin-password Secret 引用,以及目标 PostgreSQL / Redis 的连接与认证值,或已生成的 Secret。你将在 步骤 3 中准备目标命名空间的 Secret。

为方便起见,请设置以下变量:

export SRC_NS=<source namespace>
export SRC_HARBOR=<source HARBOR name>
export TGT_NS=<target namespace>
export SRC_CONTEXT=<source cluster kubeconfig context>
export TGT_CONTEXT=<target cluster kubeconfig context>

在运行下面的命令之前,请确保源集群和目标集群的上下文都指向正确的 kubeconfig 条目。本指南中所有与集群通信的 kubectl 命令都使用 --context,因此不会混淆源集群和目标集群。第 4.4 步中的本地清单补丁命令不会连接集群,因此不需要 --context

迁移概览

  1. 使源实例静默:启用 Repository Read Only,然后将源 HARBOR 工作负载缩容到 0
  2. 迁移 PostgreSQL 和 HARBOR PVC 存储:迁移 PostgreSQL,并在目标命名空间中准备好可用的 HARBOR PVC。
  3. 准备目标 Secret:沿用 HARBOR Secret,并将 PostgreSQL 和 Redis 凭据 Secret 迁移到目标命名空间。
  4. 在目标端部署 HARBOR:使用相同的 HARBOR 名称,指向已迁移的数据库、已迁移的 PVC、目标 Redis,以及已准备好的 Secret。
  5. 验证并关闭只读模式:验证登录、项目、镜像、robot 账户和镜像拉取,然后关闭只读模式。

步骤 1:使源实例静默

登录源 HARBOR,进入 Administration -> Configuration -> System Settings -> Repository Read Only 并启用它。此时推送和删除都会被拒绝,因此不会再写入新的 blob,数据库也会停止变化——这就是 PostgreSQL 迁移的一致性点。

启用只读模式后,记录当前组件副本数;你将在第 4.4 步中恢复这些副本数。然后将源 HARBOR CR 中所有组件的副本数设为 0,以停止源 HARBOR 工作负载:

export SRC_NS=<source namespace>
export SRC_HARBOR=<source HARBOR name>
export TGT_NS=<target namespace>
export SRC_CONTEXT=<source cluster kubeconfig context>
export TGT_CONTEXT=<target cluster kubeconfig context>

kubectl --context "${SRC_CONTEXT}" -n "${SRC_NS}" patch harbor "${SRC_HARBOR}" --type=merge -p '{
  "spec": {
    "helmValues": {
      "core":       { "replicas": 0 },
      "portal":     { "replicas": 0 },
      "jobservice": { "replicas": 0 },
      "registry":   { "replicas": 0 },
      "trivy":      { "replicas": 0 }
    }
  }
}'

kubectl --context "${SRC_CONTEXT}" -n "${SRC_NS}" wait --for=delete pod \
  -l app=harbor,release="${SRC_HARBOR}" \
  --timeout=10m

这会关闭无状态的 HARBOR 应用层,同时保留 PostgreSQL 和 HARBOR PVC 存储,以便迁移。直到源 HARBOR 的所有应用 Pod 都消失之前,不要继续下一步。在目标端验证完成并且你不再需要将源端作为回滚点之前,不要缩容或删除源 PostgreSQL 或 HARBOR PVC 存储。

步骤 2:迁移 PostgreSQL 和 HARBOR PVC 存储

PostgreSQL —— 请按照 Alauda Container Platoform Knownledge Base 中的 PostgreSQL 迁移文档迁移数据库。在 KB 中搜索 Migrate PostgreSQL,并按照与 PostgreSQL 部署模式相匹配的文档进行操作。

HARBOR PVC 存储 —— 请联系你的存储提供商,并按照其存储迁移流程迁移 HARBOR PVC 数据。迁移后的 PVC 必须已经存在于目标命名空间中,然后才能创建目标 HARBOR CR。这包括 registry PVC;如果源实例中存在 jobservice 日志 PVC 和 Trivy PVC,也应一并包含。记录目标 PVC 的名称和元数据。在 步骤 4 中,当迁移后的 PVC 满足由 chart 生成名称、规范以及 Helm 所有权要求时,可以保留源 StorageClass 配置;否则请使用 existingClaim 显式引用迁移后的 PVC。

步骤 3:准备目标 Secret

在创建目标实例之前,先准备好目标 HARBOR CR 将引用的所有 Secret。请按以下顺序完成此步骤:

  1. 沿用源 HARBOR 的 secretKey
  2. 迁移 existingSecretAdminPassword 引用的 admin-password Secret。
  3. 迁移 PostgreSQL 凭据 Secret,并将其更新为适配已迁移的数据库。
  4. 迁移或重新创建 Redis 凭据 Secret,用于目标 Redis 实例。
  5. 验证这四个 Secret 都存在于目标 HARBOR 命名空间中。

3.1 沿用 HARBOR secretKey

读取源 secretKey

kubectl --context "${SRC_CONTEXT}" -n "${SRC_NS}" get secret "${SRC_HARBOR}-core" -o jsonpath='{.data.secretKey}' | base64 -d; echo

记录这个 16 位字符的值,并创建目标 Secret,以便在目标 HARBOR 实例上固定使用它:

# Recommended: put the source secretKey into a Secret (the key name must be "secretKey")
kubectl --context "${TGT_CONTEXT}" -n "${TGT_NS}" create secret generic harbor-secret-key \
  --from-literal=secretKey='<SOURCE-SECRETKEY>'

Chart 会将此 Secret 中的 secretKey 值挂载到 HARBOR Core 组件中。这里没有单独的字段可以配置 key 名称,因此不能使用不同的数据键。

3.2 迁移 Admin-Password Secret

备份源 CR 的 admin-password Secret 引用,并将该 Secret 复制到目标命名空间。目标 CR 会继承源 CR 中的 existingSecretAdminPasswordexistingSecretAdminPasswordKey,因此在应用目标 CR 之前,被引用的 Secret 必须已存在于目标命名空间中:

ADMIN_PASSWORD_SECRET="$(kubectl --context "${SRC_CONTEXT}" -n "${SRC_NS}" get harbor "${SRC_HARBOR}" -o jsonpath='{.spec.helmValues.existingSecretAdminPassword}')"
ADMIN_PASSWORD_SECRET_KEY="$(kubectl --context "${SRC_CONTEXT}" -n "${SRC_NS}" get harbor "${SRC_HARBOR}" -o jsonpath='{.spec.helmValues.existingSecretAdminPasswordKey}')"

kubectl --context "${TGT_CONTEXT}" -n "${TGT_NS}" create secret generic "${ADMIN_PASSWORD_SECRET}" \
  --from-literal="${ADMIN_PASSWORD_SECRET_KEY}=$(kubectl --context "${SRC_CONTEXT}" -n "${SRC_NS}" get secret "${ADMIN_PASSWORD_SECRET}" -o go-template="{{ index .data \"${ADMIN_PASSWORD_SECRET_KEY}\" }}" | base64 -d)" \
  --dry-run=client -o yaml | kubectl --context "${TGT_CONTEXT}" apply -f -

验证复制后的 admin-password Secret 数据;如果目标引用应使用不同的实际值,请更新它。

3.3 迁移 PostgreSQL 凭据 Secret

将源 HARBOR 实例引用的 PostgreSQL 凭据 Secret 迁移到目标 HARBOR 命名空间,作为初始副本。如果目标 PostgreSQL 部署生成了新的凭据 Secret,则改为将该 Secret 复制到目标 HARBOR 命名空间。

迁移完成后,仅当目标环境中的 PostgreSQL 连接信息发生变化时,才更新 Secret 数据。如果数据库端点、凭据和认证设置保持不变,则保留现有值不变。

关于所需的连接字段、凭据格式和版本要求,请参见 PostgreSQL 凭据

记录迁移后的 PostgreSQL Secret 名称。你将在 步骤 4harbor-target.yaml 中引用它。

3.4 迁移 Redis 凭据 Secret

Redis 会在目标端重新创建。迁移源 Redis 凭据 Secret 仅作为初始副本,或者直接使用目标 Redis 部署生成的凭据 Secret。

仅当目标环境中的 Redis 连接信息发生变化时,才更新 Secret 数据。如果 Redis 端点、凭据和认证设置保持不变,则保留现有值不变。关于所需的连接字段、凭据格式和部署要求,请参见 Redis 凭据

记录目标 Redis Secret 名称。你将在 步骤 4harbor-target.yaml 中引用它。

迁移 PostgreSQL 和 Redis 凭据 Secret 并不会更新存储在 HARBOR CR 中的连接地址。你必须在 步骤 4.2 中同时更新 Secret 引用和连接信息。

3.5 验证目标 Secret

继续之前,请确认这四个目标 Secret 都已存在:

kubectl --context "${TGT_CONTEXT}" -n "${TGT_NS}" get secret \
  harbor-secret-key \
  "${ADMIN_PASSWORD_SECRET}" \
  <postgresql credential Secret> \
  <redis credential Secret>

步骤 4:在目标端部署 HARBOR

创建目标端剩余的支撑资源;如果你复用了现有的 Redis 实例,可以使用新的 Redis 实例或已迁移的 Redis 实例,然后创建 HARBOR CR。secretKey、admin-password、PostgreSQL 凭据和 Redis 凭据 Secret 应已在 步骤 3 中存在于目标命名空间。

目标 HARBOR 实例基于源 CR 创建,然后根据目标环境进行调整。请按以下顺序完成迁移:

  1. 导出源 HARBOR manifest,并为目标实例进行清理。
  2. 在 manifest 中配置目标侧的 PostgreSQL 和 Redis 连接设置。
  3. 选择并配置目标 HARBOR PVC 方式。
  4. 恢复目标工作负载的副本数。
  5. 检查生成的 manifest 并应用。

4.1 生成目标 Manifest

创建目标 HARBOR CR 的方式与创建普通 HARBOR 实例相同,但要从源 CR 开始,这样 HARBOR 版本、资源、暴露设置、externalURLexistingSecretAdminPasswordexistingSecretAdminPasswordKey 以及其他未变化的运行时设置都可以保留下来。

导出源 HARBOR CR:

kubectl --context "${SRC_CONTEXT}" -n "${SRC_NS}" get harbor "${SRC_HARBOR}" -o yaml > harbor-target.yaml

使用 yq v4 将其处理为新的目标对象:

export TGT_NS=<target namespace>

yq eval -i '
  .metadata.namespace = strenv(TGT_NS) |
  del(
    .metadata.uid,
    .metadata.resourceVersion,
    .metadata.generation,
    .metadata.creationTimestamp,
    .metadata.managedFields,
    .metadata.ownerReferences,
    .metadata.finalizers,
    .metadata.selfLink,
    .metadata.annotations."kubectl.kubernetes.io/last-applied-configuration",
    .status
  )
' harbor-target.yaml

如果没有安装 yq,也可以手动做同样的更改:保留 metadata.name 不变,设置目标 metadata.namespace,添加或保留 PostgreSQL 版本检查注解,并删除源运行时元数据和 status

继续编辑 harbor-target.yaml 中仅适用于目标端的值。请将 PostgreSQL / Redis 与 Harbor PVC 分开处理:PostgreSQL 和 Redis 来自目标环境,而已迁移的 Harbor PVC 名称通常来自源 HARBOR 实例名称。

4.2 配置 PostgreSQL 和 Redis

凭据 Secret 和 HARBOR CR 中的连接设置用途不同:Secret 提供认证数据,而 CR 告诉 HARBOR 要连接哪个 PostgreSQL 和 Redis 端点。仅迁移 Secret 并不会将 HARBOR 指向目标服务。如果目标环境中的 PostgreSQL 或 Redis 端点信息没有变化,则可以保留现有连接设置不变。

harbor-target.yaml 中,检查并更新以下目标值:

  • PostgreSQL 地址database.external.hostdatabase.external.port 必须指向已迁移的 PostgreSQL 服务,并且目标 HARBOR 命名空间可以访问它。如果目标数据库名、用户名或 SSL 模式不同,还需要更新 coreDatabaseusernamesslmode。请参见 PostgreSQL 访问凭据配置
  • PostgreSQL Secretdatabase.external.existingSecretdatabase.external.existingSecretKey 必须引用在 步骤 3.3 中准备好的 PostgreSQL 凭据 Secret 和密码键。请参见 PostgreSQL 访问凭据配置
  • Redis 地址redis.external.addr 必须指向目标 Redis 端点。对于 Sentinel 模式,请包含所有可访问的 Sentinel 端点,并将 redis.external.sentinelMasterSet 设置为目标 master set 名称。请参见 Redis 访问凭据配置
  • Redis Secretredis.external.existingSecretredis.external.existingSecretKey 必须引用在 步骤 3.4 中准备好的 Redis 凭据 Secret 和密码键。请参见 Redis 访问凭据配置
  • TLS 设置:如果 PostgreSQL 或 Redis 使用 TLS,请保留或更新目标服务对应的 SSL/TLS 字段和 CA Secret。

如果 PostgreSQL 或 Redis 运行在其他命名空间中,请使用目标 HARBOR 命名空间可解析的地址,例如服务的完全限定集群 DNS 名称。在应用目标 HARBOR CR 之前,请确认每个端点的 DNS 解析和 TCP 连通性。

此时不要恢复目标工作负载副本;在第 4.3 步配置迁移后的 PVC 之前,保持从已停止的源实例继承下来的 0 值。

4.3 配置 HARBOR PVC

使用以下命令验证迁移后的 HARBOR PVC 名称:

export REGISTRY_PVC="${REGISTRY_PVC:-${SRC_HARBOR}-registry}"
export JOBSERVICE_PVC="${JOBSERVICE_PVC:-${SRC_HARBOR}-jobservice}"
export TRIVY_PVC="${TRIVY_PVC:-data-${SRC_HARBOR}-trivy-0}"

kubectl --context "${TGT_CONTEXT}" -n "${TGT_NS}" get pvc \
  "${REGISTRY_PVC}" \
  "${JOBSERVICE_PVC}" \
  "${TRIVY_PVC}"

如果迁移后的 PVC 名称与 chart 生成的名称不同,或者你不希望 Helm 管理这些迁移后的 PVC,请使用以下 kubectl patch --local 命令更新 harbor-target.yaml

export HARBOR_SECRET_KEY="${HARBOR_SECRET_KEY:-harbor-secret-key}"

kubectl patch --local -f harbor-target.yaml --type=merge -o yaml -p "$(cat <<EOF
{
  "spec": {
    "helmValues": {
      "existingSecretSecretKey": "${HARBOR_SECRET_KEY}",
      "persistence": {
        "persistentVolumeClaim": {
          "registry": {
            "existingClaim": "${REGISTRY_PVC}",
            "storageClass": null
          },
          "jobservice": {
            "jobLog": {
              "existingClaim": "${JOBSERVICE_PVC}",
              "storageClass": null
            }
          },
          "trivy": {
            "existingClaim": "${TRIVY_PVC}",
            "storageClass": null
          }
        }
      }
    }
  }
}
EOF
)" > harbor-target-updated.yaml

mv harbor-target-updated.yaml harbor-target.yaml

4.4 恢复目标工作负载副本数

使用以下 kubectl patch --local 命令更新目标 manifest,并恢复 harbor-target.yaml 中各组件的副本数:

kubectl patch --local -f harbor-target.yaml --type=merge -o yaml -p '{
  "spec": {
    "helmValues": {
      "core":       { "replicas": 1 },
      "portal":     { "replicas": 1 },
      "jobservice": { "replicas": 1 },
      "registry":   { "replicas": 1 },
      "trivy":      { "replicas": 1 }
    }
  }
}' > harbor-target-updated.yaml

mv harbor-target-updated.yaml harbor-target.yaml

将示例值 1 替换为源实例迁移前每个组件的副本数。如果某个组件被禁用,请将其从 merge patch 中省略,并保持其副本配置不变。

4.5 检查并应用 Manifest

作为参考,生成的目标 manifest 应保留继承自源实例的字段,例如资源、existingSecretAdminPasswordexistingSecretAdminPasswordKeyexposeexternalURLglobal。下面的片段展示了带有显式 PVC 引用的目标侧依赖和 PVC 字段。副本配置在第 4.4 步中单独处理,因此此处不再重复。

metadata:
  name: <SRC_HARBOR>
  namespace: <TGT_NS>
spec:
  helmValues:
    # Other source helmValues are retained.
    existingSecretAdminPassword: <source admin password Secret copied to target namespace>
    existingSecretAdminPasswordKey: password
    existingSecretSecretKey: harbor-secret-key
    database:
      type: external
      external:
        host: <migrated-pg-service>
        port: 5432
        sslmode: require
        username: postgres
        password: null
        existingSecret: <secret storing postgresql password>
        existingSecretKey: password
    redis:
      type: external
      external:
        addr: <fresh-redis-service>:6379
        sentinelMasterSet: mymaster
        password: null
        existingSecret: <secret storing redis password>
        existingSecretKey: password
    persistence:
      persistentVolumeClaim:
        registry:
          existingClaim: <migrated registry PVC name>
        jobservice:
          jobLog:
            existingClaim: <migrated jobservice log PVC name>
        trivy:
          existingClaim: <migrated Trivy PVC name>

在应用之前,请仔细检查导出的 manifest:

  • 保留源实例中的 HARBOR 版本、组件资源设置、暴露类型以及其他未变化的运行时设置,除非目标需要不同的值。
  • 确认 HARBOR 名称保持不变,并确认目标命名空间、PostgreSQL 端点、Redis 端点和 Secret 引用都正确。
  • 确认所选的 PVC 方式:要么保留继承的 StorageClass 配置及所需的 PVC 元数据,要么使用显式的 existingClaim 值。
  • 如果外部 URL 或 ingress host 已更改,也要更新这些字段;否则它们将继承源 CR 中的值。
  • 使用 existingSecretSecretKeysecretKey 固定源 secretKey
  • 确认 core.replicasportal.replicasjobservice.replicasregistry.replicastrivy.replicas 已设置为目标所需的副本数。

然后创建目标 HARBOR CR:

kubectl --context "${TGT_CONTEXT}" apply -f harbor-target.yaml

步骤 5:验证并关闭只读模式

当所有 Pod 都处于 Running 状态后,在目标端进行以下验证:

  • 管理员登录成功(使用源实例的管理员密码)。
  • 项目和仓库都存在。
  • 镜像标签存在且可以拉取pull 一个示例镜像,或者获取其 manifest + blob)——这可以确认已迁移数据库中的元数据与复用的 HARBOR PVC 存储是匹配的。
  • robot 账户可用,且 registry 认证正常——这可以确认 secretKey 已正确固定。

最后,在目标端关闭只读模式(Administration -> Configuration -> System Settings -> 取消勾选 Repository Read Only),并测试一次 push。在完成流量切换之前,请保持源实例静默;在你拆除它之前,它仍然是你的回滚路径。