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とパーティションを持った仮想ディスクに見える、という目論見である。

Device Mapperは1セクタを一律512バイトで扱うので、4kセクタデバイスとの絡みは十分に気を付けること。

本記事の作業は低レベル操作を多用するため、間違うと容易にデータ破壊を引き起こす。自分が何をしようとしているのか、しっかりと理解した上で慎重に作業を進められたい。見様見真似のコピペ作業はおススメできない(それくらい危険で気づかぬ間にデータを壊す恐れがあるということ。)

偽装用の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が生えてくる

生成した/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.

作成した仮想ブロックデバイスを仮想マシンにアタッチする

# 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はサイコーと思いました。

  • virtualization/how_to_simulate_ppartition_as_one_of_vpartitions_in_vdisk.txt
  • 最終更新: 2021-03-30 15:30
  • by Decomo