chundev
日期:2026-05-24 環境:ESXi(VMFS6)+ Ubuntu 24.04 guest + thin provisioning
ESXi thin provisioning 的 vmdk 大小不等於當下 disk 用量,是「曾經寫過的 block 高水位」。VM 內 rm、fstrim 只是釋放檔案系統層級的 metadata,vmdk 檔本身不會跟著縮。要真正瘦身,必須:
dd if=/dev/zero of=/zerofill)vmkfstools -K vmdisk.vmdk 把 zero block 摺掉我這次的個案是 14 GB 真實用量的 VM、export 出來變 22 GB;走完上面流程後 export 變 4.1 GB。
一台 Ubuntu 24.04 VM 跑 Docker(多 image)做了一整天的部署演練:
/opt/* 與 docker volumedown -v 清掉部署完成、要 export 成 vmdk 交付客戶時發現:
| 觀測 | 數字 |
|---|---|
df -h / 當下用量 |
14 GB / 97 GB |
| ESXi export 進度 | 已跑到 22 GB 還沒完 |
換句話說,export 出來的 vmdk 比實際用量還大 60%。客戶端的 vmdk 又因為含未必要的零位元組變肥,傳輸/儲存都付出無謂成本。
Thin vmdk 在 datastore 上是只記錄已分配過的 block 的 sparse 檔案。但這裡的「已分配」是單調遞增的——歷史曾寫過的 block 即使後來檔案被刪掉、空間在 guest 內變成 free,ESXi 不會自動把這些 block 從 vmdk 摺掉。
時間軸:
T1: 寫入 16 GB 測試資料 → vmdk 從 0 GB 漲到 16 GB
T2: 客內 rm 砍掉 12 GB → guest df 看到 used=4 GB,vmdk 還是 16 GB
T3: 再寫 6 GB 新資料 → vmdk 漲到 18 GB(即使 guest df=10 GB)
T4: 又砍 8 GB → guest df=2 GB,vmdk 仍 18 GB
↑ 高水位卡在這裡
fstrim 會送 SCSI UNMAP/DISCARD 到底層、VMFS6 也有 auto unmap,但實務上常常因為以下原因 reclaim 不徹底:
vmkfstools -K所以可靠的瘦身策略不是「期待自動 reclaim」,而是主動把要 reclaim 的 block 寫成全零、再用 punchzero 強制摺掉。
# 找出 top consumer
sudo du -sh /opt/* /var/* /usr/* /home/* 2>/dev/null | sort -hr | head -10
# 砍掉備援、cache、log
sudo rm -f /path/to/redundant-large-files
sudo docker image prune -f # dangling docker images
sudo apt-get clean # apt cache
sudo journalctl --rotate
sudo journalctl --vacuum-time=1s # 清舊 journal
Ubuntu 24.04 Docker 用 containerd-snapshotter 的陷阱: 真實 image layer 是存在
/var/lib/containerd/,不是/var/lib/docker/。du /var/lib/docker看到的數字會嚴重低估。docker images報的 size 又因 layer dedup 不直接等於 disk 用量。直接看du /var/lib/containerd/才準。
sudo dd if=/dev/zero of=/zerofill bs=1M status=progress
# 跑到「No space left on device」就完成,這是預期的退出方式
sync
sudo rm -f /zerofill
這支會把 / 上所有 free block 寫成零。檔案系統層級這些 block 仍標示 used(被 /zerofill 占用),等 dd 結束、rm 釋放後,block 在 guest 內變回 free,但其資料內容已是零 —— ESXi 等下就有東西可以 reclaim。
⚠️ 跑 dd 期間 disk 會被填滿,會影響同時跑的服務(postgres、container 等可能寫入失敗)。先把要寫 disk 的服務停下來 再跑 dd 比較安全:
# 例如先停 docker stack
sudo docker compose -f /path/to/compose.yml down
sudo fstrim -av
對 VMFS6 + HW version ≥ 13 來說,這會送 SCSI UNMAP 給 datastore,讓部分區塊立刻 reclaim。但單靠 fstrim 通常不夠(前述對齊問題),所以視為加分項而非主力。
sudo shutdown -h now
SSH 進 ESXi host:
cd /vmfs/volumes/<datastore>/<vm-folder>/
vmkfstools -K <vmdisk>.vmdk
# -K 又名 --punchzero
vmkfstools -K 會掃整個 vmdk、把全零的 block 從 sparse 檔案中真的拿掉。剛剛 dd 寫的那一大片零、加上歷史 free 過的零,全部會被摺掉。
限制:
如果 datastore 是 NFS、或不方便 SSH 進 ESXi host,可以 Storage vMotion 到另一個 datastore + 選 Thin format。遷移過程 ESXi 內建會把 zero block 摺掉,產生的新 vmdk 只佔實際資料量。
vSphere → VM → Export OVF Template → disk format 選 Thin。
OVA 本身又會 gzip 壓縮,docker image layers(alpine + node binary 等)對 gzip 友善,壓縮率通常很高。
| 階段 | vmdk 大小 |
|---|---|
| 初始 export(沒做任何 reclaim) | 22 GB(still going) |
| 開機進 VM 砍 redundancy(~2 GB)+ docker prune(~3 GB)+ apt/journal 清理 | guest df: 14 GB |
| dd zero-fill 76 GB + fstrim 釋出 83 GiB | guest df: 14 GB(不變,但 free block 全是零) |
| Storage vMotion to Thin + Export OVF Thin | 4.1 GB ✓ |
從 22 GB 到 4.1 GB,少了 17.9 GB(81%)。export/傳輸時間都跟著省。
df -h 數字會誤判 export 規模rm 跟 fstrim 不足以縮 vmdk — guest 端釋放只是 metadata 層的事,ESXi 端的 vmdk 檔不會跟著變小dd if=/dev/zero + vmkfstools -K 是黃金組合:先把 reclaim 標的寫成零、再讓 hypervisor 掃零摺檔。順序不能反——直接 punchzero 沒先寫零的 vmdk 不會縮,因為那些 block 內容是舊資料、不是零/var/lib/containerd/ 而不是 /var/lib/docker/,後者只有 metadata。docker images 報的 size 也因 layer dedup 跟實際 disk 用量對不上rm /zerofill 後 disk 恢復,但中間的 transient failure 可能留下 log noise 或更糟的資料狀態-K flag 的官方說明與使用前提