从原理到实战的完整指南
目录导读
- 为什么需要清理未使用卷? – 探讨磁盘空间浪费与性能隐患
- 什么是“未使用卷”? – 定义与常见场景(Docker、KVM、LVM等)
- 自动化脚本的核心逻辑 – 如何安全识别与删除
- 实战脚本示例 – 针对Docker、LVM、云卷的清理代码
- 风险控制与最佳实践 – 避免误删与数据丢失
- 常见问题问答(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状态) |
核心原则:卷必须满足“无任何活跃引用者”且“不再需要保留数据”两个条件。
自动化脚本的核心逻辑
一个健壮的清理脚本应遵循以下流水线:
- 扫描阶段:通过系统命令或API列出所有卷及其状态。
- 过滤阶段:筛选出符合“未使用”条件的卷(如Docker的
dangling状态)。 - 二次确认(可选):检查卷目录下是否包含用户自定义的非临时数据,或核对最后修改时间。
- 执行删除:按策略(如保留最近N个卷、按大小排序保留等)执行删除。
- 日志记录:记录删除的卷名、大小、时间,便于审计与回滚。
安全第一:所有脚本应默认开启“干跑模式(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:
- 立即停止所有自动化任务,避免覆盖已释放空间。
- 如果使用LVM或云平台,部分服务提供快照恢复(如AWS EBS的从快照重建)。
- 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与云平台,并始终将”可追溯性”作为脚本的核心设计原则。