iPXE、dnsmasq、Nginxを活用したOS一括デプロイ向け自動ネットワークブートサーバーの構築

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

課題:深夜2時のベアメタル障害を乗り切る

日曜日の午前2時15分、ストレージ障害により1ラック分・計32台のコンピュートノードのブートドライブが一斉に消失したと想像してみてください。データセンターのリモートハンド(現地運用担当)が新品のNVMeドライブへ交換を完了させたのが30分前。ここからが本当の正念場です。32台のベアメタルサーバーが何もないBIOS画面のまま、OSのインストールを待っています。

USBメモリを何本も抱えてデータセンターの通路を往復していては、どれだけ時間があっても足りません。かといって従来のPXE構成も役に立ちません。従来のネットワークブートは、数百MBにおよぶカーネルやinitrdイメージをUDP経由で転送するTFTP(Trivial File Transfer Protocol)に全面的に依存しています。単一マシンの起動であれば問題ありませんが、20台以上のホストが一斉にアクセスすると、UDPのパケットロスが急増します。タイムアウトが頻発し、転送は停滞し、40分以上待たされた末に最終的に失敗してしまいます。

このボトルネックを根本的に解消するのが「2段階ブートアーキテクチャ」です。まず軽量なサービスがネットワーク設定を割り当て、ごく小さな中間ブートローダーを提供します。次に、そのブートローダーがプロトコルを切り替え、サイズ容量の大きいISOやカーネルイメージを通常のHTTP経由で取得します。DHCPとアーキテクチャ判定を担うdnsmasqと、HTTP配信に優れたNginxを組み合わせることで、サーバーラック全体のプロビジョニングを5分未満で完了できるようになります。

基本概念:従来型PXEの限界とiPXEによる解決策

設定ファイルの編集に入る前に、ネットワークファームウェア、DHCPオプション、および転送プロトコルがブート処理中にどのように連携しているかを把握しておきましょう。

TFTPのボトルネック vs HTTPストリーミング

マザーボードのNICに書き込まれている標準的なPXE ROMは、意図的に最小限の機能しか持たされていません。扱えるのは基本的なDHCPと初歩的なTFTPのみです。TFTPは1ブロック(512バイト)送信するごとにACKパケットの受信を待ってから次のブロックを送る、厳密な同期確認応答モデルを採用しています。たとえ10Gbpsのスイッチ環境であっても、ラウンドトリップ遅延の影響により、単一ストリームのTFTPスループットはおよそ1.5〜2.5MB/sに制限されます。この速度では、1.8GBのUbuntu 24.04 live-serverイメージを読み込むだけでノードあたり20〜30分もかかってしまいます。

iPXEは、メモリ上でブートローダーを直接アップグレードすることでこの問題を解決します。HTTP、HTTPS、DNS、iSCSIをサポートする本格的なTCP/IPスタックを備えたオープンソースのネットワークブートローダーです。ネイティブのPXE ROMは、TFTP経由でわずか数百KB(UEFI向けなら約350KB、BIOS向けなら約65KB)の小さなiPXEバイナリを1秒足らずでダウンロードします。起動したiPXEはNICの制御を引き継ぎ、Nginxから標準のHTTP経由でカーネルとRAMディスクを取得するため、1Gbpsや10Gbpsのネットワーク帯域を余すところなく活用できます。

レガシーBIOSとUEFIブートモード

本番環境では、旧来のレガシーBIOSホストと最新の64ビットUEFIハードウェアが混在しているケースが多々あります。単一のファイル名をハードコードして配信すると、インベントリの半数で起動に失敗します。そのため、ブートサーバーはDHCP Option 93(Client System Architecture Type、RFC 4578)を識別し、適切なバイナリを供給する必要があります。

  • undionly.kpxe: レガシーBIOSマシン用(アーキテクチャ 00:00)。標準のUNDIドライバコールを利用。
  • ipxe.efi: 64ビットUEFIシステム用(アーキテクチャ 00:07、00:09、およびHTTPブート用の00:10)。

ブートコーディネーターとしてのdnsmasqの役割

isc-dhcp-serverとtftpd-hpaを別々に組み合わせて管理する代わりに、dnsmasqを使用すれば、DNS、柔軟なDHCPタギング、および内蔵TFTPエンジンを1つの軽量なデーモンに統合できます。ベンダークラス識別子を動的に解析できるため、「初回ブート時」か「iPXEユーザークラスとして再接続してきた時」かに応じて条件分岐ブートルールをスマートに記述できます。

実践演習:iPXEデプロイエンジンの構築

今回の環境はUbuntu Server 22.04または24.04 LTSを使用し、プロビジョニング用の専用インターフェース(eth1)にデプロイ用サブネット192.168.10.0/24内の静的IP 192.168.10.10/24を割り当てて構築します。

ステップ1:必要なパッケージのインストール

パッケージリポジトリから、dnsmasq、Nginx、およびビルド済みiPXEバイナリをインストールします。

sudo apt update
sudo apt install -y dnsmasq nginx ipxe syslinux pxelinux

ステップ2:TFTPディレクトリ構造の準備

TFTPのルートディレクトリを作成し、標準的なBIOS向けおよびUEFI向けのiPXEバイナリをコピーします。

sudo mkdir -p /srv/tftp

# レガシーBIOS向けiPXEイメージをコピー
sudo cp /usr/lib/ipxe/undionly.kpxe /srv/tftp/

# UEFI 64ビット向けiPXEバイナリをコピー
sudo cp /usr/lib/ipxe/ipxe.efi /srv/tftp/

sudo chown -R nobody:nogroup /srv/tftp
sudo chmod -R 755 /srv/tftp

ステップ3:動的チェーンロード用のdnsmasq設定

専用の設定ファイルを/etc/dnsmasq.d/ipxe.confに作成します。port=0のディレクティブに注目してください。これによりDNSリスナーが無効化され、Ubuntuローカルのsystemd-resolvedスタブリスナー(ポート53)との競合を防止します。

sudo bash -c 'cat > /etc/dnsmasq.d/ipxe.conf' << "EOF"
# プロビジョニング用インターフェースにのみバインド
interface=eth1
bind-interfaces
port=0

# DHCP IPプールとゲートウェイの設定
dhcp-range=192.168.10.100,192.168.10.200,255.255.255.0,12h
dhcp-option=option:router,192.168.10.1
dhcp-option=option:dns-server,1.1.1.1,8.8.8.8

# 内蔵TFTPの設定
enable-tftp
tftp-root=/srv/tftp

# クライアントアーキテクチャの判定 (Option 93)
dhcp-match=set:bios,option:client-arch,0
dhcp-match=set:efi-x86_64,option:client-arch,7
dhcp-match=set:efi-x86_64,option:client-arch,9

# クライアントが既にiPXEを実行中か検出
dhcp-userclass=set:ipxe,iPXE

# ステージ1: クライアントがネイティブPXEで起動 -> TFTP経由でiPXEローダーを提供
dhcp-boot=tag:!ipxe,tag:bios,undionly.kpxe
dhcp-boot=tag:!ipxe,tag:efi-x86_64,ipxe.efi

# ステージ2: クライアントがiPXEで再接続 -> HTTPブートスクリプトを配信
dhcp-boot=tag:ipxe,http://192.168.10.10/boot.ipxe
EOF

構文を検証し、サービスを再起動します。

sudo dnsmasq --test
sudo systemctl restart dnsmasq
sudo systemctl status dnsmasq

ステップ4:ブートアセット配信用Nginxの設定

ISOイメージ、抽出したカーネル、cloud-init自動化ファイル用のディレクトリを作成します。

sudo mkdir -p /var/www/netboot/os/ubuntu24
sudo mkdir -p /var/www/netboot/scripts

最適化したNginxのサーバーブロックを/etc/nginx/sites-available/netbootに設定します。

server {
    listen 80;
    server_name 192.168.10.10;
    root /var/www/netboot;

    access_log /var/log/nginx/netboot_access.log;
    error_log /var/log/nginx/netboot_error.log;

    location / {
        autoindex on;
        sendfile on;
        sendfile_max_chunk 2m;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
    }
}

サイトを有効化し、デフォルト設定を削除してNginxをリロードします。

sudo ln -s /etc/nginx/sites-available/netboot /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

ステップ5:OSカーネルの抽出とiPXEメニューの作成

Ubuntu 24.04 ServerのISOをマウントします。カーネル(vmlinuz)と初期RAMディスク(initrd)を抽出し、SubiquityインストーラーがリモートマウントできるようISOイメージ全体もWebルートに配置します。

sudo mount -o loop /root/ubuntu-24.04-live-server-amd64.iso /mnt
sudo cp /mnt/casper/vmlinuz /var/www/netboot/os/ubuntu24/
sudo cp /mnt/casper/initrd /var/www/netboot/os/ubuntu24/
sudo cp /root/ubuntu-24.04-live-server-amd64.iso /var/www/netboot/os/ubuntu24/
sudo umount /mnt

Ubuntuインストーラーのcloud-initチェックでエラーが発生しないよう、/var/www/netboot/scripts/内にダミーのメタデータファイルを作成します。

sudo touch /var/www/netboot/scripts/meta-data
sudo touch /var/www/netboot/scripts/user-data

続いて、/var/www/netboot/boot.ipxeにブートスクリプトを記述します。カーネル行のinitrd=initrd引数は明示的に指定する必要があります。これにより、EFIスタブローダーがRAMディスクを適切に認識できるようになります。

sudo bash -c 'cat > /var/www/netboot/boot.ipxe' << "EOF"
#!ipxe

set server_ip 192.168.10.10
set base_url http://${server_ip}
set menu-timeout 10000

:start
menu ネットワークブートシステム - ベアメタルプロビジョニング
item --gap --             ---------------- オペレーティングシステム ----------------
item ubuntu24             Ubuntu Server 24.04 LTS のインストール (自動化)
item --gap --             ---------------- リカバリ & ツール -----------------
item shell                iPXE対話型シェルを起動
item reboot               ホストを再起動
item exit                 ローカルハードドライブから起動
choose --timeout ${menu-timeout} --default ubuntu24 target && goto ${target}

:ubuntu24
echo HTTP経由でUbuntu 24.04のカーネルとinitrdを取得中...
kernel ${base_url}/os/ubuntu24/vmlinuz initrd=initrd ip=dhcp url=${base_url}/os/ubuntu24/ubuntu-24.04-live-server-amd64.iso autoinstall ds=nocloud-net;s=${base_url}/scripts/ cloud-config-url=/dev/null
initrd ${base_url}/os/ubuntu24/initrd
echo Linuxカーネルを起動中...
boot

:shell
shell

:reboot
reboot

:exit
exit
EOF

動作確認とリアルタイムトラブルシューティング

PXEブートを有効にしたクライアントノードを起動します。サーバー側でターミナルウィンドウを2つ開き、2段階ブートの動作をリアルタイムで確認します。

1つ目のターミナルでdnsmasqのログをストリーミングします。

sudo journalctl -u dnsmasq -f

クライアントのMACアドレスから、2回連続してDHCPリクエストが届く様子が確認できるはずです。

  1. ユーザークラスのない初回DHCPDISCOVER:dnsmasqがOption 93を照合し、TFTP経由でipxe.efiまたはundionly.kpxeを提供します。
  2. User-Class: iPXEを含む2回目のDHCPDISCOVER:dnsmasqがタグを認識し、http://192.168.10.10/boot.ipxeを引き渡します。

2つ目のターミナルで、NginxがOSファイルを配信する様子を監視します。

sudo tail -f /var/log/nginx/netboot_access.log

GET /os/ubuntu24/vmlinuzおよびGET /os/ubuntu24/initrdに対して200 OKが返されるのが確認できます。標準的な1Gbpsリンクであれば、90MBのinitrdの取得は1秒未満で完了します。従来のTFTPでは2分近くかかっていた処理です。

まとめ:本番運用に耐えうる自動化基盤

純粋なTFTPブートからiPXEとHTTPを組み合わせたパイプラインへ移行することで、ベアメタルのプロビジョニングは時間のかかる重労働から、迅速かつ信頼性の高いワークフローへと生まれ変わります。dnsmasqがレガシーBIOSと最新UEFIクライアントの違いを手作業なしで吸収し、Nginxが大きなファイル転送をワイヤースピードで処理します。

この基盤が整えば、iPXEメニューをカスタムのcloud-init設定、自動化されたKickstartレシピ、あるいはハードウェアインベントリ収集スクリプトと直接連携させることも容易です。電源とネットワークを接続するだけでノードが自動セットアップされる、真のゼロタッチプロビジョニングパイプラインが実現します。

Share: