亀山がよくやる「手抜き」PC クラスタのセットアップ (buster + uefi 編)

最終更新日: 2021年8月19日(木)

まえがき

端的にいうと ハードウェア以外は「0円」「俺様の専用機」 を作る、というつもりのものです。 基本的な仕様や構成は以下のような感じ。

     eth1      +------+     eth0
---------------+ mck1 +--------------+ /home/kameyama
   外向きの    +------+ 192.168.10.1 |
  IP アドレス                        |
               +------+     eth0     |
               | mck2 +--------------+ /home/kameyama
               +------+ 192.168.10.2 | -> 192.168.10.1:/home/kameyama
                                     |
               +------+     eth0     |
               | mck3 +--------------+ /home/kameyama
               +------+ 192.168.10.3 | -> 192.168.10.1:/home/kameyama
                                     |
               +------+     eth0     |
               | mck4 +--------------+ /home/kameyama
               +------+ 192.168.10.4   -> 192.168.10.1:/home/kameyama

親機に Debian GNU/Linux をインストールする

こういう時に亀山がとる手順は以下のような感じ。

  1. インストーラを用意する。

    http://www.debian.org/CD/ から必要なもの (例えば amd64 用の netinst CD イメージ) をダウンロードしてくる。 その後、例えば以下のような手順で、ダウンロードしてきた CD イメージを (中身が潰れても惜しくない) USB メモリに書き込む。

    # cat debian-10.0.0-amd64-netinst.iso > USBメモリのデバイス名 (/dev/sdb とか)
    
    当然ながら、デバイス名の指定は慎重に。 間違った指定をすると、最悪の場合は内蔵ハードディスクを潰してしまうこともありえます。

  2. 最小限のシステムのインストール。

    上で作った USB メモリから起動すると、インストーラが立ち上がり、インストールへと進む。 亀山がやる場合、この時点では (ほとんど) 何のパッケージもインストールしない。 tasksel の選択画面が起動しても、全て解除して進める。 終わったら再起動。

  3. 必要なパッケージのインストール。

    再起動したら、追加でパッケージのインストールを進める。 だいたいは以下をインストール指定しておけば、他に必要なパッケージも「いもづる」式にインストールされる。

    # apt install g++ gfortran libopenblas-dev libopenmpi-dev make openssh-server openmpi-bin
    

    その他、亀山は (自分の好みの問題で) 以下もインストール指定する。

    # apt install bzip2 less sudo tcsh xauth
    

  4. ネットワークの設定。

    /etc/network/interfaces を以下のような内容に修正。 内向き LAN (この例では eth0) の設定はこの通りだが、外向き LAN (この例では eth1) の設定はネットワーク環境によって異なるので、それぞれ正しいものに書き換えるべし。 また、Debian 9 (stretch) 以降のバージョンでは、ネットワークインターフェースがこれとは違った新しい規則で命名されることにも注意。

    auto lo
    iface lo inet loopback
    allow-hotplug eth0
    iface eth0 inet static
    	address 192.168.10.1
    	netmask 255.255.255.0
    	network 192.168.10.0
    	broadcast 192.168.10.255
    	gateway 0.0.0.0
    allow-hotplug eth1
    iface eth1 inet static
    	address ???.???.???.???
    	netmask ???.???.???.???
    	network ???.???.???.???
    	broadcast ???.???.???.???
    	gateway ???.???.???.???
    

子機をネットワークでブートするための準備

ここの設定も全て親機上で行う。 親機 の /srv/nfsroot に子機用のシステムを構築する。 また子機は親機から DHCP で IP アドレスを受け取り、親機内の /srv/tftp 以下にあるカーネルイメージを用いて PXE でブートさせる。

  1. 必要なパッケージのインストール

    di-netboot-assistant は本来は「ネットワークインストール」環境を構築するためのものであるが、ここでは「子機」を UEFI で起動させるために必要なファイルを準備するために拝借する。

    # apt install tftpd-hpa pxelinux initramfs-tools isc-dhcp-server nfs-kernel-server ntp
    # apt --no-install-recommends install di-netboot-assistant
    
  2. tftpd の設定 (その1)。
    • tftpd の設定ファイル /etc/default/tftpd-hpa はデフォルトのままで使う。
    • /srv/tftp/pxelinux.cfg/default を以下の内容で作成。 当然ながら NFS 親機の IP アドレスは内向きのものを。 またルートファイルシステムを read-only にした場合 (安全を期そうとしたのだが) には、子機からうまく NFS マウントできなかった。
      LABEL linux
      DEFAULT vmlinuz root=/dev/nfs initrd=initrd.img nfsroot=192.168.10.1:/srv/nfsroot ip=dhcp init=/bin/systemd rw quiet
      
  3. 起動用のカーネルと RAM ディスクの準備
    • PXE ブートに必要なファイルの準備。 上記の設定により、子機のブート用のカーネルイメージは vmlinuz という名前にしておくことに注意。
      # cp /boot/vmlinuz* /srv/tftp/vmlinuz
      # cp /usr/lib/syslinux/modules/bios/ldlinux.c32 /usr/lib/PXELINUX/pxelinux.0 /srv/tftp
      
    • ブートのための RAM ディスクを作る。 この際に注意すべきであろうことは、親機用の initrd を作るためのディレクトリ /etc/initramfs-tools/ とは別の作業用ディレクトリを用意して作業すること (さもなくば、親機の initrd が書き換えられてしまいます)。 以下の例では、/srv/initramfs-tools という作業ディレクトリを新たに作って作業することにする。
      • 作業ディレクトリを準備する
        # mkdir /srv/initramfs-tools
        # cp -Rp /etc/initramfs-tools/modules /etc/initramfs-tools/scripts /srv/initramfs-tools/
        
      • 子機の RAM ディスク用の設定ファイルとして、/srv/initramfs-tools/initramfs.conf というファイルを作成し、以下の記述を行う。 なお、以下で使用する mkinitramfs コマンドの仕様により、ファイル名は必ず initramfs.conf にしておくこと。
        MODULES=netboot
        BUSYBOX=y
        KEYMAP=n
        COMPRESS=gzip
        DEVICE=
        NFSROOT=auto
        BOOT=nfs
        
      • 次のコマンドにより /srv/tftp/initrd.img を作る。
        # mkinitramfs -d /srv/initramfs-tools/ -o /srv/tftp/initrd.img
        
  4. UEFI によるネットワークブートの準備

    この設定がとても分かりにくい。仕方がないので di-netboot-assistant の力を借りることにした。

    # di-netboot-assistant --tftproot=/srv/tftp install buster
    
    これにより、/srv/tftp/d-i/n-a/ というディレクトリ以下にいろいろ作られる。 ただしこのままでは「buster をネットワークインストールする」用に構成されてしまうので、そうでない用に作り直す。 そのためには /srv/tftp/d-i/n-a/grub/grub.cfg を例えば以下のように修正する。 おそらく肝心なところは、set default 以降で、先に作った vmlinuz と initrd.img で起動させようとする指定でしょう。
    if loadfont $prefix/font.pf2 ; then
      set gfxmode=800x600
      insmod efi_gop
      insmod efi_uga
      insmod video_bochs
      insmod video_cirrus
      insmod gfxterm
      terminal_output gfxterm
    fi
    
    insmod play
    play 960 440 1 0 4 440 1
    
    set default='Debian boot'
    set timeout=1
    
    menuentry 'Debian boot' {
       linux   vmlinuz root=/dev/nfs nfsroot=192.168.10.1:/srv/nfsroot ip=dhcp init=/bin/systemd rw quiet
       initrd  initrd.img
    }
    

  5. tftpd の設定 (その2)

    ファイルのパーミッションを設定する。

    # chmod -R 777 /srv/tftp
    
    ここまでできれば tftpd の再起動。
    # systemctl restart tftpd-hpa
    

  6. DHCP サーバーの設定。
    • /etc/dhcp/dhcpd.conf を編集する。 たぶん以下のようなものがあればOKのはず。 子機の MAC アドレスは事前に調べておきましょう。 例えばどうにかして Linux を起動しておき、/sys/class/net/(インターフェース名)/address の中身をみれば MAC アドレスが分かります。 また filenameは PXE ブート用のブートローダとなるファイル名を指定し、そのファイルを持っているサーバーの IP アドレスを next-server に指定する。 以下の例では UEFI か BIOS かによって filename の指定を変えていますが、そもそも UEFI も使えるようにするには、option の指定が必須のようです。
      authoritative;
      allow booting;
      allow bootp;
      option space PXE;
      option PXE.mtftp-ip               code 1 = ip-address;
      option PXE.mtftp-cport            code 2 = unsigned integer 16;
      option PXE.mtftp-sport            code 3 = unsigned integer 16;
      option PXE.mtftp-tmout            code 4 = unsigned integer 8;
      option PXE.mtftp-delay            code 5 = unsigned integer 8;
      option arch code 93 = unsigned integer 16; # RFC4578
      subnet 192.168.10.0 netmask 255.255.255.0 {
        option broadcast-address 192.168.10.255;
      }
      host mck02 {
          next-server 192.168.10.1;
          hardware ethernet ??:??:??:??;
          fixed-address 192.168.10.2;
          option host-name "mck02";
          if option arch = 00:07 {
          filename "d-i/n-a/bootnetx64.efi";
           } else {
      	 filename "pxelinux.0";
      	 }
      }
      host mck03 {
          next-server 192.168.10.1;
          hardware ethernet ??:??:??:??;
          fixed-address 192.168.10.3;
          option host-name "mck03";
          if option arch = 00:07 {
          filename "d-i/n-a/bootnetx64.efi";
           } else {
      	 filename "pxelinux.0";
      	 }
      }
      host mck04 {
          next-server 192.168.10.1;
          hardware ethernet ??:??:??:??;
          fixed-address 192.168.10.4;
          option host-name "mck04";
          if option arch = 00:07 {
          filename "d-i/n-a/bootnetx64.efi";
           } else {
      	 filename "pxelinux.0";
      	 }
      }
      
    • /etc/default/isc-dhcp-server を編集し、INTERFACESv4= に内向き LAN のインターフェース名 (この例では eth0) を追記。 ただしバージョン 9 (stretch) 以降の Debian を新規でインストールした場合には、ネットワークインターフェースがこれとは違った新しい規則で命名されることに注意。
      INTERFACESv4="eth0"
      
    その後 dhcpd の再起動。
    # systemctl restart isc-dhcp-server
    
  7. NFS サーバーの設定。 /etc/exports を編集。 たぶん以下のようなものがあればOKのはず。
    /home	     192.168.10.0/255.255.255.0(rw,async,no_root_squash,no_subtree_check)
    /srv/nfsroot 192.168.10.0/255.255.255.0(rw,async,no_root_squash,no_subtree_check)
    
    しかしバージョン10 (buster) の Debian になってからは、これだけだと子機で走らせていたジョブが (たぶん NFS 関係のトラブルで?) 途中で死ぬというトラブルが頻発した。 その対策のために、以下を追加してみるといいかも。
    1. /etc/idmapd.conf の中の以下の行のコメントを外す。
      Domain = localdomain
    2. /etc/default/nfs-common に以下の行を追加する。
      NEED_IDMAPD=yes
    その後 nfsd の再起動。
    # systemctl restart nfs-kernel-server
    
  8. NTP サーバーの設定。 /etc/ntp.conf を編集。 たぶん以下のようなものがあればOKのはず。
    server your_ntp_server
    restrict 192.168.10.0 mask 255.255.255.0 nomodify notrap
    
    その後 ntpd の再起動。
    # systemctl restart ntp
    
  9. ホスト名の登録。 /etc/hosts を以下のように編集する。
    192.168.10.1 mck1
    192.168.10.2 mck2
    192.168.10.3 mck3
    192.168.10.4 mck4
    

子機のシステムをインストールする

まずは親機上でシステムを作るところから。 既存のシステムを丸ごと複製する手もあるようだが、ここは Unix/Linux システムからの Debian GNU/Linux のインストール よろしく、debootstrap で作り直す。

  1. 必要なパッケージのインストール。
    # apt install debootstrap
    
  2. システムの基本部分をインストール
    # mkdir /srv/nfsroot
    # debootstrap --arch=amd64 buster /srv/nfsroot http://ftp.jp.debian.org/debian
    
  3. 子機に必要なパッケージをインストール。 まずはシステムの稼働に必要なものを
    # chroot /srv/nfsroot apt install console-setup locales nfs-common ntp openssh-server
    
    続いて、計算ジョブの実行に必要なものを。
    # chroot /srv/nfsroot apt install g++ gfortran libopenblas-dev libopenmpi-dev make openmpi-bin
    
    その他、亀山は (自分の好みの問題で) 以下もインストール指定する。
    # chroot /srv/nfsroot apt install bzip2 less sudo tcsh xauth
    
  4. /srv/nfsroot/etc/ntp.conf を編集。 たぶん以下のようなものがあればOKのはず。
    server 192.168.10.1
    
    また pool を指している行は無効にしておくのがよさげかも。
  5. /srv/nfsroot/etc/network/interfaces を以下の通り修正。
    auto lo
    iface lo inet loopback
    iface eth0 inet manual
    
  6. /srv/nfsroot/etc/fstab を以下の通り修正。 ここで /var/run を tmpfs とかにマウントしてしまうと、systemd が困ってしまうので注意。 また jessie の時と違って、/var/lock を tmpfs とかにマウントするのもダメらしい。 なお root ファイルシステムの指定は起動時になされているので、ここで書かなくても大丈夫のはず。
    proc            /proc           proc    defaults        0       0
    tmpfs           /tmp            tmpfs   defaults        0       0
    tmpfs           /var/tmp        tmpfs   defaults        0       0
    tmpfs           /var/log        tmpfs   defaults        0       0
    192.168.10.1:/home /home        nfs     defaults        0       0
    
  7. NFS 関係の設定。 念のため子機にも親機と同じ設定をば。
    1. /etc/idmapd.conf の中の以下の行のコメントを外す。
      Domain = localdomain
    2. /etc/default/nfs-common に以下の行を追加する。
      NEED_IDMAPD=yes
  8. 子機に /etc/hostname はいらないので消す。
    # rm /srv/nfsroot/etc/hostname
    
  9. 子機にも /etc/hosts はいるので、親機からもらってくる。
    # cp -p /etc/hosts /srv/nfsroot/etc
    
  10. タイムゾーンの設定。 正式には chroot環境下で timedatectl set-timezone Asia/Tokyo とするのだろうが、/proc/ がマウントされていないと動かないようなので。 また /srv/nfsroot/etc/localtime は標準状態では /usr/share/zoneinfo/Etc/UTC へのシンボリックリンクになっているので、単純にコピーするのは危ないかも。
    # ln -sf /usr/share/zoneinfo/Asia/Tokyo /srv/nfsroot/etc/localtime
    
  11. アカウントの調整。 root のパスワードをつけたり、一般ユーザーのアカウントを作ったり。
    # chroot /srv/nfsroot passwd
    
ここまでできれば、子機をネットワーク経由でブートすればOK。 もしこの際に「Secure Boot Violation」とかいう赤い警告が表示された場合は、BIOS メニュー内で Secure Boot をオフにすればよいはず。

SSHの設定

クラスタを構成する全てのPCに、パスワードなしでログインできるようにしたい。

  1. 鍵の作成。

    以下の手順で、指示通りに進めていくと、秘密鍵 ~/.ssh/id_rsa と公開鍵 ~/.ssh/id_rsa.pub ができる。

    $ ssh-keygen -t rsa
    

  2. 公開鍵の登録。

    $ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
    $ chmod 600 ~/.ssh/authorized_keys
    

  3. クラスタ内の他のPCに、パスワードなしで ssh でログインできることを確認。

OpenMPI の動作確認

例えばこんな Fortran プログラムはどうでしょう。

program psample
  implicit none
  include 'mpif.h'
  character(len=MPI_MAX_PROCESSOR_NAME) :: hostname
  integer :: myrank,nprocs,ierr,hostname_len
  call mpi_init(ierr)
  call mpi_comm_size(mpi_comm_world,nprocs,ierr)
  call mpi_comm_rank(mpi_comm_world,myrank,ierr)
  call mpi_get_processor_name(hostname,hostname_len,ierr)
  write(*,'("Hello, I am rank",i4," out of",i4," running on ",a,".")') &
       myrank,nprocs,trim(hostname)
  call mpi_finalize(ierr)
end program psample
これを psample.f90 として作成し、
$ mpif90 psample.f90
$ mpirun -np 2 ./a.out
$ mpirun -np 4 -host mck1,mck2,mck3,mck4 ./a.out
などとして、しかるべく動けばOK。