DockerでNetboot.xyzを構築:OSインストールやレスキューツール向けの自宅ラボPXEブートサーバー

HomeLab tutorial - IT technology blog
HomeLab tutorial - IT technology blog

引き出しに溜まった大量のUSBメモリを断捨離する

多くの自宅ラボ(HomeLab)には、使わなくなったUSBメモリの墓場が存在します。MemTest86用、Clonezilla用、古いUbuntuのISOが入った使い古しの8GBメモリが2本、そして中身不明の2021年のUSBメモリなどが転がっているはずです。Proxmoxホストがエマージェンシーシェルに落ちたり、ミニPCのブートローダーが破損したりしたとき、復旧作業は一苦労となります。

まずは引き出しを漁って動作するメディアを探し出し、BalenaEtcherを使って遅いUSB 2.0で4GBのイメージ書き込みを10分間待ち、最後にラックの裏側に潜り込んでポートに挿す必要があります。

さらに、安価なUSBメモリは予告なく故障します。数十回書き込んだだけで劣化し、Linuxカーネルを展開している最中にI/Oエラーを吐くことも珍しくありません。3〜4台のノードで構成されるクラスタを管理している場合、物理メディアの管理は単なる時間の無駄になってしまいます。

PXEブートでハードウェアとローカルメディアを切り離す仕組み

サーバー向けマザーボードや近年のデスクトップPC用NICは、起動に物理フラッシュストレージを必要としません。ネットワークブートは1999年のIntel PXE仕様以来標準化されており、UEFIやレガシーBIOSチップにも標準で組み込まれています。ローカルのフラッシュコントローラーからブロックを読み込む代わりに、NICはプライマリOSがロードされる前にLAN経由で実行可能ブートローダーを要求します。

PXEハンドシェイクの4ステップ

  1. DHCP Discovery: クライアントマシンの電源が入り、NICが初期化されると、PXE設定タグとともにIPアドレスを要求するDHCPリクエストをブロードキャストします。
  2. Next-Serverの解決: DHCPサーバーがIPアドレス(例: 192.168.1.120)を割り当て、DHCPオプション66(TFTPサーバーのIP)およびオプション67(ブートローダーのファイル名)を返します。
  3. ブートストラップのダウンロード: クライアントはUDPポート69経由でTFTPサービスにアクセスし、netboot.xyz.efiなどの軽量なブートストラッププログラム(約1.5MB)を取得してメモリ上で実行します。
  4. HTTPによるアセット引き継ぎ: ブートローダーは低速なTFTPから標準的なHTTPへ切り替えます。これにより、1Gbpsや2.5Gbpsのネットワーク帯域をフル活用して、Linuxカーネル、initramfs、ライブsquashfsペイロードをワイヤースピードでダウンロードします。

従来、この環境を構築するにはtftpd-hpa、Syslinux、ISC-DHCP、Nginxなどを個別にセットアップして管理する必要がありました。Netboot.xyzはこれらの依存関係をコミュニティ主導の単一のiPXEスイートにまとめています。さらにDockerコンテナ化することで、5分もかからずにデプロイ可能です。

DockerによるNetboot.xyzのデプロイ

Ubuntu 24.04やDebian 12などが稼働するストレージサーバー、ミニPC、管理用VMなど、24時間365日稼働しているマシン上でこのコンテナを実行します。フットプリントは非常に小さく、アイドル時のメモリ消費は150MB未満です。

ステップ 1:ディレクトリの準備とComposeファイルの作成

ブート設定とローカルキャッシュ用ISOアセットを保持するため、ホスト上に専用ディレクトリを作成します。

sudo mkdir -p /opt/netbootxyz/config /opt/netbootxyz/assets
sudo chown -R 1000:1000 /opt/netbootxyz
cd /opt/netbootxyz

ここでは必ずホストネットワーク(host networking)を使用してください。PXEはTFTP(UDP 69)とDHCPブロードキャストリレーに依存しており、NATポートフォワーディングを行う標準的なDockerブリッジネットワークでは正常に通信できません。docker-compose.ymlファイルを作成します。

cat <<'EOF' > docker-compose.yml
services:
  netbootxyz:
    image: ghcr.io/netbootxyz/netbootxyz:latest
    container_name: netbootxyz
    network_mode: host
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
      - PORT_RANGE=30000:30010
      - SUBFOLDER=/
    volumes:
      - /opt/netbootxyz/config:/config
      - /opt/netbootxyz/assets:/assets
    restart: unless-stopped
EOF

デタッチモードでスタックを起動します。

docker compose up -d
docker compose logs -f netbootxyz

ホスト上で3つの内部サービスがすべてリッスンしていることを確認します。UDP 69(TFTP)、TCP 80(HTTPアセットサーバー)、TCP 3000(Web設定UI)が表示されるはずです。

ss -tuln | grep -E ':(69|80|3000)'

ステップ 2:ルーターの参照先をNetboot.xyzに向ける

DHCPサーバーがPXEエンドポイントを通知するまで、ネットワーク上のクライアントはブートできません。お使いのゲートウェイ(pfSense、OPNsense、OpenWrt、UniFi Dream Machineなど)の管理画面にログインします。

Dockerホストが静的IP 192.168.1.50 を使用していると仮定し、DHCPサーバーで以下のオプションを設定します。

  • Next Server / Boot Server(オプション66): 192.168.1.50
  • Bootfile Name(オプション67): netboot.xyz.efi(最新の64ビットUEFIマシンの場合)または netboot.xyz.kpxe(レガシーBIOSの場合)

ルーターでDnsmasq(OpenWrt、DD-WRT、Pi-holeなどで一般的)が動作している場合は、UEFIとBIOSのどちらか一方を選ぶ必要はありません。クライアントのアーキテクチャフラグに応じて動的に振り分けることができます。

# クライアントのアーキテクチャを判定 (オプション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

# 適切なバイナリへクライアントを誘導
dhcp-boot=tag:bios,netboot.xyz.kpxe,,192.168.1.50
dhcp-boot=tag:efi-x86_64,netboot.xyz.efi,,192.168.1.50

設定を適用し、DHCPサービスをリロードします。

ステップ 3:Web UIとカスタムメニューの設定

ブラウザで http://192.168.1.50:3000 を開きます。ダッシュボードにメニューのバージョンが表示され、GitHubからアップストリームのアセット更新を直接取得できます。

デフォルトでは、Netboot.xyzはパブリックなインターネットミラーからインストーラーイメージを取得します。ただし、ローカルのDockerサーバー上にリカバリイメージを配置することで、帯域を節約し起動を大幅に高速化できます。たとえば、1.2GBのGPartedライブイメージならギガビットLAN経由で約10秒でロードされます。カーネルとinitrdを /opt/netbootxyz/assets/gparted/ にダウンロードし、/opt/netbootxyz/config/menus/custom.ipxe にカスタムエントリを追加します。

cat <<'EOF' > /opt/netbootxyz/config/menus/custom.ipxe
#!ipxe

:custom
clear custom_choice
menu ローカルHomeLabレスキューユーティリティ
item --gap -- レスキューツール
item gparted GParted Live 1.6 (ローカルHTTP)
item shell iPXEシェルへ移行
item return メインメニューに戻る
choose custom_choice || goto custom_exit
eval ${custom_choice}
goto custom

:gparted
echo HTTP経由でGParted Liveをロード中...
set base-url http://192.168.1.50/assets/gparted
kernel ${base-url}/vmlinuz boot=live config components union=overlay noswap net.ifnames=0 nosplash fetch=${base-url}/filesystem.squashfs
initrd ${base-url}/initrd.img
boot || goto custom

:shell
shell
goto custom

:return
chain --autofree boot.cfg ||
exit

:custom_exit
exit
EOF

ステップ 4:ベアメタルノードやVMをブートする

ターゲットノード(Dell OptiPlex、Lenovo Tiny、または新規のProxmox VMなど)の電源を入れます。POST画面で F12(マザーボードによっては F11)を押してワンタイムブートメニューを開き、UEFI Network Boot を選択します。

3秒以内にクライアントがIPアドレスを取得し、TFTP経由で netboot.xyz.efi をロードして、iPXEテキストメニューが表示されます。USBメモリを1本も抜き差しすることなく、Debian 12のインストール、Proxmox VEノードのプロビジョニング、DBANによるディスク消去、Clonezillaによるパーティション複製などが実行可能です。

5台の物理ホストを備えたテスト環境では、このセットアップによりマシンの再プロビジョニングにかかる時間が1時間の手作業からわずか2分の再起動へと短縮されました。書き込み失敗や古いISOファイルに悩まされることも、サーバーラックへ足を運ぶ必要もなくなります。

自宅ラボ運用の重要ポイント

ネットワークブートを活用すれば、OSのインストールはオンデマンドなサービスへと進化します。DockerコンテナでNetboot.xyzを動かし、ルーターで2つのDHCPタグを設定するだけで、スイッチに接続されたあらゆるマシンが瞬時にOSインストールやレスキュー環境のブートを行えるようになります。転送速度を最大化するために頻繁に使うsquashfsファイルはローカルの /assets ボリュームに保持し、ポート80の競合がないことを確認したうえで、USBメモリの詰まった引き出しとはすっぱりお別れしましょう。

Share: