WSL2 Ubuntu のホームディレクトリを別ディスクにしたくなりました。そこで、Windows 上で VHDX 形式のディスクイメージを作り、それを Ubuntu にマウントする方法を探求しました。
ホームディレクトリを別ディスクにしたい理由は、新しい Ubuntu バージョンに移行するにあたり、旧環境の OS をアップグレードするのではなく、新たに新しい Ubuntu の環境を作って使いたいと思ったからです。せっかく WSL2 で複数の VM を作れるのですから、念のため古い環境を残しておきたいです。ホームディレクトリが別ディスクになっていれば、それを新しい VM に接続しなおすだけでデータを引っ越せます。
Windows 側で VHD/VHDX ディスクイメージを作り、それを Ubuntu に接続し、Ubuntu 側からパーティションを作り ext4 でフォーマットします。
「ディスクの管理」画面の下の方に、今作ったディスクイメージが表示され、「未割り当て」と表示されていることを確認します。ここでパーティションを作ったりしたくなりますが、それは Ubuntu 上でやる方が最適なパーティション配置になるようです。
このディスクを右クリックし、「VHD の切断」を選び、このディスクイメージを切り離します。そうしておかないと、後で Ubuntu に接続しようとしたときに「使用中」エラーが出てしまいます。次のようなダイアログが出ます。「OK」をクリックすれば切断できます。
今回は GUI でやりましたが、PowerShell を用いてコマンドラインでディスクイメージを作ることも可能なようです。
Ubuntu でパーティション作成やフォーマットをするために、手動でディスクイメージを Ubuntu へ接続します。Ubuntu 起動時に自動的に接続する方法は後で紹介します。
管理者モードで PowerShell か cmd.exe を開き、次のコマンドを実行します。
>wsl.exe --mount --bare --vhd D:\foo\bar.vhdx
この操作を正しく終了しました。
オプションを説明します。
オプション | 意味 |
---|---|
--mount | ディスクをすべての WSL 2 ディストリビューションに接続します |
--bare | マウントはせず、接続だけします |
--vhd DISK | ディスクイメージを指定します |
作成したディスクイメージは ext4 でフォーマットされていないので、まだマウントはできません。--bare
を指定し、マウントをしないようにします。
ちなみに、何らかの理由で接続を解除したい場合、次のコマンドで可能です。
>wsl.exe --unmount D:\foo\bar.vhdx
Windows 側の作業はここまでで終わりです。
接続したディスクイメージが Ubuntu 側からどんな名前で見えているかを確認します。dmesg
を実行し、直近のログを確認します。
$ dmesg
…
[ 9787.207257] sd 0:0:0:4: [sdd] Attached SCSI disk
ここでは sdd というデバイス名で接続されていることが分かります。以降の作業でデバイス名を間違えるとディスク内容が破損するので、慎重に確認してください。不安な場合、先ほど説明したコマンドで接続を解除し dmesg
を見れば解除時のログが見えますから、それでデバイス名の再確認をするといいでしょう。接続を解除した場合、パーティション作成の作業の前に改めて接続し直してくださいね。
作成したディスクイメージに対し、Ubuntu 側からパーティションを作り、ext4 でフォーマットします。
今回、パーティション作成には fdisk
コマンドを用いました。作業の流れは次の通りです。
g
で GPT パーティションテーブルを作成p
でパーティションテーブルを確認(この時点ではパーティションは何も表示されないはず)参考までに、筆者の環境での具体的な表示を貼っておきます。
$ sudo fdisk /dev/sdd
Welcome to fdisk (util-linux 2.39.3).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Device does not contain a recognized partition table.
Created a new DOS (MBR) disklabel with disk identifier 0xf4ed8ae3.
Command (m for help): g
Created a new GPT disklabel (GUID: 3C53E6CC-62EA-47BC-8672-2521162D7B4C).
Command (m for help): p
Disk /dev/sdd: 1000 MiB, 1048576000 bytes, 2048000 sectors
Disk model: Virtual Disk
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: 3C53E6CC-62EA-47BC-8672-2521162D7B4C
Command (m for help): n
Partition number (1-128, default 1):
First sector (2048-2047966, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-2047966, default 2045951):
Created a new partition 1 of type 'Linux filesystem' and of size 998 MiB.
Command (m for help): p
Disk /dev/sdd: 1000 MiB, 1048576000 bytes, 2048000 sectors
Disk model: Virtual Disk
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: 3C53E6CC-62EA-47BC-8672-2521162D7B4C
Device Start End Sectors Size Type
/dev/sdd1 2048 2045951 2043904 998M Linux filesystem
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
次に mkfs.ext4
でパーティションをフォーマットします。
$ sudo mkfs.ext4 /dev/sdd1
ここまでの作業で ext4 でフォーマットされたディスクイメージが準備できました。次に Ubuntu を起動したときに自動的にマウントする設定を行います。
ディスクのマウントといえば /etc/fstab だ、と思ったのですが、今回は違う方法をとります。/etc/fstab を使う方法は、マシンにディスクが接続されている必要がありますが、一度 wsl.exe --shutdown
をするとディスクイメージの接続が切れてしまうからです。マシンからディスクが取り外された状態になってしまうということです。
ということで戦略としては、Ubuntu 起動時に何とかして Windows 側へ「ディスクイメージを WSL2 に接続せよ」と指示して、その後 Ubuntu 側からマウントを行うという流れになります。Ubuntu 起動時に自動実行する仕組みはいろいろあるのですが、現代では Systemd でやるのが主流でしょう。ということで、Systemd で oneshot タスクを作ります。
筆者が試したところ、ディスクイメージが Ubuntu に再接続されるとデバイス名(/dev/sdd)が変わることがありました。これでは安定した自動マウントができませんから、UUID を用いてマウント対象ディスクを固定する方法を用います。ということで、マウントしたいパーティションの PARTUUID を確認します。
$ sudo blkid
…
/dev/sdd1: UUID="4e358cb0-f1a3-430c-b567-4bb905c142ab" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="4ec15969-105c-414a-8940-ba36975a4bde"
…
先ほどフォーマットしたパーティション /dev/sdd1
に対応する PARTUUID が表示されました。これを使うことで、デバイス名が sdg とか sdf とかに変わったとしても問題なくなります。
次に、自動マウントを行うスクリプトを作ります。どこに置いても良いですが、今回は /usr/local/bin/mount_uchan_home.sh としました。
#!/bin/sh
PS="/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe"
PARTUUID="4ec15969-105c-414a-8940-ba36975a4bde"
MNT="/home/uchan"
DISK_IMG='/mnt/d/wsl_diskimg/uchan_home.vhdx'
DISK_FOR=$(cat "${DISK_IMG%.*}.txt")
WSL_DISTRO_NAME=$(wslpath -m / | cut -d/ -f4)
if [ "$DISK_FOR" != $WSL_DISTRO_NAME ]
then
echo "Disk image is not for me: me=$WSL_DISTRO_NAME for=$DISK_FOR"
exit 0
fi
if ! mountpoint -q /mnt/uchan_home
then
"$PS" Start-Process -FilePath wsl.exe -Verb RunAs -ArgumentList "--mount","--bare","--vhd","$(wslpath -w "$DISK_IMG")"
for i in $(seq 5)
do
if blkid | grep -q "PARTUUID=\"$PARTUUID\""
then
echo "Attached. then mounting"
sudo mount -o defaults,noatime PARTUUID="$PARTUUID" "$MNT"
exit 0
fi
echo "Waiting for partition attached: PARTUUID=\"$PARTUUID\""
echo "blkid=" $(blkid)
sleep 1
done
echo "Give up waiting: PARTUUID=\"$PARTUUID\""
exit 1
fi
PARTUUID
に先ほど表示された値をそのまま設定します。MNT
にはマウントポイントを指定します。ここで指定したディレクトリはマウントに先立って存在する必要があります。
DISK_FOR
は DISK_IMG
の拡張子を .txt
に変更したパスを表します。このファイルは何かというと、中身は「Ubuntu-24.04」など、ディスクイメージをマウントする WSL ディストリビューションの名前です。ファイルシステムを同時に複数の Ubuntu へマウントすると破損が怖いので、マウント先のディストリビューションを指定することにしました。調べてみると ext4 は Multiple Mount Protection という保護機能があり、同時に複数からマウントした際の破損を軽減できるそうですが、今回はその機能を使用せず、マウント先を 1 つに限定しました。
異なるディストリビューションでマウントしたくなったら、このテキストファイルの中身をマウントしたいディストリビューション名に更新し、Ubuntu を再起動します。再起動の順序は、まずは今マウントしている Ubuntu を、次に新たにマウントする Ubuntu を再起動します。逆にすると一時的に 2 つの Ubuntu からマウントした状態になるため、危険かもしれません。
"$PS" Start-Process -FilePath wsl.exe -Verb RunAs ...
は wsl.exe
を管理者権限で実行するための記述です。-Verb RunAs
が管理者権限で起動するためのオプションです。この行が実行されようとすると、画面に権限昇格のダイアログ(UAC ダイアログ)が出てきます。
wsl.exe --mount
の後、ちょっとだけ待たないと Ubuntu 側で認識されないようなので、最大 5 秒ほど待つ仕組みにしています。
ファイルを作ったら実行権限を与えておきます。
$ sudo chown +x /usr/local/bin/mount_uchan_home.sh
Systemd を用いて上記のスクリプトを Ubuntu 起動時に実行させます。(もちろん Systemd じゃない仕組みでやっても良いですが、root ではないユーザ権限で自動起動されてしまうと、root が必要な mount コマンドが失敗しますから、root で実行される仕組みである必要があります。)
WSL2 の Ubuntu 20.04 はデフォルトで Systemd が無効なので、上記の自動マウントの仕組みを動作させるには wsl.conf を編集して Systemd を有効化する必要があります。/etc/wsl.conf
に次のような設定を追加します。
[boot]
systemd = true
これで Ubuntu を再起動させれば Systemd が有効になります。
ちなみに、WSL2 上の OS の再起動は、cmd.exe 等で wsl.exe --shutdown
や wsl.exe --terminate ディストリビューション名
で終了させ、再度 WSL ターミナルを開けば OK です。
/etc/systemd/system に次のような内容のファイルを作ります。ファイル名は何でも良いですが、私は mount-uchan-home.service という名前にしました。
[Unit]
Description=Mount VHDX image for uchan home
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/mount_uchan_home.sh
[Install]
WantedBy=multi-user.target
ExecStart に先ほど作成したスクリプトのパスを指定します。
このタスクが自動的に実行されるよう、enable します。
$ sudo systemctl enable mount-uchan-home.service
Created symlink /etc/systemd/system/multi-user.target.wants/mount-uchan-home.service → /etc/systemd/system/mount-uchan-home.service.
この状態で Ubuntu を再起動すれば自動マウントされるはずです。自動マウントがうまくいかない場合、sudo /usr/local/bin/mount_uchan_home.sh
として手動でスクリプトを実行してうまくマウントされるかを確認したり、journalctl -u mount-uchan-home
でログを確認したりして原因を探ります。
上記のユニットファイルの記述では、指定したスクリプトが実行され終わったらサービスが終了したと判定され、inactive 状態となります。記述をこだわるとしたら、ディスクイメージがマウントされている間はずっと active になり、systemctl stop
でマウントを解除できるような仕組みにするのが理想だと思います。今回の筆者の需要ではホームディレクトリをマウントするというものだったので、途中でマウント解除することは想定しずらく、そこまで手の込んだ記述をしませんでした。