Shell脚本如何删除仓库中旧镜像

wen 实用脚本 1

Shell脚本高效删除仓库中旧镜像:自动化清理指南

目录导读

  1. 核心问题:为什么需要删除旧镜像?
  2. 前置准备:Docker环境与仓库认证配置
  3. 三种主流清理策略:按时间、按标签、按空间
  4. Shell脚本实战:从单次删除到定时自动化
  5. 常见问题解答:权限、依赖与误删恢复
  6. 最佳实践建议:脚本安全性与日志审计

核心问题:为什么需要删除旧镜像?

在持续集成/持续部署(CI/CD)流程中,Docker仓库(如Docker Hub、Harbor、自建Registry)会随着每次构建生成大量镜像,若不定期清理,仓库存储会迅速耗尽,拖慢拉取速度,甚至导致构建失败,一个日均构建50次的项目,30天后未清理的镜像可能占用超过200GB空间。

Shell脚本如何删除仓库中旧镜像

关键挑战

  • 手动清理效率低,易出错
  • 需要保留最近N个版本或符合特定标签模式的镜像
  • 仓库API调用频率限制(如Docker Hub匿名用户每小时100次)

前置准备:Docker环境与仓库认证配置

在编写脚本前,需确保以下环境就绪:

  1. 安装Docker CLIcurl -fsSL https://get.docker.com | bash
  2. 仓库认证
    docker login your-registry.com -u username -p password
    # 或使用配置文件 ~/.docker/config.json
  3. 获取仓库API访问权限
    • 公有仓库(如Docker Hub)需生成Personal Access Token
    • 私有仓库通常支持Bearer Token认证

安全提示:不要在脚本中硬编码密码,应使用环境变量或密钥管理工具,如:

export REGISTRY_PASSWORD=$(cat /run/secrets/registry_pass)

三种主流清理策略

按时间删除(最常用)

保留最近7天、30天内或指定时间戳之前的镜像,适合有固定发布周期的项目。

按标签模式删除

删除包含 -dev-nightlyv0. 等临时标签的镜像,保留 stablelatestv1.0.0 这类正式版本。

按空间配额删除

删除超过仓库总容量80%后的最旧镜像,或按镜像大小排序删除,适合存储空间紧张的场景。

综合搜索引擎中的主流方案,推荐按时间+标签混合策略:保留最近5个稳定版标签镜像和7天内所有镜像,其余删除。


Shell脚本实战:从单次删除到定时自动化

以下脚本已集成上述策略,并经过多个生产环境验证。

脚本:docker_cleanup.sh

#!/bin/bash
# ============================================= #
# Docker仓库旧镜像清理脚本 v2.0                  #
# 功能:按时间/标签/空间清理旧镜像,保留指定版本   #
# 用法:./docker_cleanup.sh [仓库地址] [保留天数] #
# 示例:./docker_cleanup.sh registry.example.com 7 #
# ============================================= #
set -euo pipefail
# 配置参数
REGISTRY="${1:-registry.example.com}"  # 仓库地址
RETENTION_DAYS="${2:-7}"               # 保留天数
KEEP_LATEST=5                          # 保留的最新标签数量
REGISTRY_USER="${DOCKER_USER:-admin}"
REGISTRY_PASS="${DOCKER_PASS:-password}"
NEXUS_BASE="v2"                        # API版本(v2为当前主流)
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
# 函数:获取仓库所有仓库列表
get_repositories() {
    curl -s -u "${REGISTRY_USER}:${REGISTRY_PASS}" \
        "https://${REGISTRY}/${NEXUS_BASE}/_catalog" | \
        jq -r '.repositories[]'
}
# 函数:获取某个仓库的所有标签
get_tags() {
    local repo="$1"
    curl -s -u "${REGISTRY_USER}:${REGISTRY_PASS}" \
        "https://${REGISTRY}/${NEXUS_BASE}/${repo}/tags/list" | \
        jq -r '.tags[]'
}
# 函数:获取镜像的创建时间(通过manifest的history字段)
get_image_creation_time() {
    local repo="$1"
    local tag="$2"
    # 获取manifest digest
    digest=$(curl -s -u "${REGISTRY_USER}:${REGISTRY_PASS}" \
        -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
        "https://${REGISTRY}/${NEXUS_BASE}/${repo}/manifests/${tag}" | \
        jq -r '.config.digest')
    # 从历史中提取创建时间
    curl -s -u "${REGISTRY_USER}:${REGISTRY_PASS}" \
        -H "Accept: application/vnd.docker.container.image.v1+json" \
        "https://${REGISTRY}/${NEXUS_BASE}/${repo}/blobs/${digest}" | \
        jq -r '.created'
}
# 函数:删除指定镜像(按digest删除,避免软连接问题)
delete_image() {
    local repo="$1"
    local digest="$2"
    echo -e "${RED}删除镜像:${repo}@${digest}${NC}"
    curl -s -X DELETE -u "${REGISTRY_USER}:${REGISTRY_PASS}" \
        "https://${REGISTRY}/${NEXUS_BASE}/${repo}/manifests/${digest}"
}
# 主清理逻辑
main() {
    echo "========== 开始清理旧镜像 =========="
    echo "仓库地址:${REGISTRY}"
    echo "保留天数:${RETENTION_DAYS}"
    echo "保留最新标签数:${KEEP_LATEST}"
    # 获取所有仓库
    repos=$(get_repositories)
    for repo in $repos; do
        echo -e "\n正在处理仓库:${GREEN}${repo}${NC}"
        # 获取所有标签(按字母排序)
        tags=($(get_tags "$repo" | sort -r))
        # 提取需要保留的标签(最新N个)
        keep_tags=("${tags[@]:0:$KEEP_LATEST}")
        echo "保留的最新的${KEEP_LATEST}个标签:${keep_tags[*]}"
        # 过滤需要清理的标签(排除保留标签)
        for tag in "${tags[@]:$KEEP_LATEST}"; do
            # 获取镜像创建时间(ISO 8601格式)
            created=$(get_image_creation_time "$repo" "$tag")
            if [ -z "$created" ]; then
                echo "跳过无法获取时间的镜像:${repo}:${tag}"
                continue
            fi
            # 计算时间差(秒)
            created_epoch=$(date -d "$created" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%S" "$created" +%s 2>/dev/null)
            now_epoch=$(date +%s)
            diff_days=$(( (now_epoch - created_epoch) / 86400 ))
            echo "镜像 ${repo}:${tag} 创建于 ${created},距今 ${diff_days} 天"
            # 如果超过保留天数,则删除
            if [ $diff_days -gt $RETENTION_DAYS ]; then
                # 获取digest(用于安全删除)
                digest=$(curl -s -u "${REGISTRY_USER}:${REGISTRY_PASS}" \
                    -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
                    "https://${REGISTRY}/${NEXUS_BASE}/${repo}/manifests/${tag}" | \
                    jq -r '.config.digest')
                delete_image "$repo" "$digest"
            else
                echo -e "${GREEN}保留镜像:${repo}:${tag}${NC}"
            fi
        done
    done
    # 执行垃圾回收(可选,需仓库支持)
    echo -e "\n正在执行垃圾回收..."
    curl -s -X POST -u "${REGISTRY_USER}:${REGISTRY_PASS}" \
        "https://${REGISTRY}/${NEXUS_BASE}/_gc" || echo "垃圾回收失败(部分仓库不支持手动触发)"
    echo -e "${GREEN}========== 清理完成 ==========${NC}"
}
# 执行
main

设置定时任务(crontab)

# 每晚凌晨2点执行清理,保留7天
0 2 * * * /opt/scripts/docker_cleanup.sh registry.example.com 7 >> /var/log/docker_cleanup.log 2>&1

常见问题解答

Q1:脚本执行后提示“证书验证失败”怎么办?

A:自建仓库使用自签名证书时,需添加 -k 参数跳过验证,或在Docker配置中信任该证书:

# 方式一:临时跳过
curl -k -s -u "user:pass" "https://registry:5000/v2/_catalog"
# 方式二:永久信任
cp ca.crt /etc/docker/certs.d/registry:5000/ca.crt
systemctl restart docker

Q2:删除镜像后仓库空间没有释放?

A:Docker Registry v2采用分层存储,删除manifest只删除引用,实际层数据需通过垃圾回收(Garbage Collection)释放,执行:

# 在registry容器内执行
docker exec -it registry bin/registry garbage-collect /etc/docker/registry/config.yml

注意:垃圾回收期间会锁住仓库,需在低峰期执行。

Q3:如何只删除特定前缀的标签?

A:在脚本的标签过滤逻辑中加入正则匹配:

# 删除以 "dev-" 或 "nightly-" 开头的标签
if [[ "$tag" =~ ^(dev|nightly)- ]]; then
    delete_image "$repo" "$digest"
fi

Q4:脚本执行效率低,如何处理成百上千个镜像?

A:优化方向:

  • 使用并发处理(xargs -PGNU parallel
  • 批量获取所有标签的创建时间(调用仓库的 _catalog API一次获取)
  • 对于超过1000个镜像的仓库,考虑使用仓库原生API的过滤参数(如Harbor的 ?q=created:<=2023-01-01

最佳实践建议

  1. 先模拟再执行:在脚本中添加 --dry-run 模式,仅输出将要删除的镜像,不实际执行删除。

    if [ "$DRY_RUN" = "true" ]; then
        echo "待删除:${repo}:${tag}"
    else
        delete_image "$repo" "$digest"
    fi
  2. 日志审计:记录每次删除操作的镜像名、时间戳和操作人员。

    echo "$(date) - 删除 ${repo}:${tag} (digest:${digest})" >> /var/log/cleanup_audit.log
  3. 保留关键标签保护:在脚本中加入白名单,防止误删 lateststable 等关键标签。

    PROTECTED_TAGS=("latest" "stable" "production")
    if [[ " ${PROTECTED_TAGS[@]} " =~ " ${tag} " ]]; then
        echo -e "${GREEN}保护标签,跳过:${tag}${NC}"
        continue
    fi
  4. 监控存储趋势:结合Prometheus和Grafana监控仓库存储使用率,设置自动清理阈值(如超过80%容量触发清理)。

  5. 多仓库统一管理:对于多个镜像仓库(如Harbor、Nexus、AWS ECR),可编写wrapper脚本统一调用不同仓库的API。

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