電脳世界のケーキ屋さん

考えの甘い甘党エンジニアがいろいろ書くブログ

Linux PXE 実習(環境導入編)

はじめに

年末年始仕事と長期休暇, 暇を持て余していたため, PXEの利用について勉強をした. 本稿はそのノートである.

本稿ではPXEの動作概要と, 基礎環境の構築について述べる.

概論

PXE(Preboot eXecution Environment) はネットワークブート用のブート環境の1つである.

実際には DHCP と TFTP を利用することで, OSのような高水準なプログラムが動作していない環境に対して NBP(Network Bootstrap Program)と呼ばれるブートストラッププログラムを注入する動作がメインと言える.

ぶっちゃけその後の, 注入された NBP がどう動作するかは PXE の関するところではない.
NBP は黙々と指定された手段で OS のプロビジョニングをしてもいいし, 追加でリソースをダウンロードして, ユーザに対して選択肢を示すようなことをしても良い.

動作の流れとしては, おおまかに以下の通り.

  1. PXEクライアントが DHCPサーバに対してアドレスを要求する
  2. PXEシステムのDHCPサーバがIPのリースに併せて, 以下の情報を提示
  3. PXEシステム用のTFTPサーバのアドレス
  4. TFTPサーバに対して何という名前のNBP実行ファイルを要求すれば良いか
  5. PXEクライアントが得た情報から NBP をダウンロードして実行する
  6. NBPが動作してごにょごにょ

そのため, PXEシステムを構築する場合は, NBP をクライアントに注入するまでの DHCP と TFTP を用いた根幹部分の構築に加えて, NBP にどういった挙動を行なわせるかを設定する必要がある.
PXEの構築よりこちらの設定の方が面倒.
また NBP の挙動によっては FTPサーバや HTTPサーバ, NFSサーバなども構築する必要がある.

今回は NBP として, SYSLINUXプロジェクトの PXELINUX を利用する.
なお, Windows なら WDSNBP.COM というものが NBP となる模様.

環境導入手順

インストールのおおまかな流れは以下のようになる.

  1. OS基本設定
  2. DHCPサーバ設定
  3. TFTPサーバ設定
  4. HTTPサーバ設定(オプション)
  5. syslinux関連ファイルの展開
  6. pxelinux.cfg 設定ファイルの作成(別稿記載)

サーバについて

本稿では PXEサーバとして以下の構成の Linux サーバを利用する.

  • KVMマシン(RAM 256MiB/2CPU/x86_64)
  • Fedora 31
  • syslinux 6.03
  • isc-dhcpd-4.3.6
  • tftp-hpa 5.2
  • Nginx 1.16.1
  • SELinux は permissive 又は disabled

ストレージについては NFS で準備しているため, ここでは言及しないが, ある程度余裕がある方が望ましい(OS領域を除いて8GBくらいあると試す分には問題ない).

前提として, PXEサーバのOSのインストールとネットワーク設定は完了しているものとする.
PXEクライアントとは同一セグメントで接続されているものと仮定し, DHCPリレーエージェントなどの介在は想定していない.

実はこの環境を Ansible で構築できるようにプレイブックを作成したのだが, それについては別に記載する(予定).

追加パッケージのインストール

PXE環境構築に必要なソフトウェアをインストールする.

# dnf update
# dnf install tftp-server dhcp-server nginx

Firewalldの設定

tftp,http,dhcp のポートへの受信を許可する.
Fedora だとデフォルトのゾーンは FedoraServer らしい

# firewall-cmd --add-service=tftp --zone=FedoraServer --permanent
# firewall-cmd --add-service=http --zone=FedoraServer --permanent
# firewall-cmd --add-service=dhcp --zone=FedoraServer --permanent
# firewall-cmd --reload

DHCPサーバの設定

Fedoraの isc-dhcpd の設定ファイルは /etc/dhcp/dhcpd.conf である.
以下設定と仮定して, 設定ファイルを追記する.

  • サブネットは 192.168.4.0/24
  • PXEサーバのアドレスは 192.168.4.28
  • リースするアドレスは 192.168.4.33 - 192.168.4.64
  • PXEクライアントのアーキテクチャによってNBPの指定を変更する
    • LegacyBIOS(x86_64) の場合は /bios/lpxelinux.0
    • UEFI64 の場合は /efi64/syslinux.efi

この設定ファイルでは PXEクライアントは LegacyBIOS か UEFI で動作する x86_64マシンだろうと想定している.
必要であれば architecture-type をごにょごにょすることで ARM プロセッサとかにも対応できる(はず)

dhcpd の設定ファイルはそれぞれの項目の末尾にセミコロンが必要なことに注意.
忘れていると当たり前だけどサービスの起動に失敗する.

一応 dhcpd -t -cf /etc/dhcp/dhcpd.conf で文法チェックをできる.

#~~~~~~~~~~~~~~~~~~~~~~~~
# /etc/dhcp/dhcpd.conf
#~~~~~~~~~~~~~~~~~~~~~~~~
option architecture-type code 93 = unsigned integer 16;
option space pxelinux;
option pxelinux.magic      code 208 = string;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;

local-address 192.168.4.28;

subnet 192.168.4.0 netmask 255.255.255.0 {
  allow booting;
  allow bootp;

  option subnet-mask 255.255.255.0;
  next-server 192.168.4.28;

  range 192.168.4.33 192.168.4.64;
  default-lease-time 3600;
  max-lease-time 21600;

  site-option-space "pxelinux";
  option pxelinux.magic f1:00:74:7e;
  if exists dhcp-parameter-request-list {
    option dhcp-parameter-request-list = concat(option dhcp-parameter-request-list,d0,d1,d2,d3);
  }

  if option architecture-type = 00:00 {
    # Intel x86PC (legacy bios)
    option pxelinux.pathprefix "/bios/";
    filename "bios/lpxelinux.0";
  } elsif option architecture-type = 00:09 {
    # EFIx64
    filename "efi64/syslinux.efi";
    option pxelinux.pathprefix "/efi64/";
  } elsif option architecture-type = 00:07 {
    # EFIx64
    filename "efi64/syslinux.efi";
    option pxelinux.pathprefix "/efi64/";
  }
}

設定を投入したらサービスの起動と有効化を行う.

# systemctl enable dhcpd
# systemctl start dhcpd
# systemctl status dhcpd
  // Active(running) であることを確認

動作については journalctl で確認できる.

# journalctl -u dhcpd.service
Jan 04 13:03:34 vPXE-Stack dhcpd[14687]: DHCPREQUEST for 192.168.4.33 from 52:54:00:XX:XX:XX via ens4
Jan 04 13:03:34 vPXE-Stack dhcpd[14687]: DHCPACK on 192.168.4.33 to 52:54:00:XX:XX:XX via ens4
※MACは伏せ字にしている

TFTPサーバの設定

TFTPサーバはセキュアにしたいとかルートを変えたいとかの理由がなければ設定をする必要がない.
サービスを有効化すれば動き出すのでお手軽.

なおこの環境では /var/lib/tftpboot が TFTP サーバのルートである.

個人的にはここで適当なクライアントからファイルのダウンロードができることを確認しておくことを推奨する.
PXEクライアントだと何がどうなっているのか分かりづらい.

なお, FedoraServer の tftpd は systemd ソケットユニットを使って動作している.
例えば待ち受けるアドレスを指定したい場合はユニットファイルを編集することで実現できる.

# /etc/systemd/system/tftp.socket
[Unit]
Description=Tftp Server Activation Socket
After=network-online.target

[Socket]
ListenDatagram=192.168.4.28:69

[Install]
WantedBy=sockets.target
WantedBy=network-online.target

サービスについては tftp.socket を有効化しておく. tftp.service については Inactive な状態で問題ない.

# systemctl daemon-reload
# systemctl enable tftp.socket
# systemctl start tftp.socket

FedoraServer の tftpd はデフォルトでアクセスログを journal に流してくれるので, 動作確認がしやすい

# journalctl -u tftp.service
...
Feb 15 00:05:23 vPXE-Stack systemd[1]: Started Tftp Server.
Feb 15 00:07:14 vPXE-Stack in.tftpd[1063]: Client 192.168.4.3 finished /bios/lpxelinux.0

HTTPサーバの設定

今回はHTTPサーバとして Nginx を利用する.
必要最低限のコンテンツサーバとしての動作しかさせないので, Apache でも問題はない.

インストールができたら /etc/nginx/nginx.conf を編集し, var/lib/tftpboot をルートに動作するように設定する.

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events {
        worker_connections 1024;
}
http {
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
        '$status $body_bytes_sent "$http_referer" '
        '"$http_user_agent" "$http_x_forwarded_for"';
        access_log  /var/log/nginx/access.log  main;
        server {
                listen       192.168.4.28:80;
                root         /var/lib/tftpboot/;
        }
}

Nginx の設定ファイルについては nginx -t -c /etc/nginx/nginx.conf で文法チェックを行える.
問題なければサービスを有効化しておく.

# systemctl enable nginx.service
# systemctl start nginx.service

SYSLINUX の展開

SYSLINUXプロジェクトのページから tarball をダウンロードする.
実は dnf リポジトリにもあるといえばあるのだが, ファイルが揃ってないので今回は利用しない.

https://wiki.syslinux.org/wiki/index.php

ダウンロードについては少し分かりづらいが kernel.org から出来る.

# cd /tmp/
# wget https://mirrors.edge.kernel.org/pub/linux/utils/boot/syslinux/syslinux-6.03.tar.xz
# tar xvf syslinux-6.03.tar.xz

tarball の中のファイルを TFTP で取得できる場所にコピーする.

じつは TFTP のディレクトリ構造をどうするのがベストかというのはあまり決まっていない模様.
今回は x86_64 マシンについて LegacyBIOS と UEFI の両方に対応したいという要件が個人的にあったので, 以下のようなディレクトリ構成を生成している.

/var/lib/tftpboot/
 |--- bios/
 | |--- ldlinux.c32
 | |--- libutil.c32
 | |--- lpxelinux.0
 | |--- menu.c32
 | |--- pxelinux.cfg/
 |   |--- default
 |--- efi64/
 | |--- ldlinux.e64
 | |--- libutil.c32
 | |--- menu.c32
 | |--- syslinux.efi
 | |--- pxelinux.cfg/
 |   |--- default
 |--- image
   |--- OSイメージ周り(省略)

コマンドだと以下の通り.

# mkdir -p /var/lib/tftpboot/{bios,efi64}/pxelinux.cfg
# cp syslinux-6.03/bios/core/lpxelinux.0 /var/lib/tftpboot/bios/lpxelinux.0
# cp syslinux-6.03/bios/com32/menu/menu.c32 /var/lib/tftpboot/bios/menu.c32
# cp syslinux-6.03/bios/com32/libutil/libutil.c32 /var/lib/tftpboot/bios/libutil.c32
# cp syslinux-6.03/bios/com32/elflink/ldlinux/ldlinux.c32 /var/lib/tftpboot/bios/ldlinux.c32
# cp syslinux-6.03/efi64/efi/syslinux.efi /var/lib/tftpboot/efi64/syslinux.efi
# cp syslinux-6.03/efi64/efi/com32/menu/menu.c32 /var/lib/tftpboot/efi64/menu.c32
# cp syslinux-6.04/efi64/efi/com32/libutil/libutil.c32 /var/lib/tftpboot/efi64/libutil.c32
# cp syslinux-6.03/efi64/efi/com32/elflink/ldlinux/ldlinux.e64 /var/lib/tftpboot/efi64/ldlinux.e64
# touch /var/lib/tftpboot/{bios,efi64}/pxelinux.cfg/default

dhcpd.confoption pxelinux.pathprefix "/bios/"; という設定により, PXEクライアントが TFTP でファイルをリクエストする際にプレフィックスを付けてくれるようになる.
これで LegacyBIOS と UEFI で同じファイル名でもディレクトリを分けて別のファイルを利用できる.

SYSLINUX で使ったファイルについては以下の通り.

ファイル名 補足
pxelinux.0 PXELINUX の NBPファイル(Legacy BIOS用)
lpxelinux.0 pxelinux.0 の強化版(こなみ)
ldlinux.c32 pxelinux.0 の依存ファイル
menu.c32 メニュー画面を作るのに必要なファイル
libutil.c32 menu.c32 の依存ファイル
syslinux.efi UEFI64用のNBPファイル
ldlinux.e64 syslinux.efi の依存ファイル

NBPの設定(pxelinux.cfg/default の編集)

今回はデフォルトではPXEブート時にメニューを表示するように動作させる.
bios/pxelinux.cfg/default, efi64/pxelinux.cfg/default はそれぞれ以下のように記述する.
bios と efi64 で微妙に先頭の記述が異なるので注意すること.
そこ以外は基本的に同じ設定を使い回せる.

bios/pxelinux.cfg/default

DEFAULT menu.c32

LABEL Menu-01
 MENU LABEL Menu-01Label
 KERNEL <vmlinuz-file path>
 APPEND <kernel-option>

LABEL Menu-02
 MENU LABEL Menu-02Label
 KERNEL <vmlinuz-file path>
 APPEND <kernel-option>
...

efi64/pxelinux.cfg/default

DEFAULT menu.c32

LABEL Menu-01
 MENU LABEL Menu-01Label
 KERNEL <vmlinuz-file path>
 APPEND <kernel-option>

LABEL Menu-02
 MENU LABEL Menu-02Label
 KERNEL <vmlinuz-file path>
 APPEND <kernel-option>
...

MENU LABEL に設定した値が実際のメニューに表示される.

KERNEL に vmlinuz のようなカーネルファイルのパスを指定する.
指定の方法には幾つか方法があり, TFTPやFTP, HTTP などを使うことができる.

APPEND にはカーネル引数を指定する.
通常は初期RAMイメージ(initrdやinitramfs)の設定が必要になる.

具体的な pxelinux.cfg の設定については別記事に記載する.

備考

pxelinux.0 と lpxelinux.0 の違い

lpxelinux.0 は pxelinux.0 より新しいバイナリである.
SYSLINUX の 5.10 から実装されたらしい.

大きな相違点としては, ファイルの取得に HTTP や FTP などを使えることにある.
従来の pxelinux.0 では TFTP しか使えず, カーネルや初期RAMファイルが大きい場合, 転送に時間がかかる問題があり, その点を改善したものと言える.

そのため, NBP として lpxelinux.0 を指定した場合は, 以下のようにすることで HTTPを利用してカーネルと初期RAMファイルをダウンロードすることができる.

# /pxelinux.cfg/default
DEFAULT menu.c32

LABEL Fedora
 MENU LABEL Installer-Fedora
 KERNEL http://192.168.4.27/images/fedora/vmlinuz
 APPEND initrd=http://192.168.4.27/images/fedora/initrd.img ip=dhcp inst.repo=http://192.168.16.27/images/fedora/root/

なお, この TFTP しか使えない問題の解決については gPXE/iPXE を用いるという方法もある.
gPXE/iPXE は PXEクライアントで動作する PXEプログラム の強化版だと思って良い.
gPXE は開発が終了して iPXE が引き継ぎている形のようなので, 今後見るならば iPXE となるだろう.

PXEクライアントが iPXE で動作している場合は, lpxelinux.0 を使わず, pxelinux.0 でも HTTP や FTP を利用することができる.

iPXE が動いている場合は起動画面で iPXE... と分かりやすく出力されるはず.
困ったら lpxelinux.0 を渡す形で良いかと個人的には判断している.

initrd の中身

何らかの理由で初期RAMイメージの中身を見たくなることもあると思われる.
FedoraCentOS, RHEL の場合は dracut ユーティリティが入っていれば操作ができる.

2020年現在の初期RAMイメージは CPIO形式でアーカイブ化され, gz や xz 辺りで圧縮されたイメージとなっている.
なので解凍すればそのまま中身を確認することができる.
圧縮形式は file コマンドで判定可能.

このイメージの先頭に CPUマイクロコードを付与している場合もある(early-microcode).
その場合は, /usr/bin/dracut/skipcpio を通すことでCPIOアーカイブの部分のみを抽出できる.

lsinitrd を使うとその辺りを自動判別してくれる.
dracut のビルドオプションを最初に出力してくれるので, そこから圧縮形式やCPUマイクロコードの有無を確認できる.

# lsinitrd <initrdファイル> | head     ※Argumentを確認
Image: ../initrd.img: 71M
========================================================================
Version: dracut-049-27.git20181204.fc31.1

Arguments: --nomdadmconf --nolvmconf --xz --install '/.buildstamp' \
  --no-early-microcode --add 'fips' --add 'anaconda pollcdrom qemu qemu-net' \
  --force

dracut modules:
bash
systemd
...

// xzで圧縮されており, マイクロコードは含まれていない
# mkdir img
# cd img
# xzcat ../initrd.img | cpio -i -d

参考文献

wiki.syslinux.org wiki SYSLINUX > PXELINUX
www.pxe.net PXE Specification