本文发布于Cylon的收藏册,转载请著名原文链接~


本文将介绍 Harbor 从 v1.10.7 升级到 v2.10.0,以及如何将 Harbor 从 v2.10 回滚到 v1.10.7。

升级条件

  • Linux服务器
  • 4 个 CPU 和 8 GB 内存(强要求),100G可用空间(跨多版本时存放备份文件以及镜像文件,这部分要求)
  • Docker-compose > 1.19.0+
  • 备份现有的 Harbor /data/database 目录

本次升级主要是使用了 harbor 内置的数据库,所以升级步骤比较容易。

官方升级路线

harbor 的升级,是不能跨很多版本进行升级,官方对此有详细说明 [1] ,可以看到路线为:

1.10.0 [1] => 2.4.0 [2] => 2.6.0 [3] => 2.8.0 [4] => 2.10.0 [5]

模拟升级步骤

github release 页下载对应的安装包

解压

# 命令主要为将harbor压缩包内文件解压到指定目录中,由于 harbor 解压后文件名无论版本如何都为“harbor”
$ mkdir ./harbor-v1.10 && tar -xf harbor-offline-installer-v1.10.0.tgz -C ./harbor-v1.10 --strip-components 1

备份默认的配置文件(仅限于 v1.10.x,v2.x均为 harbor.tmpl)

cp harbor.yml harbor.yaml.backup

清除注释

grep -Ev '^#|^$|^\s*(#|//)' harbor.yml.tmpl > harbor.yml.clean_annotation

修改一些默认配置

\cp -a harbor.yml.clean_annotation harbor.yml
# 如果需要则关闭https
sed -i  '/https:/,+3s/.*/# &/' harbor.yml
# 替换默认harbor郁闷
sed -i "s@hostname: reg.mydomain.com@hostname: img.test.com@g" harbor.yml
# 修改默认目录
sed -i "s@data_volume: /data@data_volume: /data/harbor@g" harbor.yml

启动服务

./install.sh

升级步骤

在升级前首先要缕清升级的内容,官方在升级时存在两个步骤,配置文件升级与数据库 schema 升级;并且需要知道升级的路线图,这里是从 1.10.0 升级至本文撰写时最新版本 2.10.0,所以查看官方升级路线为 1.10.0 => 2.4.0 => 2.6.0 => 2.8.0 => 2.10.0。总结升级所需变更如下:

  • harbor的配置文件升级
  • 数据库 schema 升级,由 harbor-core 组件自动完成
  • 升级路线:1.10.0 => 2.4.0 => 2.6.0 => 2.8.0 => 2.10.0

备份当前 harbor 版本

备份当前版本是为了如果需要回滚的话,可以快速的回滚到所需的版本

cd harbor 
docker-compose down 

备份 Harbor 的当前文件,以便您可以在必要时回滚到当前版本。

备份数据库文件

我们知道了,升级主要是对数据库 schema 进行reschema,Harbor 的每次新版本发布时新的功能及对老功能、代码的重构都会导致数据库模型的变更,因此几乎每次升级都需要升级数据库模式。配置文件数据,是指 Harbor 组件的配置文件,在部分新功能或者新的组件出现时,都需要在配置文件中新增其参数;在老功能、组件重构或者废弃时,也会对配置文件进行更新。

cp -r /data/database /my_backup_dir/

reschema的工作是由 harbor-core 完成的,所以我们只需要备份即可,当新版本在启动时,第一次会 reschema,这个步骤的时间会随着 harbor 的使用量而增加,这里数据库目录为 13G,1.10.7 => 2.4.0 时间大概在20分钟左右。

harbor的配置文件升级

harbor 配置的升级是需要手动执行的,命令是包含在 offline 安装包中,被包含在 “goharbor/prepare:v2.x.0” 镜像中。用户可以在 Harbor 的离线安装包中找到它,也可以在 Docker Hub 上获取,官方给出升级指南中的命令如下

# 1.10
docker run -it --rm -v ./harbor.yml:/harbor-migration/harbor-cfg/harbor.yml goharbor/harbor-migrator:v1.10.0 --cfg up


# 2.4
# 后的yaml文件必须是旧版本的
# 这步骤是将旧的 harbor.yaml 配置文件升级到新版本
# 升级后旧版本的配置文件就没有了,如果需要需要自行备份
# docker run -it --rm -v /:/hostfs goharbor/prepare:[tag] migrate -i ${path to harbor.yml}
docker run -it --rm -v /:/hostfs goharbor/prepare:v2.4.0 migrate -i ./harbor.yml

“-v /:/hostfs” 是将主机的根目录 “/” 挂载到容器中的 “/hostfs” 目录中。因为命令是运行在容器中的,而文件是在宿主机上的,为了能在容器中访问到指定的文件,需要这样挂载,之后 prepare 会对 “/hostfs” 这个文件做特殊处理,使得在容器里也能访问主机上的指定文件。

”-i“ 是指定旧版本的 harbor 配置文件

migrate 命令有如下3个参数。

​ –input(缩写形式为“-i”):是输入文件的绝对路径,也就是需要升级的原配置文件。

​ –output(缩写形式为“-o”):是输出文件的绝对路径,也是升级后的配置文件,是可选参数,如果取默认值,则升级后的文件会被写回输入文件中。

​ –target(缩写形式为“-t”):是目标版本,也就是打算升级到的版本,也是可选参数,如果取默认值,则版本为此工具发布时所支持的最新版本。

这里我们可以使用如下命令

docker run -v :/hostfs goharbor/prepare:v2.4.0 migrate -i home/harbor/upgrade/harbor.yml

升级成功会有如下输出

migrating to version 2.0.0
migrating to version 2.1.0
migrating to version 2.2.0
migrating to version 2.3.0
migrating to version 2.4.0
Written new values to home/harbor/upgrade/harbor.yml

启动新服务

在新版本 harbor 目录中,运行 ./install.sh 脚本来安装新的 Harbor 实例,这里会导入离线安装包,生成配置文件,启动服务等操作

替换 docker-compose 文件

docker-compose 的生成是在 prepare 脚本中执行的,可以看出,是调用的 prepare 镜像

# Run prepare script
docker run --rm -v $input_dir:/input \
                    -v $data_path:/data \
                    -v $harbor_prepare_path:/compose_location \
                    -v $config_dir:/config \
                    -v /:/hostfs \
                    --privileged \
                    goharbor/prepare:dev prepare $@

这种情况下,如果我们需要自定义的 docker-compose.yaml 就可以挂在到对应目录即可,模板文件可以在 photon/prepare/templates/docker_compose 处下载进行替换。

替换后,使用 sed 命令,替换 prepare 脚本中的启动命令即可。

sed -i "/-v \/:\/hostfs/a \\\t\\t -v /root/docker_compose/docker-compose.yml.jinjia.${version}:/usr/src/app/templates/docker_compose/dockercompose.yml.jinjia \\\\" ./prepare

批量升级脚本

#!/bin/bash

# 根据当前版本和目标版本选择对应的下载地址
declare -A harbor_versions=(
  ["v1.10.0"]="https://github.com/goharbor/harbor/releases/download/v1.10.0/harbor-offline-installer-v1.10.0.tgz"
  ["v2.4.0"]="https://github.com/goharbor/harbor/releases/download/v2.4.0/harbor-offline-installer-v2.4.0.tgz"
  ["v2.6.1"]="https://github.com/goharbor/harbor/releases/download/v2.6.1/harbor-offline-installer-v2.6.1.tgz"
  ["v2.8.0"]="https://github.com/goharbor/harbor/releases/download/v2.8.0/harbor-offline-installer-v2.8.0.tgz"
  ["v2.10.0"]="https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz"
)

#
# Set Colors
#

bold=$(tput bold)
underline=$(tput sgr 0 1)
reset=$(tput sgr0)

red=$(tput setaf 1)
green=$(tput setaf 76)
white=$(tput setaf 7)
tan=$(tput setaf 202)
blue=$(tput setaf 25)

#
# Headers and Logging
#

underline() { printf "${underline}${bold}%s${reset}\n" "$@"
}
h1() { printf "\n${underline}${bold}${blue}%s${reset}\n" "$@"
}
h2() { printf "\n${underline}${bold}${white}%s${reset}\n" "$@"
}
debug() { printf "${white}%s${reset}\n" "$@"
}
info() { printf "${white}➜ %s${reset}\n" "$@"
}
success() { printf "${green}✔ %s${reset}\n" "$@"
}
error() { printf "${red}✖ %s${reset}\n" "$@"
}
warn() { printf "${tan}➜ %s${reset}\n" "$@"
}
bold() { printf "${bold}%s${reset}\n" "$@"
}
note() { printf "\n${underline}${bold}${blue}Note:${reset} ${blue}%s${reset}\n" "$@"
}

set -e
# 设置根目录和工作目录
ROOT_DIR=$(cd $(dirname $0); pwd)
export ROOT_DIR
# 设置步骤变量

usage()
{
cat <<EOF
Usage: ${CMD} [ OPTION ]
Commands:
   Some commands take arguments or -h for usage.
     -d        download harbor rolling dependencies offline installer package
     -u        upgrade harbor to latest
     -r        rollback to old version
     -h        this message
EOF
    return 0
}

compare_versions()
{
  local current_version=$1
  local target_version=$2

  if [[ $current_version == v* ]]; then
    current_version=${current_version#v}
  fi

  if [[ $target_version == v* ]]; then
    target_version=${target_version#v}
  fi

  if [[ $current_version == "$target_version" ]]; then
    return 2
  fi

  IFS='.' read -ra current_version_parts <<< "$current_version"
  IFS='.' read -ra target_version_parts <<< "$target_version"

  for (( i=0; i<${#current_version_parts[@]}; i++ )); do
    if (( ${target_version_parts[$i]} > ${current_version_parts[$i]} )); then
      return 0
    elif (( ${target_version_parts[$i]} < ${current_version_parts[$i]} )); then
      return 1
    fi
  done

  return 2
}

set_env()
{
	# 设置工作目录
	WORK_DIR="${ROOT_DIR}/_work"
	
	# 获取当前版本和目标版本
	read -p "Please input current version [1.10.0]: " CURRENT_VERSION
	CURRENT_VERSION=${CURRENT_VERSION:-v1.10.0}

	if [[ ${CURRENT_VERSION:0:1} != "v" ]]; then
		CURRENT_VERSION="v${CURRENT_VERSION}" 
	fi

	read -p "Please input target version [2.10.0]: " TARGET_VERSION
	TARGET_VERSION=${TARGET_VERSION:-v2.10.0}

	if [[ ${TARGET_VERSION:0:1} != "v" ]]; then
		TARGET_VERSION="v${TARGET_VERSION}" 
	fi

	read -p "Please input harbor path [/root/harbor-v1.10]: " CURR_HARBOR_PATH
	CURR_HARBOR_PATH=${CURR_HARBOR_PATH:-/root/harbor-v1.10}

	read -p "Please input harbor database [/data/harbor]: " DATA_DIR
	DATA_DIR=${DATA_DIR:-/data/harbor}
	
	# 设置备份目录
	BACKUP_DIR="${ROOT_DIR}/harbor_backup"
	
	# 当前版本的备份路径
	CURRENT_VERSION_BACKUP_PATH="$BACKUP_DIR/${CURRENT_VERSION}"
	
	export WORK_DIR CURRENT_VERSION TARGET_VERSION CURR_HARBOR_PATH DATA_DIR BACKUP_DIR CURRENT_VERSION_BACKUP_PATH

    versions=""
    sorted_versions=""
    export versions sorted_versions
}

set_env_rollback()
{
	# 设置工作目录
	WORK_DIR="${ROOT_DIR}/_work"
	
	# 获取当前版本和目标版本
	read -p "Please input current version [2.10.0]: " CURRENT_VERSION
	CURRENT_VERSION=${CURRENT_VERSION:-v2.10.0}

	if [[ ${CURRENT_VERSION:0:1} != "v" ]]; then
		CURRENT_VERSION="v${CURRENT_VERSION}" 
	fi

	read -p "Please input target version [1.10.0]: " TARGET_VERSION
	TARGET_VERSION=${TARGET_VERSION:-v1.10.0}

	if [[ ${TARGET_VERSION:0:1} != "v" ]]; then
		TARGET_VERSION="v${TARGET_VERSION}" 
	fi

	read -p "Please input old harbor dirname [harbor-v1.10.0]: " CURR_HARBOR_PATH
	CURR_HARBOR_PATH=${CURR_HARBOR_PATH:-harbor-v1.10.0}

	read -p "Please input harbor database [/data/harbor]: " DATA_DIR
	DATA_DIR=${DATA_DIR:-/data/harbor}
	
	# 设置备份目录
	BACKUP_DIR="${ROOT_DIR}/harbor_rollback_backup"
	
	# 当前版本的备份路径
	CURRENT_VERSION_BACKUP_PATH="$BACKUP_DIR/${CURRENT_VERSION}"
	
	export WORK_DIR CURRENT_VERSION TARGET_VERSION CURR_HARBOR_PATH DATA_DIR BACKUP_DIR CURRENT_VERSION_BACKUP_PATH
}

initialize_workspace()
{
	# 创建工作目录(如果不存在)
	[ -d ${WORK_DIR} ] || mkdir -pv ${WORK_DIR}
	
	# 创建备份目录(如果不存在)
	[ -d ${BACKUP_DIR} ] || mkdir -pv ${BACKUP_DIR}
}

clean_annotation()
{
    find ${WORK_DIR}/${version}/ \
        -name "harbor.yml.*" \
        ! -name "harbor.yml.clean_annotation" \
        ! -name "harbor.yml.tmpl" -type f -exec \
        grep -Ev '^#|^$|^\s*(#|//)' {} + > ${WORK_DIR}/${version}/harbor.yml.clean_annotation
}

replace_configuration()
{
    cp -a ${WORK_DIR}/${version}/harbor.yml.clean_annotation ${WORK_DIR}/${version}/harbor.yml
    sed -i "s@hostname: reg.mydomain.com@hostname: your-harbor-domain.com@g" ${WORK_DIR}/${version}/harbor.yml
    sed -i "s@data_volume: /data@data_volume: ${DATA_DIR}@g" harbor.yml
}

compare_versions() 
{
    local current_version=$1
    local target_version=$2

    if [[ $current_version == v* ]]; then
        current_version=${current_version#v}
    fi

    if [[ $target_version == v* ]]; then
        target_version=${target_version#v}
    fi

    if [[ $current_version == $target_version ]]; then
        return 2
    fi

    IFS='.' read -ra current_version_parts <<< "$current_version"
    IFS='.' read -ra target_version_parts <<< "$target_version"

    for (( i=0; i<${#current_version_parts[@]}; i++ )); do
        if (( ${target_version_parts[$i]} > ${current_version_parts[$i]} )); then
            return 0
        elif (( ${target_version_parts[$i]} < ${current_version_parts[$i]} )); then
            return 1
        fi
    done

    return 2
}

check_version_number()
{
	set +e
	# 校验版本号
	compare_versions "$version" "${CURRENT_VERSION}"

	if [ $? -eq 0 ]; then
		warn "${CURRENT_VERSION} Greater than ${version}."
		continue
	fi

	compare_versions "$version" "$CURRENT_VERSION"
	if [ $? -eq 2 ]; then
		warn "${TARGET_VERSION} equal ${version}."
		continue
	fi
	set -e
}

swtich_version()
{
    CURRENT_VERSION=${version:-$CURRENT_VERSION}
    # 当前版本的备份路径
    CURRENT_VERSION_BACKUP_PATH="${BACKUP_DIR}/${CURRENT_VERSION}"
    # 当前harbor的启动路径
    CURR_HARBOR_PATH="${WORK_DIR}/${CURRENT_VERSION}"
    export CURRENT_VERSION CURRENT_VERSION_BACKUP_PATH CURR_HARBOR_PATH
}

pause() 
{
    success "Press enter to continue..."
    read -r
}

sorted_version()
{
	# 获取harbor版本列表
	versions=("${!harbor_versions[@]}")

	# 对版本列表进行排序
	sorted_versions=($(printf '%s\n' "${versions[@]}" | sort -V))
	export versions sorted_versions
}

initial_backup_dir()
{
    h2 "[Backup initialization]: Starting backup ${CURRENT_VERSION} ..."
    # 创建备份目录(如果不存在)
    [ -d ${CURRENT_VERSION_BACKUP_PATH} ] || mkdir -pv ${CURRENT_VERSION_BACKUP_PATH}
    [ -d ${CURRENT_VERSION_BACKUP_PATH}"_database" ] || mkdir -pv ${CURRENT_VERSION_BACKUP_PATH}"_database"
    [ -d ${CURRENT_VERSION_BACKUP_PATH}"_redis" ] || mkdir -pv ${CURRENT_VERSION_BACKUP_PATH}"_redis"
    note "backup dir is: ${CURRENT_VERSION_BACKUP_PATH} is checked." 
    note "backup database dir ${CURRENT_VERSION_BACKUP_PATH}_database is checked." 
}

backup_current_version()
{
    h2 "[Backup progess]: starting backup harbor ${CURRENT_VERSION} ..."
    # 备份harbor
    cp -r ${CURR_HARBOR_PATH} ${CURRENT_VERSION_BACKUP_PATH}/
    # 备份harbor数据目录的database
    cp -Rpf ${DATA_DIR}/database ${CURRENT_VERSION_BACKUP_PATH}"_database"
    cp -Rpf ${DATA_DIR}/redis ${CURRENT_VERSION_BACKUP_PATH}"_redis"
    # 备份 harbor.yml 防止升级被覆盖从而无法回滚
    cp ${CURR_HARBOR_PATH}/harbor.yml ${CURRENT_VERSION_BACKUP_PATH}/harbor.yml.$(date +%F)
    note "harbor ${CURRENT_VERSION} is backup completed, in ${CURRENT_VERSION_BACKUP_PATH}"
}

stop_old_harbor_progress()
{
    # 停止旧版容器组
    h2 "[Progress stop]: stopping ${CURRENT_VERSION} ..."
    cd ${CURR_HARBOR_PATH} && docker-compose down && cd ${ROOT_DIR} && note "harbor ${CURRENT_VERSION} is stopped ..."
}

upgrade_configfile()
{   
    h2 "[Upgrade]: upgrade ${CURRENT_VERSION} to ${version} ..."
    docker run -v /:/hostfs goharbor/prepare:${version} migrate -i ${CURRENT_VERSION_BACKUP_PATH}/harbor.yml.$(date +%F) -o ${WORK_DIR}/${version}/harbor.yml
    note "harbor config version is ${version}"
}

rollback()
{
    step=0
    h2 "[Step $step]: set up rollback env ..."; let step+=1
    set_env_rollback

    # 停止容器
    h2 "[Progress stop]: stopping ${CURRENT_VERSION} ..."
    cd ${WORK_DIR}/${CURRENT_VERSION}/ && docker-compose down && cd ${ROOT_DIR} && note "harbor ${CURRENT_VERSION} is stopped ..."

    # 备份当前版本
    initial_backup_dir
    backup_current_version

    FILE_NAME=${WORK_DIR}/${CURRENT_VERSION}

    # 检查工作目录是否存在已下载文件
    if [ ! -d ${FILE_NAME} ]; then
        error "${CURRENT_VERSION} not found"
        exit 1
    fi

    h2 "[Step $step]: starting switch harbor ${TARGET_VERSION} ..." ; let step+=1
    rm -fr ${DATA_DIR}/database && rm -fr ${DATA_DIR}/redis && \
        cp -Rpf "${ROOT_DIR}/harbor_backup/${TARGET_VERSION}_database/database" ${DATA_DIR}/database
        cp -Rpf "${ROOT_DIR}/harbor_backup/${TARGET_VERSION}_redis/redis" ${DATA_DIR}/redis
    note "Switch database to ${TARGET_VERSION} is completed."

    sleep $((RANDOM % 6 + 10))
    h2 "[Step $step]: starting harbor ${TARGET_VERSION} ..."
    cd "${ROOT_DIR}/harbor_backup/${TARGET_VERSION}/${CURR_HARBOR_PATH}" && ./install.sh
    success $"----Harbor ${CURRENT_VERSION} to ${TARGET_VERSION} has been rollback.----"

}

download()
{   
    sorted_version
	# 更新harbor
    for version in "${sorted_versions[@]}"; do
        DOWNLOAD_URL=${harbor_versions[$version]}
        FILE_NAME=$(basename $DOWNLOAD_URL)
        FOLDER_NAME=${FILE_NAME%.*}
        ARCHIVE_FILE="${ROOT_DIR}/$FILE_NAME"

        if [ -f ${ARCHIVE_FILE} ]; then
            warn "${FILE_NAME} existed, skip download.."
            continue
        fi

        note "[Downloader]: ${harbor_versions[$version]}"
        wget "${harbor_versions[$version]}"
    done
}

rotate_upgrade_harbor_versions()
{
	# 获取harbor版本列表
    sorted_version

	# 更新harbor
    for version in "${sorted_versions[@]}"; do
        # 向下传递变量
        export version
        
        h2 "[Install ${version}]: starting upgrade ..." 
        
        # 检查更新是否合法
        # 如果当前版本等于要更新的版本,则不更新
        check_version_number

        # 停止旧版本的服务
        stop_old_harbor_progress

        # 备份当前版本 harbor
        initial_backup_dir
        backup_current_version
        
        h2 "[install checking]: Starting install checking ..."
        # 检查offline安装包是否下载
        DOWNLOAD_URL=${harbor_versions[$version]}
        FILE_NAME=$(basename $DOWNLOAD_URL)
        FOLDER_NAME=${FILE_NAME%.*}
        ARCHIVE_FILE="${ROOT_DIR}/$FILE_NAME"

        # 检查工作目录是否存在已下载文件
        if [ ! -f ${ARCHIVE_FILE} ]; then
            error "Offline installer ${FILE_NAME} not found,Please download first ${DOWNLOAD_URL}"
            exit 1
        fi
        
        # 创建对应版本的工作目录(如果不存在)

        [ -d "${WORK_DIR}"/"${version}" ] || mkdir -pv "${WORK_DIR}"/"${version}"
        note "install checked"

        h2 "[Uncompress]: starting uncompress harbor offline installer ..."
        # 解压对应版本安装包
        mkdir -pv "${WORK_DIR}"/"${version}" && tar -xf "${ROOT_DIR}"/"${FILE_NAME}" -C "${WORK_DIR}"/"${version}"/ --strip-components 1
        note "harbor ${version}: ${WORK_DIR}/${version}"

        # 导入镜像
        set +e
        h2 "[Load image]: starting load harbor ${version} ..."
        cd "${WORK_DIR}/${version}" && docker image load -i harbor."${version}".tar.gz
        note "harbor image loaded"
        set -e
        
        h2 "[Replace configration]: start replace default config file ..."
        
        # 清除 harbor.yml 中的注释
        clean_annotation
        # 替换为所需的config
        replace_configuration
        note "replaced"
        
        # 更新操作
        upgrade_configfile

        h2 "[Installer]: start install harbor ${version} ..."
        # 如果使用了定制化 docker-compose 则开启这行
        # 使用准备好的模板来更换容器内部的模板
        # 这样保证了可以随意定制 docker-compose的文件
        cd "${WORK_DIR}/${version}" && sed -i "/-v \/:\/hostfs/a \\\t\\t -v /root/docker_compose/docker-compose.yml.jinjia.${version}:/usr/src/app/templates/docker_compose/dockercompose.yml.jinjia \\\\" ./prepare
        cd "${WORK_DIR}/${version}" && ./install.sh
        note "${version} installed"

        success $"----Harbor ${CURRENT_VERSION} to ${version} has been upgraded.----"
        # 切换变量,把这次更新好的版本作为下次要更新的旧版本进行传递
        swtich_version
        pause
    done

    # 完成升级
    success $"----Harbor ${TARGET_VERSION} has been installed and started successfully.----"
}

upgrade()
{
    step=0
    h2 "[Step $step]: set up env ..."; (( step+1 ))
    set_env

    # 初始化目录
    h2 "[Step $step]: initailizaion workspace ..."; (( step+1 ))
    initialize_workspace

    # 滚动版本更新harbor
    h2 "[Step $step]: Starting upgrade gradually ..."; (( step+1 ))
    rotate_upgrade_harbor_versions
}

MAIN(){

    if [ $# -eq 0 ]; then
        warn "Non option ..."
        usage
        exit 1
    fi

    while getopts "duhr" option; do
        case ${option} in
            d)
                download
                R=$?
                ;;
            u)
                upgrade
                R=$?
                ;;
            r)
                rollback
                R=$?
                ;;
            h)
                usage
                R=$?
                ;;
            \?)
                usage
                ;;
        esac
    done
    exit ${R}
}

MAIN ${@}

Reference

[1] Upgrade Harbor and Migrate Data - v1.10.0

[2] Upgrade Harbor and Migrate Data - v2.4.0

[3] Upgrade Harbor and Migrate Data - v2.6.0

[4] Upgrade Harbor and Migrate Data - v2.8.0

[5] Upgrade Harbor and Migrate Data - v2.10.0

[6] Prepare 脚本

[6] 生产系统中升级 Harbor 的完整流程

[6] Upgrade Harbor from v1.10.7 to v2.4.0 then 2.6.0

本文发布于Cylon的收藏册,转载请著名原文链接~

链接:https://www.oomkill.com/2024/03/upgrade-harbor/

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」 许可协议进行许可。