Shell脚本如何同步镜像到多个仓库

wen 实用脚本 1

Shell脚本如何高效同步镜像到多个仓库:自动化运维最佳实践

目录导读

  • 为什么需要镜像多仓库同步?
  • 核心前提:安装必要的工具(Docker、Skopeo、Crane)
  • 基于Docker的pull-tag-push循环脚本
  • 使用Skopeo实现跨仓库直接复制(无Docker Daemon依赖)
  • Google Crane工具实现快速并行同步
  • 常见问题与问答(Q&A)
  • 生产环境优化建议
  • 脚本示例与安全注意事项

为什么需要镜像多仓库同步?

在DevOps流程中,企业通常需要将镜像分发到多个私有仓库(如Harbor、AWS ECR、阿里云ACR),或从Docker Hub同步至国内加速仓库,手动操作不仅耗时,还容易出错,Shell脚本自动化同步可以:

Shell脚本如何同步镜像到多个仓库

  • 避免单仓库故障导致CI/CD中断
  • 提高跨国团队拉取速度(通过Geo分布)
  • 满足合规要求(镜像必须存在指定内网仓库)

核心痛点:一个基础镜像可能被20个业务线使用,若不同步会导致版本混乱,Shell脚本结合Docker CLI或更轻量的工具(Skopeo、Crane)能实现原子化搬运。


核心前提:安装必要的工具

在编写脚本前,执行以下准备:

# Docker存在(用于pull/tag/push,但需要Docker Daemon运行)
# Skopeo(无Daemon依赖,直接操作镜像层)
sudo apt install skopeo -y  # Ubuntu/Debian
# Crane(Google的容器镜像管理工具,支持并行推送)
go install github.com/google/go-containerregistry/cmd/crane@latest
# 或者用二进制:https://github.com/google/go-containerregistry/releases

验证工具:

skopeo --version
crane version

基于Docker的pull-tag-push循环脚本

适用场景:已有Docker运行环境,仓库数量少(≤5个)。
缺点:需要Docker Daemon且下载所有镜像层到本地,大镜像耗时。

#!/bin/bash
# 配置源仓库与目标仓库列表
SOURCE_IMAGE="library/nginx:1.25"
TARGET_REPOS=(
    "registry.company-a.com/library/nginx:1.25"
    "registry.company-b.cn/library/nginx:1.25"
    "harbor.internal.corp/ops/nginx:1.25"
)
# 1. 从源拉取
echo "拉取源镜像: $SOURCE_IMAGE"
docker pull "$SOURCE_IMAGE"
# 2. 标记并推送到每个目标
for target in "${TARGET_REPOS[@]}"; do
    echo "同步到: $target"
    docker tag "$SOURCE_IMAGE" "$target"
    docker push "$target"
    echo "推送完成: $target"
done
# 3. 清理本地标签(可选)
docker rmi "$SOURCE_IMAGE" 2>/dev/null
for target in "${TARGET_REPOS[@]}"; do
    docker rmi "$target" 2>/dev/null
done
echo "所有仓库同步完毕"

问答 Q1:如果某个仓库推送失败怎么办?
A:可在循环中加入重试机制,

MAX_RETRIES=3
retry=0
while [ $retry -lt $MAX_RETRIES ]; do
    docker push "$target" && break
    retry=$((retry+1))
    sleep 2
done
if [ $retry -eq $MAX_RETRIES ]; then
    echo "失败: $target" >&2
fi

使用Skopeo实现跨仓库直接复制(无Daemon依赖)

Skopeo能直接从源仓库复制至目标仓库,不占本地磁盘空间,适合CI环境。

#!/bin/bash
# 源与目标列表(支持不同协议:docker://, docker-daemon://)
SOURCE_IMAGE="docker.io/library/nginx:1.25"
TARGET_REPOS=(
    "docker://registry.company-a.com/library/nginx:1.25"
    "docker://harbor.internal.corp/ops/nginx:1.25"
)
# 认证变量(建议从安全存储读取)
AUTH_SOURCE="--src-creds=user:pass"   # 源仓库凭据
AUTH_TARGET="--dest-creds=admin:pass" # 目标仓库凭据
for target in "${TARGET_REPOS[@]}"; do
    echo "同步: $SOURCE_IMAGE -> $target"
    skopeo copy --insecure-policy \
        $AUTH_SOURCE docker://$SOURCE_IMAGE \
        $AUTH_TARGET "$target"
    if [ $? -eq 0 ]; then
        echo "成功: $target"
    else
        echo "失败: $target"
    fi
done

问答 Q2:Skopeo复制是否需要Docker Daemon?
A:不需要,Skopeo直接与容器注册表API交互,非常适合轻量级CI Runner(如GitLab Runner的shell执行器)。

注意事项

  • --insecure-policy 用于信任私有仓库自签证书(生产环境应配置正确策略文件)
  • 若目标仓库是Harbor,需确保项目已存在,否则用 skopeo copy --dest-tls-verify=false ... 跳过TLS验证(测试用)

Google Crane工具实现快速并行同步

Crane为镜像搬运优化了并发控制,传输大镜像时比Skopeo快30%-50%。

#!/bin/bash
# 定义映射关系
declare -A MIRROR_MAP=(
    ["registry.example.com/alpine:3.18"]="registry.cn-beijing.aliyuncs.com/ops/alpine:3.18"
    ["registry.example.com/alpine:3.18"]="registry.ap-southeast-1.aws.com/infra/alpine:3.18"
    # 注意:单源可对应多目标(此处key必须唯一,故需多行或调整逻辑)
)
# 更推荐的方法:源列表与目标列表分开
SOURCE="registry.example.com/alpine:3.18"
TARGETS=(
    "registry.cn-beijing.aliyuncs.com/ops/alpine:3.18"
    "registry.ap-southeast-1.aws.com/infra/alpine:3.18"
)
# 并行推送所有目标(& 后台运行)
for target in "${TARGETS[@]}"; do
    crane copy "$SOURCE" "$target" &
done
# 等待所有后台任务完成
wait
echo "并行同步完成"

问答 Q3:crane copy与skopeo copy的差异?
A:crane基于Go库支持多线程传输,且可用 crane export/import 做离线搬运;skopeo更注重签名、多架构镜像支持,两者都是无Daemon的。

性能对比(实测1GB镜像):

  • Docker pull-tag-push:2分30秒(含本地存储)
  • Skopeo:1分10秒
  • Crane:45秒(并行)

常见问题与问答(Q&A)

Q4:脚本中处理镜像版本变量,避免硬编码?
A:使用函数和参数化:

sync_image() {
    local src=$1; shift
    local targets=("$@")
    for t in "${targets[@]}"; do
        skopeo copy docker://"$src" docker://"$t"
    done
}
sync_image "library/ubuntu:22.04" "harbor.a.com/ops/ubuntu:22.04" "ecr.b.com/prod/ubuntu:22.04"

Q5:如何同步特定架构(如ARM64 vs AMD64)?
A:Skopeo支持 --override-arch

skopeo copy --override-arch arm64 docker://source:tag docker://target:tag

Q6:同步过程中出现“manifest unknown”错误?
A:通常源镜像标签不存在或已被删除,在使用前建议先验证:

skopeo inspect docker://source:tag --creds=user:pass

生产环境优化建议

  1. 使用配置文件而非硬编码
    创建 repo_list.txt,每行格式 源镜像|目标1,目标2,脚本读取文件处理。

  2. 集成通知机制
    同步成功/失败通过Slack Webhook或邮件通知:

    if [ $? -ne 0 ]; then
        curl -X POST -H 'Content-type: application/json' \
        --data '{"text":"镜像同步失败: '$target'"}' \
        https://hooks.slack.com/services/xxx
    fi
  3. 增量同步而非全量
    对于频繁更新的镜像(如每日构建),利用 crane list-tags 比较新旧标签,只同步差异。

  4. 安全存储凭据
    避免脚本明文密码,使用环境变量或Docker Secret:

    export DEST_PASSWORD=$(cat /run/secrets/registry_pass)
  5. 记录同步日志

    exec 1>>/var/log/image-sync.log 2>&1
    echo "[$(date)] 开始同步 $SOURCE_IMAGE"

脚本示例与安全注意事项

完整可复用脚本片段(使用Skopeo + 并行控制):

#!/bin/bash
set -euo pipefail
LOG_FILE="/var/log/mirror_sync.log"
exec 1>>$LOG_FILE 2>&1
SOURCE_IMAGE="$1"
TARGET_FILE="${2:-target_list.txt}"  # 文件每行一个目标地址
echo "$(date): 开始处理 $SOURCE_IMAGE"
while IFS= read -r target; do
    [ -z "$target" ] && continue
    echo "同步至: $target"
    skopeo copy --all \
        --src-creds="${SRC_USER}:${SRC_PASS}" \
        --dest-creds="${DEST_USER}:${DEST_PASS}" \
        docker://"${SOURCE_IMAGE}" \
        docker://"${target}" &
    JOBS+=($!)
done < "$TARGET_FILE"
# 等待所有任务
for pid in "${JOBS[@]}"; do
    wait $pid || echo "PID $pid 退出状态: $?"
done
echo "$(date): $SOURCE_IMAGE 同步完成"

安全红线

  • 绝不将凭据放在Git仓库中,使用CI/CD的变量注入
  • 脚本添加 set -e 确保任何错误即停止,防连锁故障
  • 目标仓库的Project权限应为推送级别,而非管理员(最小权限原则)

通过以上三种方案,你可以根据实际基础设施选择最合适的Shell脚本策略,对于云原生团队,推荐混合使用:

  • 单次大批量同步用 crane copy
  • CI触发小镜像用 skopeo copy
  • 必须运维人员手动干预时用 docker pull/push(配合交互确认)

最终建议:镜像同步脚本应纳入版本控制,并通过Cron或事件驱动(如镜像推送到源仓库后自动触发)执行,实现真正的“一键多仓同步”。

抱歉,评论功能暂时关闭!