最終更新:

genpack イメージの起動機序

概要

genpack で生成された SquashFS イメージは、ディスクにインストールされた実機環境と、vm コマンドによる QEMU/KVM 準仮想化環境の 2 つの方法で起動できます。いずれの場合も共通の initramfs(dracut-genpack)が overlayfs ルートを構成し、genpack-init が system.ini に基づいて初期設定を行った後に systemd へ制御を渡す、という流れは同じです。

本ドキュメントでは両方式の起動シーケンスを詳細に解説します。

system.img 方式(ディスクインストール)

ディスクレイアウト

genpack-install でディスクにインストールすると、以下のパーティション構成が作られます。

パーティションファイルシステム内容
1: ブートパーティションFAT32EFI ブートローダー、カーネル、initramfs、system.img(4GiB 未満の場合)、system.ini
2: データパーティションBtrfsoverlayfs upper 層、system(4GiB 以上の場合)

ブートパーティションのサイズはイメージサイズから自動計算されます。--superfloppy オプションを指定すると、パーティションテーブルを作成せずディスク全体を FAT32 として使用します(データパーティションなし)。

MBR と GPT はディスクサイズに応じて自動選択されます(2TiB 以下かつ 512 バイトセクタなら MBR、それ以外は GPT)。

起動シーケンス

UEFI/BIOS GRUB Linux カーネル initramfs (dracut-genpack) overlayfs ルート構成 genpack-init (PID 1) Python プラグイン exec /sbin/init (systemd)

カーネルコマンドラインには root=systemimg:<UUID> または root=systemimg:auto が指定されます。

ブートローダー

genpack-install は各アーキテクチャ向けの GRUB ブートローダーをビルドし、SquashFS イメージの /usr/lib/genpack-install/ に同梱します。

EFI ブートローダー:

grub-mkstandalone を使い、grub.cfg を内蔵した単体 EFI バイナリとして生成されます。ディスク上に外部の設定ファイルを必要としません。

バイナリターゲット
bootx64.efix86_64
bootia32.efii386
bootaa64.efiARM64
bootriscv64.efiRISC-V 64

BIOS ブートローダー:

boot.img(MBR ステージ 1)と core.imggrub-mkimage で生成)の組み合わせです。core.img にはプレフィックス (,msdos1)/boot/grub がハードコードされており [10]、ブートパーティションの /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. システムイメージの検出
    • ブートパーティション上の system.img を検索
    • 見つからない場合はデータパーティション上の system を検索(ラベル data-<UUID>d-<UUID> → パーティション番号によるフォールバック)
  4. SquashFS のマウントとカーネル検出
    • loopback コマンドで SquashFS をループバックマウントし set root=loop でルートを切り替え
    • イメージ内に /boot/grub/grub.cfg があれば configfile で委譲 [7]
    • 存在しない場合は以下の処理を続行
  5. タイムアウトの決定
    • boottime.txt が残存(前回 unclean shutdown の証拠 [1])→ 10 秒 [6]
    • 通常 → 1 秒
  6. カスタム設定の読み込み
    • ブートパーティション上に system.cfg があれば source で読み込み [8]
    • 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 を使用(ループバックマウント済み)
    • MemTest86 が利用可能な場合は追加のメニューエントリを表示

上記の処理でシステムイメージが見つからなかった場合、データパーティション上の grub.cfg やカーネルでの直接起動を試みるフォールバックパスも存在します [9]

initramfs の処理(dracut-genpack)

dracut-genpack は 2 つのフックで構成されます。

cmdline フック(check-systemimg-root.sh):

カーネルコマンドラインの root= パラメータを確認します。root=systemimg:... 形式であれば、genpack のブートシーケンスを開始します。

mount フック(mount-genpack.sh):

  1. ブートパーティションの検出とマウント
    • root=systemimg:<UUID>: 指定 UUID のパーティションをマウント
    • root=systemimg:auto: 全 FAT パーティションを走査し system.img を含むものを検出
    • FAT の場合は fsck.fat -aw で自動修復後にマウント
    • マウントポイント: /run/initramfs/boot
  2. データパーティションの検出とマウント
    • ブートパーティションの UUID を基にラベル data-<UUID> で検索
    • フォールバック: d-<UUID>, wbdata-<UUID>、または次のパーティション番号 [2]
    • 見つからない場合は virtiofs (fs タグ) を試行 [3]
    • それでもマウントできない場合は tmpfs にフォールバック(トランジェントモード)
    • genpack.transient カーネルパラメータで明示的にトランジェントモードを指定可能
    • マウントポイント: /run/initramfs/rw
  3. SquashFS イメージのマウント
    • ブートパーティション上の /run/initramfs/boot/system.img を検索
    • 見つからない場合はデータパーティション上の /run/initramfs/rw/system を使用
    • read-only で /run/initramfs/ro にマウント
  4. overlayfs の構成
    • lowerdir: /run/initramfs/ro(SquashFS、読み取り専用)
    • upperdir: /run/initramfs/rw/root(Btrfs または tmpfs) [5]
    • workdir: /run/initramfs/rw/work
    • $NEWROOT に overlay をマウント
    • lower 層の /usr タイムスタンプを upper 層に同期 [4]
  5. シャットダウンプログラムの配置
    • /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 で実装されており、以下の処理を行います。

  1. /run/initramfs/boot/system.ini(ブートパーティション経由)または /run/initramfs/rw/system.ini(データパーティション経由)を読み込む
  2. /usr/lib/genpack-init/*.py 内の全 Python モジュールをファイル名順にロード
  3. 各モジュールの configure(ini) 関数を実行(タイムゾーン、ロケール、バナー表示、マシン ID 生成など)
  4. exec /sbin/init で systemd に制御を引き渡す

パラバーチャル方式(vm コマンド)

vm コマンドの概要

vm コマンドは genpack イメージを QEMU/KVM で直接起動するためのツールです。ディスクへのインストールは不要で、SquashFS ファイルをそのまま指定して起動できます。

起動シーケンス

vm run system.squashfs SquashFS からカーネル抽出 (memfd) qemu-system-<arch> initramfs (dracut-genpack) overlayfs ルート構成 genpack-init (PID 1) exec /sbin/init (systemd)

カーネルと initramfs の抽出

vm コマンドは squashfuse ライブラリを使用して、SquashFS イメージの /boot/ ディレクトリからカーネルと initramfs を直接読み出します。ディスクに展開する必要はなく、memfd_create で作成したメモリ上のファイルディスクリプタに書き出して QEMU に渡します。

検索順序:

  1. boot/kernel または boot/vmlinuz(固定名)
  2. 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=...

ディスクの提供:

virtio デバイスシリアル内容
vdasystemSquashFS イメージ(読み取り専用)
vdbdataデータディスク(あれば)
vdcswapスワップファイル(あれば)

SquashFS イメージは virtio-blk-pci デバイスとして read-only で接続されます。initramfs の mount-genpack.shroot=block:* ではなくカーネルコマンドラインの root=/dev/vda を参照し、/dev/vda を直接 SquashFS として /run/initramfs/ro にマウントします。

virtiofs モード:

vm コマンドは virtiofs もサポートしています。virtiofsd を起動してホストのディレクトリをゲストに共有し、overlayfs の upper 層として使用できます。virtiofs 使用時、initramfs は root=fs rootfstype=virtiofs rw に基づいて virtiofs をルートとしてマウントします。

vm サービスモード

vm コマンドには vm.ini ファイルを読み取るサービスモードがあります。各 VM のディレクトリ構成は以下の通りです。

vm.initype=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.iniFAT32 パーティション上virtiofs 経由または fw_cfg
initramfs の動作共通(dracut-genpack)共通(dracut-genpack)
genpack-init の動作共通共通

シャットダウン

genpack イメージのシャットダウンは通常の systemd シャットダウンプロセスの後、dracut の initramfs に制御が戻り、/run/initramfs/shutdown(genpack-shutdown)が実行されます。genpack-shutdown は以下を行います。

  1. /oldroot 以下の全マウントポイントを逆順にアンマウント
  2. /run/initramfs/rw(データパーティション)と /run/initramfs/boot(ブートパーティション)を安全に移動・アンマウント
  3. ブートパーティション上の boottime.txt を削除 [1]
  4. reboot(2) または poweroff を実行
  1. boottime.txt はブート時に作成されます。clean shutdown 時に削除されるため、次回ブート時にこのファイルが残存していれば前回の unclean shutdown(クラッシュや電源断)の証拠となります。
  2. d-<UUID> が現在の正式なラベルフォーマットです(Btrfs のラベル長制限のため短縮形を使用)。data-<UUID> および wbdata-<UUID> は Walbrix(genpack の前身)時代の互換性のために残されています。
  3. systemimg 方式は実機専用ではなく、vm フロントエンドを介さずに QEMU 上で直接実行される場合もあります。このフォールバックにより、baremetal プロファイルのイメージも準仮想化環境で起動可能です。baremetal プロファイルが systemimg の特化として存在するのもこの理由です。
  4. systemd は /usr のタイムスタンプを参照して ld.so.cache の再生成が必要かを判定します。overlayfs では upper 層が存在すると lower 層のタイムスタンプが隠されるため、lower 層の /usr タイムスタンプを明示的に upper 層に伝播させる必要があります。
  5. 旧バージョンでは upperdir が rw/rw/root という冗長なパスでした。rw/root に簡略化されましたが、既存環境との互換性のため initramfs は旧パス rw/rw/root も引き続き認識します。
  6. 前回の unclean shutdown 後にタイムアウトを延長することで、オペレータがトランジェントモードや MemTest86 を選択する猶予を確保します。正常シャットダウン時には boottime.txt が削除されるため、通常起動では 1 秒の短いタイムアウトで自動ブートします。
  7. イメージ固有のカーネルパラメータが必要な場合やスプラッシュ画面を表示したい場合など、イメージ側でブート構成をカスタマイズするための仕組みです。BOOT_PARTITIONBOOT_PARTITION_UUID がエクスポートされるため、委譲先の grub.cfg からもブートパーティション情報を参照できます。
  8. GRUB は INI ファイルフォーマットをネイティブに解析できないため、ブートローダー段階で必要な設定は GRUB スクリプト形式の system.cfg に、genpack-init 段階で必要なシステム設定は INI 形式の system.ini にという 2 層構造になっています。
  9. genpack 以外のシステムとの共存を想定した隠し機能です。データパーティション上に独立した grub.cfg やカーネル/initramfs があれば、そちらからの起動を試みます。常に成功する保証はありません。
  10. BIOS ブートは MBR パーティション構成が前提です。GPT ディスクの場合は EFI ブートが使用されます。

ソースリファレンス

このドキュメントは以下のリポジトリのスナップショットに基づいて作成されました:

更新履歴