PVE 虚拟机磁盘空间优化与自动回收指南 (qcow2)

📖 目录

  1. 核心原理:为什么 df -h 显示很大但实际占用很小?
  2. PVE 宿主机端配置 (关键步骤)
  3. 虚拟机内部配置 (Linux)
  4. 验证与测试
  5. 自动化维护脚本 (一键压缩)
  6. 常见问题与注意事项

1. 核心原理:为什么 df -h 显示很大但实际占用很小?

🤔 现象

在虚拟机内运行 df -h,看到磁盘总大小(Size)是 174G,已用(Used)是 61G
用户常误以为:“我压缩了磁盘,为什么这里还是显示 174G?是不是没成功?”

💡 真相:逻辑大小 vs 物理大小

  • 逻辑大小 (Logical Size / Size):

    • 这是告诉操作系统:“你有一个 174G 的硬盘可以用”。
    • 压缩不会改变这个值。如果您希望这个值变小,必须要在分区层面进行“缩小分区”操作(高风险,通常不必要)。
    • 作用:保证您未来有空间写入新数据,无需频繁扩容。
  • 物理大小 (Physical Size / Actual Usage):

    • 这是 qcow2 文件在 PVE 宿主机硬盘上实际占用的空间
    • qcow2 的特性:它是“稀疏文件”(Sparse File)。它只存储实际写入的数据块
    • 效果
      • 虚拟机认为有 174G。
      • 实际上只用了 61G 数据。
      • 宿主机硬盘只被占用约 61G(加上少量元数据)。
      • 剩下的 113G 空闲空间在宿主机上完全不占位置

🔄 删除文件后空间会自动回来吗?

  • 默认情况不会
    • 当您在虚拟机里删除一个 10G 的文件,文件系统标记这些块为“空闲”,但 qcow2 文件仍然保留这些块的历史记录(因为不知道您以后会不会写新数据进去,或者是否只是临时删除)。
  • 解决方案:需要启用 TRIM/Discard 机制,告诉底层“这些块彻底没用了,可以释放”。

2. PVE 宿主机端配置 (关键步骤)

此步骤必须在 PVE 管理界面 完成,否则虚拟机内的优化指令无法传递到底层。

🛠️ 操作步骤

  1. 选中虚拟机 -> 点击 **硬件 (Hardware)**。
  2. 双击需要优化的硬盘 (例如 scsi0sata0)。
  3. 在弹出的窗口中,确保勾选/设置以下选项:
选项 推荐设置 说明
丢弃 (Discard) 勾选 核心项。允许虚拟机发送 TRIM 指令,通知宿主机回收空闲块。
IO thread 勾选 提升并发 I/O 性能,避免阻塞。
SSD 仿真 (SSD Emulation) 勾选 如果宿主机是 SSD,勾选此项让虚拟机系统启用 SSD 优化策略。
缓存 (Cache) 默认 (无缓存) 最安全。除非有 UPS 且追求极致性能,否则不要选 Write Back。
异步 IO io_uring 现代 Linux 内核的高效 I/O 接口 (PVE 7+ 推荐)。
  1. 点击 OK 保存。
  2. 重要:部分设置(特别是 Discard 和 SSD 仿真)可能需要 重启虚拟机 才能生效。

3. 虚拟机内部配置 (Linux)

此步骤在 虚拟机终端 内执行。

🚀 启用自动 Trim (fstrim)

现代 Linux 发行版通常自带 fstrim.timer,但可能未启用。

  1. 检查状态

    systemctl status fstrim.timer
    • 如果显示 active (waiting)enabled,则无需操作。
    • 如果显示 disabledinactive,请继续。
  2. 启用并启动定时任务

    sudo systemctl enable fstrim.timer
    sudo systemctl start fstrim.timer
    • 作用:系统会默认每周自动运行一次 fstrim,扫描并回收空闲空间。
    • 安全性:完全安全,只操作标记为“未使用”的块,不影响现有数据。
  3. **手动立即执行一次 (可选)**:

    sudo fstrim -v /
    • 输出示例/: 12.5 GiB was trimmed
    • 这表示有 12.5G 的空间被标记为可回收。注意:此时 qcow2 文件大小可能不会立即变小,但这为后续的快照合并或手动压缩做好了准备。

4. 验证与测试

✅ 验证 1:确认 PVE 端设置生效

在虚拟机内运行:

lsblk -D

查看输出中的 DISC-GRANDISC-MAX 列。

  • 如果有数值(如 512K4M),且 DISC-ZERO1,说明 Discard 已生效
  • 如果全为 0B,说明 PVE 端的“丢弃”选项未勾选或未重启虚拟机。

✅ 验证 2:确认宿主机空间节省

PVE 宿主机 SSH 中运行:

ls -lh /var/lib/vz/images/<VMID>/vm-<VMID>-disk-0.qcow2
  • 对比文件大小:它应该接近虚拟机内 df -hUsed 列大小,而不是 Size 列大小。
  • 对比总空间:运行 df -h / (假设您的存储池在根目录),可用空间应显著增加。

5. 自动化维护脚本 (一键压缩)

虽然 fstrim 能标记空闲块,但 qcow2 文件本身不会自动收缩(Shrink)。如果您删除了大量数据并希望 立即 看到 .qcow2 文件变小,需要运行此脚本。

⚠️ 注意事项

  • 该脚本会 停止虚拟机 进行压缩,会有短暂停机时间。
  • 建议每月执行一次,或在清理大量数据后执行。

📜 脚本内容 (optimize_disk.sh)

#!/bin/bash

# ================= 配置区域 =================
VMID=107 # 请修改为您的虚拟机 ID
STORAGE_PATH="/var/lib/vz/images/$VMID"
DISK_NAME="vm-$VMID-disk-0.qcow2" # 请根据实际文件名修改,通常是这个格式
TEMP_DISK="/tmp/${DISK_NAME}.temp"
# ===========================================

echo "🚀 开始优化虚拟机 $VMID 的磁盘空间..."

# 1. 检查虚拟机状态
STATUS=$(qm status $VMID | awk '{print $2}')
if [ "$STATUS" == "running" ]; then
echo "⏹️ 正在停止虚拟机 $VMID ..."
qm stop $VMID
if [ $? -ne 0 ]; then
echo "❌ 停止虚拟机失败,退出。"
exit 1
fi
WAS_RUNNING=true
else
WAS_RUNNING=false
echo "ℹ️ 虚拟机当前处于停止状态。"
fi

# 2. 检查磁盘文件是否存在
if [ ! -f "$STORAGE_PATH/$DISK_NAME" ]; then
echo "❌ 找不到磁盘文件: $STORAGE_PATH/$DISK_NAME"
# 尝试自动查找第一个 qcow2 文件
DISK_NAME=$(ls $STORAGE_PATH/*.qcow2 | head -n 1 | xargs basename)
if [ -z "$DISK_NAME" ]; then
echo "❌ 未找到任何 qcow2 磁盘文件,退出。"
exit 1
fi
echo "🔍 自动检测到磁盘文件: $DISK_NAME"
fi

FULL_PATH="$STORAGE_PATH/$DISK_NAME"

# 3. 执行压缩转换
echo "️ 正在压缩磁盘 (这可能需要几分钟)... "
echo "源文件: $FULL_PATH"
echo "临时文件: $TEMP_DISK"

# -c: 启用压缩
# -O qcow2: 输出格式
qemu-img convert -f qcow2 -O qcow2 -c "$FULL_PATH" "$TEMP_DISK"

if [ $? -ne 0 ]; then
echo "❌ 压缩失败!清理临时文件并退出。"
rm -f "$TEMP_DISK"
# 尝试重启虚拟机
if [ "$WAS_RUNNING" == true ]; then
echo "🔄 尝试重新启动虚拟机..."
qm start $VMID
fi
exit 1
fi

# 4. 替换原文件
echo "💾 正在替换原磁盘文件..."
mv "$FULL_PATH" "${FULL_PATH}.backup"
mv "$TEMP_DISK" "$FULL_PATH"

# 5. 清理备份 (可选,确认无误后再删,这里为了自动化直接删)
# 如果想保留备份以防万一,注释掉下面这行
rm -f "${FULL_PATH}.backup"

# 6. 重启虚拟机
if [ "$WAS_RUNNING" == true ]; then
echo "▶️ 正在启动虚拟机 $VMID ..."
qm start $VMID
fi

# 7. 显示结果
NEW_SIZE=$(ls -lh "$FULL_PATH" | awk '{print $5}')
OLD_SIZE_BACKUP="${FULL_PATH}.backup" # 此时已删除,如需对比需在删除前记录

echo "✅ 优化完成!"
echo "📉 新磁盘文件大小: $NEW_SIZE"
echo "💡 提示:如果之前有 .old 或 .backup 大文件,请手动检查并删除以释放空间。"

📝 使用方法

  1. 在 PVE 宿主机创建文件:nano optimize_disk.sh
  2. 粘贴代码,修改 VMID
  3. 赋予权限:chmod +x optimize_disk.sh
  4. 运行:./optimize_disk.sh

6. 常见问题与注意事项

Q1: 为什么我运行了 fstrim,但 .qcow2 文件大小没变?

A: 这是正常的。

  • fstrim 只是标记块为“空闲”。
  • qcow2 格式为了性能,不会实时收缩文件。
  • 只有在执行以下操作时,物理文件才会真正变小:
    1. 运行上述的 qemu-img convert -c 压缩脚本。
    2. 创建新快照并删除旧快照(合并过程中会回收)。
    3. 进行备份还原。
  • 建议:日常依赖 fstrim 保持内部健康,每月运行一次压缩脚本即可。

Q2: 开启 Discard 会影响性能吗?

A: 几乎不会。

  • 在现代 SSD 和 PVE 版本上,开销微乎其微。
  • 相反,长期不开启会导致 SSD 写入性能下降(写入放大),开启反而能维持高性能。

Q3: 我可以把磁盘大小(Size)从 174G 改回 65G 吗?

A: 极度不推荐,除非万不得已。

  • 这需要进入虚拟机内部缩小分区和文件系统,风险极高,容易导致数据丢失或系统无法启动。
  • 优势:保持 174G 的逻辑大小,让您未来随时可以写入更多数据而无需再次调整分区,且不占用额外宿主机空间。留着它是最好的选择。

Q4: 脚本运行期间虚拟机不可用吗?

A: 是的。

  • 压缩过程需要独占磁盘文件,因此脚本会自动停止虚拟机。
  • 请选择业务低峰期(如凌晨)运行。

总结

  1. PVE 端:勾选 Discard, IO Thread, SSD
  2. 虚拟机端:启用 fstrim.timer
  3. 定期维护:每月运行一次压缩脚本(可选,视数据变动量而定)。

这样即可实现 “逻辑大空间,物理小占用,自动可回收” 的完美状态!