Skip to content

下载rancher2.x镜像到私人仓库脚本

一、背景

由于一些国外的镜像国内的docker无法下载,则通过国外的服务加载,并且通过scp命令传到国内的服务器。

二、私有仓库

如果需要在私有仓库harbor上面进行配置,需要将镜像先发布到私有仓库,再拉取。

以2.6.4为例:https://github.com/rancher/rancher/releases/tag/v2.6.4

从下面的assets里获取其中的几个脚本

  • rancher-save-images.sh 这个脚本会从 DockerHub 中拉取在文件rancher-images.txt中描述的所有镜像,并将它们保存为文件rancher-images.tar.gz。
  • rancher-load-images.sh 这个脚本会载入文件rancher-images.tar.gz中的镜像,并将它们推送到您自己的私有镜像库。
  • rancher-images.txt 此文件包含安装 Rancher、创建集群和运行Rancher 工具所需的镜像列表。

2.1 去除私服重复项

**注意:**里面存在同一镜像多个版本,可保留一个版本即可,去除重复的镜像源命令如下:

sort -u rancher-images.txt -o rancher-images.txt

这里有个优化方案,如果私有镜像仓库已经有镜像,再打包下载会影响效率,比如原本安装的2.6.3,再去安装2.6.4,中间会有很多镜像重复,需要剔除txt文件里的重复文件;

安装jq工具

yum -y install epel-release
yum -y install jq

这里需要执行一下脚本过滤

#!/bin/bash

# 定义Harbor的地址和登录信息
HARBOR_HOST="https://192.168.1.21"
USERNAME='用户名' # 登录Harbor的用户名
PASSWORD='登录密码' # 登录密码

# 定义要查询的项目名称
PROJECT_NAME="rancher"

INPUT_FILE="rancher-images.txt"
OUTPUT_FILE="new-rancher-images.txt"
# 获取项目信息
PROJECT_INFO=$(curl -s -k -u "${USERNAME}:${PASSWORD}" -X GET "${HARBOR_HOST}/api/v2.0/projects?name=${PROJECT_NAME}")
PROJECT_ID=$(echo ${PROJECT_INFO} | jq -r ".[0].project_id")

# 获取仓库数量
REPO_COUNT=$(echo ${PROJECT_INFO} | jq -r ".[0].repo_count")

# 创建集合文件
COLLECTION_FILE="images.txt"
touch $COLLECTION_FILE

# 遍历获取仓库镜像和标签信息
PAGE_SIZE=100
PAGE=1
while [[ $((PAGE_SIZE * PAGE)) -lt $((REPO_COUNT + PAGE_SIZE)) ]]; do
    REPOSITORIES=$(curl -s -k -u "${USERNAME}:${PASSWORD}" -X GET "${HARBOR_HOST}/api/v2.0/projects/${PROJECT_NAME}/repositories?page=${PAGE}&page_size=${PAGE_SIZE}")
    REPO_SIZE=$(echo ${REPOSITORIES} | jq length)

    if [[ $REPO_SIZE -eq 0 ]]; then
        break
    fi

    for (( i=0; i<${REPO_SIZE}; i++ )); do
        REPO_NAME=$(echo ${REPOSITORIES} | jq -r ".[${i}].name")
        REPO_NAME=${REPO_NAME#"$PROJECT_NAME/"}

        ARTIFACTS=$(curl -s -k -u "${USERNAME}:${PASSWORD}" -X GET "${HARBOR_HOST}/api/v2.0/projects/${PROJECT_NAME}/repositories/${REPO_NAME}/artifacts?page=1&page_size=100" | jq -r '.[].tags[].name')

        for ARTIFACT in ${ARTIFACTS}; do
            ITEM="$PROJECT_NAME/${REPO_NAME}:${ARTIFACT}"
            grep -q "$ITEM" $COLLECTION_FILE || echo "$ITEM" >> $COLLECTION_FILE
        done
    done

    PAGE=$((PAGE+1))
done

# 读取txt文件并进行判断输出
grep -Fvxf $COLLECTION_FILE $INPUT_FILE > $OUTPUT_FILE

2.2 官方推荐下载

将这三个文件放在服务器上的其中一个文件夹下,执行以下命令下载所需镜像:

#下载镜像到rancher.tar
./rancher-save-images.sh --image-list ./new-rancher-images.txt
#推送到指定私有仓库,my.registry.com:5000为仓库地址
./rancher-load-images.sh -i rancher-images.tar.gz -r my.registry.com:5000

2.3 优化方案

减少磁盘的损耗,因为镜像过多导致磁盘占用过高

#!/bin/bash

LOAD_FILE="new-rancher-images.txt"
SUCCESS_FILE="success-rancher-images.txt"
USERNAME='用户名' # 登录Harbor的用户名
PASSWORD='登录密码' # 登录密码

docker login --username=${USERNAME} 192.168.1.21 --password=${PASSWORD}

while read -r image_name || [[ -n "$image_name" ]]; do
    tag_image_name=192.168.1.21/$image_name
    docker pull $image_name;
    docker tag $image_name tag_image_name

    # 执行命令
    command_result=$(docker push tag_image_name)

    # 检查命令执行结果
    if [ $? -eq 0 ]; then
        echo "$image_name" >> "$SUCCESS_FILE"  # 将已完成的数据写入文件B
        sed -i "/$image_name/d" "$LOAD_FILE"  # 从文件A中删除已完成的数据
        docker rmi $tag_image_name
        docker rmi $image_name
    fi
done < "$LOAD_FILE"

2.4 国外下载镜像到国内

由于rancher的镜像是国外的,国内下载时会因为墙的缘故无法下载,从国内服务器下载然后通过scp上传到服务器上,这里的前提条件是ssh已经设置了免密登录,注意传输完成以后把免密登录的ssh秘钥删除

2.4.1 下载和推送脚本

#!/bin/bash
list="rancher-images.txt"
images="rancher-images.tar.gz"
file_name="rancher-images"
file_type=".tar.gz"
source_registry=""
#镜像路径
images_path="/images/images"
#镜像列表txt路径
txt_path="/images/txt"
user="root"
#远程服务器地址
host="192.168.1.111"
#ssh端口
port=22

usage () {
    echo "USAGE: $0 [--image-list rancher-images.txt] [--images rancher-images.tar.gz]"
    echo "  [-s|--source-registry] source registry to pull images from in registry:port format."
    echo "  [-l|--image-list path] text file with list of images; one image per line."
    echo "  [-i|--images path] tar.gz generated by docker save."
    echo "  [-h|--help] Usage message"
}

POSITIONAL=()
while [[ $# -gt 0 ]]; do
    key="$1"
    case $key in
        -i|--images)
        images="$2"
        shift # past argument
        shift # past value
        ;;
        -l|--image-list)
        list="$2"
        shift # past argument
        shift # past value
        ;;
        -s|--source-registry)
        source_registry="$2"
        shift # past argument
        shift # past value
        ;;
        -h|--help)
        help="true"
        shift
        ;;
        *)
        usage
        exit 1
        ;;
    esac
done

if [[ $help ]]; then
    usage
    exit 0
fi

source_registry="${source_registry%!/(MISSING)}"
if [ ! -z "${source_registry}" ]; then
    source_registry="${source_registry}/"
fi

counter=0  # 初始化计数器
file_counter=1

pulled=""
while IFS= read -r i; do
    [ -z "${i}" ] && continue
    i="${source_registry}${i}"
    target_txt="${file_name}_${file_counter}.txt"
    if docker pull "${i}" > /dev/null 2>&1; then
        echo "Image pull success: ${i}"
        pulled="${pulled} ${i}"
        # Escape the special characters in the image name to avoid incorrect replacement
        escaped_i=$(printf "%s\n" "${i}" | sed 's/[[\.*^$/]/\\&/g')
        
        # Delete the exact matching line from the file
        sed -i "/^${escaped_i}\$/d" "${list}"
          # 将当前行写入到子文件中
        echo "${i}" >> "${target_txt}"
    else
        if docker inspect "${i}" > /dev/null 2>&1; then
            pulled="${pulled} ${i}"
        else
            echo "Image pull failed: ${i}"
            continue
        fi
    fi
    ((counter++))  # 每次循环计数器加1
    if ((counter >= 10)); then
        echo "Creating ${images} with $(echo ${pulled} | wc -w | tr -d '[:space:]') images"
        docker save $(echo ${pulled}) | gzip --stdout > ${images}

        # 通过SCP传输文件
        target_image="${file_name}_${file_counter}${file_type}"

        scp -P ${port} ${images} ${user}@${host}:${images_path}/${target_image}
        scp -P ${port} ${target_txt} ${user}@${host}:${txt_path}/${target_txt}
        exit_code=$?
        if [ $exit_code -eq 0 ]; then
            counter=0
            echo "File transferred successfully"
            docker rmi ${pulled}
            rm -rf ${images} ${target_txt}
            pulled=""
            ((file_counter++))
        else
            echo "Command execution failed"
            break
        fi
    fi
done < "${list}"

#如果不是末尾还有镜像,需要再传一次
if ((counter > 0)); then
        docker save $(echo ${pulled}) | gzip --stdout > ${images}

        # 通过SCP传输文件
        target_image="${file_name}_${file_counter}${file_type}"
        scp -P ${port} ${images} ${user}@${host}:${images_path}/${target_image}
        scp -P ${port} ${target_txt} ${user}@${host}:${txt_path}/${target_txt}
        exit_code=$?
        if [ $exit_code -eq 0 ]; then
            counter=0
            echo "File transferred successfully"
            docker rmi ${pulled}
            rm -rf ${images} ${target_txt}
            pulled=""
            ((file_counter++))
        else
            echo "Command execution failed"
            break
        fi
fi

2.4.2 加载镜像脚本

这里依赖官方的加载脚本进行修改,并且由于加载镜像占用空间的缘故,加载完以后删除镜像文件,腾出空间

#!/bin/bash

# 指定目录路径和需要操作的文件后缀
dir_path="/images/images"
file_suffix=".tar.gz"
script_path="/software/rancher-load-images.sh"
harbor_host="192.168.1.21"
txt_path="/images/txt"
username='用户名' # 登录Harbor的用户名
password='登录密码' # 登录密码

#先登录镜像仓库
docker login --username=${username} 192.168.1.21 --password=${password}

# 遍历目录,找到所有指定后缀的文件进行操作
for file in "${dir_path}"/*"${file_suffix}"; do
    # 检查文件是否存在
    if [ -f "${file}" ]; then
        # 使用另一个脚本对该文件进行操作
        txt_name=$(echo "$file" | sed "s|$dir_path|$txt_path|" | sed 's/.tar.gz$/.txt/')
        sh ${script_path} -i ${file} -r ${harbor_host} -l ${txt_name}
        echo "推送镜像文件:${file},txt文件:${txt_name}成功"
        while IFS= read -r i; do
            docker rmi ${harbor_host}/${i} ${i}
        done < "${txt_name}"
    fi
done

加载镜像脚本2

由于txt文件和image关系如果不对应,则会影响推送的数据,所以修改数据

#!/bin/bash

# 指定目录路径和需要操作的文件后缀
dir_path="/images/images"
file_suffix=".tar.gz"
script_path="/software/rancher-load-images.sh"
harbor_host="192.168.1.21"
txt_path="/images/txt/rancher-images.txt"
success_txt_path="/images/txt/sucess_images.txt"
username='用户名' # 登录Harbor的用户名
password='登录密码' # 登录密码

#先登录镜像仓库
docker login --username=${username} ${harbor_host} --password=${password}

# 遍历目录,找到所有指定后缀的文件进行操作
for file in "${dir_path}"/*"${file_suffix}"; do
    # 检查文件是否存在
    if [ -f "${file}" ]; then
        # 加载镜像文件
        docker load -i ${file}
    fi
done

while IFS= read -r i; do
    push_image=${harbor_host}/${i}
    docker tag "${i}" "${push_image}" 
    docker push "${push_image}"
    exit_code=$?
    if [ $exit_code -eq 0 ]; then
        echo "${push_image} push success"
        docker rmi ${push_image} {i}
        escaped_i=$(printf "%s\n" "${i}" | sed 's/[[\.*^$/]/\\&/g')
        # Delete the exact matching line from the file
        sed -i "/^${escaped_i}\$/d" "${txt_path}"
        # 将当前行写入到子文件中
        echo "${i}" >> "${success_txt_path}"
    else
        echo "${harbor_host}/${i} push failed"
    fi

done < "${txt_path}"