Vagrantで容量が膨れ上がったvmdkを圧縮する方法(Vagrant + VirtualBox)

# 対象
- Windows上でVagrant + VirtualBoxを使用して、Linuxを稼働させている。
- 容量を使用していないのに、なぜかvmdkのファイル容量が大きい。
- 既存のデータやソフトウェアを削除せずに容量を軽くしたい。

# 原因
VirtualBoxで仮想ディスク（vmdkファイル）を「可変サイズのストレージ」に設定している場合、一度増えた容量は使っていなくても増えたままになってしまう。
そのため、本来のサイズよりもvmdkファイルで確保している領域が大きくなってしまい、ディスクをひっ迫する原因となる。

# 対処
仮想ディスクで使用されていないリソースを解放してあげればよい。
ただし、vmdk形式のままでは圧縮できないので、複製したvmdkをvdiに変換してから圧縮する。
最後に、vdiをvmdkに変換しなおして、VirtualBox上の仮想マシンにvmdkファイルを割り当てれば設定完了。

※ 2回ほど仮想ディスクを複製するため、仮に10GBの仮想ディスクであれば20GB程度の空き容量が必要になります。場合によっては外付けハードディスクなどの利用をおススメします。

# 手順

1. VirtualBoxのインストールフォルダにPathを通す
2. Linux（ゲストOS）で使用していない領域をクリア
3. Windows（ホストOS）で仮想ディスクを変換・圧縮
4. VirtualBox上の仮想マシンにvmdkファイルを割り当て
5. 変換元になった仮想ディスクとそれに紐づいた仮想マシンを解放

## 1.　VirtualBoxのインストールフォルダにPathを通す

VirtualBox.exeがあるインストールフォルダ（
初期設定であれば、```C:\Program Files\Oracle\VirtualBox```）を環境変数に登録します。

以下のコマンドをPowershellで実行すれば、環境変数 ```Path``` の末尾に、VirtualBoxのインストールディレクトリが追加されます。
※ インストールディレクトリが異なる場合は、```addPath``` を適宜変更してください。

```powershell
function checkPath{
    param($addPath, $systemPath)
    if(!(Test-Path $addPath)){
        Write-Host "Path does not exist."
        return $false
    }
    if($systemPath.IndexOf($addPath) -ne -1){
        Write-Host "Path already exists."
        return $false
    }
    return $true
}

$oldSystemPath = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
$addPath = 'C:\Program Files\Oracle\VirtualBox\'
if(checkPath $addPath $oldSystemPath){
    $oldSystemPath += ";$addPath"
    [System.Environment]::SetEnvironmentVariable("Path", $oldSystemPath, "Machine")

    Write-Host $oldSystemPath
    Write-Host "Adding Path is completed!"
}
```

## 2.　Linux（ゲストOS）で使用していない領域をクリア

```vagrant up```でインスタンスを起動してSSH接続後、以下のコマンドを入力実行。
（```dd``` コマンドは一歩間違えると、データが上書きされてしまう危険性があるので要注意）

```bash
sudo dd if=/dev/zero of=zero bs=4k; \rm zero
```

上記コマンドで、未使用の領域をゼロで上書きできる。
「/dev/zero」は常にNULL文字（0x00）を出力し続けるという特殊なデバイスで、今回はcountを指定していないため、空き容量すべて使用してNULL文字のファイルを作成している。 ```of``` オプションには出力先を指定するが、今回の処理では```rm```で削除することで未使用の領域を解放している。

## 3.　Windows（ホストOS）でVirtualBoxの仮想ディスクを変換・圧縮

### 仮想ディスク（vmdkファイル）の場所を探す

Powershellを開いて、以下のコマンド（ ```VBoxManage.exe list hdds``` ）を実行してください。

```powershell
>> VBoxManage.exe list hdds

UUID:           913bedef-edb0-420c-a364-35e900ac7110
Parent UUID:    base
State:          created
Type:           normal (base)
Location:       C:\Users\[userName]\VirtualBox VMs\instance1_default_1565318560385_64490\ubuntu-18.04-amd64-disk001.vmdk
Storage format: VMDK
Capacity:       65536 MBytes
Encryption:     disabled
```

Locationに記載されたパスが、今回圧縮する予定のvmdkファイルの場所になります。

### vmdkファイルを複製して、vdi形式に変換する

```VBoxManage.exe clonehd [inputFile] [outputFile]``` で、ファイルを複製することができます。```[inputFile]``` に先ほど見つけたlocationのパスを設定し、```[outputFile]``` には出力先およびファイル名を設定します。
今回はファイルを変換するので ```--format vdi``` をオプションとして設定します。

```powershell
VBoxManage.exe clonehd "C:\Users\[userName]\VirtualBox VMs\instance1_default_1565318560385_64490\ubuntu-18.04-amd64-disk001.vmdk" "C:\Users\[userName]\VirtualBox VMs\instance1_default_1565318560385_64490\clone.vdi" --format vdi
```

### vdiファイルを圧縮する

先ほど作成したvdiファイルを、```VBoxManage.exe modifyhd [inputFile] --compact``` で圧縮する。

```powershell
VBoxManage.exe modifyhd "C:\Users\[userName]\VirtualBox VMs\instance1_default_1565318560385_64490\clone.vdi" --compact
```

### vdiファイルをvmdk形式に変換

先ほどとは逆に ```.\VBoxManage.exe clonehd [inputFile] [outputFile]``` VDI形式からVMDK形式のクローンを作成します。
今度は ```--format vmdk``` をオプションとして設定します。
※参照した記事にならって、2GB単位でファイルを分割して管理するオプションを指定しています。

```powershell
VBoxManage.exe clonehd "C:\Users\[userName]\VirtualBox VMs\instance1_default_1565318560385_64490\clone.vdi" "C:\Users\[userName]\VirtualBox VMs\instance1_default_1565318560385_64490\box_disk.vmdk" --format vmdk --variant Split2G
```

## 4. VirtualBox上の仮想マシンにvmdkファイルを割り当て

仮想マシンの操作を行うため、 ```VBoxManage.exe list vms``` でUUIDを取得します。
```{}``` 内の16進数標記の文字列がUUIDです。

```powershell
>> VBoxManage.exe list vms

"ubuntu_tensor_default_1570674600311_51555" {c3e4ce85-1a1f-4569-9cc8-13ef3da1b3eb}
```

仮想ディスクのアタッチとデタッチには ```storageattach```を使います。
（GUIをつかってもいいですが、あとでバッチ処理での実行を想定して、今回はCLIのみで操作します。）

### 既存の仮想ディスクをデタッチ

```powershell
VBoxManage.exe storageattach [ VMs_UUID ] `
                        --storagectl "SATA Controller" `
                        --port 0 `
                        --type hdd `
                        --medium emptydrive
```

### 複製して圧縮した仮想ディスクをアタッチ

```powershell
VBoxManage.exe storageattach [ VMs_UUID ] `
                        --storagectl "SATA Controller" `
                        --port 0 `
                        --device 0 `
                        --type hdd `
                        --medium "C:\Users\[userName]\VirtualBox VMs\instance1_default_1565318560385_64490\box_disk.vmdk"
```

## 5. 変換元になった仮想ディスクを解放

```list hdds``` で仮想ディスクの状態を確認します。

```powershell
>> VBoxManage.exe list hdds

UUID:           913bedef-edb0-420c-a364-35e900ac7110
Parent UUID:    base
State:          created
Type:           normal (base)
Location:       C:\Users\[userName]\VirtualBox VMs\instance1_default_1570674600311_51555\ubuntu-18.04-amd64-disk001.vmdk
Storage format: VMDK
Capacity:       65536 MBytes
Encryption:     disabled

UUID:           fe9ab526-7500-46d7-aad8-a8f6ad394f98
Parent UUID:    base
State:          created
Type:           normal (base)
Location:       C:\Users\[userName]\VirtualBox VMs\instance1_default_1570674600311_51555\box_disk.vmdk
Storage format: VMDK
Capacity:       65536 MBytes
Encryption:     disabled
```

不要になったhddsは、以下のコマンドで解放しておきます。
(解放する前に、locationのPathはメモしておく)

```powershell
VBoxManage.exe closemedium disk [ DISKs_UUID ]
```

また、解放したhddsの元ファイルも削除します。

```powershell
rm 'C:\Users\[userName]\VirtualBox VMs\instance1_default_1570674600311_51555\ubuntu-18.04-amd64-disk001.vmdk'
```

これで仮想マシンの圧縮は完了です。
（※これをバッチ処理で実行できるようにしたい…。）

# 参考
[VirtualBoxの仮想ディスク容量を圧縮する方法](https://labo.ysreading.co.jp/2016/04/04/virtualbox%E3%81%AE%E4%BB%AE%E6%83%B3%E3%83%87%E3%82%A3%E3%82%B9%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E5%9C%A7%E7%B8%AE%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/)
[VirtualBox の仮想ディスクのサイズを変更する](https://qiita.com/niwashun/items/f71b0b805a6f97b514ec)
[【 dd 】コマンド――ブロック単位でファイルをコピー、変換する](https://www.atmarkit.co.jp/ait/articles/1711/30/news027.html)
[Vagrantの仮想マシンとの紐付けの直し方](https://elm-arata.hatenablog.com/entry/2013/09/25/175547)

以上