Fedora で作る PC クラスター part1

# 概要
1台ごとに計算ソフトをインストールし、環境構築を行という面倒を解消するために、Fedora 40 server edition で PC クラスターを作成した際のログ。
使用するソフトは、Gaussian16、Gromacs、Amber、AlphaFold3。
セットアップの流れは、以下の通り。

1. HDD のマウント
1. ネットワーク設定
1. Ansible の設定
1. NFS の設定
1. LDAP の設定
1. SSSD の設定
1. SLURM のインストール
1. Singularityを導入し、コンテナを共有ディレクトリに配置
1. ユーザー管理
1. テストジョブ
1. バックサーバーの設定

Part 1 では、NFS の設定までを行う。

# 検証環境

![PC_cluster.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4035613/3d545b85-b143-42c1-aa25-1a3a1e7b5c6c.png)

ヘッドノートは、各計算機からのデータの I/O と Slurm の制御を行うため、コア数の多い CPU が好ましい。バックアップノードは負荷が低いため、性能の低い CPU で十分。

・ヘッドノード（1台）
AMD Ryzen Threadripper 3990x (64core/128thread)
RAM 128GB
M.2 SSD 4TB + HDD 18TB x 2 (RAID 0)

・バックアップノード（1台）
Intel core i7-7700 (4core/8thread)
RAM 128GB
SSD 1TB + HDD 18TB x 2 (RAID 0)
Fedora 40 server edition

・計算ノード（7台）
AMD Ryzen 9 9950x or 7950x (16core/32thread)
RAM 96GB
M.2 SSD 2TB
GPU: RTX A4000 or A6000 or RX 4080 super
Fedora 40 server edition

・スイッチ
NETGEAR 卓上型コンパクト アンマネージスイッチングハブ GS316 ギガビット 16ポート 

# STEP.1 HDD のマウント
ヘッドノード、バックアップノードともに M.2 SSD にシステムをインストールした。18TB HDD 2 台、マウントしてストレージとして使用する。

まずは、ヘッドノードを設定する。

```shell
lsblk

# 以下のように表示される。
NAME              MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sda                 8:0    0 16.4T  0 disk 
├─sda1              8:1    0  600M  0 part 
├─sda2              8:2    0    1G  0 part 
└─sda3              8:3    0 16.4T  0 part 
sdb                 8:16   0 16.4T  0 disk 
└─sdb1              8:17   0 16.4T  0 part 
zram0             252:0    0    8G  0 disk [SWAP]
nvme0n1           259:0    0  3.6T  0 disk 
├─nvme0n1p1       259:1    0  600M  0 part /boot/efi
├─nvme0n1p2       259:2    0    1G  0 part /boot
└─nvme0n1p3       259:3    0  3.6T  0 part 
  └─fedora00-root 253:0    0  3.6T  0 lvm  /
```

この場合、バックアップ用の HDD は、/dev/sda と /dev/sdb で、システム用 SSD は /dev/nvme0n1 となる。

``` shell
sudo dnf install mdadm
sudo mdadm --create --verbose /dev/md0 --level=0 --raid-devices=2 /dev/sda /dev/sdb
sudo mkfs.ext4 /dev/md0
sudo mkdir -p /mnt/data
sudo mount /dev/md0 /mnt/data  # マウントを実行
sudo mdadm --detail --scan | sudo tee -a /etc/mdadm.conf  # RAID 情報を保存
sudo blkid /dev/md0
# 以下のように表示される
# /dev/md0: UUID="dd2ffc42-3098-4570-b068-75dab35f5e61" BLOCK_SIZE="4096" TYPE="ext4"

# 自動マウント設定（fstab）
sudo vi /etc/fstab
# 以下の情報を書き込む
# UUID=dd2ffc42-3098-4570-b068-75dab35f5e61 /mnt/data   ext4   defaults,nofail   0 0

<注意>
nofail をつけておかないと、再起動時に emergency mode になってしまうことがある。
nofail は、起動時にこのディスクが見つからなくても emergency mode に入らず、正常起動するようにとの意味。

# マウントを確認する
sudo umount /mnt/data
sudo systemctl daemon-reload
sudo mount -a
# 何もコメントが出なければ成功！

# RAID ステータスを確認
cat /proc/mdstat

```

次に、バックアップノードを設定する

```shell
# HDD の接続名を調べる
lsblk

NAME            MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda               8:0    0  16.4T  0 disk 
└─sda1            8:1    0  16.4T  0 part 
sdb               8:16   0 931.5G  0 disk 
├─sdb1            8:17   0   600M  0 part /boot/efi
├─sdb2            8:18   0     1G  0 part /boot
└─sdb3            8:19   0 929.9G  0 part 
  └─fedora-root 253:0    0 929.9G  0 lvm  /
sdc               8:32   0  16.4T  0 disk 
└─sdc1            8:33   0  16.4T  0 part 
zram0           252:0    0     8G  0 disk [SWAP]
```

この場合、バックアップ用の HDD は、/dev/sda と /dev/sdc で、システム用 SSD は /dev/sdb となる。

ディスクを初期化して、設定を行う。
```shell
sudo dnf install mdadm
sudo mdadm --create --verbose /dev/md0 --level=0 --raid-devices=2 /dev/sda /dev/sdc
sudo mkfs.ext4 /dev/md0
# マウントポイントの作成
sudo mkdir -p /mnt/backup
sudo mount /dev/md0 /mnt/backup
sudo mdadm --detail --scan | sudo tee -a /etc/mdadm.conf
sudo blkid /dev/md0

# UUID を確認する
sudo blkid /dev/md0
# 以下のように表示される
# /dev/md0: UUID="232bfdc2-0f94-4563-84b2-24c98ca8c22c" BLOCK_SIZE="4096" TYPE="ext4"

# 次に /etc/fstab に UUID を書き込む
sudo vi /etc/fstab
# 以下の情報を /etc/fstab に書き込む。terminal 用のコマンドではない。
UUID=232bfdc2-0f94-4563-84b2-24c98ca8c22c   /mnt/backup   ext4   defaults   0 0

# 確認
sudo umount /mnt/backup
systemctl daemon-reload
sudo mount -a
# 何もメッセージが出なければ成功！
```


# STEP.2 ネットワーク設定
計算ノードは静的 IP（固定IP）にする。
ip アドレスの情報は、/etc/hosts に書き込む。

各計算ノードで固定 ip アドレスを設定する手順は以下。
```shell
# デバイス名を確認
nmcli device

# 以下のように表示される
DEVICE        TYPE      STATE                   CONNECTION 
enp69s0       ethernet  connected               enp69s0    
lo            loopback  connected (externally)  lo         
eno1          ethernet  disconnected            --         
wlo2          wifi      disconnected            --         
p2p-dev-wlo2  wifi-p2p  disconnected            --   
```
enp69s0 がインターネットに接続されている LAN ポートで、eno1 がクラスター内通信用の LAN ポートである。
単にヘッドノードと計算ノードを繋いでも disconnected と表示されてしまう。
そこで、クラスター内通信用として static IP を設定する。


```shell
# ヘッドノード
# eno1 の部分は、nmcli device で確認したデバイス名に変える
sudo nmcli connection add type ethernet ifname eno1 con-name cluster-net ip4 192.168.17.1/24
sudo nmcli connection up cluster-net

nmcli device
# 以下のように表示される
DEVICE        TYPE      STATE                   CONNECTION  
enp69s0       ethernet  connected               enp69s0     
eno1          ethernet  connected               cluster-net 
lo            loopback  connected (externally)  lo          
wlo2          wifi      disconnected            --          
p2p-dev-wlo2  wifi-p2p  disconnected            --  
```

続いて、計算ノードの設定を行う。
192.168.17.1, 192.168.17.2, 192.168.17.3 のように割り当てていく。

```shell
# 計算ノード
nmcli device
# enp5s0 の部分は、nmcli device で確認したデバイス名にする。以下の例では、192.168.17.4 に設定している。
sudo nmcli connection add type ethernet ifname enp5s0 con-name cluster-net \
  ip4 192.168.17.4/24
sudo nmcli connection up cluster-net
```

もし設定を間違えた場合は、以下のように設定を削除する。
```shell
nmcli connection show
# UUID を確認する。例えば、以下のように表示される。
# cluster-net  d9674e33-19f9-4b20-9b5f-e01e8e4f2066  ethernet  eno1 
# 以下のコマンドで削除する。
sudo nmcli connection delete uuid d9674e33-19f9-4b20-9b5f-e01e8e4f2066
```


```shell
# 接続プロファイル名を確認
nmcli connection show
# ip アドレスを設定
sudo nmcli connection modify "Wired connection 1" ipv4.addresses 192.168.100.10/24
sudo nmcli connection modify "Wired connection 1" ipv4.gateway 192.168.100.1
sudo nmcli connection modify "Wired connection 1" ipv4.dns "192.168.100.1"
sudo nmcli connection modify "Wired connection 1" ipv4.method manual
# ネットワーク再起動
sudo nmcli connection down "Wired connection 1" && sudo nmcli connection up "Wired connection 1"
```

続いて /etc/hosts の設定を行う。
ヘッドノートと全ての計算ノードの /etc/hosts に以下のように記述
以下は、計算ノード tableaux_02 の例
```shell
# hostname の設定
127.0.1.1    tableaux_02

# 計算クラスターの設定
192.168.17.100  rachmaninov
192.168.17.1    tableaux_01
192.168.17.2    tableaux_02
192.168.17.3    tableaux_03
192.168.17.4    tableaux_04
192.168.17.5    tableaux_05
192.168.17.6    tableaux_06
192.168.17.7    tableaux_07
192.168.17.8    tableaux_08
192.168.17.9    tableaux_09
192.168.17.10   tableaux_10

...
```

また以下のコマンドを実行して、hostname を切り替える。
```shell
# tableaux_02の部分は、それぞれの計算ノード名に変える。
sudo hostnamectl set-hostname tableaux_02
# terminal の表示が、
# odyssey@localhost:~$ から
# odyssey@tableaux02:~$ に変わる
```

# STEP.3 Ansible の設定
それぞれのノードにログインして設定を行うのは手間がかかるので、Ansible を用いて一括設定する。

ヘッドノードで以下を実行。
```shell
sudo dnf install ansible -y

# Ansible のバージョンを確認する
ansible --version

# ansible の
sudo mkdir -p /etc/ansible
sudo vi /etc/ansible/hosts

# 以下の内容を追記していく
[cluster]
tableaux_01 ansible_host=192.168.17.1
tableaux_02 ansible_host=192.168.17.2
tableaux_03 ansible_host=192.168.17.3
tableaux_04 ansible_host=192.168.17.4
tableaux_05 ansible_host=192.168.17.5
tableaux_08 ansible_host=192.168.17.8
tableaux_09 ansible_host=192.168.17.9

[cluster:vars]
ansible_user=odyssey
ansible_ssh_private_key_file=/home/odyssey/.ssh/id_rsa

```

クラスター間での公開鍵認証設定を行う。
```shell
ssh-keygen -t rsa -b 4096
# パスワードは入力しない。empty のまま。

# 各計算ノードに公開鍵を配布する
ssh-copy-id odyssey@tableaux_01
ssh-copy-id odyssey@tableaux_02
...

# 以下のコマンドで問題ないことを確認する
ansible cluster -m ping
```

Playbookの作成は以下のように行う
```shell
vi update_nodes.yml
```

以下の内容を update_nodes.yml に書き込む。
以下の内容は、書き方の例であり、このまま実行するとエラーが起きる。
```shell
# ソフトウェアのインストール
---
- name: Update all cluster nodes
  hosts: cluster
  become: true
  tasks:
    - name: Update DNF package cache
      dnf:
        update_cache: yes
    
    - name: Upgrade all packages
      dnf:
        name: "*"
        state: latest

# ファイルの配布
---
- name: Deploy configuration files
  hosts: cluster
  become: true
  tasks:
    - name: Copy configuration file
      copy:
        src: /path/to/local/config.conf
        dest: /etc/application/config.conf
        owner: root
        group: root
        mode: '0644'

# ユーザー管理
---
- name: Setup users on cluster
  hosts: cluster
  become: true
  tasks:
    - name: Add cluster user
      user:
        name: cluster_user
        shell: /bin/bash
        groups: wheel
        append: yes


```

ヘッドノードで以下の設定を行う。

```shell
mkdir ~/cluster-setup
cd ~/cluster-setup
```
この中に、以下のようにファイルを置く。
```shell
cluster-setup/
├── ansible.cfg
├── inventory.ini
├── playbooks/
│   ├── hosts.yml
│   ├── nfs_client.yml
│   ├── ldap_sssd.yml
│   └── slurm.yml
├── templates/
│   └── hosts.j2
├── files/
│   ├── slurm.conf
│   └── sssd.conf
```

~/cluster-setup/ansible.cfg の中身
```shell
[defaults]
inventory = inventory.ini
host_key_checking = False
remote_user = odyssey
timeout = 60
```
inventory.ini の中身
```shell
[compute]
tableaux_01
tableaux_02
tableaux_03
tableaux_04
tableaux_05
tableaux_06
tableaux_07
tableaux_08
tableaux_09
tableaux_10
```

## Ansible で /etc/hosts を配布する場合
templates/hosts.j2 の中身
```shell
127.0.0.1   localhost
127.0.1.1   {{ inventory_hostname }}

192.168.17.100  rachmaninov
192.168.17.1    tableaux_01
192.168.17.2    tableaux_02
192.168.17.3    tableaux_03
192.168.17.4    tableaux_04
192.168.17.5    tableaux_05
192.168.17.6    tableaux_06
192.168.17.7    tableaux_07
192.168.17.8    tableaux_08
192.168.17.9    tableaux_09
192.168.17.10   tableaux_10
```
playbooks/hosts.yml の中身
```shell
- name: Deploy /etc/hosts to all compute nodes
  hosts: compute
  become: yes
  tasks:
    - name: Copy /etc/hosts file
      template:
        src: templates/hosts.j2
        dest: /etc/hosts
        owner: root
        group: root
        mode: '0644'
```
## Ansible で NFS クライアント設定
~/cluster-setup/playbooks/nfs_client.yml の中身
```shell
- name: Configure NFS client on compute nodes
  hosts: compute
  become: yes
  tasks:

    - name: Install NFS utilities
      package:
        name: nfs-utils
        state: present

    - name: Create mount point directory
      file:
        path: /mnt/data
        state: directory
        owner: root
        group: root
        mode: '0755'

    - name: Ensure NFS mount entry in /etc/fstab
      lineinfile:
        path: /etc/fstab
        line: "rachmaninov:/mnt/data  /mnt/data  nfs  defaults  0  0"
        state: present
        create: yes

    - name: Mount NFS share
      mount:
        path: /mnt/data
        src: "rachmaninov:/mnt/data"
        fstype: nfs
        state: mounted
```

# STEP.4 NFS の設定
ヘッドノード側での設定
```shell
sudo dnf install -y nfs-utils
sudo nano /etc/exports
```
/etc/exports には以下の内容を記述
```shell
/mnt/data  192.168.17.0/24(rw,sync,no_root_squash)
```
```shell
# NFSサービスを有効化・起動
sudo systemctl enable --now nfs-server
sudo exportfs -rav
# 動作確認
showmount -e localhost

# 以下のように表示される
# Export list for localhost:
# /mnt/data 192.168.17.0/24
```

計算ノードの設定は Ansible を利用して行う。
1. nfs-utils のインストール
1. /mnt/data ディレクトリ作成
1. /etc/fstab に NFSマウント設定を追記
1. 実際に /mnt/data をマウント

ファイル構成
```shell
cluster-setup/
├── ansible.cfg
├── inventory.ini
├── playbooks/
│   └── nfs_client.yml
```

~/cluster-setup/playbooks/nfs_client.yml
```shell
- name: Configure NFS client on compute nodes
  hosts: compute
  become: yes
  tasks:

    - name: Install NFS utilities
      package:
        name: nfs-utils
        state: present

    - name: Create mount point directory
      file:
        path: /mnt/data
        state: directory
        owner: root
        group: root
        mode: '0755'

    - name: Ensure NFS mount entry in /etc/fstab
      lineinfile:
        path: /etc/fstab
        line: "rachmaninov:/mnt/data  /mnt/data  nfs  defaults  0  0"
        state: present
        create: yes

    - name: Mount NFS share
      mount:
        path: /mnt/data
        src: "rachmaninov:/mnt/data"
        fstype: nfs
        state: mounted
```

まだ準備中の計算ノードがある場合は、inventory.ini で対象のノードをコメントアウトしておく。

以下のコマンドで Ansible による NFS の設定を実行。
```shell
# 実行時に計算ノードでのパスワードを聞かれる。
ansible-playbook playbooks/nfs_client.yml --ask-become-pass -e ansible_become_flags='-E'
```
成功すると以下のように表示される。
この操作では、各ノード dnf 経由で nfs-utils をインストールするので、インターネット接続がないとエラーが起きてしまう。
```shell
PLAY [Configure NFS client on compute nodes] **************************************************

TASK [Gathering Facts] ************************************************************************
ok: [tableaux_03]
ok: [tableaux_01]
ok: [tableaux_02]
ok: [tableaux_05]
ok: [tableaux_04]
ok: [tableaux_09]
ok: [tableaux_08]

TASK [Install NFS utilities] ******************************************************************
ok: [tableaux_03]
ok: [tableaux_02]
ok: [tableaux_04]
ok: [tableaux_01]
ok: [tableaux_05]
ok: [tableaux_08]
ok: [tableaux_09]

TASK [Create mount point directory] ***********************************************************
ok: [tableaux_04]
ok: [tableaux_03]
ok: [tableaux_02]
ok: [tableaux_05]
ok: [tableaux_01]
ok: [tableaux_09]
ok: [tableaux_08]

TASK [Ensure NFS mount entry in /etc/fstab] ***************************************************
ok: [tableaux_04]
ok: [tableaux_03]
ok: [tableaux_02]
ok: [tableaux_05]
ok: [tableaux_01]
ok: [tableaux_09]
ok: [tableaux_08]

TASK [Mount NFS share] ************************************************************************
changed: [tableaux_02]
changed: [tableaux_05]
changed: [tableaux_03]
changed: [tableaux_04]
changed: [tableaux_01]
changed: [tableaux_09]
changed: [tableaux_08]

PLAY RECAP ************************************************************************************
tableaux_01                : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
tableaux_02                : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
tableaux_03                : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
tableaux_04                : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
tableaux_05                : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
tableaux_08                : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
tableaux_09                : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```

各計算ノードで `df -h` で確認すると以下のように `rachmaninov:/mnt/data` が表示される。
```shell
df -h
Filesystem               Size  Used Avail Use% Mounted on
/dev/mapper/fedora-root  3.7T  723G  3.0T  20% /
devtmpfs                 4.0M     0  4.0M   0% /dev
tmpfs                     16G     0   16G   0% /dev/shm
efivarfs                 256K   58K  194K  23% /sys/firmware/efi/efivars
tmpfs                    6.3G  1.9M  6.3G   1% /run
/dev/nvme0n1p2           960M  653M  308M  68% /boot
tmpfs                     16G     0   16G   0% /tmp
/dev/nvme0n1p1           599M  7.6M  592M   2% /boot/efi
tmpfs                    3.2G   12K  3.2G   1% /run/user/1000
rachmaninov:/mnt/data     33T  2.0M   31T   1% /mnt/data
```