Shell脚本如何清理未使用的容器镜像

wen 实用脚本 1

高效清理未使用的Docker容器与镜像:Shell脚本实战指南

目录导读

  1. 为何需要清理未使用的容器与镜像

    Shell脚本如何清理未使用的容器镜像

    • 磁盘空间占用与性能影响
    • 安全与合规性考量
  2. 理解Docker资源状态:容器与镜像的关系

    • 运行中、已停止、悬空镜像的区别
    • 常用命令回顾:docker ps -adocker images
  3. Shell脚本清理策略设计

    • 安全过滤:避免误删正在使用的资源
    • 按时间、标签、大小分层清理
  4. 实战Shell脚本:从基础到高级

    • 清理所有已停止的容器
    • 删除未被任何容器引用的悬空镜像(dangling images)
    • 按日期清理老旧镜像(保留最近30天)
  5. 自动化与定时任务集成

    • cron作业设置技巧
    • 日志记录与异常通知
  6. 常见问题解答(FAQ)

    • Q1:如何只清理未被任何容器使用的镜像?
    • Q2:清理后镜像可以恢复吗?
    • Q3:大规模清理时如何防止脚本超时?
  7. 最佳实践与安全建议

    • 提前备份关键镜像
    • 使用--filter参数降低误删风险

为何需要清理未使用的容器与镜像

Docker在开发与生产环境中广泛使用,但长期运行后,系统会积累大量已退出的容器未引用的镜像以及悬空镜像<none>:<none>),这些资源不会自动删除,可能导致:

  • 磁盘空间枯竭:典型的悬空镜像可能占用数GB,尤其在频繁构建的应用中。
  • 性能下降docker ps -adocker images命令需解析大量元数据,影响CI/CD流水线速度。
  • 安全风险:过时的镜像可能包含已知漏洞,未清理的容器可能残留敏感数据。

核心问题:手动执行docker system prune -a虽方便,但会删除所有未被使用的资源(包括运行中的卷和网络),不适用于精细化场景,编写自定义Shell脚本成为精准清理的首选方案。


理解Docker资源状态:容器与镜像的关系

在编写脚本前,需掌握三个关键概念:

  • 已停止的容器docker ps -aSTATUSExitedCreated的容器,它们仍占用磁盘(容器层数据),但不再消耗CPU/内存。
  • 悬空镜像docker imagesREPOSITORYTAG均为<none>的镜像,这些是构建过程中残留的中间层。
  • 未使用的镜像:未被任何容器(包括停止的容器)引用的镜像。docker image ls --filter dangling=false可列出这类镜像。

安全清理原则:脚本应保留与运行中或已停止容器关联的镜像,只删除“孤儿”资源。


Shell脚本清理策略设计

一个健壮的清理脚本应包含以下模块:

  1. 安全过滤:使用docker ps -a -q获取所有容器ID,避免误删被容器引用的镜像。
  2. 分层清理
    • 第一层:删除所有已停止的容器(回收容器层空间)。
    • 第二层:删除悬空镜像(立即释放空间)。
    • 第三层:按创建时间删除未被引用的老旧镜像(例如保留最近30天)。
  3. 日志记录:将清理结果输出到文件,便于审计。

示例伪代码

删除所有状态为Exited的容器
删除所有REPOSITORY和TAG为<none>的镜像
对于每个镜像,检查是否被任何容器引用,若未被引用且创建时间早于30天前,则删除

实战Shell脚本:从基础到高级

以下是一个经过测试的Shell脚本(docker-cleanup.sh),完整覆盖上述策略:

#!/bin/bash
# Docker资源清理脚本 v2.1
# 功能:清理未使用的容器、悬空镜像及30天前的未引用镜像
# ---------- 配置区 ----------
KEEP_DAYS=30                    # 保留镜像的天数
LOG_FILE="/var/log/docker-cleanup.log"
DRY_RUN=false                   # 设为true进行模拟运行
# ---------- 容器清理 ----------
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 开始清理已停止的容器..." | tee -a $LOG_FILE
STOPPED_CONTAINERS=$(docker ps -a -q -f "status=exited" -f "status=created")
if [ -n "$STOPPED_CONTAINERS" ]; then
  if [ "$DRY_RUN" = false ]; then
    docker rm $STOPPED_CONTAINERS 2>&1 | tee -a $LOG_FILE
  else
    echo "模拟删除容器:$STOPPED_CONTAINERS" | tee -a $LOG_FILE
  fi
else
  echo "无已停止的容器待清理。" | tee -a $LOG_FILE
fi
# ---------- 悬空镜像清理 ----------
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 开始清理悬空镜像..." | tee -a $LOG_FILE
DANGLING_IMAGES=$(docker images -f "dangling=true" -q)
if [ -n "$DANGLING_IMAGES" ]; then
  if [ "$DRY_RUN" = false ]; then
    docker rmi $DANGLING_IMAGES 2>&1 | tee -a $LOG_FILE
  else
    echo "模拟删除悬空镜像:$DANGLING_IMAGES" | tee -a $LOG_FILE
  fi
else
  echo "无悬空镜像待清理。" | tee -a $LOG_FILE
fi
# ---------- 按时间清理未引用镜像 ----------
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 开始清理30天前的未引用镜像..." | tee -a $LOG_FILE
# 获取所有镜像及其创建时间(时间戳格式)
TOTAL_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}|{{.ID}}|{{.CreatedAt}}")
# 获取所有正在使用(被容器引用)的镜像ID
USED_IMAGES=$(docker ps -a -q | xargs -r docker inspect --format '{{.Image}}' | sort -u)
# 遍历每个镜像,判断是否被使用且创建时间超过KEEP_DAYS
while IFS='|' read -r REPO_TAG IMAGE_ID CREATED_AT; do
  # 跳过悬空镜像(已在上一部分处理)
  if [ "$REPO_TAG" = "<none>:<none>" ]; then
    continue
  fi
  # 检查镜像是否被任何容器引用
  IS_USED=false
  for used_image in $USED_IMAGES; do
    if [ "$used_image" = "$IMAGE_ID" ] || [ "$used_image" = "$REPO_TAG" ]; then
      IS_USED=true
      break
    fi
  done
  if [ "$IS_USED" = false ]; then
    # 计算镜像创建时间与当前时间的差值(秒)
    CREATED_EPOCH=$(date -d "$CREATED_AT" +%s)
    NOW_EPOCH=$(date +%s)
    AGE_DAYS=$(( (NOW_EPOCH - CREATED_EPOCH) / 86400 ))
    if [ $AGE_DAYS -gt $KEEP_DAYS ]; then
      if [ "$DRY_RUN" = false ]; then
        docker rmi $IMAGE_ID 2>&1 | tee -a $LOG_FILE
      else
        echo "模拟删除镜像:$REPO_TAG (ID: $IMAGE_ID, 已存在 $AGE_DAYS 天)" | tee -a $LOG_FILE
      fi
    fi
  fi
done <<< "$TOTAL_IMAGES"
# ---------- 完成 ----------
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 清理完成。" | tee -a $LOG_FILE

使用说明

  • 赋予执行权限:chmod +x docker-cleanup.sh
  • 安全测试:修改DRY_RUN=true查看模拟结果。
  • 正式运行:./docker-cleanup.sh

自动化与定时任务集成

将脚本加入cron实现每周自动清理(例如每周日凌晨3点):

crontab -e
# 添加以下行:
0 3 * * 0 /path/to/docker-cleanup.sh > /dev/null 2>&1

注意:生产环境建议先手动运行一次,确认无异常后再启用cron,日志文件/var/log/docker-cleanup.log会记录每一次操作。


常见问题解答(FAQ)

Q1:如何只清理未被任何容器使用的镜像?

A:可使用docker image prune -a,但这会清理所有未引用镜像(包括最近创建的),若需按时间过滤,采用上述脚本中的“按时间清理未引用镜像”部分,通过检查docker ps -a的镜像引用列表实现。

Q2:清理后镜像可以恢复吗?

A:除非镜像已推送到远程仓库(如Docker Hub或私有Registry),否则本地删除后无法恢复,建议在清理前执行docker save备份关键镜像。

Q3:大规模清理时如何防止脚本超时?

A:对于数千个镜像,可添加timeout命令限制单个docker rmi操作时间:

timeout 30 docker rmi $IMAGE_ID 2>&1 | tee -a $LOG_FILE

同时检查docker system df命令预估空间占用,避免同时删除过多资源导致I/O突增。


最佳实践与安全建议

  1. 始终先模拟运行:将DRY_RUN=true设为默认,查看日志后再改为false
  2. 使用白名单保护:如果需要保留特定镜像(如nginx:latest),在脚本中添加排除逻辑:
    if [[ "$REPO_TAG" == "nginx"* ]]; then continue; fi
  3. 关联容器状态:清理镜像前,先清理已停止的容器(容器层数据可能包含仅被该容器引用的镜像层引用)。
  4. 监控磁盘使用:集成df -h /var/lib/docker到日志中,跟踪清理效果。
  5. 避免使用docker system prune -a --volumes:该命令会删除所有未使用的卷,可能导致数据丢失,专用脚本更安全。

通过以上Shell脚本,您可以精准控制Docker资源的清理范围,在释放磁盘空间的同时避免误删关键数据,根据实际环境调整KEEP_DAYSDRY_RUN参数,即可满足开发、测试及生产环境的多样化需求。

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