概要
genpack で生成された SquashFS イメージは、ディスクにインストールされた実機環境と、vm コマンドによる QEMU/KVM 準仮想化環境の 2 つの方法で起動できます。いずれの場合も共通の initramfs(dracut-genpack)が overlayfs ルートを構成し、genpack-init が system.ini に基づいて初期設定を行った後に systemd へ制御を渡す、という流れは同じです。
本ドキュメントでは両方式の起動シーケンスを詳細に解説します。
system.img 方式(ディスクインストール)
ディスクレイアウト
genpack-install でディスクにインストールすると、以下のパーティション構成が作られます。
| パーティション | ファイルシステム | 内容 |
|---|---|---|
| 1: ブートパーティション | FAT32 | EFI ブートローダー、カーネル、initramfs、system.img(4GiB 未満の場合)、system.ini |
| 2: データパーティション | Btrfs | overlayfs upper 層、system(4GiB 以上の場合) |
ブートパーティションのサイズはイメージサイズから自動計算されます。--superfloppy オプションを指定すると、パーティションテーブルを作成せずディスク全体を FAT32 として使用します(データパーティションなし)。
MBR と GPT はディスクサイズに応じて自動選択されます(2TiB 以下かつ 512 バイトセクタなら MBR、それ以外は GPT)。
起動シーケンス
UEFI/BIOS
→ GRUB (efi/boot/bootx64.efi 等)
→ Linux カーネル (root=systemimg:<UUID> or root=systemimg:auto)
→ initramfs (dracut-genpack)
→ overlayfs ルート構成
→ genpack-init (PID 1)
→ Python プラグインで初期設定
→ exec /sbin/init (systemd)
ブートローダー
genpack-install は各アーキテクチャ向けの GRUB ブートローダーをビルドし、SquashFS イメージの /usr/lib/genpack-install/ に同梱します。
EFI ブートローダー:
grub-mkstandalone を使い、grub.cfg を内蔵した単体 EFI バイナリとして生成されます。ディスク上に外部の設定ファイルを必要としません。
| バイナリ | ターゲット |
|---|---|
bootx64.efi |
x86_64 |
bootia32.efi |
i386 |
bootaa64.efi |
ARM64 |
bootriscv64.efi |
RISC-V 64 |
BIOS ブートローダー:
boot.img(MBR ステージ 1)と core.img(grub-mkimage で生成)の組み合わせです。core.img にはプレフィックス (,msdos1)/boot/grub がハードコードされており [1]、ブートパーティションの /boot/grub/grub.cfg を読み込みます。BIOS の場合、grub.cfg はバイナリに内蔵されず、genpack-install がディスクインストール時にブートパーティションへ配置します。
grub.cfg の処理フロー
EFI バイナリに内蔵された(BIOS の場合はブートパーティション上の)grub.cfg は以下の処理を行います。
1. シリアルコンソールの初期化
COM0 を 115200 baud で試行し、成功すればシリアルとコンソールの両方を入出力端末として設定します。
2. ブートパーティションの特定
GRUB 変数 $cmdpath(ブートローダーの起動元パス)からブートパーティションを推定し、probe -u で UUID を取得します。
3. システムイメージの検出
以下の順序で SquashFS イメージを検索します。
- ブートパーティション上の
system.img - データパーティション上の
system(ラベルdata-<UUID>→d-<UUID>→ パーティション番号によるフォールバックの順で検索)
4. SquashFS のマウントとカーネル検出
loopback コマンドで SquashFS をループバックマウントし、set root=loop でルートを切り替えます。イメージ内に /boot/grub/grub.cfg が存在する場合は configfile で読み込みを委譲します [2]。
存在しない場合は以下の処理を続行します。
5. タイムアウトの決定
ブートパーティション上に boottime.txt が残存している場合(前回 unclean shutdown の証拠 [3])はタイムアウトを 10 秒に設定し [4]、通常時は 1 秒に設定します。
6. カスタム設定の読み込み
ブートパーティション上に system.cfg が存在すれば source で読み込みます [5]。このファイルで LINUX_ARGS 変数を設定することでカーネルコマンドラインをカスタマイズできます。
7. カーネルコマンドラインの構成
panic=30をデフォルトで付与(明示的な指定がない場合)- x86 系の場合、
console=ttyS0,115200n8r console=tty0を追加(明示的な指定がない場合)
8. メニューエントリ
| エントリ | カーネルコマンドライン |
|---|---|
| Normal mode | linux /boot/kernel root=systemimg:<UUID> $LINUX_ARGS systemd.firstboot=0 |
| Transient mode | 上記に genpack.transient=1 を追加 |
カーネルと initramfs は SquashFS 内の /boot/kernel と /boot/initramfs が使用されます(ループバックマウント済みのため、GRUB は SquashFS 内のファイルを直接参照できます)。MemTest86 が利用可能な場合は追加のメニューエントリが表示されます。
上記の処理でシステムイメージが見つからなかった場合、データパーティション上の grub.cfg やカーネルでの直接起動を試みるフォールバックパスも存在します [6]。
initramfs の処理(dracut-genpack)
dracut-genpack は 2 つのフックで構成されます。
cmdline フック(check-systemimg-root.sh):
カーネルコマンドラインの root= パラメータを確認します。root=systemimg:... 形式であれば、genpack のブートシーケンスを開始します。
mount フック(mount-genpack.sh):
-
ブートパーティションの検出とマウント
root=systemimg:<UUID>の場合: 指定 UUID のパーティションをマウントroot=systemimg:autoの場合: 全 FAT パーティションを走査し、system.imgを含むものを検出- FAT の場合は
fsck.fat -awで自動修復後にマウント - マウントポイント:
/run/initramfs/boot
-
データパーティションの検出とマウント
-
SquashFS イメージのマウント
- ブートパーティション上の
/run/initramfs/boot/system.imgを検索 - 見つからない場合はデータパーティション上の
/run/initramfs/rw/systemを使用 - read-only で
/run/initramfs/roにマウント
- ブートパーティション上の
-
overlayfs の構成
-
シャットダウンプログラムの配置
/run/initramfs/ro/usr/libexec/genpack-shutdownを/run/initramfs/shutdownにコピー- シャットダウン時に overlayfs と SquashFS を安全にアンマウントするために使用
genpack-init
dracut の initramfs 処理が完了すると、$NEWROOT にルートが切り替わり、/usr/bin/genpack-init が PID 1 として起動します(init=/usr/bin/genpack-init が dracut モジュールにより cmdline に追加される)。
genpack-init は C++ + pybind11 で実装されており、以下の処理を行います。
/run/initramfs/boot/system.ini(ブートパーティション経由)または/run/initramfs/rw/system.ini(データパーティション経由)を読み込む/usr/lib/genpack-init/*.py内の全 Python モジュールをファイル名順にロード- 各モジュールの
configure(ini)関数を実行(タイムゾーン、ロケール、バナー表示、マシン ID 生成など) exec /sbin/initで systemd に制御を引き渡す
パラバーチャル方式(vm コマンド)
vm コマンドの概要
vm コマンドは genpack イメージを QEMU/KVM で直接起動するためのツールです。ディスクへのインストールは不要で、SquashFS ファイルをそのまま指定して起動できます。
起動シーケンス
vm run system.squashfs
→ SquashFS からカーネルと initramfs を抽出 (memfd)
→ qemu-system-<arch> -kernel <kernel> -initrd <initramfs>
-append "root=/dev/vda ro ..."
-drive file=system.squashfs,...,serial=system (virtio-blk)
-drive file=data,...,serial=data (virtio-blk, あれば)
→ initramfs (dracut-genpack)
→ overlayfs ルート構成
→ genpack-init (PID 1)
→ Python プラグインで初期設定
→ exec /sbin/init (systemd)
カーネルと initramfs の抽出
vm コマンドは squashfuse ライブラリを使用して、SquashFS イメージの /boot/ ディレクトリからカーネルと initramfs を直接読み出します。ディスクに展開する必要はなく、memfd_create で作成したメモリ上のファイルディスクリプタに書き出して QEMU に渡します。
検索順序:
boot/kernelまたはboot/vmlinuz(固定名)boot/kernel-*またはboot/vmlinuz-*(タイムスタンプが最新のもの)
initramfs も同様に boot/initramfs, boot/initramfs.img, boot/initrd.img の順で検索されます。
カーネルバイナリの ELF ヘッダまたは PE ヘッダからアーキテクチャ(x86_64, aarch64, riscv64 等)を自動判定し、対応する qemu-system-<arch> を起動します。
QEMU の起動構成
vm コマンドは QEMU のダイレクトカーネルブート(-kernel, -initrd, -append)を使用します。ブートローダーは介在しません。
カーネルコマンドライン:
root=/dev/vda ro net.ifnames=0 systemd.firstboot=0 systemd.hostname=<vmname> console=...
root=/dev/vda: SquashFS イメージが virtio-blk デバイスとして提供される- virtiofs モードの場合は
root=fs rootfstype=virtiofs rw
ディスクの提供:
| virtio デバイス | シリアル | 内容 |
|---|---|---|
| vda | system | SquashFS イメージ(読み取り専用) |
| vdb | data | データディスク(あれば) |
| vdc | swap | スワップファイル(あれば) |
SquashFS イメージは virtio-blk-pci デバイスとして read-only で接続されます。initramfs の mount-genpack.sh は root=block:* ではなくカーネルコマンドラインの root=/dev/vda を参照し、/dev/vda を直接 SquashFS として /run/initramfs/ro にマウントします。
virtiofs モード:
vm コマンドは virtiofs もサポートしています。virtiofsd を起動してホストのディレクトリをゲストに共有し、overlayfs の upper 層として使用できます。virtiofs 使用時、initramfs は root=fs rootfstype=virtiofs rw に基づいて virtiofs をルートとしてマウントします。
vsock によるホスト通信
QEMU/KVM は virtio-vsock デバイスを通じてゲストとホスト間の通信チャネルを提供します。vm コマンドはすべての VM に対して vsock を自動的に有効化します。
ゲスト CID
各 VM には vsock::determine_guest_cid() によって VM 名から一意に決定されるゲスト CID(Context Identifier)が割り当てられます。起動時に CID が表示されます:
Guest CID: 12345
`ssh user@vsock%12345` to login to the VM.
vsock を使った SSH ログインには、ゲストイメージ側に socket_vmid の systemd-ssh-generator 設定が必要です。
--sock-forward オプション
SLIRP の NAT ではホスト上の Unix ドメインソケットに直接到達できません [11]。--sock-forward オプションは、ゲスト内の vsock 接続をホスト上の Unix ドメインソケットサーバーにブリッジするプロキシプロセス(socat)を vm コマンドが起動・管理する機能です。
vm run system.squashfs --sock-forward /run/user/1007/aichannel.sock
複数指定可能です:
vm run system.squashfs \
--sock-forward /run/user/1007/foo.sock \
--sock-forward /run/user/1007/bar.sock
vsock ポート番号の決定:
vsock のリッスンポート番号はゲスト CID と同じ値になります。複数指定した場合は CID+0、CID+1、CID+2、… と順に割り当てられます。ゲスト内から /dev/vsock への IOCTL_VM_SOCKETS_GET_LOCAL_CID ioctl で自身の CID を取得できるため、ゲスト側スクリプトは「ポート番号 = 自分の CID + オフセット」という規則だけで正しいポートに接続できます。
通信経路:
[ゲスト内アプリ]
↓ TCP localhost:PORT
[ゲスト内 socat ブリッジ] ← systemd ユニットで自動起動
↓ VSOCK CID=2(host):PORT
[ホスト側 socat プロキシ] ← vm コマンドが起動・管理
↓ UNIX-CONNECT(接続ごとに fork)
[ホスト上の Unix ドメインソケットサーバー]
fork オプションにより接続ごとに独立した Unix ソケット接続が確立されるため、並行接続・keep-alive タイムアウトの問題が発生しません [12]。
ゲスト側ブリッジ:
ゲスト側の TCP → vsock ブリッジは以下のヘルパースクリプトと systemd テンプレートユニットで実現できます。
/usr/local/bin/sock-forward:
#!/usr/bin/python3
import os, fcntl, struct, socket, sys
fd = os.open("/dev/vsock", os.O_RDONLY)
cid = struct.unpack("I", fcntl.ioctl(fd, socket.IOCTL_VM_SOCKETS_GET_LOCAL_CID, bytes(4)))[0]
os.close(fd)
arg = sys.argv[1].split(":")
port, offset = arg[0], int(arg[1]) if len(arg) > 1 else 0
os.execlp("socat", "socat", f"TCP-LISTEN:{port},fork,reuseaddr", f"VSOCK-CONNECT:2:{cid + offset}")
/etc/systemd/system/[email protected]:
[Unit]
Description=VM service bridge (TCP %i -> vsock host)
[Service]
ExecStart=/usr/local/bin/sock-forward %i
Restart=on-failure
[Install]
WantedBy=multi-user.target
systemctl enable sock-forward@8080 とすれば localhost:8080 → vsock:2:<自分の CID> のブリッジが起動します。ポート番号に :1、:2 … のオフセットを付加することで(例: sock-forward@8080:1)複数のホストソケットに対応できます。
vm サービスモード
vm コマンドには vm.ini ファイルを読み取るサービスモードがあります。各 VM のディレクトリ構成は以下の通りです。
/var/vm/<vmname>/
├── vm.ini # VM 設定(メモリ、CPU、ネットワーク等)
├── system # SquashFS イメージ(シンボリックリンク可)
├── data # データディスク(オプション)
├── swapfile # スワップ(オプション)
├── fs/ # virtiofs 共有ディレクトリ
├── docker # Docker 用ディスク(オプション、serial=docker)
└── mysql # MySQL 用ディスク(オプション、serial=mysql)
vm.ini の type=genpack(デフォルト)でダイレクトカーネルブートが使用されます。
system.img 方式との共通点と相違点
| 項目 | system.img 方式 | パラバーチャル方式 |
|---|---|---|
| ブートローダー | GRUB (EFI/BIOS) | なし(ダイレクトカーネルブート) |
| カーネル格納場所 | ブートパーティション上のファイル | SquashFS 内から memfd に抽出 |
| root= パラメータ | systemimg:<UUID> or systemimg:auto |
/dev/vda |
| SquashFS の提供 | ブート/データパーティション上のファイル | virtio-blk デバイス |
| データ永続化 | Btrfs パーティション | data ディスクファイルまたは virtiofs |
| トランジェントモード | genpack.transient カーネルパラメータ |
data ディスクを指定しなければ自動 |
| system.ini | FAT32 パーティション上 | virtiofs 経由または fw_cfg |
| initramfs の動作 | 共通(dracut-genpack) | 共通(dracut-genpack) |
| genpack-init の動作 | 共通 | 共通 |
シャットダウン
genpack イメージのシャットダウンは通常の systemd シャットダウンプロセスの後、dracut の initramfs に制御が戻り、/run/initramfs/shutdown(genpack-shutdown)が実行されます。genpack-shutdown は以下を行います。
/oldroot以下の全マウントポイントを逆順にアンマウント/run/initramfs/rw(データパーティション)と/run/initramfs/boot(ブートパーティション)を安全に移動・アンマウント- ブートパーティション上の
boottime.txtを削除 [3:1] reboot(2)またはpoweroffを実行
ソースリファレンス
このドキュメントは以下のリポジトリのスナップショットに基づいて作成されました:
- wbrxcorp/genpack-install @ HEAD
- wbrxcorp/genpack-overlay @ HEAD (sys-kernel/dracut-genpack, genpack/base)
- wbrxcorp/genpack-init @ HEAD
- shimarin/vm @ HEAD
BIOS ブートは MBR パーティション構成が前提です。GPT ディスクの場合は EFI ブートが使用されます。 ↩︎
イメージ固有のカーネルパラメータが必要な場合やスプラッシュ画面を表示したい場合など、イメージ側でブート構成をカスタマイズするための仕組みです。
BOOT_PARTITIONとBOOT_PARTITION_UUIDがエクスポートされるため、委譲先の grub.cfg からもブートパーティション情報を参照できます。 ↩︎boottime.txt はブート時に作成されます。clean shutdown 時に削除されるため、次回ブート時にこのファイルが残存していれば前回の unclean shutdown(クラッシュや電源断)の証拠となります。 ↩︎ ↩︎
前回の unclean shutdown 後にタイムアウトを延長することで、オペレータがトランジェントモードや MemTest86 を選択する猶予を確保します。正常シャットダウン時には boottime.txt が削除されるため、通常起動では 1 秒の短いタイムアウトで自動ブートします。 ↩︎
GRUB は INI ファイルフォーマットをネイティブに解析できないため、ブートローダー段階で必要な設定は GRUB スクリプト形式の system.cfg に、genpack-init 段階で必要なシステム設定は INI 形式の system.ini にという 2 層構造になっています。 ↩︎
genpack 以外のシステムとの共存を想定した隠し機能です。データパーティション上に独立した grub.cfg やカーネル/initramfs があれば、そちらからの起動を試みます。常に成功する保証はありません。 ↩︎
d-<UUID>が現在の正式なラベルフォーマットです(Btrfs のラベル長制限のため短縮形を使用)。data-<UUID>およびwbdata-<UUID>は Walbrix(genpack の前身)時代の互換性のために残されています。 ↩︎systemimg 方式は実機専用ではなく、vm フロントエンドを介さずに QEMU 上で直接実行される場合もあります。このフォールバックにより、baremetal プロファイルのイメージも準仮想化環境で起動可能です。baremetal プロファイルが systemimg の特化として存在するのもこの理由です。 ↩︎
旧バージョンでは upperdir が
rw/rw/rootという冗長なパスでした。rw/rootに簡略化されましたが、既存環境との互換性のため initramfs は旧パスrw/rw/rootも引き続き認識します。 ↩︎systemd は
/usrのタイムスタンプを参照してld.so.cacheの再生成が必要かを判定します。overlayfs では upper 層が存在すると lower 層のタイムスタンプが隠されるため、lower 層の/usrタイムスタンプを明示的に upper 層に伝播させる必要があります。 ↩︎SLIRP は TCP/UDP のみを対象としたユーザー空間 NAT です。ホスト上の Unix ドメインソケットは TCP/IP ネットワークの外に存在するため SLIRP では到達できません。ホスト上の TCP サービスへのアクセスは SLIRP の NAT で直接届くため、
--sock-forwardの対象外です。 ↩︎QEMU の
--guestfwdオプションも Unix ドメインソケットへの転送をサポートしますが、すべての TCP 接続に対して 1 本の chardev(Unix ソケット接続)を使い回す設計のため、並行接続や keep-alive タイムアウトがある実際の使用には耐えられません。vsock + socat のアーキテクチャはこの制約を回避します。 ↩︎