DHCPサーバとmatchboxを利用してUbuntuのiPXE Netbootをやってみた


先日DHCPとmatchboxというOSSを利用してNetbootを行ったので、その時のメモを残しておきます。

前提として私はネットワーク初心者ですので、誤りがある場合は優しく指摘していただけると助かります。

また一般的にはNetbootを行う際はDHCPによるIPの払い出しは行わないと思います...。

## 実現したいこと

物理マシンの電源を入れてしばらく待つと自動でUbuntu Serverがインストールされて利用可能状態になっていること


## 環境について

今回は学習目的(興味本位)で、物理マシンと仮想マシンの両方で同一手法によるNetbootを試してみました。

基本的にここで説明している手順は実験環境2で利用したものになっています。(IPアドレスなど)


### 実験環境1 (物理マシン)

- Thinkpad 2台(研究室の備品を借りたため執筆時に手元にないため型番はわかりません...)

ネットワークアドレスを192.168.10.0/24とし、サーバー役に192.168.10.150を割り当てました。

サーバ役にはOSとしてUbuntu Desktop 22.04を用いました。

### 実験環境2 (仮想マシン)

ハイパーバイザーとしてProxmoxを利用し、そのうえにVMを2つ立てて検証しました。

- DHCPサーバ(10.10.10.12/24)
  - OS: Ubuntu Server 22.04
  - CPU: 4 cores
  - RAM: 4GB
  - HDD: 32GB
- クライアント(dhcp)
  - CPU: 4 cores
  - RAM: 6GB
  - HDD: 32GB

## DHCPサーバを準備する

DHCPサーバーとしてisc-dhcp-serverを利用しました。

### インストール

以下のコマンドでインストールしました。

```bash
$ sudo apt install -y isc-dhcp-server
```

### 設定

``dhcpd.conf``を作成し、以下のように記述しました。

```bash
$ nano dhcpd.conf
```
```conf
DHCPARGS="ens18";

option domain-name-servers 8.8.8.8;

default-lease-time 600;
max-lease-time 7200;

subnet 10.10.10.0 netmask 255.255.255.0 {
  range 10.10.10.10 10.10.10.200;
  option routers 10.10.10.254;
  option broadcast-address 10.10.10.255;

  if exists user-class and option user-class = "iPXE" {
    filename "http://10.10.10.12:80/boot.ipxe";
  } elsif substring(option vendor-class-identifier, 0, 9) = "PXEClient" {
    filename "undionly.kpxe";
  }
}
```

コンフィグファイルについては、多少のネットワークの知識があれば読み解けるようになっていると思います。

また、今回はBIOSのみを想定しています。UEFIの場合はもう少し別の設定を入れる必要があります。

```conf
if exists user-class and option user-class = "iPXE" {
  filename "http://10.10.10.12:80/boot.ipxe";
} elsif substring(option vendor-class-identifier, 0, 9) = "PXEClient" {
  filename "undionly.kpxe";
}
```

この部分はDHCPのDiscoverにあるOption 60を見て送信元がPXEかiPXEか判定しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1447932/5729c8a3-7300-0991-7d5b-cb98e99ee21a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1447932/54c3640b-5969-a03f-d847-119cb7863ffe.png)

- [参考: Man page of dhcpd\-options](https://linuxjm.osdn.jp/html/dhcp/man5/dhcp-options.5.html#:~:text=option-,vendor%2Dclass%2Didentifier,-string%3B)

filename で指定しているURLは後述するmatchboxのURLです。

### 設定の上書き

```bash
$ sudo cp dhcpd.conf /etc/dhcp/dhcpd.conf
```

isc-dhcp-serverは``/etc/dhcp/dhcpd.conf``を参照しているため、手元のdhcpd.confで上書きしておきます。


### 起動

```
$ sudo systemctl restart isc-dhcp-server
```

起動後、以下コマンドで起動できているか確認します。

```
$ sudo systemctl status isc-dhcp-server
```

active になっていれば問題なく設定ができています。

## TFTPサーバを準備する

PXEからiPXEにチェーンロードするために、後述するundionly.kpxeというバイナリを提供する必要があります。

PXEはTFTPによるファイルの取得が行えるため、TFTPサーバを用意します。DHCPサーバと別に用意してもいいですが、今回はDHCPサーバと兼任させることにしました。

### インストール

```bash
$ sudo apt install -y tftpd-hpa
```

インストール出来れば、設定ファイルが作成されるので確認します。

```bash
$ cat /etc/default/tftpd-hpa
# /etc/default/tftpd-hpa

TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--secure"
```

デフォルトでは``/srv/tftp/``をもとにファイルを提供するようなので、作成していない場合はこのディレクトリを作成しておきます。

```bash
$ sudo mkdir /srv/tftp
```

### 起動
```
$ sudo systemctl restart tftpd-hpa
```

## undionly.kpxe を準備する

undionly.kpxeはPXEからiPXEにチェーンロードするために必要になるバイナリです。

- [参考: iPXE \- open source boot firmware \[howto:chainloading\]](https://ipxe.org/howto/chainloading)
- [ダウンロードリンク](https://boot.ipxe.org/)

### ダウンロード

```bash
$ wget https://boot.ipxe.org/undionly.kpxe
```

### TFTPのディレクトリに配置する

```bash
$ sudo mv undionly.kpxe /srv/tftp/undionly.kpxe
```


## matchboxを準備する

matchboxは公式サイトで以下のように説明されています。

> Matchbox is a service that matches bare-metal machines to profiles that PXE boot and provision clusters.

> Matchboxは、ベアメタルマシンと、PXEブートとクラスタのプロビジョニングを行うプロファイルをマッチングするサービスです。(DeepL翻訳)

- [参考: matchbox](https://matchbox.psdn.io/)

よくわからなかったので、matchboxが提供するエンドポイントにリクエストを送信すると事前定義したプロファイルに基づき、対応したネットブート設定を吐き出してくれるサービスと私は認識しました。

### インストール

[matchboxのGitHub](https://github.com/poseidon/matchbox/releases)から最新のリリースを取得します

- [参考: Releases · poseidon/matchbox](https://github.com/poseidon/matchbox/releases/latest)

```bash
$ wget https://github.com/poseidon/matchbox/releases/download/v0.10.0/matchbox-v0.10.0-linux-amd64.tar.gz
$ tar -zxvf matchbox-v0.10.0-linux-amd64.tar.gz
```

解凍すると、その中にmatchboxという名前のバイナリがあるので、適当にパスを通してください。

### 基本ディレクトリの作成

matchboxでは以下のディレクトリをデフォルトで利用するので、作成しておきましょう。

- /var/lib/matchbox/
- /var/lib/matchbox/assets/
- /var/lib/matchbox/groups/
- /var/lib/matchbox/profiles/

### グループの作成

グループではプロファイルに一致させるためのセレクターなどを記述します。

今回はMACアドレスをベースにマッチングを行いました。

``/var/lib/matchbox/groups/``に``node-1.json``を作成します。

```json
{
  "id": "node-1",
  "name": "Install node 1",
  "profile": "node-1-install",
  "selector": {
    "mac": "対象のMACアドレス"
  }
}
```

matchboxではこのグループにマッチした場合、``profile``というキーをもとに``/var/lib/matchbox/profiles/``内にあるプロファイルを自動選択します。

### プロファイルの作成

プロファイルではネットブートについて定義します。

``/var/lib/matchbox/profiles/``に``install-node-1.json``を作成します。

```json
{
  "id": "node-1-ubuntu-install",
  "name": "Install Ubuntu on node-1",
  "boot": {
    "kernel": "/assets/ubuntu/2004/vmlinuz",
    "initrd": [
      "/assets/ubuntu/2004/initrd"
    ],
    "args": [
      "autoinstall",
      "ip=dhcp",
      "ds=nocloud-net;s=http://10.10.10.12/assets/",
      "url=http://10.10.10.12/assets/ubuntu/2004/ubuntu-22.04.6-live-server-amd64.iso"
    ]
  }
}
```

Ubuntuを起動する場合は引数に、``ip=dhcp``を指定する必要があります。

Ubuntu起動後にcloud-initによる自動化を図る場合は、``autoinstall``と``ds=nocloud-net;s=http://10.10.10.12/assets/``を引数に指定する必要があります。

注意点ですが、``ds=nocloud-net;s={URL}``でURLを指定する場合は、最後が``/``で終わっている必要があります。

また、URL先にはcloud-initの仕様として``meta-data``と``user-data``が格納されている必要があります。

- [参考: Ubuntu Manpage: casper](https://manpages.ubuntu.com/manpages/focal/man7/casper.7.html)
- [参考: Netbooting the server installer on amd64 \| Ubuntu](https://ubuntu.com/server/docs/install/netboot-amd64)
- [参考: Bug \#1848018 “\[solved\] PXE boot/nfsboot broken; Eoan Ermine; 19\.\.\.” : Bugs : casper package : Ubuntu](https://bugs.launchpad.net/ubuntu/+source/casper/+bug/1848018)
- [参考: pxe \- Deploying Ubuntu 22\.04 with PXEBoot using Syslinux \- Super User](https://superuser.com/questions/1767915/deploying-ubuntu-22-04-with-pxeboot-using-syslinux)

### assetsの用意

先ほど作成したプロファイルに記述があったisoファイルと、その中にあるvmlinuz、initrdを取り出してassetsディレクトリに用意しておきます。

```bash
$ sudo mkdir -p /var/lib/matchbox/assets/ubuntu/2004/
$ cd $_
```

ISOのダウンロードとマウント

```bash
$ sudo wget https://releases.ubuntu.com/focal/ubuntu-20.04.6-live-server-amd64.iso
$ sudo mkdir /mnt/ubuntu2004/
$ sudo mount -t iso9660 -o loop ubuntu-20.04.6-live-server-amd64.iso /mnt/ubuntu2004/
```

必要なファイルをコピー

```bash
$ sudo cp /mnt/ubuntu2004/casper/vmlinuz /var/lib/matchbox/assets/ubuntu/2004/vmlinuz
$ sudo cp /mnt/ubuntu2004/casper/initrd /var/lib/matchbox/assets/ubuntu/2004/initrd
```

これで準備は終わりです。

## cloud-init のデータを用意する

cloud-initによる自動化を行うため、user-dataを作成します。

```bash
$ nano user-data
```
```yaml
#cloud-config
autoinstall:
  version: 1
  identity:
    hostname: ubuntu-server
    password: "$6$exDY1mhS4KUYCE/2$zmn9ToZwTKLhCw.b4/b.ZRTIZM30JZ4QrOQ2aOXJ8yk96xpcCof0kxKwuX1kqLG/ygbJ1f8wxED22bTL4F46P0"
    username: ubuntu

```

パスワードは``ubuntu``です。

- [参考: Automated Server install quickstart \| Ubuntu](https://ubuntu.com/server/docs/install/autoinstall-quickstart)

また、meta-dataも作成しておきます。

meta-dataの中身は空っぽのままで大丈夫です。

```bash
$ touch meta-data
```

これら二つを``/var/lib/matchbox/assets/``直下に移動しておきます。

```bash
$ sudo mv user-data /var/lib/matchbox/assets/user-data
$ sudo mv meta-data /var/lib/matchbox/assets/meta-data
```

複数のディストリビューションなどをインストールする場合は、別ディレクトリに移動すべきですが今回はUbuntuのみを想定しているので問題ありません。

## matchboxを起動する

```bash
$ sudo matchbox --address=10.10.10.12:80
```

## クライアントを起動する

すべての準備が終わったので、ターゲットとなるクライアントPCの電源を入れます。

全行程が問題なく終わっていれば、PXEからiPXEにチェーンロードします。

その後、Ubuntuが起動し、cloud-initが走ります。

しばらく待つと、ubuntu-serverというホスト名でUbuntuが起動しているはずです。

もし、ISOファイル取得中に``no space left on device``と表示され処理が中断された場合は、メモリが足りません。

VMの場合は割り当てを増やしましょう。

## [余談] 奇妙なバグが起きた

今回はUbuntu20.04, 22.04でNetbootが正しく行えるかチェックしました。

Ubuntu20.04は物理マシンも仮想マシンもどちらも正常にNetboot + cloud-initを完了することができました。

しかし、仮想マシンでUbuntu22.04をNetbootした場合のみ、cloud-initが実行されませんでした。

仮想マシンでUbuntu22.04を起動した場合、Netbootまでは正常に動作しましたが、cloud-initを行う段階において``ds=nocloud-net``で指定しているURLにリクエストを送信しないというバグが発生しました。

できる範囲で調査は行ったのですが、原因らしい原因は見つけることができませんでした。

また、物理マシンにおいてもUbuntu22.04はcloud-initによる設定がUbuntu20.04のときよりも処理に時間が掛かっていました。

時間がなかったため、時間の測定などは十分に行えていないので検証すべき点はたくさんあります...。

## おわりに

以上がDHCPサーバとmatchboxを使ってUbuntuをNetbootしたときのメモ書きです。

Ubuntu 20.04では正常に動作したものが22.04で動作しなかったのは少モヤモヤしますが、やりたいことは実現できたのでよかったです。

原因はいつか解明したいと思います。