Skip to content

Kickstart on XCP-ng: Five Silent Traps

2026年にもなってIDCでKickstartをやっている。

Kickstartとは

OSの自動インストールの仕組み。設定ファイル1つでパーティション、ネットワーク、ユーザー、パッケージをすべて定義し、人間の介入なしにインストールを完走させる。Red HatがRed Hat Linux 5.2(1998年)で導入した。同時期のSolarisにはJumpStartがあり、UNIXの世界ではOS自動インストールという概念自体は珍しいものではなかった。Kickstartは四半世紀を超えてなお、RHELとその派生ディストリビューション(AlmaLinux、Rocky Linux)の標準的なプロビジョニング手段として現役だ。

クラウドではない。物理サーバーにXCP-ngを載せて、その上にHVM仮想マシンを並べている。GUIの管理ツールはある。XenCenter(Windows)やXen Orchestra(Web)を使えばマウスでポチポチVMを作れる。使わない。Macしかないというのもあるが、それ以上に妙な矜持がGUIを許さなかった。CLIならスクリプトにできる。再現できる。SSHとxeコマンドだけでVMを作る。AlmaLinuxのminimal ISOをKickstartで自動インストールして、テンプレートVMを焼く。それだけのことに、丸一日かかった。

XCP-ngとは

Citrix XenServerのオープンソースフォーク。2018年にXenServerのフリー版が機能制限されたことを受けて、Vates社が完全なOSS代替として立ち上げた。ハイパーバイザーにXenを使い、dom0(管理用の特権VM)からxeコマンドでゲストVMを操作する。商用のVMware ESXiやHyper-Vに対するOSSの選択肢だ。

5つの罠を踏んだ。どれも「静かに失敗する」タイプで、エラーメッセージが出ない。

罠1: CD VBDが静かに失敗する

VMを作ってISOを挿入して起動した。40秒でhaltした。ディスクは空のまま。

VBD(Virtual Block Device)とは

Xenにおける仮想ディスクデバイス。物理マシンのSATAポートやCDドライブに相当する仮想的な接続点で、ここにVDI(Virtual Disk Image、実際のディスクイメージ)をマウントする。CD VBDは仮想CDドライブにあたる。

XCP-ngのxe vm-installで作ったVMにはCDドライブがない。xe vm-cd-insertはCD VBDが存在しないと、エラーも出さずに何もしない。ISOを挿入したつもりが、何も起きていなかった。

bash
# これは静かに失敗する(CD VBDがないため)
xe vm-cd-insert vm=my-vm cd-name=AlmaLinux-minimal.iso

# 明示的にCD VBDを作成してからマウントする
ISO_VDI=$(xe vdi-list sr-uuid=${ISO_SR} name-label="AlmaLinux-minimal.iso" params=uuid --minimal)
xe vbd-create vm-uuid=${VM_UUID} vdi-uuid=${ISO_VDI} device=3 type=CD mode=RO bootable=true

vm-cd-insertを信用しない。vbd-createで明示的にCD VBDを作る。これが最初の教訓だった。

罠2: HVM VMにブートパラメータを渡せない

Kickstartを使うには、カーネルパラメータにinst.ks=を渡す必要がある。PV-argsxenstore-writeで設定しようとした。無視された。

HVMとPV——Xenの二つの仮想化方式

Xenには歴史的に二つの仮想化モードがある。PV(Paravirtualization、準仮想化) はゲストOSのカーネルを改変してXenと直接やりとりする方式で、Xen初期(2003年〜)の中心的な技術だった。PVではXenがカーネルを直接ロードするので、PV-argsでカーネルパラメータを自由に渡せる。HVM(Hardware Virtual Machine) はCPUのハードウェア仮想化支援(Intel VT-x / AMD-V)を使い、未改変のOSをそのまま動かす方式。HVMではBIOS/UEFIからGRUBを経てカーネルが起動するので、Xen側からパラメータを差し込む余地がない。現在のXCP-ngではHVMが標準で、PVはレガシーな位置づけだ。

HVM VMはBIOS/GRUBで起動する。Xenのパラ仮想化パラメータはHVMモードでは効かない。GRUBの設定ファイルを直接書き換えたカスタムISOを作るしかない。

ここで問題になるのがISO作成ツールだ。genisoimagemkisofs)では--grub2-boot-infoオプションが使えない。GRUB2ブートに必要なフラグだ。xorriso一択になる。

genisoimageからxorrisoへ

mkisofsはISO 9660イメージを作成するツールとして長く使われてきた。そのフォークであるgenisoimageがDebianやRHEL系に同梱されている。だが、GRUB2のBIOSブートに必要な--grub2-boot-infoフラグをサポートしていない。xorrisoはGNU xorrisoプロジェクトが開発したISO操作ツールで、GRUB2を含むモダンなブート構成に完全に対応する。RHEL系のカスタムISOを作るなら、現時点ではxorriso一択だ。

xorrisoのコマンドは長い。でもこの方法でしかHVM VMにKickstartパラメータを渡せない。

bash
# ISO展開
xorriso -osirrox on -indev ${ISO_SRC} -extract / ${WORKDIR}/

# GRUB cfgにカーネルパラメータを追加
KERNEL_ARGS="console=tty0 console=ttyS0,115200n8 earlycon=uart8250,io,0x3F8,115200n8 inst.text inst.headless"
sed -i "s|inst.stage2=hd:LABEL=AlmaLinux quiet|inst.stage2=hd:LABEL=AlmaLinux ${KERNEL_ARGS}|g" \
  ${WORKDIR}/boot/grub2/grub.cfg

# ISO再作成(--grub2-mbr と --grub2-boot-info が必須)
xorriso -as mkisofs \
  -V "AlmaLinux" \
  --grub2-mbr --interval:local_fs:0s-15s:zero_mbrpt,zero_gpt:${ISO_SRC} \
  --protective-msdos-label \
  -partition_cyl_align off -partition_offset 0 \
  -partition_hd_cyl 92 -partition_sec_hd 32 \
  --boot-catalog-hide \
  -b images/eltorito.img -no-emul-boot -boot-load-size 4 \
  -boot-info-table --grub2-boot-info \
  -o ${ISO_DST} ${WORKDIR}/

罠3: メディアチェックで12時間止まる

カスタムISOで起動した。画面に何も出ない。5分待った。10分待った。進まない。

原因はgrub.cfgset default="1"だった。元のAlmaLinux ISOでは、メニューの0番が「Install」、1番が「Test this media & install」になっている。デフォルトが1——つまりメディアチェックが自動で走る。

xorrisoで再作成したISOにはチェックサム情報がない。メディアチェックは当然失敗する。そしてMedia check failed! System will halt in 12 hoursと表示されて、12時間待つ。

bash
# default を 0 に変更("Install" を自動選択)
sed -i 's/set default="1"/set default="0"/' ${WORKDIR}/boot/grub2/grub.cfg

一行の修正だ。これを忘れると12時間待ちぼうけになる。しかもシリアルコンソールを設定する前にこの罠を踏むと、メッセージすら見えない。ただ何も起きないまま時間だけが過ぎる。最も気づきにくいトラップだった。

罠4: シリアルコンソールが映らない

GUIなし環境ではxl consoleでシリアル接続するしかない。これが映らないとブラインド作業になる。罠3で12時間待つ羽目になったのも、そもそもコンソールが映っていなかったからだ。

シリアルコンソールには4つの設定が必要で、1つでも欠けると何も表示されない。

設定場所役割
serial --speed=115200 ... + terminal_input/output serialGRUB cfg 先頭GRUBメニューをシリアルに出力
earlycon=uart8250,io,0x3F8,115200n8カーネルパラメータカーネル初期段階の出力
inst.headlessカーネルパラメータAnacondaのVGA初期化を抑制
platform:hvm_serial=ptyXCP-ng VM設定dom0側のPTY割り当て
earlyconとは

Linuxカーネルの起動初期段階で、通常のコンソールドライバが初期化される前にシリアルポートへ出力するための仕組み。earlycon=uart8250,io,0x3F8,115200n8は「I/Oポート0x3F8にある8250互換UARTを115200bpsで使え」という指定。物理マシンのCOM1に相当する。Xen HVMではdom0側がUARTをエミュレートしており、この指定がないとカーネルがエミュレートされたUARTを正しく認識できない。

GRUBの設定はカスタムISOのgrub.cfg先頭に追加する。

nginx
serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1
terminal_input serial console
terminal_output serial console

VM側の設定はxeコマンドで。

bash
xe vm-param-set uuid=${VM_UUID} platform:hvm_serial=pty

earlyconがないとXen HVMのUARTエミュレーションをカーネルが初期化できない。GRUBメニューは出るのにカーネル起動後に何も出なくなる、という中途半端な状態になる。

罠5: AlmaLinuxのKickstartが世代間で互換でない

前の世代で動いていたKickstartをそのまま持っていった。パースエラーの嵐だった。

AlmaLinuxとは

CentOS 8のEOLを受けて2021年に誕生したRHELの1:1互換ディストリビューション。Red Hatが2020年にCentOS Streamへの転換を発表した(いわゆるCentOS終了事件)とき、CloudLinux社がフォークとして立ち上げた。Rocky Linuxと並んでCentOSの後継として広く採用されている。RHELのメジャーバージョンアップに伴い、Kickstartの構文にも破壊的変更が入る。

項目AlmaLinux 8(RHEL 8)AlmaLinux 9(RHEL 9)
EULAeula --agreed廃止。書くとエラー
ブートローダbootloader --location=mbrbootloader --boot-drive=xvda
NTPtimezone --ntpservers=...timesource --ntp-server=... に分離
パーティション/boot + swap + /+ biosboot 1MB(GPT+BIOSで必須)
network行継続network ... \(次行へ)行継続不可。1行で書く
biosbootパーティションとは

GPT(GUID Partition Table)ディスクからBIOSでブートするために必要な小さなパーティション。MBRの場合、GRUBのステージ1.5コードはMBRとパーティションの隙間(MBR gap)に書き込まれる。GPTではこの隙間が存在しないため、代わりにbiosbootパーティション(通常1MB)を用意してそこにGRUBのコアイメージを配置する。UEFI環境では不要だが、XCP-ngのHVM VMはBIOSブートなので必須になる。

GPTがデフォルトになったことで、BIOSブートにはbiosbootパーティションが必須になった。これがないとGRUBがインストールされず、OSが起動しない。

nginx
part biosboot --fstype=biosboot --size=1
part /boot --fstype=ext4 --size=1024
part swap  --size=4096
part /     --fstype=xfs --grow --size=1

networkコマンドの行継続が使えなくなったのも地味に痛い。静的IPだと1行が非常に長くなる。

nginx
network --bootproto=static --device=link --onboot=yes --activate --ip=192.168.1.200 --netmask=255.255.255.0 --gateway=192.168.1.1 --nameserver=8.8.8.8,1.1.1.1 --hostname=tmpl-alma10

読みにくいが、こう書くしかない。

最終形

IDCにDHCPはない。Kickstartにも静的IPを直書きする。テンプレート用に192.168.1.200を1つ予約して、すべてのインストールでこのIPを使う構成にした。KickstartファイルとGRUB設定を分離する方法もあるが、今回はカスタムISOにすべて埋め込んだ。ISOを1つ作れば、あとはxeコマンドだけでVMが立ち上がる。

最終的なKickstartの構成はこうなっている。

nginx
#--- 基本設定 ---
text                              # テキストモードインストール
firstboot --disable
reboot                            # 完了後に自動再起動

#--- ロケール ---
lang en_US.UTF-8
keyboard --xlayouts='jp'
timezone Asia/Tokyo --utc
timesource --ntp-server=ntp.nict.jp          # ← timezone から分離
timesource --ntp-server=ntp.jst.mfeed.ad.jp

#--- ネットワーク(テンプレート用固定IP、1行で書く) ---
network --bootproto=static --device=link --onboot=yes --activate --ip=192.168.1.200 --netmask=255.255.255.0 --gateway=192.168.1.1 --nameserver=8.8.8.8,1.1.1.1 --hostname=tmpl-alma10

#--- ディスク ---
ignoredisk --only-use=xvda
clearpart --all --initlabel --drives=xvda
zerombr
bootloader --boot-drive=xvda                 # ← --location=mbr は廃止

part biosboot --fstype=biosboot --size=1     # ← GPT+BIOSで必須
part /boot --fstype=ext4 --size=1024
part swap  --size=4096
part /     --fstype=xfs --grow --size=1

#--- ユーザー ---
rootpw --iscrypted $6$...
user --name=libraz --shell=/bin/bash ...

#--- パッケージ ---
%packages --ignoremissing
@minimal-environment
%end

#--- Post-install ---
%post --log=/root/ks-post.log
# SSH鍵配置、sudoers設定、sshd_config、シェル環境...
# 追加パッケージのdnf installはここではやらない
%end

クローン後にIPを変更する運用だ。Kickstartのネットワーク設定を毎回書き換えるのは手間なので、テンプレートを1つ作ってxe vm-cloneで複製する方が現実的だった。

5つの罠に共通すること

どれもエラーメッセージが出ない。CDが挿さらない。パラメータが無視される。メディアチェックが黙って走る。コンソールに何も映らない。構文が黙って変わっている。

Kickstartの設計思想は「人間の介入なしに完走する」だ。だから途中で止まって聞いてこない。それは正しい設計だ。ただし、設定を間違えたときも聞いてこない。

シリアルコンソールを最初に確保すること。これがすべての起点になる。コンソールさえ映れば、何が起きているかはわかる。何が起きているかがわかれば、直せる。