Raspberry Pi4でネットワークブートする 

# きっかけ
　プログラムを並列処理するための並列クラスタが作れたら面白そうだなとふと思った。
 作る際に、各クラスタをネットワークブートにすると以下の利点がありそうなので、ラズパイで試しに作ってみることにしてみた。
 ・同一のOSイメージ/rootファイルから起動すれば全てのクラスタが同じ環境になる
 ・クラスタ用のOSイメージやrootファイルの一元管理がしやすそう

# 手順
## クライアント(クラスタ)側

- 通常の起動方法と同様にOSが書き込まれたmicroSDを挿して起動する
　
- ネットワークブートの設定を有効にする
    - 下記コマンドで設定を開く
    ```
    $ sudo raspi-config
    ```
    - 以下```raspi-config```内での設定する
        1. ```6 Advanced Options Configure advanced settings``` を選択
        2. ```A4 Boot Order Choose SD, network, USB or NVMe device boot priority``` を選択
        3. ```B3 Network Boot  Boot from Network unless override by SD Card```を選択
        4. 設定を保存して終了

    <br>
    
    - 再起動後、以下コマンドを入力して```BOOT_ORDER=0xf21```が出力されることを確認する
    ```
    pi@raspberrypi:~ $ vcgencmd bootloader_config
    [all]
    BOOT_UART=0
    WAKE_ON_GPIO=1
    POWER_OFF_ON_HALT=0
    
    [all]
    BOOT_ORDER=0xf21
    ```
    
    - microSDカードを抜く


## サーバー(ネットワークブート用のデータ格納)側

- ブートディレクトリとルートディレクトリの作成と設定
    
    - 起動するカーネルイメージをダウンロードする
    ```
    $ wget https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2024-11-19/2024-11-19-raspios-bookworm-armhf-lite.img.xz
    $ xz -dv 2024-11-19-raspios-bookworm-armhf-lite.img.xz #解凍する
    ```
    - 起動させるカーネルイメージに含まれるパーティションをデバイスファイルにマップし、マウントする。各パーティションの中身を別のディレクトリにコピーする
    ```
    $ sudo apt install kpartx
    $ sudo kpartx -av 2024-11-19-raspios-bookworm-armhf-lite.img
     add map loop0p1 (253:0): 0 524288 linear 7:4 8192    ←マップされたブートパーティション
     add map loop0p2 (253:1): 0 3096576 linear 7:4 532480  ←マップされたルートディレクトリのパーティション
     
    $ sudo mkdir /mnt/boot #仮マウントするディレクトリを作成
    $ sudo mkdit /mnt/rootfs #仮マウントするディレクトリを作成
    $ sudo mount /dev/mapper/loop0p1 /mnt/boot #ブートパーティションをマウント
    $ sudo mount /dev/mapper/loop0p2 /mnt/rootfs #ルートディレクトリをマウント
    $ sudo mkdir -p /nfs/client1 #ルートディレクトリ格納場所の作成
    $ sudo mkdir /tftpboot #ブートパーティションをの内容格納場所の作成
    $ sudo chmod 777 /tftpboot #アクセス権限を変更する
    $ sudo rsync -av /mnt/boot/ /tftboot #ブートパーティションの内容をコピーする
    $ sudo rsync -av /mnt/rootfs/ /nfs/client1 #ルートディレクトリの内容をコピーする 
    
    $ sudo umount /mnt/boot #アンマウントする
    $ sudo umount /mnt/rootfs #アンマウントする
    $ sudo kpartx -d 2024-11-19-raspios-bookworm-armhf-lite.img #マッピングを解除する
    
    ```
    - ブートディレクトリのcmdline.txtを編集する

    ```
    $ sudo mv /tftpboot/cmdline.txt /tftpboot/cmdline.txt.old #一応バックアップとる
    $ echo "console=serial0,115200 console=tty1 root=/dev/nfs nfsroot=192.168.0.12:/nfs/client1,vers=3, rw ip=dhcp rootwait elevator=deadline" | sudo tee /tftpboot/cmdline.txt #ネットワークブート時のNFSでのルートディレクトリの場所や設定を記述(IPアドレスはサーバーのアドレス)
    ```
    - ルートディレクトリ上のfstabの設定(マウント先)を変更する
    ※fstabには元から記述されているprocとechoで追加するブートディレクトリのみにする
     元々ある```/```や```boot/firmware```等の他のマウント設定が記述されていると起動しない
    ```
    $ echo "192.168.0.xx:/tftpboot /boot/firmware/ nfs defaults,vers=3 0 0" | sudo tee -a /nfs/client1/etc/fstab #IPアドレスはサーバーのアドレスを入力する
    ```
<!--
    - カーネルイメージをダウンロードする

    ```
    $ sudo apt install git bc bison flex libssl-dev make libc6-dev libncurses5-dev crossbuild-essential-armhf #ビルドに必要なパッケージをインストールする
    $ git clone --depth=1 --branch rpi-5.10.y https://github.com/raspberrypi/linux #ラズパイ公式のカーネルソースをダウンロードする
    ```

---
※必要な人向け  
 - カーネルコンフィグで設定する(fs-verityとdm-verityを有効化する) 
```
$ cd linux
$ KERNEL=kernel7l #BCM2711ではこのカーネルを指定するらしい
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2711_defconfig #コンフィグを生成する
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig #コンフィグを修正する
   File systems     
     [*] FS Verity (read-only file-based authenticity protection)       
     [*]   FS Verity builtin signature support    
  Device Drivers   
     [*] Multiple devices driver support (RAID and LVM)    
       <*>   Device mapper support 
       [*]     Device mapper debugging support    
       <*>     Verity target support
       [*]       Verity data device root hash signature verification support 

```
参考：
https://leavatail.hatenablog.com/entry/2021/03/01/000000

---
- カーネルソースのビルド
    - カーネルをビルドする
    ```
    $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) zImage modules dtbs
    ```
    - カーネルモジュールをルートディレクトリにインストールする
    ```
    $ sudo env PATH=$PATH make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=/nfs/client1 modules_install
    ```

    - カーネルイメージをブートディレクトリにインストールする
    ```
    $ sudo cp arch/arm/boot/zImage /tftboot/$KERNEL.img
    $ sudo cp arch/arm/boot/dts/*.dtb /tftboot
    $ sudo cp arch/arm/boot/dts/overlays/*.dtb* /tftboot/overlays/
    $ sudo cp arch/arm/boot/dts/overlays/README /tftboot/overlays/
    ```
     - chrootでルートディレクトリ内の設定を変更する
    ```
    $ sudo chroot /srv/rootfs/ /usr/bin/bash
    root@server:/# systemctl enable ssh.service #初回起動時からsshを有効にしたい場合
    
    ##fsverityが必要な場合
    root@server:/# apt-get install libssl-dev fio
    root@server:/# wget https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/fsverity-utils.git/snapshot/fsverity-utils-1.2.tar.gz
    root@server:/# tar xf fsverity-utils-1.2.tar.gz
    root@server:/# cd fsverity-utils-1.2
    root@server:/fsverity-utils-1.2# make && make install
    ```
-->
---


- ネットワークブートサーバーのの設定

    - ```10-eth0.netdev```に以下内容を追加する

    ```sudo vi /etc/systemd/network/10-eth0.netdev
    [Match]
    Name=eth0
    
    [Network]
    DHCP=no
    
    ```
    - ```11-eth0.netdev```に以下内容を追加する

    ```sudo vi /etc/systemd/network/11-eth0.netdev
    [Match]
    Name=eth0
    
    [Network]
    Address=192.168.0.xx/24 サーバー側のアドレス
    DNS=192.168.0.1  #Gate way(ルーター)のアドレス
    
    [Route]
    Gateway=192.168.0.1 #Gate way(ルーター)のアドレス
    ```

    - ```resolved.conf```に以下内容を追加する
    ```sudo vi /etc/systemd/resolved.conf
    [Resolve]
    DNS=192.168.0.1 #Gate way(ルーター)のアドレス　
    #FallbackDNS=
    ```

    - ```systemd-networkd``` を有効にする
    ```
    sudo systemctl enable systemd-networkd
    sudo reboot
    ```
    
    - ```dnsmasq```をインストールし、有効にする
    ```
    sudo apt install tcpdump dnsmasq
    sudo systemctl enable dnsmasq
    ```

    - ```dnsmasq.conf```を新規作成し、設定を追加する
    ```
    echo | sudo tee /etc/dnsmasq.conf
    ```
    ```sudo vi /etc/dnsmasq.conf
    # Note: comment out port if you want DNS services for systems on the network.
    port=0
    dhcp-range=192.168.0.255,proxy
    log-dhcp
    enable-tftp
    tftp-root=/tftpboot
    pxe-service=0,"Raspberry Pi Boot"
    ```
    - ```dnsmasq.service```を再起動する
    ```
    sudo systemctl restart dnsmasq.service
    ```

---
- NFSサーバーの設定
    - ```nfs-kernel-server```をインストールする
    ```
    sudo apt install nfs-kernel-server
    ```
    - ```/etc/exports```にNFSの設定を書き込む
    ```
    echo "/nfs/client1 *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports
    ```

    - 関連するサービスを有効にする
    ```
    sudo systemctl enable rpcbind
    sudo systemctl restart rpcbind
    sudo systemctl enable nfs-kernel-server
    sudo systemctl restart nfs-kernel-server
    ```

# 参考にしたサイト
https://www.raspberrypi.com/documentation/computers/remote-access.html#network-boot-your-raspberry-pi

https://leavatail.hatenablog.com/entry/2021/01/01/000000