ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

VMware Thin磁盘空间回收:SDelete与Storage vMotion实战

VMware Thin磁盘空间回收:SDelete与Storage vMotion实战

简介:面向 VMware 虚拟化运维人员的一份精简磁盘空间回收实操文档,聚焦 vmfs5 文件系统不支持自动回收、Thin 磁盘删除文件后 VMDK 仍占用宿主机容量的常见困扰。压缩包仅 408KB,内含 1 个 docx 文件,围绕两条回收路径展开:一是借助微软 SDelete 清零 Windows 虚拟机空闲空间,关机后通过 SSH 登录 ESXi 主机执行 vmkfstools -K 回收 VMDK;二是用 Storage VMotion 在线迁移,将磁盘格式改为厚置备延迟归零以释放空间,再迁回 Thin 保持精简。文中还交代了停机执行命令的前提、目标存储容量校验等易错细节,并给出磁盘实际占用 27.8G 而 VMDK 达 36.7G 的案例数据,便于对照判断是否命中同类问题。已有 2021 人学习,适合需要处理存储告警、优化 Thin 卷占用的中初级虚拟化运维人员参考。

1. 一块 36.7G 的 VMDK 只装了 27.8G 数据:Thin 磁盘空间去哪了

整理一套测试环境的存储容量时,我翻出一台 Windows Server 2012 R2 虚拟机:系统里看实际只用了 27.8G,数据存储上那个 VMDK 却实打实占着 36.7G。既不是快照,也不是内存交换文件,就是我前阵子在里面删掉的一批日志和安装包——文件在 Guest OS 里没了,VMFS 上的块还挂着。这就是 VMware Thin 精简置备最典型的「只涨不跌」:写入时按需分配,删除时没人通知底层。要把它收回来,绕不开两条路,一是在系统里把空闲块清零再让 ESXi 回收,二是用 Storage vMotion 搬一趟把空洞挤掉。下面按我实际操作的顺序,把两条路的命令、参数、耗时和翻车点都摊开讲,环境以 ESXi 主机加 vCenter、Guest 为 Windows Server 2012 R2 为基准,其他版本差异我会单独标出来。

2. 先把账算清:Thin、厚置备与 VMFS 回收的底层逻辑

动手之前得先搞明白一件事:你看到的「虚拟机里用了 27.8G」和「存储上占了 36.7G」,统计的压根不是同一层的东西。前者是 NTFS 的文件系统账本,后者是 VMFS 数据块分配表。中间隔着一层虚拟磁盘格式,账对不上是常态,对上了才是巧合。这一章不讲操作,只把后面两条路线的判断依据立住,否则照着命令敲一遍,出问题时你连该看哪个日志都不知道。

2.1 Thin 磁盘为什么会「涨上去就下不来」

精简置备的分配逻辑是写时分配:Guest OS 第一次往某个偏移写入数据,ESXi 才在 VMFS 上真正划出对应的块并建立映射。一台 100G 的 Thin 虚拟机刚建好时可能只占几百兆,随着业务写入慢慢涨,这个方向是单向的。问题出在反向——当 Guest OS 删除一个文件,NTFS 做的事只是把簇标记为可用、改一改 MFT 记录,它根本不知道、也没有义务告诉下面的 hypervisor「这几块我不要了」。

于是 VMFS 侧的映射表原封不动,那些块仍然算在这台虚拟机头上。你在系统里删了 9G 日志,VMDK 一个字节都不会缩。更麻烦的是这些「幽灵块」会被后续写入复用,但复用是随机的,磁盘上形成大量空洞,实际占用和真实数据量越拉越开。我见过最夸张的一台文件服务器,置备 500G、系统内实际 120G、VMDK 占 480G,剩下那 360G 全是历年删删写写留下的碎片。

这里有个容易被忽略的细节:Thin 的回收粒度不是文件级的。哪怕你在系统里只删了一个 10G 的镜像文件,只要这 10G 在虚拟磁盘上不是连续的、对齐的整块区域,回收工具也只能按块处理,能吐回来的可能只有七八个 G。所以回收前先用系统自带工具做一次碎片整理(或者至少确认没有大范围碎片),对最终效果影响不小——这一步很多人跳过,然后抱怨「清了半天只回来一点点」。

还有一类「假 Thin」值得单独点出来:磁盘格式显示是 Thin,但虚拟机上挂着快照。此时写入落在 delta 文件里,基础盘被锁成只读,你后面做的任何清零和回收动作都作用不到真正的数据盘上。查快照是回收前的第一件事,比看容量还优先。

2.2 VMFS5 与 UNMAP:为什么自动回收指望不上

理论上这事有标准解法,SCSI 协议里有个 UNMAP 命令(ATA 世界叫 TRIM),允许 Guest OS 主动告诉存储层「这些块空了,你收回去」。VMware 在 VMFS 上实现了这套机制,但默认状态下它基本等于没开,原因很实在:早期版本里 UNMAP 是同步阻塞执行的,一次大规模删除可能让整台主机的 I/O 卡住几十秒,严重影响同数据存储上的其他虚拟机。

所以 ESXi 给了一堆开关,默认值偏保守。想确认你这台主机到底是什么状态,SSH 上去查:

# 列出 /VMFS3 下与块删除、UNMAP 相关的所有高级参数及当前值 # 不同 ESXi 版本参数名略有差异,用关键词过滤最稳 esxcli system settings advanced list -o /VMFS3 | grep -iE "delete|discard|unmap" # 单独看某个参数(示例,实际名称以你环境上一条命令的输出为准) esxcli system settings advanced get -o /VMFS3/EnableBlockDelete

EnableBlockDelete控制的是 VMFS 层的块删除通知,EnableDiscards一类参数控制是否把 Guest 的 discard 请求透传下去。代码里我特意没有写死参数全名,因为这几个开关在 ESXi 5.5、6.0、6.5、7.x 上叫法和默认值都动过,照抄网上某篇老文章的路径大概率扑空。先list再get,是血泪经验。

即便你把开关全打开,还有两道坎。第一道在 Guest 侧:Windows 直到 Server 2012 之后才对虚拟磁盘的 UNMAP 有像样的支持,而且需要 Guest 内的磁盘控制器驱动配合,老系统直接没戏。第二道在性能:即使异步化之后,大规模 UNMAP 仍然会带来可观的元数据开销,生产环境里 DBA 和存储管理员通常都不愿意在业务高峰做这件事。

VMFS6(ESXi 6.5 起)在这方面做了改进,自动 UNMAP 是默认开启的,粒度也从 1MB 细化到更小。但它是「延迟回收」,删除之后要等一段时间才真正生效,而且回收期间对 I/O 有影响。所以哪怕在 VMFS6 环境,遇到容量告急、需要立刻拿回空间的场景,手动回收依然是更快更可控的选择。判断标准很简单:你要的是「最终会回收」还是「今天下午就要腾出 80G」,前者可以等,后者必须手动上。

2.3 两条路线的选型:停机清零 vs 在线迁移

搞清楚原理之后,两条路线的取舍其实很清楚。方法一走的是「让 Guest 主动把空闲块写成 0,再由 ESXi 把这些全零块剔除」的路子,本质上是用一次全盘写入换一次精确回收。方法二走的是「把虚拟机整体搬到另一个数据存储,搬的过程中只复制有效数据」的路子,空洞在搬运途中自然消失。

我把实际运维中要权衡的几个维度列成表,你可以直接对着自己的场景选:

对比维度SDelete + vmkfstools -KStorage vMotion 两次迁移
是否需要停机需要,回收命令要求虚拟机完全关机不需要,在线迁移
额外存储需求无,原地回收需要目标存储能放下整个置备容量
典型耗时(30G 级)清零 1~3 小时,回收 20~60 分钟单次迁移 30~90 分钟,两跳累计翻倍
对业务 I/O 的影响清零阶段 Guest 磁盘压力大迁移阶段源和目标存储带宽压力大
最终磁盘格式保持 Thin 不变取决于第二次迁移选的格式,可回到 Thin
适用场景单机维护窗口、存储余量紧张业务不能停、有富余存储或可临时借用

选型上我一般这么判断:如果这台机器有明确维护窗口、且数据存储本身快满了腾不出第二份空间,走方法一,虽然慢但不需要额外容量。如果业务 7×24 不能停,或者数据存储余量充足、甚至有另一套存储可以做临时落脚点,走方法二,代价是两次迁移的带宽和时间。

方法二有个变种值得提一句:如果你本来就要把虚拟机迁到新存储上,那顺手把第一次迁移的格式选成厚置备延迟归零、落位到目标存储,空间在这一次迁移里就释放了,等于白捡一次回收,不需要再迁回来。只有当你必须留在原来的 Thin 数据存储上时,才需要走完整的两跳。

还有一条不怎么上台面但确实存在的野路子:直接用vmkfstools克隆一份新磁盘再替换。本质上和迁移是一回事,但中间态更难管理,出错回滚成本高,除非你对这台虚拟机的磁盘布局非常熟,否则我不推荐。下面两章分别把两条主路线拆到能照着敲的程度。

3. 方法一实战:SDelete 归零 + vmkfstools -K 两步走

这条路线的核心思路是把「哪些块是空的」这个信息,从 Guest OS 的模糊认知变成 ESXi 能识别的确定信号——空闲块全部写成 0,vmkfstools -K扫到连续全零的块就直接从映射表里摘掉。两步缺一不可,只做清零不做回收,空间一点都不会变;只做回收不做清零,工具会认为满盘都是有效数据。

3.1 在 Guest OS 内跑 SDelete:命令、参数与耗时估算

SDelete 是 Sysinternals 套件里的命令行工具,微软官方站点可以下到,解压出来就一个sdelete64.exe(32 位系统用sdelete.exe)。它会往磁盘上空闲簇写入指定模式的垃圾数据再清零,对虚拟磁盘来说,这一步的唯一目的就是让那些块变成「一眼能看出是空的」。

先把工具丢到系统盘根目录,用管理员权限开 CMD。上来先看帮助确认参数——这不是客套,SDelete 各版本参数有过调整,尤其是覆盖次数相关的选项:

:: 管理员权限运行,先确认参数拼写与当前版本支持项 C:\sdelete64.exe /? :: -z 表示清零空闲空间(zero free space),-p 指定覆盖遍数 :: 对虚拟磁盘场景,覆盖 1 遍足够,默认 3 遍纯属浪费三倍时间 C:\sdelete64.exe -z C: -p 1 :: 多分区环境逐个执行,别指望一条命令扫全盘 C:\sdelete64.exe -z D: -p 1

-z是关键参数,它的语义是「清零空闲空间」而不是「安全擦除文件」,后者用-c或-s配合路径。-p 1把覆盖遍数压到一遍,原因很直接:SDelete 的多次覆盖是为物理磁盘防恢复设计的,而在虚拟机里,ESXi 判断块是否可回收只看内容是不是全零,你覆盖三遍它也只认最后一遍的结果,白白多花两倍时间。

-accepteula这个参数建议加上,否则首次运行会弹一个许可协议窗口等你点同意,脚本化执行时直接卡死在那儿。另外记得关掉系统的卷影副本和备份任务,VSS 快照占用的块不会被 SDelete 当作空闲空间,也不会被后续回收释放,这是最常见的「清了等于没清」的原因。

耗时方面给个参考:全盘写入的速度受限于数据存储的写带宽和 Guest 内磁盘队列深度,SATA 级别的虚拟盘大约 30~60 MB/s,SSD 后端能到 200 MB/s 以上。一台实际使用 27.8G、置备 40G 的虚拟机,需要清零的空闲空间约 12G,机械盘后端差不多要跑一到一个半小时。进度条走完之后别急着关机,让系统把缓存刷干净。

提示:清零期间虚拟机的写 I/O 会持续处于高位,如果这台机器和其他业务共用同一数据存储,尽量安排在低峰期。清零过程中磁盘剩余空间可能短暂变小,这是正常现象。

3.2 关机后 SSH 进 ESXi:定位 VMDK 与 vmkfstools -K 的真实行为

清零做完,接下来必须把虚拟机关掉。这一点没有商量余地——vmkfstools -K需要独占打开 VMDK 文件并修改其块映射,虚拟机运行时会持有文件锁,命令会直接失败退出。我见过有人图省事用挂起(Suspend)状态去执行,同样拿不到锁。

先在 vSphere Client 里把虚拟机关机,然后开启 ESXi 主机的 SSH 服务。图形界面的路径是选中主机 → 配置 → 系统 → 服务 → 找到 SSH → 启动;如果你只有 DCUI 命令行界面,也可以在主机的物理控制台上操作。SSH 开启后从跳板机登进去:

# 登录到虚拟机所在的那台 ESXi 主机 ssh root@192.168.10.21 # 列出所有数据存储,确认虚拟机落在哪个卷上 esxcli storage filesystem list # 进入虚拟机目录(路径里的卷名和虚拟机名换成你实际的) cd /vmfs/volumes/ds-thin-01/win2012-test # 看清目录下到底有哪些 vmdk,别对描述符文件动手 ls -lh *.vmdk

ls这一步非常关键。一个正常的虚拟机目录里,你会看到win2012-test.vmdk(小文件,几百字节到几 KB,这是描述符)和win2012-test-flat.vmdk(大文件,真正存数据的)。-K参数要作用在描述符文件上,也就是那个小的.vmdk,vmkfstools 会自动关联到对应的 flat 文件。如果你对着-flat.vmdk敲命令,工具会报错或者行为异常。目录里如果还有-000001.vmdk这种带数字后缀的,说明存在快照,必须先处理掉,否则回收的是快照层,基础盘纹丝不动。

确认无误后执行回收:

# -K 即 punch zero,扫描 VMDK 中全零的块并从映射表摘除 # 参数必须是描述符文件,不是 -flat.vmdk vmkfstools -K win2012-test.vmdk # 部分版本用长参数写法,效果一致 vmkfstools --punchzero win2012-test.vmdk

命令跑起来后会显示进度百分比。这个过程只读不写业务数据,但需要遍历整个虚拟磁盘的块映射表,30~40G 的盘通常 10~40 分钟,具体取决于数据存储的元数据操作性能和后端是不是机械盘。中途不要 Ctrl+C,中断可能留下不一致的映射状态,虽然一般不至于损坏数据,但会让你搞不清到底回收了多少。

跑完之后我习惯再确认一次文件的实际占盘量:

# du 统计的是 VMFS 上真实分配的块数,和 ls 显示的逻辑大小不是一回事 du -h win2012-test-flat.vmdk # 对照整个目录的总占用 du -sh /vmfs/volumes/ds-thin-01/win2012-test

ls -lh看到的永远是置备容量(比如 40G),因为这个数字写在描述符里不会变。真正反映回收效果的是du,它读的是文件系统实际分配的块。这两个数字的差值,就是这轮操作省下来的空间。

3.3 验证效果:怎么确认空间真的回来了

回收跑完,别急着开机交付。先在 ESXi 侧和 vCenter 侧各核对一遍,两边数据对得上才算数。

ESXi 侧的核对用上面那条du就够了。vCenter 侧看的是虚拟机的存储摘要:选中虚拟机 → 摘要 → 存储,里面有「已使用空间」和「已置备空间」两个值。回收成功的标志是已使用空间明显下降、向 Guest 内的实际用量靠拢。如果关机前是 36.7G、清零回收后变成 28.5G 左右,说明这轮干得不错;如果纹丝不动,回去查三件事:快照有没有清干净、SDelete 是不是真跑完了、回收命令作用的文件对不对。

还有一点容易误判:某些版本的 vCenter 统计有缓存延迟,回收完立刻刷新可能数字没变,等几分钟或者对虚拟机做一次重新扫描(Rescan Datastore)再看。我遇到过一次以为失败、准备重跑,结果五分钟后再刷数字已经对了,白紧张一场。

开机之后建议观察一两天再把这个流程写进运维手册。有些业务在清零后会触发大量的首次写入,Thin 磁盘又会慢慢涨回去一部分,这是正常的——涨回去的是真实写入的数据,不是幽灵块。判断标准不是「涨没涨」,而是「涨到的位置和 Guest 内实际用量是否匹配」。

注意:如果你的数据存储是 VMFS6 且自动 UNMAP 已开启,手动回收完成后的一段时间里可能还会看到零星的空间下降,那是后台延迟回收在收尾,属正常现象,不用重复执行。

4. 方法二实战:Storage vMotion 两次迁移把 Thin 榨干

停机回收的问题不在于技术难度,而在于那个维护窗口往往排不上。这时候 Storage vMotion 就是救命稻草:虚拟机全程在线,数据从源存储搬到目标存储的过程中,只有真正的有效数据会被复制过去,空洞在传输途中自动消失。代价是你得有一块能放下整台虚拟机的地方。

4.1 迁移前的前置检查:目标存储余量与格式选择

这一章最容易翻车的地方不在操作,在算容量。很多人以为「源存储上这台虚拟机只占 30G,那目标存储有 40G 就够了」,结果迁移到 92% 卡住失败。原因在于你第一次迁移选的是厚置备(Thick)格式,它按置备容量分配空间,不是按实际使用量。

假设虚拟机置备 200G、实际用了 120G,你要把它迁成厚置备延迟归零,目标存储就必须实打实腾出 200G。迁移过程中源端的 VMDK 还没删,等于同一时间存在两份,源端占着 120G、目标端占着 200G。所以容量检查要盯三个数:置备容量、源端实际占用、目标存储剩余可用空间。

具体检查清单我一般这么过一遍:

  • 目标存储可用空间 ≥ 虚拟机置备容量 × 1.05,留 5% 余量给快照文件和交换文件。
  • 虚拟机没有未合并的快照。有快照时迁移只搬当前活动的 delta,合并后可能又涨回去。
  • 虚拟机的 CD/DVD 驱动器没有挂载物理客户端设备或 ISO 镜像,这类挂载在迁移时经常报「设备或文件不可访问」。
  • 源主机和目标主机在同一 vCenter 下,且 CPU 厂商一致或开启了 EVC 基线。跨厂商 CPU 集群的热迁移会被直接拒绝。
  • 目标存储的块大小、VMFS 版本能容纳这台虚拟机的最大磁盘文件。VMFS5 单文件上限 62TB 基本不用担心,但如果目标是 NFS 就要确认挂载参数。

这些检查用 PowerCLI 一条命令就能扫个大概,比起在界面里一个个点开看快得多:

# 连接 vCenter,地址换成你环境的 Connect-VIServer -Server vcenter.example.local -User admin@example.local # 看目标数据存储的剩余容量(GB) Get-Datastore -Name 'ds-thick-01' | Select-Object Name, @{n='FreeGB';e={[math]::Round($_.FreeSpaceGB,1)}}, @{n='CapacityGB';e={[math]::Round($_.CapacityGB,1)}} # 看虚拟机的置备容量和实际占用,以及有没有快照 $vm = Get-VM -Name 'win2012-test' $vm | Select-Object Name, ProvisionedSpaceGB, UsedSpaceGB, @{n='Snapshots';e={($_ | Get-Snapshot).Count}}

ProvisionedSpaceGB是虚拟机所有磁盘的置备容量之和,UsedSpaceGB是 VMFS 上真实占用的块,两者之差就是你能收回来的理论上限。Get-Snapshot返回非空就得先把快照合并掉,否则这趟迁移等于白搬。

4.2 第一跳:迁到厚置备延迟归零

第一次迁移的目标不是回收空间,而是把虚拟磁盘从「带空洞的 Thin」变成「没有空洞的 Thick」——厚置备在创建时就把整个容量写满零块,数据的物理布局是紧凑连续的,没有任何空洞残留。延迟归零(Lazy Zeroed)和全零厚置备(Eager Zeroed Thick)的区别在于零块是「按需写」还是「立刻写」:前者的迁移速度快得多,后者更安全但耗时可能翻几倍。日常回收用延迟归零就够。

图形界面的路径是:选中虚拟机 → 右键 → 迁移 → 选择「仅更改存储」→ 目标数据存储选厚置备那块卷 → 虚拟磁盘格式下拉框选「厚置备延迟归零」→ 下一步确认 → 完成。整个过程虚拟机会在线,唯一能感知到的是磁盘 I/O 在高负载时段可能略有抖动。

我更习惯用 PowerCLI 做,一来参数明确不容易点错,二来可以顺手记日志:

# 第一跳:Thin -> 厚置备延迟归零,把空洞填实 # -DiskStorageFormat Thick 对应界面上的「厚置备延迟归零」 Move-VM -VM (Get-VM -Name 'win2012-test') ` -Datastore (Get-Datastore -Name 'ds-thick-01') ` -DiskStorageFormat Thick ` -Confirm:$false # 迁移是异步任务,等它真正结束再进下一步 Get-Task | Where-Object {$_.State -eq 'Running'} | Wait-Task

-DiskStorageFormat有三个取值要分清:Thin(精简)、Thick(厚置备延迟归零)、EagerZeroedThick(厚置备全零)。选Thick意味着目标端立刻按置备容量划走空间,这也是为什么前置检查要那么严格。-Confirm:$false是跳过确认提示,批量场景下有用,手工单台执行时我一般留着确认,多一次复核机会。

迁移进度在 vSphere Client 的「近期任务」里能看到百分比。卡在某个数字几分钟不动是正常的,尤其是接近尾声的时候,ESXi 在做磁盘链的切换和源端清理。我遇到过卡在 92% 停了将近十分钟,当时以为挂了,结果只是源端的块清理耗时。判断是否真卡死的办法是看主机性能图表里目标存储的写带宽,只要还有读写流量,就说明在干活。

4.3 第二跳:迁回 Thin 并核对真实占用

第一跳完成之后,虚拟机的 VMDK 是一个没有空洞的厚置备文件,占满整个置备容量。这时候如果你接受厚置备,流程就结束了——空间确实从「36.7G 的虚胖 Thin」变成了「40G 的厚盘」,但总占用反而变大了,没有任何意义。真正把空间收回来的动作在第二跳:迁回原来的 Thin 数据存储,格式选回 Thin。

# 第二跳:厚置备 -> Thin,此时源盘没有空洞,转 Thin 后只占实际数据量 Move-VM -VM (Get-VM -Name 'win2012-test') ` -Datastore (Get-Datastore -Name 'ds-thin-01') ` -DiskStorageFormat Thin ` -Confirm:$false # 迁回后核对实际占用 Get-VM -Name 'win2012-test' | Select-Object Name, ProvisionedSpaceGB, UsedSpaceGB

第二跳的原理和第一跳对称:源端是一块紧凑的厚盘,数据块连续排列,转换目标格式为 Thin 时,ESXi 只为有效数据分配块,于是 40G 的厚盘落地成 28G 的 Thin。整个过程虚拟机同样在线,不需要停机。

有个细节值得注意:第二跳必须等第一跳彻底完成、源端 VMDK 已经清理干净才能开始,否则会出现「迁移任务排队中」的等待,白白浪费时间。上面的Wait-Task就是干这个的。另外,如果你的目标存储和源存储是同一块物理设备的不同逻辑卷,两跳之间的数据搬运其实是设备内部的,速度会快很多,这时候两跳的总耗时可能比单跳还短。

迁完之后立刻核对。UsedSpaceGB应该明显低于ProvisionedSpaceGB,且和 Guest 内系统看到的用量接近。如果两个数字还贴着,八成是第一跳选的格式不对(比如误选了 EagerZeroedThick,某些场景下转换会保留原有分配),或者是第二跳的目标存储被识别成了厚置备。用Get-HardDisk确认磁盘的StorageFormat属性最终是Thin,这一步别省:

# 逐块盘确认格式,多磁盘虚拟机要每块都对 Get-VM -Name 'win2012-test' | Get-HardDisk | Select-Object Name, CapacityGB, StorageFormat, Filename

Filename会显示 VMDK 的完整路径,路径里的卷名能反向验证它到底落在了哪块数据存储上。多磁盘虚拟机经常出现「系统盘迁过去了、数据盘还在原地」的情况,多半是当初只勾选了部分磁盘。用这条命令一次看全,比在界面里翻强。

5. 避坑与排查:这五种翻车我见过不止一次

前面两章给的是「一切顺利时怎么做」,但回收这件事的失败率其实不低,因为它依赖一个跨越 Guest OS、虚拟化层、存储层三层的状态一致性。任何一层有信息没同步,结果就是白忙一场。下面五条是我和同事实际踩过的,每条按现象、原因、解决三段写,遇到对应症状直接对号入座。

5.1 空间纹丝不动:先查快照与卷影副本

现象:SDelete 从头跑到尾、vmkfstools -K也显示 100% 完成,回头一看 VMDK 占用一个字节没少。

原因:排在最前的是快照。虚拟机存在快照时,所有新写入都落到-000001.vmdk这个 delta 文件里,基础盘的块映射被冻结,-K就算扫到全零块也改不了基础盘的映射表。第二常见的是 Windows 卷影副本(VSS):系统还原点、备份软件留下的快照、文件服务器上的「以前的版本」,这些占用的块在 NTFS 看来是有效数据,SDelete 不会碰,自然也就回收不掉。第三种是 pagefile 和 hiberfil 这类被系统独占的巨型文件,它们占的块同样不在空闲空间之列。

解决:先vim-cmd vmsvc/snapshot.get <vmid>或界面里确认快照已全部删除并合并。然后在 Guest 内跑vssadmin list shadows看有没有残留的卷影副本,确认不需要之后用vssadmin delete shadows /for=C: /all清掉。pagefile 不用管,它占的空间本来就是真实需要的,硬要回收只能关机后挂到另一台虚拟机上删掉再重建,收益和风险不成比例。

5.2 回收命令报文件锁定:虚拟机没真正关干净

现象:vmkfstools -K执行后立刻返回Failed to open disk: The file is locked或类似的错误。

原因:最直接的原因是虚拟机没关机,或者处于挂起状态。次常见的是这台虚拟机被某个备份任务挂载了——很多备份软件走的是热添加快照的方式,会在 ESXi 侧短暂持有一个只读句柄,这时候哪怕虚拟机是关着的,文件也是锁的。第三种情况是上次回收中途被打断,ESXi 侧留下了陈旧的锁记录。

解决:先用vim-cmd vmsvc/power.getstate <vmid>确认电源状态是真的 powered off,别信界面上那个可能没刷新的图标。然后查有没有正在跑的备份或复制任务,等它们结束。如果确认都没有还是锁着,用vmfsfilelockinfo -p /vmfs/volumes/.../win2012-test.vmdk查是谁持有锁,定位到具体主机和进程再处理。实在不行重启管理代理services.sh restart,但这一步会影响该主机上其他虚拟机的管理连接,挑时间做。

5.3 迁移卡在 90% 以上:大多数时候不是真卡

现象:Storage vMotion 进度条冲到 90 多之后长时间不动,几分钟甚至十几分钟没有任何变化。

原因:迁移的收尾阶段做的事和前面完全不同——前面是复制数据块,最后这一段是在做磁盘链切换、源端快照合并、旧文件删除。这些操作都是元数据密集型而非带宽密集型,用性能图表看就是带宽掉下来了但任务没结束。另外,如果虚拟机在迁移期间有大量写入,增量数据需要反复同步,也会让最后几个百分点拖得很久,这是在线迁移的固有代价。

解决:先别动它。用Get-Task | Where-Object {$_.State -eq 'Running'} | Format-List看任务状态,用esxcli storage core device stats get在主机侧确认还有没有 I/O。只要任务状态是 Running 且主机没有报错,等下去就是最优解。真正需要干预的情况是任务报错退出,此时看 vCenter 的「近期任务」里那条红色的错误描述,常见的是目标存储空间不足或源端文件被锁定,这两种在对应用户手册里都有明确处理路径。

5.4 清零之后磁盘反而更大:别慌

现象:SDelete 跑到一半或者刚跑完,看数据存储上的 VMDK 占用比执行前还大了几个 G。

原因:清零动作本身是往磁盘写数据,虽然写的是零,但在 VMFS 看来依然是有效的块分配。同时 Windows 在清零期间可能因为内存压力临时扩大 pagefile,或者日志系统被触发写入。这些都会让占用暂时上升。只有等vmkfstools -K把这些全零块摘除之后,数字才会掉下来。

解决:什么都不用做,按流程走完回收步骤再看。真正需要警惕的是回收完成后仍然比清零前大,那说明清零期间产生了新的有效数据,且这些数据量超过了你回收的量,这种情况要回头查这台虚拟机最近有没有异常写入——我遇到过一次是某个服务在疯狂写日志,回收完一周又涨回去,问题不在回收工具而在那个服务。

5.5 回收效果打折:碎片和分区对齐在拖后腿

现象:Guest 内明明删了 15G 数据,回收完成后只拿回来 6G。

原因:Thin 的回收粒度是块级的,虚拟磁盘上的块是 1MB 对齐的。如果被删除的数据在虚拟磁盘上分散成大量小块、彼此之间夹杂着有效数据,那么每个 1MB 块里只要还有 1 个字节的有效数据,整块就不能回收。碎片越严重,回收率越低。另一个因素是分区对齐:老系统上如果分区起始偏移没有按 1MB 对齐,文件系统的簇边界和 VMFS 的块边界错位,同一个小文件可能横跨两个 VMFS 块,进一步拉低回收率。

解决:回收前先在 Guest 内做一次碎片整理(Windows 自带的defrag C: /O对虚拟磁盘有效),把有效数据尽量集中,让空闲区域连成片。分区对齐的问题没法在线修,只能重建分区,对存量虚拟机不现实,遇到这种情况就把回收率预期调到 50%~70%,别指望全收回。判断是否碎片导致的方法很简单:跑一次defrag C: /A看分析报告里的碎片百分比,超过 20% 就先整理再回收。

6. 进阶:把回收做成月度巡检,而不是等告警了才动手

手动回收这套流程跑通之后,最大的问题不是技术,是遗忘。Thin 磁盘的膨胀是温水煮青蛙,等到数据存储告警 85% 才想起来处理,往往已经被迫在业务高峰做停机操作。我现在的做法是每月初跑一次巡检,把「空洞率」超过阈值的虚拟机提前筛出来,在计划维护窗口里批量处理。

空洞率的定义是(置备容量 - VMFS 实际占用) / 置备容量,用 PowerCLI 捞一遍全环境的 Thin 虚拟机:

# 连接 vCenter 后执行,输出空洞超过 10G 且空洞率大于 15% 的 Thin 虚拟机 $result = foreach ($hd in (Get-VM | Get-HardDisk | Where-Object {$_.StorageFormat -eq 'Thin'})) { # LayoutEx 里的 diskExtent 才是真实落在 VMFS 上的块 $extents = (Get-View -Id $hd.Parent.Id).LayoutEx.File | Where-Object {$_.fileType -eq 'diskExtent'} $onDiskGB = [math]::Round((($extents | Measure-Object -Property size -Sum).Sum / 1GB), 1) $provisionedGB = [math]::Round($hd.CapacityGB, 1) [pscustomobject]@{ VM = $hd.Parent.Name ProvisionedGB = $provisionedGB OnDiskGB = $onDiskGB HoleGB = [math]::Round($provisionedGB - $onDiskGB, 1) HoleRate = if ($provisionedGB -gt 0) { [math]::Round(($provisionedGB - $onDiskGB) / $provisionedGB * 100, 1) } else { 0 } } } $result | Where-Object {$_.HoleGB -gt 10 -and $_.HoleRate -gt 15} | Sort-Object HoleGB -Descending | Export-Csv -Path 'thin-reclaim-plan.csv' -NoTypeInformation -Encoding UTF8

这段脚本的关键在LayoutEx.File这个属性。Get-HardDisk返回的对象里,CapacityGB是置备容量,永远不变;要找真实占用量,得从虚拟机的 LayoutEx 布局信息里取fileType为diskExtent的文件项,把它们的size加起来。这才是 VMFS 上真正分配掉的字节数,也是和du结果对得上的那个数。多磁盘虚拟机要注意每一块盘单独算,脚本里是逐块Get-HardDisk循环的,输出到 CSV 后按 VM 名分组看更直观。

阈值我一般定成「空洞超过 10G 且空洞率超过 15%」,理由是低于这个量级,回收带来的收益抵不上操作风险和停机成本。但这只是我的环境基线,你们如果存储紧、窗口多,可以放宽到 5G 和 10%。收回来的空间也算一笔账:一台机器平均能回 8~12G,几十台虚拟机批量跑一轮,腾出半个数据存储是常事,比去申请新存储走采购流程快得多。

处理策略上,我给这些机器分了三档。有维护窗口、存储余量紧张的走方法一,安排在周末凌晨清零加回收。业务 7×24 不能停、并且有富余存储的走方法二的两跳迁移。空洞率低于 15% 但处于持续增长的先记录在案,观察两个周期再决定,因为这种机器往往是正常业务增长导致的,回收一次很快又涨回去,治标不治本。

空洞率档位建议动作典型窗口
> 35%立即处理,优先选在线迁移当周
15% ~ 35%排入月度维护窗口当月
< 15%只记录,连续两月上升再处理观察期

这套巡检跑了大半年之后我的习惯就定型了:每月第一个工作日导出 CSV,按空洞率排序,前 10% 的机器进当月窗口,其余的下月再看。有一次偷懒跳过了一个月,正好赶上某台文件服务器疯狂涨盘,数据存储直接顶到 92%,最后不得不在周三下午做在线迁移,业务虽然没断,但迁移期间那台机器的响应时间翻了三倍,被业务方追着问了半天。从那以后我每次做回收之前,都强制先跑一遍快照检查和容量核算这两步,哪怕再确定也跑,因为翻车从来不是因为命令敲错,而是因为状态没看全。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表