Device Mapperで物理パーティションを仮想パーティションに偽装
仮想マシンに物理ディスクのパーティションをパススルーする、いわゆるRDM (Raw Device Mapping)を行うと、物理パーティションは仮想ブロックデバイスの扱いとなる。つまり、VMから見ると単なる仮想ディスク扱いなので、VMでパーティションを作るとパーティションの中にパーティションがある状態となる。
RDMの趣旨からすれば当然の挙動ではあるものの、Proxmox VE環境にて物理パーティションを仮想ディスク内のいちパーティションとして扱いたくなった。図にすると↓こんな感じ。
Linuxの機能であるDevice Mapperを使うと実現できそうだったので試してみた。
試した環境
- Proxmox VE 6.3-4 (Debian 10.7)
実際の物理ディスクと仮想ディスクの構成は下図のとおり。
物理HDD1,2のFreeBSDシステムパーティションを、仮想HDD1,2のパーティション化しVMで起動するのが目的である。そのために必要なGPTやESPを含んだ仮想ブロックデバイスを、Device Mapperで生成するのが肝である。
物理HDD3はデバイス全体をRDMしてるので、そのままVM側で物理と同じ構造の仮想HDDとして認識される。
物理ディスクの構成は下表となる。
デバイス | 4kセクタ換算 | 512バイトセクタ換算 | 容量 | 種類 | ||||
---|---|---|---|---|---|---|---|---|
開始LBA | 終了LBA | セクタ数 | 開始LBA | 終了LBA | セクタ数 | |||
/dev/nvme0n1 | 0 | 255 | 256 | 0 | 2047 | 2048 | 1MiB | GPT |
/dev/nvme0n1p1 | 256 | 131327 | 131072 | 2048 | 1050616 | 1048576 | 512MiB | ESP |
/dev/nvme0n1p2 | 131328 | 26345727 | 26214400 | 1050624 | 210765816 | 209715200 | 100GiB | FreeBSD ZFS |
/dev/nvme0n1p3 | 26345728 | 402260223 | 375914496 | 210765824 | 3218081784 | 3007315968 | 1.4TiB | Solaris /usr & Apple ZFS |
/dev/nvme0n1p4 | 402260224 | 415367423 | 13107200 | 3218081792 | 3322939384 | 104857600 | 50GiB | Solaris /usr & Apple ZFS |
FreeBSDのシステムパーティションを素直にディスクイメージ化しないのは、手をかけずに物理に戻せる状態を維持しておきたいから。このFreeBSD環境は長年使っており、同様の手法で物理と半仮想環境を行ったり来たりしてるため、今回も技術的興味の一環だ。加えて、しょーもない理由として、完全仮想化でLinuxにおんぶにだっこ状態になるのは、なんか負けたような気がして癪だから(笑
手順
Device Mapperは複数のブロックデバイスを結合し1つの仮想ブロックデバイスを作ることができる。これを利用し、GPT + ESP + 物理パーティションからなる仮想デバイスを作成してやる。そして、そのデバイスをRDMすれば、VMからはGPTでESPとパーティションを持った仮想ディスクに見える、という目論見である。
本記事の作業は低レベル操作を多用するため、間違うと容易にデータ破壊を引き起こす。自分が何をしようとしているのか、しっかりと理解した上で慎重に作業を進められたい。見様見真似のコピペ作業はおススメできない(それくらい危険で気づかぬ間にデータを壊す恐れがあるということ。)
偽装GPT, ESPの準備
偽装用のGPT、EFIシステムパーティションを保存するファイルを作る。
セカンダリGPTの分も忘れずに。さもないと、偽装後の最終パーティション(ここでならnvme0n1p2)の末端数セクタに意図せず確保され、データの破壊に繋がると思われる。容量は40セクタ@512Bあれば十分だろうが、念のため10MiBにした。
# cd /var/lib/vz/images/100 # dd if=/dev/zero of=./vm-100-fake_gpt1_primary.raw count=2048 # dd if=/dev/zero of=./vm-100-fake_gpt1_secondary.raw count=20480 # dd if=/dev/zero of=./vm-100-fake_gpt2_primary.raw count=2048 # dd if=/dev/zero of=./vm-100-fake_gpt2_secondary.raw count=20480 # dd if=/dev/zero of=./vm-100-fake_esp2.raw count=1048576
それぞれループデバイスを作成する。/dev/loop0
~/dev/loop4
が生えてくる。
# losetup --show -f ./vm-100-fake_gpt1_primary.raw /dev/loop0 # losetup -f ./vm-100-fake_gpt1_secondary.raw # losetup -f ./vm-100-fake_gpt2_primary.raw # losetup -f ./vm-100-fake_gpt2_secondary.raw # losetup -f ./vm-100-fake_esp.raw
仮想ブロックデバイスの作成
Device Mapperで上記ループデバイスと物理パーティションを結合した、仮想ブロックデバイスを作成する。
テーブルファイルの作成
仮想ブロックデバイスの定義ファイルを作る。前述のとおり、Device Mapperのセクタ指定の数値は512バイトセクタ基準なので注意のこと。
便宜上、物理パーティションの指定には従来のデバイスファイル名を使っているが、実際は/dev/disk/by-id/~
の永続的な名前の方を使うべきだ。デバイス名が変わったら、即データ破壊なので。
0 2048 linear /dev/loop0 0 2048 1048576 linear /dev/nvme0n1p1 0 1050624 209715200 linear /dev/nvme0n1p2 0 210765824 20480 linear /dev/loop1 0
仮想ブロックデバイスに割り当てるデバイスを1行ずつ書いていく。上から順に、プライマリGPT、FreeBSD用の物理ESP、FreeBSDの物理システムパーティション、セカンダリGPTとなっている。
各行の書式は「logical_start_sector num_sectors target_type target_args」となっている。
logical_start_sector | 仮想ブロックデバイスの割り当て領域の先頭セクタ |
num_sectors | 割り当てセクタ数 |
target_type | 割り当て方法 |
target_args | 割り当て方法に応じた引数。linearの場合、割り当てるデバイス名、そのデバイスの割り当て開始セクタを指定する。 |
例えば、上記定義の2行目は「仮想デバイスの2048セクタから1048576個分の領域として、/dev/nvme0n1p1のセクタ0からlinearに割り当てる」と読める。
仮想ブロックデバイスの作成
dmsetupコマンドで仮想ブロックデバイスを作成する。
# cat fbsd_disk0.table | dmsetup create fbsd_disk0
成功すると/dev/mapper/fbsd_disk0
が生えてくる
偽装GPTの作成
生成した/dev/mapper/fbsd_disk0
にパーティションテーブルを作る。
原則、fdiskはパーティションテーブル、すなわちディスクの先頭1MiBにのみ影響する。ここでの操作は、/dev/mapper/fbsd_disk0
→ /dev/loop0
,loop1
を経由し、vm-100-fake_gpt1_primary.raw, vm-100-fake_gpt1_secondary.rawファイルに対する変更となる。
512バイトセクタを明示するため、fdiskは-b 512
オプションを付けて起動すること。fdiskはDevice Mapperデバイスをデフォルトで4kセクタと認識する一方、PVE (QEMU?)は512バイトセクタと認識するため、セクタサイズを合わせておかないとパーティションが正しく認識されてない。
# fdisk -b 512 /dev/mapper/fbsd_disk0
pコマンドで512バイトセクタとなっているか確認。
Command (m for help): p Disk /dev/mapper/fbsd_disk0: 100.5 GiB, 107922587648 bytes, 210786304 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 4096 bytes
GPT作ってー
Command (m for help): g Created a new GPT disklabel (GUID: BBACB4EC-FB5C-7C41-BC43-A45877943EDA).
パーティション追加。
Last sectorはFist sectorからのオフセットなので、確保するセクタ数-1を指定する点に注意。言わずもがな、512バイトセクタ換算のセクタ数である。
セクタ指定が正しければ、Device Mapperで結合した物理パーティションの既存のESPを認識し「VFATのシグネチャあるけど消す?」と言ってくる。Yesにすると物理パーティションの方に影響する気がするので、Noにしておく。知らんけど。
Command (m for help): n Partition number (1-128, default 1): First sector (2048-210786270, default 2048): Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-210786270, default 210786270): +1048575 Created a new partition 1 of type 'Linux filesystem' and of size 512 MiB. Partition #1 contains a vfat signature. Do you want to remove the signature? [Y]es/[N]o: n
その後、tコマンドでパーティションタイプを変更。
Command (m for help): t Selected partition 1 Partition type (type L to list all types): 1 Changed type of partition 'Linux filesystem' to 'EFI System'.
他のパーティションも同様に追加&変更し、偽装GPTを作り上げる。最終的に以下のようになる。
Command (m for help): p Disk /dev/mapper/fbsd_disk0: 100.5 GiB, 107922587648 bytes, 210786304 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 4096 bytes Disklabel type: gpt Disk identifier: BBACB4EC-FB5C-7C41-BC43-A45877943EDA Device Start End Sectors Size Type /dev/mapper/fbsd_disk0-part1 2048 1050623 1048576 512M EFI System /dev/mapper/fbsd_disk0-part2 1050624 210765823 209715200 100G FreeBSD ZFS
パーティション情報、特にセクタが間違ってないか入念に確認し、wで書き込む。
システムにパーティションを追加できなかったと言われるが、偽装GPTは正しく書き込まれているので心配無用。
Command (m for help): w The partition table has been altered. Failed to add partition 1 to system: Invalid argument Failed to add partition 2 to system: Invalid argument The kernel still uses the old partitions. The new table will be used at the next reboot. Syncing disks.
VMに接続
作成した仮想ブロックデバイスを仮想マシンにアタッチする
# qm set 100 -scsi0 /dev/mapper/fbsd_disk0
まずはインストーラISOなどでVMを起動し、作成した偽装ディスクに書き込まれない環境で確認するのが無難。下図はFreeBSDのインストーラのシェルで偽装ディスクのパーティションを表示したものだ。Linuxのfdiskと同じ結果となっていることがわかる。
末尾の空き領域が20447セクタということは、セカンダリGPTは33セクタってことですな。
入念を期すなら、ホストとVMのそれぞれでパーティションのハッシュ値を求め、一致することを確認すればよいだろう。
設定の永続化
iosetupとdmsetupは揮発性なので、ホスト起動時に生成されるようにする。
systemdでどうにかするのが正しい気がするけど、Linuxなんもわからんマンなのでcrontabで処理する。
#!/bin/sh DIR=/var/lib/vz/images/100 LOSETUP=/sbin/losetup DMSETUP=/sbin/dmsetup $LOSETUP -f $DIR/vm-100-fake_gpt1_primary.raw $LOSETUP -f $DIR/vm-100-fake_gpt1_secondary.raw $LOSETUP -f $DIR/vm-100-fake_gpt2_primary.raw $LOSETUP -f $DIR/vm-100-fake_gpt2_secondary.raw $LOSETUP -f $DIR/vm-100-fake_esp2.raw cat $DIR/fbsd_disk0.table | $DMSETUP create fbsd_disk0 cat $DIR/fbsd_disk1.table | $DMSETUP create fbsd_disk1
# crontab -e @reboot /var/lib/vz/images/100/make_fake_disk.sh
あとがき
本記事の方法で、物理パーティションのFreeBSD環境をそっくりVM上で動かすことができた。とりあえず、ZFS scrubでチェックしたところエラーもなく正常に稼働しているようだ。
こういう小回りが利きまくるProxmox VEはサイコーと思いました。