まえがき
端的にいうと ハードウェア以外は「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 12 (bookworm) の amd64 を使う。
- コンパイラは GNU のものを使う。 ライセンスがどうとかの問題を考えたくなくなったので、Intel-free な計算機環境を作りたいが故。
- 行列計算ライブラリとして、OpenBLAS (+ これに付随する LAPACK) を利用。 亀山が触った感じでは、Debian 標準の BLAS/LAPACK とか ATLAS よりも OpenBLAS が速い (もしかして Intel® MKL とさほど遜色ないくらいなのかも)。 ただし wheezy 以前の OpenBLAS には LAPACK をがついていないようで。
- ノード間並列は OpenMPI で実現させる。 各 PC のマザーボードについている LAN の口 (eth0) を使って、クラスタ内部のネットワークを構成する。 ただし「親機」となる1台 (mck1) は、外向きのLANの口 (eth1) も用意する。
- 記憶領域 (ハードディスク) は「親機」となる1台 (mck1) のみに持たせる。 他の「子機」はディスクレスとし、tftp によりネットワーク経由でブートし、ホームディレクトリやシステムファイルも「親機」から NFS でマウントする。 また最近は UEFI を使わないといけない場合も出てきたので、旧来の BIOS に加えて UEFI でも「子機」をブートできるようにする。
- アカウントは「親機」と「子機」で個別に用意する (わざわざ NIS を使うのも面倒だし)。 ただし「子機」のルートファイルシステムは NFS で共用しているので、「子機」どうしではアカウント情報の使い回しが効いている。
- キューイングシステムのような、コッたものは使わない (ロードの監視なんかもしない)。
親機に Debian GNU/Linux をインストールする
こういう時に亀山がとる手順は以下のような感じ。
- インストーラを用意する。
http://www.debian.org/CD/ から必要なもの (例えば amd64 用の netinst CD イメージ) をダウンロードしてくる。 その後、例えば以下のような手順で、ダウンロードしてきた CD イメージを (中身が潰れても惜しくない) USB メモリに書き込む。
# cat debian-12.0.0-amd64-netinst.iso > USBメモリのデバイス名 (/dev/sdb とか)
当然ながら、デバイス名の指定は慎重に。 間違った指定をすると、最悪の場合は内蔵ハードディスクを潰してしまうこともありえます。 - 最小限のシステムのインストール。
上で作った USB メモリから起動すると、インストーラが立ち上がり、インストールへと進む。 亀山がやる場合、この時点では (ほとんど) 何のパッケージもインストールしない。 tasksel の選択画面が起動しても、全て解除して進める。 終わったら再起動。
- 必要なパッケージのインストール。
再起動したら、追加でパッケージのインストールを進める。 だいたいは以下をインストール指定しておけば、他に必要なパッケージも「いもづる」式にインストールされる。
# apt install g++ gfortran libopenblas-dev libopenmpi-dev make openssh-server openmpi-bin
その他、亀山は (自分の好みの問題で) 以下もインストール指定する。
# apt install bzip2 less sudo tcsh xauth
- ネットワークの設定。
/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)
DHCP サーバーといえば昔は isc-dhcp-server だったが、bookworm の次のリリースからはパッケージが提供されなくなるらしい。ということで、代替として dnsmasq を使うようにしてみる。また dnsmasq には tftp サーバーの機能もあるそうなので、tftpd-hpa もいらなくなった。
# apt install dnsmasq pxelinux initramfs-tools nfs-kernel-server ntpsec
- 必要なパッケージのインストール (その2)
di-netboot-assistant は本来は「ネットワークインストール」環境を構築するためのものであるが、ここでは「子機」を UEFI で起動させるために必要なファイルを準備するために拝借する。また UEFI での起動のために grub-efi-amd64-bin、セキュアブートのために shim-signed と grub-efi-amd64-signed もインストールする。
# apt --no-install-recommends install di-netboot-assistant grub-efi-amd64-bin shim-signed grub-efi-amd64-signed
- 子機の起動用のカーネルと RAM ディスクの準備
- まず最初に、tftp で転送する起動用のファイルを置く場所を作っておく。ディレクトリ名は tftpd-hpa の標準にならった。
# mkdir /srv/tftp
- カーネルイメージの準備。ここでは vmlinuz という名前にしてある。
# cp /boot/vmlinuz* /srv/tftp/vmlinuz
- ブートのための 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
- 作業ディレクトリを準備する
- まず最初に、tftp で転送する起動用のファイルを置く場所を作っておく。ディレクトリ名は tftpd-hpa の標準にならった。
- tftp により子機を起動するための設定 (その1; BIOS 起動用)
- /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
- BIOS によるネットワークブートの準備
# cp /usr/lib/syslinux/modules/bios/ldlinux.c32 /usr/lib/PXELINUX/pxelinux.0 /srv/tftp
- /srv/tftp/pxelinux.cfg/default を以下の内容で作成。当然ながら NFS 親機の IP アドレスは内向きのものを。またルートファイルシステムを read-only にした場合 (安全を期そうとしたのだが) には、子機からうまく NFS マウントできなかった。
- tftp により子機を起動するための設定 (その2; UEFI 起動用)
この設定がとても分かりにくい。仕方がないので di-netboot-assistant の力を借りることにした。
# di-netboot-assistant --tftproot=/srv/tftp install bookworm
これにより、/srv/tftp/d-i/n-a/ というディレクトリ以下にいろいろ作られる。 ただしこのままでは「bookworm をネットワークインストールする」用に構成されてしまうので、そうでない用に作り直すべく、/srv/tftp/d-i/n-a/grub/grub.cfg の末尾に以下のようにな内容を追記する。 この設定のミソは、/srv/tftp/pxelinux.cfg/default の記述にならい、先に作った vmlinuz と initrd.img で起動させるように指定することでしょう。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 }
- dnsmasq の設定
以下のような内容で /etc/dnsmasq.d/pxe-tftp.conf を作成する。ミソは以下の通り。
- DNS サーバーを起動させない (port=0)
- eth0 からのみ DHCP リクエストを受け付ける
- dhcp-range の指定がないと DHCP サーバーが起動しない。
- 子機に固定の IP アドレスを割り当てる (dhcp-host=)。子機の MAC アドレスは事前に調べておきましょう。例えばどうにかして Linux を起動しておき、/sys/class/net/(インターフェース名)/address の中身をみれば MAC アドレスが分かります。
- dhcp-match と dhcp-boot の組み合わせにより、BIOS で起動させるか UEFI で起動させるかによって、dhcp-boot で渡すファイルを変更できるようにしてある。
port=0 interface=eth0 dhcp-range=192.168.10.2,192.168.10.10 dhcp-host=11:22:33:44:55:66,mck02,192.168.10.2 dhcp-host=11:22:33:44:55:66,mck03,192.168.10.3 dhcp-host=11:22:33:44:55:66,mck04,192.168.10.4 dhcp-match=set:efi-x86_64,option:client-arch,7 dhcp-match=set:efi-x86_64,option:client-arch,9 dhcp-match=set:efi-x86,option:client-arch,6 dhcp-match=set:bios,option:client-arch,0 dhcp-boot=tag:efi-x86_64,d-i/n-a/bootnetx64.efi dhcp-boot=tag:efi-x86,d-i/n-a/bootnetx64.efi dhcp-boot=pxelinux.0 dhcp-option=option:ntp-server,192.168.10.1 enable-tftp tftp-root=/srv/tftp
念のためファイルのパーミッションを設定し、その後 dnsmasq の再起動。
# chmod -R 777 /srv/tftp # systemctl restart dnsmasq
- 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)
その後 nfsd の再起動。# systemctl restart nfs-kernel-server
- NTP サーバーの設定。
/etc/ntpsec/ntp.conf を編集。
たぶん以下のようなものがあればOKのはず。
server your_ntp_server restrict 192.168.10.0 mask 255.255.255.0 nomodify notrap
その後 ntpd の再起動。# systemctl restart ntpsec
- ホスト名の登録。
/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 で作り直す。
- 必要なパッケージのインストール。
# apt install debootstrap
- システムの基本部分をインストール
# mkdir /srv/nfsroot # debootstrap --arch=amd64 bookworm /srv/nfsroot http://ftp.jp.debian.org/debian
- 子機に必要なパッケージをインストール。
まずはシステムの稼働に必要なものを
# chroot /srv/nfsroot apt install console-setup locales nfs-common openssh-server systemd-timesyncd
続いて、計算ジョブの実行に必要なものを。# chroot /srv/nfsroot apt install g++ gfortran libopenblas-dev libopenmpi-dev make openmpi-bin
その他、亀山は (自分の好みの問題で) 以下もインストール指定する。# chroot /srv/nfsroot apt install bzip2 less sudo tcsh xauth
- /srv/nfsroot/etc/systemd/timesyncd.conf を編集。
最終行に以下のような指定があればOKのはず。
NTP=192.168.10.1
- /srv/nfsroot/etc/network/interfaces を以下の通り修正。
auto lo iface lo inet loopback iface eth0 inet manual
- /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
- 子機に /etc/hostname はいらないので消す。
# rm /srv/nfsroot/etc/hostname
- 子機にも /etc/hosts はいるので、親機からもらってくる。
# cp -p /etc/hosts /srv/nfsroot/etc
- タイムゾーンの設定。
正式には 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
- アカウントの調整。
root のパスワードをつけたり、一般ユーザーのアカウントを作ったり。
# chroot /srv/nfsroot passwd # chroot /src/nfsroot adduser --no-create-home kameyama
SSHの設定
クラスタを構成する全てのPCに、パスワードなしでログインできるようにしたい。
- 鍵の作成。
以下の手順で、指示通りに進めていくと、秘密鍵 ~/.ssh/id_rsa と公開鍵 ~/.ssh/id_rsa.pub ができる。
$ ssh-keygen -t rsa
- 公開鍵の登録。
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys $ chmod 600 ~/.ssh/authorized_keys
- クラスタ内の他のPCに、パスワードなしで ssh でログインできることを確認。
OpenMPI の動作確認
例えばこんな Fortran プログラムはどうでしょう。
program psample !$ use omp_lib implicit none include 'mpif.h' character(len=MPI_MAX_PROCESSOR_NAME) :: hostname integer :: myrank,nprocs,ierr,hostname_len logical :: with_openmp=.false. !$ with_openmp = .true. 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) if (with_openmp) then !$omp parallel !$ write(*,'("Hello, I am rank",i4," out of",i4," running on ",a," and thread",i4," out of",i4,".")') & !$ myrank,nprocs,trim(hostname),omp_get_thread_num(),omp_get_num_threads() !$omp end parallel else write(*,'("Hello, I am rank",i4," out of",i4," running on ",a,".")') & myrank,nprocs,trim(hostname) endif call mpi_finalize(ierr) end program psampleこれを psample.f90 として作成し、
$ mpif90 psample.f90 $ mpirun -np 2 ./a.out $ mpirun -np 8 -host mck1:2,mck2:2,mck3:2,mck4:2 ./a.outあるいは OpenMP も組み合わせるなら
$ mpif90 -fopenmp psample.f90 $ mpirun -np 2 ./a.out $ mpirun -np 1 -host mck1 --bind-to none ./a.out $ mpirun -np 4 -host mck1,mck2,mck3,mck4 ./a.outなどとして、しかるべく動けばOK。