之前有紀錄過,微軟有提供「Optimize-VHD」這個 Hyper-V 的 PowerShell cmdlet(官方文件),可以用來透過釋出沒有用到的空間來最佳化動態擴充的 VHDX 的大小。
而當最近當發現自己用 Hyper-V 架設的 GitLab 的 VHDX 檔案所占用的空間越來越大、遠遠超過實際的內容(實際檔案只有不到 100GB,但是檔案卻有 640GB…),想透過「Optimize-VHD」來縮小檔案的時候,卻發現它幾乎沒有用!?
後來查了一下,才發現微軟的 Optimize-VHD 主要應該還是以對應 Windows 的 NTFS 為主,對於 Linux 的檔案系統看來是沒辦法很好地去偵測檔案裡的那些區塊並沒有實際被用到、所以基本上無法有效地縮小檔案。
問題的原因
會有這樣的問題,基本上是因為作業系統在對磁碟機做存取的時候,針對刪除檔案的動作,實際上都不會真的把檔案的內容從磁碟機上刪除,而僅是去修改檔案結構列表而已;之後如果要寫入新的檔案,就會去參考檔案結構列表,寫到這些雖然有資料、但是沒已經在用的磁區上。
這樣省略掉把資料刪除動作(通常是寫
0)的設計基本上是為了效能的緣故,也是一個很普遍的做法,幾乎所有作業系統都會這樣做;一般所謂的檔案救援、反刪除軟體,也都是因為這樣的機制才能使用的。
而由於微軟提供的 Hyper-V 畢竟主要還是針對 Windows 系統,所以如果是 Windows 的 NTFS 檔案系統,Optimize-VHD
就會透過檔案系統去分析那些區塊才是真的有被用到的、進而將沒用到的區塊釋出。
但是針對 Linux 使用的 ext4 這類的檔案系統,Optimize-VHD 就不會去做這種分析,而導致他會因為已經刪除的檔案所佔用的磁區上的值不是
0、而視為還在使用中、進而導致無法所小 VHD 檔案的問題。
解決方法(在 Linux 中執行)
因為上述的原因,真的要去縮小 Linux 系統的 Hyper-V VM 虛擬磁碟機的檔案,就必須要先在 Linux
系統中,去做磁碟清理的動作。比較明確一點的說法,就是必須要去把沒有用到的磁碟機區塊都重新寫入 0,這樣 Optimize-VHD
才會確實知道這個區塊已經沒在用了。
而 Heresy 這邊查到的方法,大概有四種:
- fstrim:
- 系統內建指令(文件)
- 實際上不是填零、而是讓 SSD 這類的裝置可以快速地回收沒在使用的磁區。
- 參考指令:fstrim -a -v
- zerofree:
- 需要另外安裝(文件)
- 需要將檔案系統以唯讀的形式 mount 才能使用
- 針對系統碟的話,就需要透過其他系統(Live CD)來處理會比較合適。
- 針對資料碟的話,unmount 後應該就可以了
- 參考指令:zerofree -v /dev/sda1
- sfill:
- 需要安裝 secure-delete
這個套件(文件) - 透過重複寫入隨機資料來做磁碟的安全刪除工具,透過 -llz
這樣的參數,可以相對快速地在未使用區塊填 0。 - 參考指令:sfill -llz /mnt/moutt_point
- 有多個磁碟機的話,會需要在每個磁碟機上都跑過。
- dd:
- 系統內建指令(文件)
- 檔案複製、轉換工具。這邊是用來產生內容為 0 的檔案。
- 參考指令:
dd if=/dev/zero of=/mnt/mount_point/zeroes sync rm /mnt/mount_point/zeros
其中,後面兩個 sfill 和 dd 的實際動作算是一樣的,都是透過建立一個內容為 0 的檔案把磁碟塞滿;所以根據剩下空間的不同,需要的時間可能會拖很久,最後可能也會跳出磁碟空間已滿的警告。
另外,也建議在執行之前,先把可能會對於該磁碟機進行寫入的程式/服務關閉,這樣會比較保險。
執行完了之後,把 VM 關機後再從 Windows 端的 PowerShell 執行 Optimize-VHD -Path Disk.vhdx,應該就可以成功地把 VHD 檔案縮小了。
實際測試
而實際上的狀況怎樣呢?Heresy 這邊的 Ubuntu VM 上面掛了兩個 VHD 檔案,分別是:
- 系統磁碟:容量 32GB、實際使用約 17GB,VHD 檔案使用 32GB
- 可能要注意的是這顆磁碟機因為是系統,所以除了主要的分割區外,前後都還有一個小分割區。
- 資料磁碟:容量 1TB、實際使用約 93GB、VHD 檔案使用 640GB
首先是測試不用離線、跑最快的 fstrim:
- 系統磁碟:17 / 32GB、縮小後從 32GB 變成 30GB
- 資料磁碟:93GB / 1TB、縮小後從 640GB 變成 320GB
雖然檔案有明確地縮小,但是感覺還是很肥大?所以接下來 Heresy 又試著切換成用 Ubuntu 光碟開機,離線來跑 zerofree,其結果是:
- 系統磁碟:17 / 32GB、縮小後從 30GB 變成 29GB
- 資料磁碟:93GB / 1TB、縮小後從 320GB 變成 180GB
時間上,Heresy 如果是把資料碟 unmount 後下去跑,大概是得跑半個小時左右了。(實體儲存是 NVMe SSD)
之後也有試過用 sfill
跑,不過感覺沒有能明顯地再縮小檔案,所以就不列了。
感覺上,儘管檔案還是比實際使用量大了許多,但是應該還是算是有所改善了。
所以以 Heresy 這邊的狀況來看,狀況大概是:
- 由於 Optimize-VHD 一定得關閉 VM 離線進行,所以基本上這個操作應該是不可能不斷線完成了。
- fstrim 雖然很快速、而且可以在系統運作的時候直接執行,但是對於後續 Optimize-VHD 的效果來說,雖然確實有用,但是不算非常好。
- zerofree 要寫完 1TB 的剩餘空間需要相當的時間,但是看來其結果應該算是比較好的(但是檔案還是虛胖就是)。
不過,如果要針對系統碟作處理也還得用特殊模式開機,操作相當麻煩;而即便是針對資料碟、由於僅能以唯讀模式掛載,所以這段期間服務得關閉,會讓停機時間拖得更久。 - 要執行 sfill 或 dd 的時候,固然可以直接在運作狀態下執行,但是由於他會把磁碟空間吃完,所以建議還是先把可能會去存取磁碟機的程式、服務關閉,以免造成干擾;也就是說,實質上還是建議先關閉服務再進行。
- 雖然 zerofree、sfill 和 dd 都是在做全磁碟的剩餘空間寫入的動作,但是由於動態擴展 VHD 本身的機制,在寫入 0 的時候它會知道是在刪除磁區、所以不用擔心再處理的過程會增加 VHD 檔案的大小。
實際上,這時候應該也不會有太多的資料真的寫入到實體磁碟機上,只有真正需要抹除的區塊才會有寫入的動作。
而由於不管怎麼完,最後要執行 Optimize-VHD 是一定要離線進行的,所以看來這個要嘗試縮小 VHD 的動作,應該不太適合頻繁地進行,而是要固定一段時間後再來跑會比較合適了。
另外,Heresy 在處理完後重新啟動後,把 GitLab 服務打開了一陣子,不知道背景跑了什麼後,縮小到 180GB 的檔案大小沒多久後又跑到 200GB 了。
而 Heresy 在建立一個檢查點後,沒幾個小時新的 avhdx 檔案也又跑到 78GB 了…這個時候,感覺應該是 GitLab 有在清除已經過期的 artifact,所以實際上使用空間還縮小到了 83GB。
但是把 200GB 的 VHDX 和 78GB 的檢查點合併後,卻還是變成了 234GB…所以看來如果真的要用檢查點,可能得再想想該怎麼玩會比較合適了。
此外,感覺上 WSL2 的 VHD 檔案要縮小的話,感覺好像直接跑 Optimize-VHD 就可以、而不需要在 WSL 內跑上面的清理指令?不知道這是不是因為微軟有去針對 WSL 修改過 Linux kernel 的關係?
如果是的話,那不知道是不是有可能把檔案系統的部分撈出來給一般的 Hyper-V VM 用?可以的話感覺會方便很多…