Fedora で作る PC クラスター part2

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

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

Part 2 では、LDAP、SSSD、SLURM の設定を行う。

# 検証環境

![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)
Fedora 40 server edition

・バックアップノード（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.5 LDAP の設定
＜注意＞本記事の内容は、Fedora41 ではエラーが起きる。全ノード Fedora40 を使用する。

ヘッドノードで LDAP の設定を行う。
```shell
# 必要なパッケージのインストール
sudo dnf install -y 389-ds-base
# セットアップスクリプトの実行（対話形式）
sudo dscreate interactive

# 以下のように回答する。passward と ready to install 以外は Enter キーを押すだけ。
Enter system\'s hostname [rachmaninov]: 
Enter the instance name [rachmaninov]: 
Enter port number [389]: 
Create self-signed certificate database [yes]: 
Enter secure port number [636]: 
Enter Directory Manager DN [cn=Directory Manager]: 
Enter the Directory Manager password: 
Password must be at least 8 characters long
Enter the Directory Manager password: 
Confirm the Directory Manager Password: 
Choose whether mdb or bdb is used. [mdb]: 
Enter the lmdb database size [20Gb]: 
Enter the database suffix (or enter "none" to skip) [dc=rachmaninov]: 
Create sample entries in the suffix [no]: 
Create just the top suffix entry [no]: 
Do you want to start the instance after the installation? [yes]: 

Are you ready to install? [no]: yes
Starting installation ...
Validate installation settings ...
Create file system structures ...
Create self-signed certificate database ...
Perform SELinux labeling ...
Create database backend: dc=rachmaninov ...
Perform post-installation tasks ...
Completed installation for instance: slapd-rachmaninov
```

```shell
# サービスの起動と有効化
sudo systemctl enable --now dirsrv@rachmaninov
# 何も表示されなければ OK

# さらに確認
ls /etc/dirsrv/
# 結果：config  schema  slapd-rachmaninov  ssca

sudo dsconf slapd-rachmaninov backend suffix list
# 結果：dc=rachmaninov (userroot)

# 管理ツールを設定(GUI)
sudo dnf install cockpit cockpit-389-ds -y
sudo systemctl enable --now cockpit.socket
```

web ブラウザで管理ページ（GUI）にアクセスする。
https://rachmaninov:9090/system
アクセスできない場合は、rachmaninov を ip アドレスに置き換える。

ここから設定ファイルを作成する。
ヘッドノードのデスクトップ上の `cluster-setup` というディレクトリ内に `ldap` というディレクトリを作成する。
そして、basedn.ldif  base.ldif  user_odyssey.ldif という 3 つ .ldif ファイルを作成する。
それぞれの内容は、
```shell
#basedn.ldif の内容
dn: dc=rachmaninov
objectClass: top
objectClass: domain
dc: rachmaninov

# base.ldif の内容
dn: ou=People,dc=rachmaninov
objectClass: organizationalUnit
ou: People

dn: ou=Group,dc=rachmaninov
objectClass: organizationalUnit
ou: Group

# user_odyssey.ldif の内容
dn: uid=odyssey,ou=People,dc=rachmaninov
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: odyssey
sn: Odyssey
uid: odyssey
uidNumber: 1001
gidNumber: 1001
homeDirectory: /mnt/data/home/odyssey
loginShell: /bin/bash
userPassword: {SSHA}xxxxxxxxxxxx

# 上記の {SSHA}の後のパスワードには、以下のコマンドで生成したパスワードを貼り付ける
# openssl passwd -6
# 公開鍵認証の場合は、userPassword の行を削除し、Ansible で公開鍵情報を一括管理する。
```

homeDirectory では、NFS で共有しているディレクトリを指定するように気をつける。

# STEP.6 SSSD の設定
ヘッドノード側での設定
```shell
# 必要なパッケージをインストール
sudo dnf install -y sssd openldap-clients oddjob-mkhomedir
続いて、/etc/sssd/sssd.conf を作成する。
sudo mkdir -p /etc/sssd
sudo vi /etc/sssd/sssd.conf
sudo chmod 600 /etc/sssd/sssd.conf
```

/etc/sssd/sssd.conf の内容は以下の通り。

```shell
[sssd]
services = nss, pam
config_file_version = 2
domains = LDAP

[domain/LDAP]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldap://localhost
ldap_search_base = dc=rachmaninov
ldap_tls_reqcert = never
cache_credentials = true
enumerate = true

```

/etc/sssd/sssd.conf の内容を ~/cluster-setup/files にコピーする
```shell
sudo cp /etc/sssd/sssd.conf ~/cluster-setup/files/.
```

このとき、/cluster-setup/files/sssd.conf 内の
ldap_uri = ldap://localhost の行を
ldap_uri = ldap://rachmaninov のように書き直す。

/etc/nsswitch.conf を編集
以下3行を必ず sss を含めるようにする。
```shell
passwd:     files sss
group:      files sss
shadow:     files sss
```

SSSDサービスを有効化・起動する
``` shell
sudo systemctl enable --now sssd

# ホームディレクトリ自動作成の設定
sudo systemctl enable --now oddjobd
# /etc/pam.d/sshd の末尾に以下を追加
session required pam_mkhomedir.so skel=/etc/skel/ umask=0077
```

動作確認
```shell
id odyssey
# uid=1000(odyssey) gid=1000(odyssey) groups=1000(odyssey),10(wheel) と表示される
```

ここからは、計算ノード側の設定を行う。
まず、ヘッドノードで以下の playbook を作成する。
~/cluster-setup/playbooks/ldap_sssd.yml
```shell
- name: Configure LDAP + SSSD on compute nodes
  hosts: compute
  become: yes
  tasks:

    - name: Install required packages
      package:
        name:
          - sssd
          - openldap-clients
          - oddjob-mkhomedir
        state: present

    - name: Ensure /etc/sssd directory exists
      file:
        path: /etc/sssd
        state: directory
        owner: root
        group: root
        mode: '0755'

    - name: Copy sssd.conf with correct permissions
      copy:
        src: ../files/sssd.conf
        dest: /etc/sssd/sssd.conf
        owner: root
        group: root
        mode: '0600'

    - name: Enable and start sssd
      service:
        name: sssd
        state: started
        enabled: yes

    - name: Update nsswitch.conf for passwd
      lineinfile:
        path: /etc/nsswitch.conf
        regexp: '^passwd:'
        line: 'passwd:     files sss'

    - name: Update nsswitch.conf for group
      lineinfile:
        path: /etc/nsswitch.conf
        regexp: '^group:'
        line: 'group:      files sss'

    - name: Update nsswitch.conf for shadow
      lineinfile:
        path: /etc/nsswitch.conf
        regexp: '^shadow:'
        line: 'shadow:     files sss'

    - name: Enable and start oddjobd (for home dir creation)
      service:
        name: oddjobd
        state: started
        enabled: yes

    - name: Enable mkhomedir in pam.d/sshd
      lineinfile:
        path: /etc/pam.d/sshd
        insertafter: EOF
        line: 'session required pam_mkhomedir.so skel=/etc/skel/ umask=0077'
        state: present

```

そして、以下のコマンドを実行する。
```shell
ansible-playbook playbooks/ldap_sssd.yml --ask-become-pass 
```

# STEP.7 SLURM のインストール
以下の流れで設定を行う。
1. Munge のインストールとキー共有
1. SLURM パッケージのインストール
1. slurm.conf の作成と配布（ノード情報を含む）
1. slurmctld（ヘッドノード）と slurmd（計算ノード）の起動
1. sinfo, srun, sbatch で動作確認

Ansible を用いて、以下のように行う。
1. Ansible で Munge をインストール＆鍵を共有
ヘッドノードで `/etc/munge/munge.key` を作成
Ansible で計算ノードへ配布

2. Ansible で SLURM をインストール
`dnf install slurm slurm-slurmd slurm-slurmctld` など
`slurm.conf` を `/etc/slurm/` に配布

3. サービスの起動
`systemctl enable --now munge`
`systemctl enable --now slurmd or slurmctld`

以下の Ansible ファイルを作成する。
```shell
cluster-setup/
├── inventory.ini
├── ansible.cfg
├── playbooks/
│   ├── munge.yml           ← munge.key 配布＆起動
│   ├── slurm_common.yml    ← SLURMパッケージインストール
│   ├── slurm_controller.yml← ヘッドノード設定
│   └── slurm_compute.yml   ← 計算ノード設定
└── files/
    ├── slurm.conf
    ├── gres.conf
    └── munge.key
```

まずは、ヘッドノードで munge の設定を行う。
```shell
sudo dnf install -y munge munge-libs

# 1. 必要なディレクトリを作成
sudo mkdir -p /etc/munge
sudo chown munge:munge /etc/munge
sudo chmod 700 /etc/munge

# 2. munge.key を手動生成
sudo dd if=/dev/urandom bs=1 count=1024 of=/etc/munge/munge.key
sudo chown munge:munge /etc/munge/munge.key
sudo chmod 400 /etc/munge/munge.key
sudo cp /etc/munge/munge.key ~/cluster-setup/files/
sudo chmod 644 ~/cluster-setup/files/munge.key

# 3. munge サービスの起動
sudo systemctl enable --now munge

#4. 動作確認
munge -n | unmunge
# 以下のように表示される。
STATUS:          Success (0)
ENCODE_HOST:     rachmaninov (192.168.17.100)
ENCODE_TIME:     2025-04-09 17:50:51 +0900 (1744188651)
DECODE_TIME:     2025-04-09 17:50:51 +0900 (1744188651)
TTL:             300
CIPHER:          aes128 (4)
MAC:             sha256 (5)
ZIP:             none (0)
UID:             odyssey (1000)
GID:             odyssey (1000)
LENGTH:          0

```

`~/cluster-setup/playbooks/munge.yml` の中身
```shell
- name: Setup munge
  hosts: all
  become: yes
  tasks:
    - name: Install munge
      package:
        name: munge
        state: present

    - name: Ensure /etc/munge exists
      file:
        path: /etc/munge
        state: directory
        owner: munge
        group: munge
        mode: '0700'

    - name: Copy munge.key
      copy:
        src: ../files/munge.key
        dest: /etc/munge/munge.key
        owner: munge
        group: munge
        mode: '0400'

    - name: Enable and start munge
      systemd:
        name: munge
        enabled: true
        state: started
```

`~/cluster-setup/playbooks/slurm_common.yml` の中身
```shell
- name: Install Slurm packages
  hosts: all
  become: yes
  tasks:
    - name: Ensure slurm user and group exist
      ansible.builtin.user:
        name: slurm
        shell: /sbin/nologin
        system: yes

    - name: Install slurm and dependencies
      ansible.builtin.package:
        name:
          - slurm
          - slurm-slurmd
          - slurm-slurmctld
        state: present

    - name: Create slurm spool/log directories
      ansible.builtin.file:
        path: "{{ item }}"
        state: directory
        owner: slurm
        group: slurm
        mode: '0755'
      loop:
        - /var/spool/slurmctld
        - /var/spool/slurmd
        - /var/log/slurm
```


`~/cluster-setup/files/slurm.conf` の中身
```shell
ClusterName=research-cluster
SlurmctldHost=rachmaninov
SlurmUser=slurm
SlurmdUser=root
StateSaveLocation=/var/spool/slurmctld
SlurmdSpoolDir=/var/spool/slurmd
AuthType=auth/munge
SelectType=select/cons_tres
SelectTypeParameters=CR_Core_Memory
SchedulerType=sched/backfill
NodeName=tableaux_[01-09] CPUs=16 Gres=gpu:1 State=UNKNOWN
PartitionName=normal Nodes=tableaux_[01-09] Default=YES MaxTime=INFINITE State=UP
```


`~/cluster-setup/playbooks/slurm_controller.yml` の中身
```shell
- name: Setup SLURM controller (slurmctld)
  hosts: controller
  become: yes
  tasks:
    - name: Copy slurm.conf
      copy:
        src: ../files/slurm.conf
        dest: /etc/slurm/slurm.conf
        owner: root
        group: root
        mode: '0644'

    - name: Enable and start slurmctld
      systemd:
        name: slurmctld
        enabled: true
        state: started
```

`~/cluster-setup/playbooks/slurm_compute.yml` の中身
```shell
- name: Setup SLURM compute nodes (slurmd)
  hosts: compute
  become: yes
  tasks:
    - name: Copy slurm.conf
      copy:
        src: ../files/slurm.conf
        dest: /etc/slurm/slurm.conf
        owner: root
        group: root
        mode: '0644'

    - name: Copy gres.conf (if needed)
      copy:
        src: ../files/gres.conf
        dest: /etc/slurm/gres.conf
        owner: root
        group: root
        mode: '0644'

    - name: Enable and start slurmd
      systemd:
        name: slurmd
        enabled: true
        state: started
```

前回作った inventory.ini は、以下のようにヘッドノードへの接続方法と計算ノードの GPU の枚数を書き加えておく。
```shell
#rachmaninov
rachmaninov ansible_connection=local

[compute]
tableaux_01
tableaux_02
tableaux_03
tableaux_04
tableaux_05
tableaux_08
tableaux_09

[compute:vars]
gpu_count_map={"tableaux_01": 2,"tableaux_02": 2,"tableaux_03": 2,"tableaux_04": 1,"tableaux_05": 1,"tableaux_08": 1,"tableaux_09": 1}
```

以下のコマンドで順次実行する。
```shell
cd ~/cluster-setup
ansible-playbook playbooks/munge.yml --ask-become-pass
ansible-playbook playbooks/slurm_common.yml --ask-become-pass
ansible-playbook playbooks/slurm_controller.yml --ask-become-pass
ansible-playbook playbooks/slurm_compute.yml --ask-become-pass
```

以下のコマンドで正常に起動しているかを確認する。
```shell
# ヘッドノード
sudo systemctl status slurmctld
sudo systemctl status slurmd
# 計算ノード
sudo systemctl status slurmd
```

slurm が読み込む slurm.conf の場所を指定する
```shell
sudo mkdir -p /var/log/slurm
sudo vi /etc/systemd/system/slurmd.service.d/override.conf

# 以下を追記する
[Service]
Environment="SLURM_CONF=/etc/slurm/slurm.conf"
Environment="SLURM_LOG_DIR=/var/log/slurm"

# 以下のコマンドで設定を反映する
sudo systemctl daemon-reload
sudo systemctl restart slurmd
```

また、各計算ノードの slurm 通信用のポートを開ける
```shell
sudo firewall-cmd --list-all
sudo firewall-cmd --add-port=6817-6818/tcp --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
```

以下のコマンドで各ノードの状態を確認し、問題なければ slurm の設定完了。
```shell
sinfo
```

## エラー対応

初期設定でうまくいかない場合は、ノード間の通信状況を確認する。
ping や ssh で接続できても、6817-6818/tcp のポートが空いていないと通信エラーが起きる。

再起動後に slurm がうまく起動しない場合は、journalctl -xe で詳細を確認する。
また、/etc/slurm/gres.conf 内に gpu の path がきちんと設定してあり、その path が存在していることを確認する。再起動後に /dev/nvidia0 が消えることがある。その場合は、以下のコマンドを実行する。

```shell
# 各計算ノードで実行する。
nvidia-smi
nvidia-modprobe
ls /dev/nv*

# /dev/nvidia0 が存在することを確認してから、以下を実行。
sudo systemctl restart slurmd
```