自动化脚本如何清理未使用卷

wen 实用脚本 10

从原理到实战的完整指南

目录导读

  1. 为什么需要清理未使用卷? – 探讨磁盘空间浪费与性能隐患
  2. 什么是“未使用卷”? – 定义与常见场景(Docker、KVM、LVM等)
  3. 自动化脚本的核心逻辑 – 如何安全识别与删除
  4. 实战脚本示例 – 针对Docker、LVM、云卷的清理代码
  5. 风险控制与最佳实践 – 避免误删与数据丢失
  6. 常见问题问答(Q&A) – 解答高频率疑惑

为什么需要清理未使用卷?

在运维与开发环境中,未使用卷(Unused Volumes) 是常见的隐形“磁盘杀手”,它们通常由Docker容器、LVM快照、虚拟化平台(如KVM)或云存储卷残留产生,这些卷虽未被任何进程挂载,但持续占用物理空间,导致:

自动化脚本如何清理未使用卷

  • 磁盘利用率飙升:尤其在CI/CD流水线中,容器频繁创建销毁,残留卷可迅速累积至数十GB甚至TB级。
  • 备份与迁移负担:无用卷被备份系统一并处理,增加存储成本。
  • 性能下降:文件系统元数据膨胀,影响inode查询与I/O效率。

手动清理既不现实(需逐一检查挂载状态)也充满风险(可能误删有数据卷)。自动化脚本是唯一可持续的解决方案


什么是“未使用卷”?常见场景举例

场景 卷类型 未使用判定标准
Docker Docker卷(/var/lib/docker/volumes/ 未被任何容器引用(docker volume ls -f dangling=true
LVM 逻辑卷(Logical Volume) 未被挂载至任何文件系统,且无快照依赖
KVM/QEMU qcow2或raw磁盘文件 未关联任何运行的虚拟机
云存储 AWS EBS、Azure托管磁盘 未被挂载至任何实例(Available状态)
Kubernetes PVC对应的PV 被删除的PVC残留的PV(Released状态)

核心原则:卷必须满足“无任何活跃引用者”且“不再需要保留数据”两个条件。


自动化脚本的核心逻辑

一个健壮的清理脚本应遵循以下流水线:

  1. 扫描阶段:通过系统命令或API列出所有卷及其状态。
  2. 过滤阶段:筛选出符合“未使用”条件的卷(如Docker的dangling状态)。
  3. 二次确认(可选):检查卷目录下是否包含用户自定义的非临时数据,或核对最后修改时间。
  4. 执行删除:按策略(如保留最近N个卷、按大小排序保留等)执行删除。
  5. 日志记录:记录删除的卷名、大小、时间,便于审计与回滚。

安全第一:所有脚本应默认开启“干跑模式(Dry-run)”,仅在显式确认后才真正删除。


实战脚本示例

清理Docker未使用卷(最常用场景)

#!/bin/bash
# 文件名:cleanup_docker_volumes.sh
# 功能:删除所有未被容器引用的dangling卷
set -euo pipefail
LOG_FILE="/var/log/docker_volume_cleanup.log"
DRY_RUN=true   # 设置为false时执行实际删除
echo "=== $(date) ===" >> "$LOG_FILE"
# 列出所有dangling卷
dangling_volumes=$(docker volume ls -qf dangling=true)
if [[ -z "$dangling_volumes" ]]; then
    echo "未发现未使用卷" | tee -a "$LOG_FILE"
    exit 0
fi
echo "将要清理以下卷:" >> "$LOG_FILE"
echo "$dangling_volumes" >> "$LOG_FILE"
if [[ "$DRY_RUN" == true ]]; then
    echo "(干跑模式,未实际删除)" >> "$LOG_FILE"
else
    docker volume rm $dangling_volumes >> "$LOG_FILE" 2>&1
    echo "清理完成,释放了 $(docker system df | grep 'Volumes' | awk '{print $3}')" >> "$LOG_FILE"
fi

清理LVM未挂载卷(需谨慎)

#!/bin/bash
# 功能:列出并删除未挂载的LVM卷(仅用于演示,生产环境需额外校验)
vg_name="vg_data"
# 获取未挂载卷
unused_lvs=$(lvdisplay "$vg_name" | grep -B1 "LV Status.*NOT available" | grep "LV Name" | awk '{print $3}')
for lv in $unused_lvs; do
    # 二次确认:检查是否包含重要数据
    mountpoint=$(mount | grep "$lv" || true)
    if [[ -z "$mountpoint" ]]; then
        echo "卷 $lv 未被挂载,准备删除..."
        # lvremove -f "$vg_name/$lv"   # 实际删除需取消注释
    fi
done

云厂商API清理(以AWS EBS为例)

import boto3
ec2 = boto3.client('ec2')
# 获取状态为available(未挂载)的卷
volumes = ec2.describe_volumes(Filters=[{'Name': 'status', 'Values': ['available']}])
for vol in volumes['Volumes']:
    # 建议过滤:跳过具有标签“Keep=true”的卷
    if 'Keep' in vol.get('Tags', []):
        continue
    # 干跑模式:仅打印
    print(f"将删除卷 {vol['VolumeId']} ({vol['Size']}GB, 创建于 {vol['CreateTime']})")
    # 实际删除(取消注释下一行)
    # ec2.delete_volume(VolumeId=vol['VolumeId'])

风险控制与最佳实践

  • 永远先试运行:脚本默认DRY_RUN=true,确认无误后再切换。
  • 添加白名单机制:通过标签(Tags)或自定义前缀(如keep-)保护重要数据卷。
  • 基于时间的保留策略:例如只删除“创建时间超过30天”的未使用卷。
  • 监控告警集成:将清理结果发送至Slack、邮件或日志系统,便于追踪。
  • 权限隔离:使用专用服务账号,仅授予最小必要权限(如Docker的docker volume rm,而非全量root)。

常见问题问答(Q&A)

Q1:脚本扫描时,如何判断一个卷是否真的“未使用”?
A:不同平台标准不同,Docker通过docker volume ls -f dangling=true可查出未被任何容器引用的卷,LVM需检查lvdisplay中的状态是否为“NOT available”且未被任何文件系统挂载,云平台则依据挂载状态(如AWS的available)。关键:不要仅凭目录存在与否判断,需结合挂载点或引用关系。

Q2:误删了重要卷怎么办?
A

  1. 立即停止所有自动化任务,避免覆盖已释放空间。
  2. 如果使用LVM或云平台,部分服务提供快照恢复(如AWS EBS的从快照重建)。
  3. Docker卷删除后无法直接恢复(除非有备份)。最佳实践:脚本执行前应自动创建快照或备份,或至少保留最近一次清理日志。

Q3:清理脚本应该多久运行一次?
A:取决于系统负载与缓存生成速度,Docker环境推荐每天运行一次(例如凌晨3点 cron任务);LVM或静态云卷可降至每周一次;高频率CI/CD系统可每小时检查一次,但需配合阈值(如卷总数超过50个才执行)。

Q4:脚本能同时支持Docker和LVM吗?
A:可以,但建议拆分编写,因为两者的接口与风险等级不同,可设计一个主调度脚本,分别调用各模块,并将结果汇总到统一日志。不推荐混写在一个函数内,增加了意外操作的复杂度。

Q5:对于Kubernetes中的PV,如何清理?
A:当PVC被删除后,其对应的PV通常进入Released状态,自动化脚本需执行kubectl delete pv <pv-name>前检查:

  • PV的回收策略(Reclaim Policy)是否为Delete(自动删除)。
  • 如果策略是Retain,需人工确认后再删除。
    脚本示例:kubectl get pv --field-selector=status.phase=Released -o json | jq -r '.items[].metadata.name' 后删除。

自动化清理未使用卷是节省存储成本、维持系统健康的关键运维操作,通过合理的脚本设计(干跑优先、日志记录、白名单机制),您可以安全地将重复性劳动交给机器,同时避免数据灾难。建议先从Docker环境入门,再逐步扩展到LVM与云平台,并始终将”可追溯性”作为脚本的核心设计原则。

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