====== Device Mapperで物理パーティションを仮想パーティションに偽装 ====== 仮想マシンに物理ディスクのパーティションをパススルーする、いわゆるRDM (Raw Device Mapping)を行うと、物理パーティションは仮想ブロックデバイスの扱いとなる。つまり、VMから見ると単なる仮想ディスク扱いなので、VMでパーティションを作るとパーティションの中にパーティションがある状態となる。 RDMの趣旨からすれば当然の挙動ではあるものの、Proxmox VE環境にて物理パーティションを仮想ディスク内のいちパーティションとして扱いたくなった。図にすると↓こんな感じ。 {{ :virtualization:rdm_vs_simulated_disk_on_vdisk.png |}} 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として認識される。 {{ :virtualization:actual_device_mapping_strategy.png |}} 物理ディスクの構成は下表となる。 ^ デバイス ^ 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とパーティションを持った仮想ディスクに見える、という目論見である。 **Device Mapperは1セクタを一律512バイト**で扱うので、4kセクタデバイスとの絡みは十分に気を付けること。 本記事の作業は低レベル操作を多用するため、間違うと容易にデータ破壊を引き起こす。自分が何をしようとしているのか、しっかりと理解した上で慎重に作業を進められたい。見様見真似のコピペ作業はおススメできない(それくらい危険で気づかぬ間にデータを壊す恐れがあるということ。) ==== 偽装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と同じ結果となっていることがわかる。 {{ :virtualization:show_partition_table_of_fake_hdd_on_freebsd.png |}} 末尾の空き領域が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はサイコーと思いました。 ===== 参考サイト ===== * [[https://wiki.archlinux.org/index.php/QEMU#Using_any_real_partition_as_the_single_primary_partition_of_a_hard_disk_image|QEMU - ArchWiki]] * [[https://wiki.gentoo.org/wiki/Device-mapper|Device-mapper - Gentoo Wiki]] * [[https://en.wikipedia.org/wiki/Device_mapper|Device mapper - Wikipedia]] * [[https://man7.org/linux/man-pages/man8/dmsetup.8.html|dmsetup(8) - Linux manual page]] (テーブルフォーマットの詳細) * [[https://man7.org/linux/man-pages/man8/losetup.8.html|losetup(8) - Linux manual page]]