Goで作るモダン・ブートサーバー Day5 - iPXEサーバー実装編

この記事は「Go で作るモダン・ブートサーバー」の 5 日目の記事です。 この記事では iPXE ブートに対応したブートサーバーを実装します。

シリーズの記事は以下のリンクからどうぞ。

前回は PXE ブートを実装しました。 この記事では、iPXE ブートを実装します。

前回は PXE ブートができるブートサーバーを実装しました。 それを元に、iPXE ブートもできるように改良します。 今回作成するコードは以下のレポジトリで公開しています。

iPXE ブート

iPXEは PXE のオープンな実装の 1 つで、TFTP ブート以外にも様々なプロトコルからブートできます

  • HTTP
  • iSCSI
  • Fibre Channel SAN over Ethernet
  • ATA over Ethernet
  • ワイヤレス・ネットワーク
  • WAN
  • InfiniBand

また iPXE では、独自のスクリプト記法を記述することで、ブートの設定を柔軟に設定できます。

実は前回の記事で、検証時に利用したクライントマシンは、PXE ではなく iPXE です。 多くの Linux ディストリビューションのパッケージには、QEMU と一緒に PXE ではなく iPXE が同梱されているためです。

前回は iPXE の判定をしなかったため、強制的に PXE ブートが開始しました。 ブートサーバーは、クライアントの種類を判断して、正しいブート方法を提供する方法があります。 クライアントの種類は、DHCP リクエストメッセージの Vendor class identifier オプション(60)User Class オプション (77) から判定できます。

Vendor class identifier オプション (60)

Vendor class identifier はクライアントのブート方式や CPU アーキテクチャの情報があります。 代表的な PXE クライアントは、以下のプレフィックスを持ちます。

クライアントの種類 プレフィクス
PXE クライアント (BIOS) PXEClient:Arch:00000:
PXE クライアント (x86 UEFI) PXEClient:Arch:00006:
PXE クライアント (x64 UEFI) PXEClient:Arch:00007:
PXE クライアント (32-bit arm UEFI) PXEClient:Arch:00018:
PXE クライアント (64-bit arm UEFI) PXEClient:Arch:00019:

ブートサーバーはこの値をみて適切なパラメータを返します。 たとえばクライアントが x64 UEFI なら、x64 UEFI 用のバイナリを返す、などです。 その他の値についてはIETF で定義されています。

User class オプション (77)

もう 1 つ、User class オプション (77) がクライアントの種類を表します。 iPXE の場合は、このオプションに "iPXE" という値を設定します。 ブートサーバーはこの値を見て、クライアントが iPXE なのか否かを判定できます。

クライアントの種類
iPXE iPXE

DHCP サーバーの改良

go.universe.tf/netboot/dhcp4 パッケージでは、 dhcp4.PacketOptions フィールドから DHCP オプションを取得できます。 値の先頭を比較して、クライアントの種類を判定できます。

DHCP オプションに値を載せない DHCP クライアントもあるので、事前にオプションの有無をチェックします。

_, ok := req.Options[dhcp4.OptVendorIdentifier]
if !ok {
	return errors.New("vendor class identifier not set")
}
opt, err := req.Options.String(dhcp4.OptVendorIdentifier)
if err != nil {
	return err
}
if strings.HasPrefix(opt, "PXEClient:Arch:00000:") {
	// PXEクライアント (BIOS) 用のブート情報を返す
} else if strings.HasPrefix(opt, "PXEClient:Arch:00006:") {
	// PXEクライアント (x86 UEFI) 用のブート情報を返す
} else if strings.HasPrefix(opt, "PXEClient:Arch:00007:") {
	// PXEクライアント (x64 UEFI) 用のブート情報を返す
} else if strings.HasPrefix(opt, "HTTPClient:Arch:00015:") {
	// PXEクライアント (32-bit arm UEFI) 用のブート情報を返す
} else if strings.HasPrefix(opt, "HTTPClient:Arch:00016:") {
	// PXEクライアント (64-bit arm UEFI) 用のブート情報を返す
}

コード中の PXEClientBIOSVendorClass 型は、クライアントの種類を表す型です。

UserClass も同じ方法で取得できます。

userclass, err := req.Options.String(77)
ipxe := err == nil && userclass == "iPXE"
if ipxe {
	// iPXEクライアント用のブート情報を返す
}

クライアントの種類が判定できれば、ブート情報を DHCP メッセージに設定します。

PXE ブートでは file オプションに TFTP 上のパスを指定しました。 iPXE で HTTP ブートからブートするには、file フィールドにパスだけでなく http://... で始まる URL を渡します。

iPXE クライアントも互換性のために、単純なパスを指定すると TFTP からブートします。 そしてプロトコルとアドレスを指定するとそれぞれのプロトコルから起動情報をロードします。 また iPXE では PXELINUX のようなバイナリだけでなく、iPXE 独自のスクリプトもロードできます。

// PXE TFTP Boot
resp.BootFilename = "/path/to/pxelinux/pxelinux.0"

// iPXE HTTP Boot
resp.BootFilename = "http://http-server-address/path/to/script.ipxe"

HTTP サーバーの実装

HTTP サーバーの実装は特に難しいことはありません。 Go 標準パッケージの http.FileServer() と http.Dir() を使ってファイルサーバーを実装します。 詳しくは http.goを参照してください。

iPXE スクリプト

iPXE スクリプトは Syslinux の設定ファイルのように、ブートの設定を記述できます。 例えば以下のスクリプトで、Linux (Rancher OS) のブートができます。

iPXE クライアントはこのスクリプトをロードすると、kernel, initrd で指定された HTTP サーバー上のパスから、それぞれカーネルと initrd のロードを試みます。 このスクリプトを、DHCP レスポンスの file フィールドで設定したパスから取得できるように、HTTP サーバーを設定しましょう。

#!ipxe

kernel /rancher/vmlinuz
initrd /rancher/initrd
imgargs vmlinuz console=ttyS0 rancher.autologin=ttyS0
boot

VM を起動

さて、HTTP サーバーのチェックができればいよいよ起動です。 VM の起動は第 1 回に利用したシェルスクリプトを利用します。

まずは仮想ネットワークを作成します。

sudo ./bin/setup network --name br0 --address 172.24.0.1/16

そしてクライアントの VM を起動します。

sudo ./bin/setup node --network br0

するとブートサーバーの標準出力に、DHCP リクエストや HTTP リクエストのログが流れるはずです。 また VM の iPXE のログに、以下のような http://... というログが出力されるのにも注目してください。

Linuxがブートする画面

おわりに

この記事では iPXE を使った Linux のブート手順を解説しました。 途中のコードは若干省略している部分もあるので、フルバージョンは以下のレポジトリを参照してください。

HTTP サーバーは標準ライブラリのみで実装できる言語も多く、TFTP よりも柔軟なリクエストが書けます。 そのため実際のデータセンターでも、iPXE を使うことが多いです。 一方で iPXE を実際に組み込んでいる NIC は少ないです。 そのため UEFI HTTP ブートや PXE クライアントから、iPXE をロードして使われることが多いです。

次回の記事では、UEFI HTTP ブートや、UEFI から iPXE の起動について触れたいともいます。 それでは次回お楽しみに!


Profile picture

Shin'ya Ueoka

B2B向けSaaSを提供する会社の、元Webエンジニア。今はエンジニアリング組織のマネジメントをしている。