OpenPBSクラスターを構築する【Ubuntu22.04編】

本稿では、OpenPBSのクラスターをUbuntu 22.04 LTS上で構築する方法について述べる。

以下、[Altair EngineeringのOpenPBS](https://github.com/openpbs/openpbs)を、OpenPBSまたは単にPBSと呼ぶ。

OpenPBSの基本的な使い方については[前回の記事](https://qiita.com/amasaki203/items/4c78d08a100a99cfc323)を参照されたい。

## 概要

本稿では以下の内容を扱う。

- PBSのインストール
- PBSクラスターの構築
  - ノードの設定
  - キューの構築
- MPIのインストール
- フック（プラグイン）の作成・導入

UbuntuのバージョンはUbuntu Server 22.04.4 LTSを、Pythonのバージョンは3.11を使用する。

### 構成

- サーバーノードを1つ
  - ホスト名は`kanri`
  - これはジョブの受付、クラスターの管理などを受け持つ。ジョブの計算もする。
  - 4コア
- 計算ノードを1つ
  - ホスト名は`com1`
  - これはジョブの計算を担う
  - 8コア



以下、本稿ではコマンドの先頭に記す`%`をプロンプトとし、場合によってはその前にどのホストでの実行かを示す`@kanri`、`@com1`を付けることとする。

## 前準備

- SSHの設定：それぞれのユーザーについて、各ノードで互いに自分の鍵認証でSSH接続できるようにしておく。
- `/home`ディレクトリをNFSなどでノード間で共有している状態を仮定する。

### `/etc/hosts`

`/etc/hosts`には各ノードで以下のように記述して、名前解決できるようにしておく。ここでループバックアドレス`127.0.0.1`に`localhost`以外のホスト名が記述されていたら、それを削除する。

```shell
% cat /etc/hosts
127.0.0.1       localhost
192.168.0.11    kanri
192.168.0.12    com1
```

`127.0.0.1 kanri`や`127.0.0.1 com1`などというアドレスが存在している場合、PBSサーバープロセスを起動できない問題が生じるので削除しておく（この行はUbuntuのインストール時に追加される）。

### ポートの開放

ファイアーウォールを構成している場合は、15001番から15009番と17001番を開放する。

```shell
% sudo ufw allow 15001:15009/tcp
% sudo ufw allow 17001
```

本稿のテスト環境ではファイアーウォールを有効にせず進めるので、有効にする場合の詳しい情報については提供することができない。

## インストール

### パッケージマネージャーの更新

パッケージマネージャーを最新の状態にしておく。

```fortran
% sudo apt update
% sudo apt upgrade -y
```

### 依存関係パッケージのインストール

ビルド時依存関係のパッケージをインストールする。

```shell
% sudo apt install gcc make libtool libhwloc-dev libx11-dev libxt-dev libedit-dev \
   libical-dev ncurses-dev perl tcl-dev tk-dev swig libexpat-dev libssl-dev \
   libxext-dev libxft-dev autoconf automake g++ python3.11 python3.11-dev \
   postgresql-contrib postgresql-server-dev-all
```

実行時依存関係のパッケージをインストールする。

```shell
% sudo apt install expat libedit2 postgresql python3.11 python3.11-dev \
  sendmail-bin sudo tcl tk libical3 postgresql-contrib postgresql-server-dev-all
```

ビルドはサーバーノードで行って、計算ノードではインストールするだけならば、計算ノードには後者があればよい。

### OpenPBSのインストール

#### ソースコードをダウンロードして解凍する

GitHubのリリースページからソースコードのTarballをダウンロードする（現在のPBSの最新版は23.06.06）。

```shell
% wget https://github.com/openpbs/openpbs/archive/refs/tags/v23.06.06.tar.gz
```

ダウンロードしたファイルを`tar`コマンドで解凍して、そのディレクトリへ移動する。

```shell
% tar xvzf v23.06.06.tar.gz
% cd openpbs-23.06.06
```

#### パッチを当てる

システムのTclを使うように指示し、Python3.11でビルドできるようにするためのパッチを当てる。パッチは2つあり、以下の通りである。

```diff
--- m4/with_tcl.m4      2023-06-06 00:40:11.000000000 +0900
+++ m4/with_tcl.m4.patched      2024-03-07 23:03:58.144195644 +0900
@@ -82,10 +82,10 @@
   AC_SUBST(tk_version)
   AS_IF([test x$TCL_INCLUDE_SPEC = x],
     # Using developer installed tcl
-    [tcl_inc="-I$tcl_dir/include"]
-    [tcl_lib="$tcl_dir/lib/libtcl$TCL_VERSION.a $TCL_LIBS"]
-    [tk_inc="-I$tcl_dir/include"]
-    [tk_lib="$tcl_dir/lib/libtcl$TCL_VERSION.a $tcl_dir/lib/libtk$TK_VERSION.a $TK_LIBS"],
+#    [tcl_inc="-I$tcl_dir/include"]
+#    [tcl_lib="$tcl_dir/lib/libtcl$TCL_VERSION.a $TCL_LIBS"]
+#    [tk_inc="-I$tcl_dir/include"]
+#    [tk_lib="$tcl_dir/lib/libtcl$TCL_VERSION.a $tcl_dir/lib/libtk$TK_VERSION.a $TK_LIBS"],
     # Using system installed tcl
     [tcl_inc="$TCL_INCLUDE_SPEC"]
     [tcl_lib="$TCL_LIB_SPEC $TCL_LIBS"]
```

```diff
--- pbs_python_external.c       2023-06-05 15:40:11.000000000 +0000
+++ pbs_python_external.c.new   2024-03-08 05:48:52.773168993 +0000
@@ -54,7 +54,7 @@
 #ifdef PYTHON

 #include <pbs_python_private.h> /* private python file  */
-#include <eval.h>		/* For PyEval_EvalCode  */
+#include <ceval.h>		/* For PyEval_EvalCode  */
 #include <pythonrun.h>		/* For Py_SetPythonHome */
 #include <sys/types.h>
 #include <sys/stat.h>
```

前者のパッチファイルの名前を`first.patch`、後者のそれを`second.patch`と仮定しよう。カレントディレクトリで以下のコマンドを実行してパッチを適用する。

```shell
% patch m4/with_tcl.m4  < first.patch
% patch src/lib/Libpython/pbs_python_external.c < second.patch
```

このとき、必要なら`--ignore-whitespace`オプションを付ける。

#### 環境変数を準備する

Ubuntu 22.04の環境に合わせて、ビルドを通すために次の環境変数を設定する。

```shell
% export CFLAGS="-I/usr/include/tcl8.6"
% export LDFLAGS="-lm -ltk -ltcl"
% export PYTHON="/usr/bin/python3.11"
```

#### ビルド

インストール先は`/opt/pbs`とする。なお、`sendmail`コマンドの場所を`type sendmail`で確認しておく。

`authgen.sh`と`configure`を実行して構成を行い、その後`make`コマンドでビルドする。`make`の並列数はCPUに合わせて任意に変更して欲しい。

```
% ./autogen.sh
% ./configure --prefix=/opt/pbs --with-sendmail=/usr/sbin/sendmail
% make -j8
```

#### インストール

ビルドが完了したら、各ノードで`make install`コマンドを実行してPBSをインストールする。

```shell
@kanri % sudo make install
@com1 % sudo make install
```

以下のディレクトリが生成されて、中にファイルが配置されていればビルドは成功である。

```shell
% ls -1 /opt/pbs/
bin
etc
include
lib
libexec
sbin
share
unsupported
```

## インストール後

### `pbs_postinstall`スクリプト

PBSにはインストール後に実行しなければならないスクリプト`pbs_postinstall`が存在する。このスクリプトは`/opt/pbs/libexec`ディレクトリに配置されているので次のコマンドでこのスクリプトを実行する。

```shell
@kanri % sudo /opt/pbs/libexec/pbs_postinstall
@com1 % sudo /opt/pbs/libexec/pbs_postinstall
```

以下のようなメッセージが表示されれば完了である。

```
% sudo /opt/pbs/libexec/pbs_postinstall
*** PBS Installation Summary
***
*** Postinstall script called as follows:
*** /opt/pbs/libexec/pbs_postinstall ''
***
*** No configuration file found.
*** Creating new configuration file: /etc/pbs.conf
*** Replacing /etc/pbs.conf with /etc/pbs.conf.23.06.06
*** /etc/pbs.conf has been created.
***
*** Registering PBS as a service.
update-rc.d: error: no runlevel symlinks to modify, aborting!
Synchronizing state of pbs.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable pbs
Created symlink /etc/systemd/system/multi-user.target.wants/pbs.service → /lib/systemd/system/pbs.service.
***
*** PBS_HOME is /var/spool/pbs
*** Creating new file /var/spool/pbs/pbs_environment
*** WARNING: TZ not set in /var/spool/pbs/pbs_environment
***
*** The PBS server has been installed in /opt/pbs/sbin.
*** The PBS scheduler has been installed in /opt/pbs/sbin.
***
*** The PBS communication agent has been installed in /opt/pbs/sbin.
***
*** The PBS MOM has been installed in /opt/pbs/sbin.
***
*** The PBS commands have been installed in /opt/pbs/bin.
***
*** End of /opt/pbs/libexec/pbs_postinstall
```

ここで、`update-rc.d: error: no runlevel symlinks to modify, aborting!`というエラーが出た場合には、以下のコマンドを実行して自動起動の設定をしておく。

```
% sudo update-rc.d pbs defaults
```

### パーミッションの変更

`qsub`や`qstat`などのコマンドを一般ユーザーが使えるようにするために、特定の2つの実行ファイルに特殊なアクセス権限に変更しておく。

```shell
@kanri % sudo chmod 4755 /opt/pbs/sbin/pbs_iff /opt/pbs/sbin/pbs_rcp
```

### パスを通す

環境変数を読み込む設定を行う。すべてのユーザーでPBSのコマンドを使えるように、`/etc/bash.bashrc`に記述を追加する。このコマンドはroot権限で実行する。

```
@kanri # echo "source /etc/profile.d/pbs.sh" >> /etc/bash.bashrc
```

## クラスターの構成

インストール後スクリプトを実行すると`/etc/pbs.conf`や`/var/spool/pbs`ディレクトリなどが用意される。`pbs.conf`には、そのノードの役割に応じた設定を記述する。

### `pbs.conf`

#### サーバーノード

サーバーノードではサーバー、スケジューラー、コミュニケーターのデーモンを実行するのでそれぞれ、`PBS_START_SERVER`、 `PBS_START_SCHED`、`PBS_START_COMM`を値`1`に設定する。また、計算も実行するので`PBS_START_MOM`は値`1`に設定する。

```shell
@kanri % cat /etc/pbs.conf
PBS_SERVER=kanri
PBS_START_SERVER=1
PBS_START_SCHED=1
PBS_START_COMM=1
PBS_START_MOM=1
PBS_EXEC=/opt/pbs
PBS_HOME=/var/spool/pbs
PBS_CORE_LIMIT=unlimited
PBS_SCP=/usr/bin/scp
```

#### 計算ノード

計算ノードでは上の3つは実行する必要がないので値`0`に設定し、`PBS_START_MOM`の値を`1`に設定する。

```shell
@com1 % cat /etc/pbs.conf
PBS_SERVER=kanri
PBS_START_SERVER=0
PBS_START_SCHED=0
PBS_START_COMM=0
PBS_START_MOM=1
PBS_EXEC=/opt/pbs
PBS_HOME=/var/spool/pbs
PBS_CORE_LIMIT=unlimited
PBS_SCP=/usr/bin/scp
```

### `mom_priv/config`

`/home`ディレクトリをNFSなどで共有している前提なので、ジョブの結果のファイルの転送にSCPではなくCPを使うようにPBSに指示する。具体的には`/var/spool/pbs/mom_priv/config`を以下のように記述する。

```
% sudo cat /var/spool/pbs/mom_priv/config
$restrict_user_maxsysid 999
$clienthost kanri
$usecp *:/home/ /home/
```

- `$clienthost`には、サーバーノードのホスト名を記述する。

- `$usecp`には、`$usecp <hostname>:<source directory> <destination directory>`のように記述する。上の設定例では全てのホストの`/home`ディレクトリ内の移動にCPを使うように指示している。詳細は参考文献1のページAG-441を参照。

### `pbs_environment`

`/var/spool/pbs/pbs_enviroment`はジョブ実行時の環境変数を設定するファイルである。此のファイルにタイムゾーンの情報を追加する。

```shell
% sudo cat /var/spool/pbs/pbs_environment
PATH=/bin:/usr/bin
TZ="Asia/Tokyo"
```

### デーモンの起動

ここで、PBSのデーモンを起動する。

```
@com1  % sudo /etc/init.d/pbs start
...
@kanri % sudo /etc/init.d/pbs start
Starting PBS
PBS Home directory /var/spool/pbs needs updating.
Running /opt/pbs/libexec/pbs_habitat to update it.
***
*** Setting default queue and resource limits.
***
cp: cannot stat '/usr/pgsql-14.11/lib/*': No such file or directory
cp: cannot stat '/usr/pgsql-14.11/lib/*': No such file or directory
cp: cannot stat '/usr/pgsql-14.11/share/timezonesets/*': No such file or directory
cp: cannot stat '/usr/pgsql-14.11/share/timezonesets/*': No such file or directory
cp: cannot stat '/usr/lib/postgresql/14/bin/pg_resetxlog': No such file or directory
*** End of /opt/pbs/libexec/pbs_habitat
Home directory /var/spool/pbs updated.
/opt/pbs/sbin/pbs_comm ready (pid=14344), Proxy Name:amphibole:17001, Threads:4
PBS comm
PBS mom
PBS sched
Connecting to PBS dataservice...connected to PBS dataservice@amphibole
PBS server
```

次のコマンドで、デーモンのプロセスIDを確認することができる。

```shell
% sudo /etc/init.d/pbs status
pbs_server is pid 14474
pbs_mom is pid 14356
pbs_sched is pid 14358
pbs_comm is 14344
```

管理者権限がないと以下のように`not running`と表示されてしまうので注意。

```shell
% /etc/init.d/pbs status
pbs_server is not running
pbs_mom is pid 14356
pbs_sched is not running
pbs_comm is not running
```

この時点で`qstat`コマンドを実行してエラーが無いか確認しておこう。何も表示されなければ問題ない。

```
@kanri % qstat
```

次のようなメッセージが出る場合には、デーモンになにかのエラーが生じているので、 `/var/spool/pbs/server_logs`以下にあるログファイルを見るのがよいだろう（[トラブルシューティング](#トラブルシューティング)を参照）。

```shell
@kanri % qstat
Connection refused
qstat: cannot connect to server kanri (errno=15010)
```

### ノードの設定をする

#### `qmgr`コマンド

`qmgr`コマンドを使ってPBSのノード構成を行う。このコマンドはroot権限が必要である。

```
@kanri % sudo su
@kanri # qmgr
Qmgr:
```

`Qmgr:`というプロンプトが表示され、インタラクティブにコマンドを実行して構成を行うことができる。

ここでは例えば、現在のサーバー設定を表示するには`print server`コマンドを実行する。

```
Qmgr: print server
#
# Create queues and set their attributes.
#
#
# Create and define queue workq
#
create queue workq
set queue workq queue_type = Execution
set queue workq enabled = True
set queue workq started = True
#
# Set server attributes.
#
set server scheduling = True
set server default_queue = workq
set server log_events = 511
set server mailer = /usr/sbin/sendmail
set server mail_from = adm
set server query_other_jobs = True
set server resources_default.ncpus = 1
set server default_chunk.ncpus = 1
set server scheduler_iteration = 600
set server resv_enable = True
set server node_fail_requeue = 310
set server max_array_size = 10000
set server pbs_license_min = 0
set server pbs_license_max = 2147483647
set server pbs_license_linger_time = 31536000
set server eligible_time_enable = False
set server max_concurrent_provision = 5
set server max_job_sequence_id = 9999999
```

`quit`または`q`と入力することで、Qmgrを終了することができる。

#### ノードの構成

他のユーザーのジョブを問い合わせできるようにする。

```
Qmgr: set server query_other_jobs = True
```

`flatuid`属性を有効にしてもよい（これについては参考文献2のページUG-6を参照）。

```
Qmgr: set server flatuid = True
```

次に計算ノードの登録を行う。

```
Qmgr: create node com1
```

計算ノードの利用可能な並列数をここで設定することができる。

```
Qmgr: set node com1 resources_available.ncpus=8
```

ノードの構成を表示するには`print node`コマンドを使用する。

```
Qmgr: print node com1
#
# Create nodes and set their properties.
#
#
# Create and define node com1
#
create node com1
set node com1 state = free
set node com1 resources_available.arch = linux
set node com1 resources_available.host = com1
set node com1 resources_available.mem = 8060064kb
set node com1 resources_available.ncpus = 8
set node com1 resources_available.vnode = com1
set node com1 resv_enable = True
```

代わりに`print node @active`を使用してもよい。

使用されなくなったノードは`delete`コマンドで消すことができる。

```
Qmgr: delete node com1
```

登録されたノードは`pbsnodes`コマンドで確認することができる。

```
% pbsnodes -a
com1
     Mom = com1
     ntype = PBS
     state = free
     pcpus = 8
     resources_available.arch = linux
     resources_available.host = com1
     resources_available.mem = 8060064kb
     resources_available.ncpus = 8
     resources_available.vnode = com1
     resources_assigned.accelerator_memory = 0kb
     resources_assigned.hbmem = 0kb
     resources_assigned.mem = 0kb
     resources_assigned.naccelerators = 0
     resources_assigned.ncpus = 0
     resources_assigned.vmem = 0kb
     resv_enable = True
     sharing = default_shared
     license = l
     last_state_change_time = Fri Mar  8 00:19:31 2024
     last_used_time = Thu Mar  7 20:44:46 2024
```

## キューを設定する

以下のようなQmgrコマンドを使用して、キュー`ALL`を構成する。

```Qmgr: create queue ALL
Qmgr: create queue ALL
Qmgr: set queue ALL queue_type = Execution
Qmgr: set queue ALL Priority = 10
Qmgr: set queue ALL resources_default.ncpus = 12
Qmgr: set queue ALL resources_default.nodect = 2
Qmgr: set queue ALL resources_default.nodes =2
Qmgr: set queue ALL max_run_res.ncpus = [u:PBS_GENERIC=12]
Qmgr: set queue ALL enabled = True
Qmgr: set queue ALL started = True
```

このキューでは最大12コア（`kanri`の4コアと`com1`の8コア）を使用するように構成している。最後に

```shell
Qmgr: set queue ALL enabled = True
Qmgr: set queue ALL started = True
```

の2つのコマンドを実行することで、キューがアクティブになる。

現在アクティブなキューについて、以下のコマンドで設定を表示することができる。

```
Qmgr: print queue @active
#
# Create queues and set their attributes.
#
#
# Create and define queue workq
#
create queue workq
set queue workq queue_type = Execution
set queue workq enabled = True
set queue workq started = True
#
# Create and define queue ALL
#
create queue ALL
set queue ALL queue_type = Execution
set queue ALL Priority = 10
set queue ALL resources_default.ncpus = 2
set queue ALL resources_default.nodect = 2
set queue ALL resources_default.nodes = 2
set queue ALL max_run_res.ncpus = [u:PBS_GENERIC=12]
set queue ALL enabled = True
set queue ALL started = True
```

## MPIのインストール

すべての計算ノードにおいて、MPIライブラリをインストールすることで、分散メモリ並列計算の環境が整う。ここではOpenMPIを選択することにする（他のオプションとして、MPICHやIntelのoneAPI HPC Toolkitなどがある）。

```shell
sudo apt install openmpi-bin libopenmpi-dev
```

インストールはこれで完了。

OpenMPIの構成について調べてみる。

```shell
% ompi_info | grep "Configure command line"
  Configure command line: '--build=x86_64-linux-gnu' '--prefix=/usr' 
     '--includedir=${prefix}/include' '--mandir=${prefix}/share/man' 
     '--infodir=${prefix}/share/info' '--sysconfdir=/etc' '--localstatedir=/var' 
     '--disable-option-checking' '--disable-silent-rules'
     '--libdir=${prefix}/lib/x86_64-linux-gnu' '--runstatedir=/run' 
     '--disable-maintainer-mode' '--disable-dependency-tracking' '--disable-silent-rules'
     '--disable-wrapper-runpath' '--with-package-string=Debian OpenMPI' '--with-verbs'
     '--with-libfabric' '--with-psm' '--with-psm2' '--with-ucx'
     '--with-pmix=/usr/lib/x86_64-linux-gnu/pmix2' 
     '--with-jdk-dir=/usr/lib/jvm/default-java' '--enable-mpi-java'
     '--enable-opal-btl-usnic-unit-tests' '--with-libevent=external' 
     '--with-hwloc=external' '--disable-silent-rules' '--enable-mpi-cxx'
     '--enable-ipv6' '--with-devel-headers' '--with-slurm' '--with-sge' 
     '--without-tm' '--sysconfdir=/etc/openmpi' 
     '--libdir=${prefix}/lib/x86_64-linux-gnu/openmpi/lib'
     '--includedir=${prefix}/lib/x86_64-linux-gnu/openmpi/include'
```

`--without-tm`が指定されている。理想的には`--with-tm=/opt/pbs`を指定してビルドしインストールするべきであるが、パッケージマネージャーのバイナリでもとりあえず問題なく動作するようだ。

## ジョブを投入する

詳細は[前回の記事「ジョブスケジューラーOpenPBS/PBS Proの使い方」](https://qiita.com/amasaki203/items/4c78d08a100a99cfc323)を参照のこと。

以下のようなFortranのコードがあるとする。

```fortran
program main
   use :: mpi_f08
   implicit none

   integer :: ierr, me, petot
   character(8) :: hostname

   call mpi_init(ierr)
   call mpi_comm_size(mpi_comm_world, petot, ierr)
   call mpi_comm_rank(mpi_comm_world, me, ierr)

   ierr = hostnm(hostname)

   print *, hostname, ":", me, '/', petot

   call mpi_finalize()

end program main
```

これを`mpif90`コマンドでコンパイルする。

```
% mpif90 ./main.f90 -o a.out
```

このコマンドで生成された実行ファイル`a.out`を次のようなジョブスクリプト`sample.sh`を使って、ジョブをキューに投入する。

```bash
#!/bin/bash
### sample script for PBS
#PBS -q ALL
#PBS -N foo
#PBS -l select=1:ncpus=4:mpiprocs=4+1:ncpus=8:mpiprocs=8
#PBS -l walltime=60
#PBS -j oe

cd $PBS_O_WORKDIR
PETOT=$(wc -l < $PBS_NODEFILE)
/opt/pbs/bin/mpiexec -np $PETOT --hostfile $PBS_NODEFILE ./a.out
```

```shell
% qsub sample.sh
```

ジョブが完了すると、ディレクトリに`foo.oX`が生成され、結果を見ることができる。

```shell
% cat foo.o*
 kanri   :           0 /          12
 kanri   :           3 /          12
 kanri   :           1 /          12
 kanri   :           2 /          12
 com1    :           5 /          12
 com1    :           6 /          12
 com1    :           7 /          12
 com1    :           9 /          12
 com1    :          10 /          12
 com1    :          11 /          12
 com1    :           8 /          12
 com1    :           4 /          12
```

## フックを導入する

PBSではフック（Hook）と呼ばれるPythonで書かれたプラグインを導入することができる。

フックは、それが実行される段階で区別され、以下のような種類（フックタイプ）が存在する。

- ジョブフック

  - 実行前フック（サーバーフック）

    - `queuejob`：ジョブが提出され、キューに入れられる前に実行される

    - `modifyjob`：キューに入れられた後、ジョブを変更する場合に実行される。
    - `movejob`：キューに入れられた後、ジョブを移動する場合に実行される。
    - `runjob`：サーバーがMoM（**M**achine **O**riented **M**ini-server、計算ノードのデーモンのこと）にジョブを送信する前に実行される。
  - 実行フック（MoMフック）
    - `execjob_begin`
    - `execjob_attach`
    - `execjob_prologue`
    - `execjob_launch`
    - `execjob_postsuspend`
    - `execjob_preresume`
    - `execjob_end`
    - `execjob_epilogue`
    - `execjob_preterm`
- 非ジョブフック

  - `exechost_startup`
  - `exechost_periodic`
  - `resvsub`
  - `resv_end`
  - `provision`
  - `periodic`

各フックタイプの実行タイミングの詳細については、参考文献3の第4章 "Hook Basics"を参照されたい。

以下では、ドキュメント（参考文献3の234ページ）に例として掲載されている「`walltime`が指定されていないジョブをはじく」フックについて、その書き方とその導入方法について見ていこう。

### `queuejob`フックの例

#### `RequireWalltime.py`

以下のような、Pythonのコード`RequireWalltime.py`があるとする。

```python
import pbs
import sys

eve = pbs.event()
job = eve.job

try:
    if job.Resource_List["walltime"] == None:
        eve.reject("Job has no walltime requested.")

except SystemExit:
    pass

except pbs.UnsetResourceNameError:
    eve.reject("Job has no walltime requested.")
```

このコードがフック`RequreWalltime`の全体であり、次に各行で何をしているかを細かく説明する。

まず、ヘッダーの2行で`pbs`と`sys`モジュールをインポートしている。

その後、`pbs`の関数`event()`をコールして結果を変数`eve`に代入し、さらに`eve`のコンポーネント`job`を変数`job`に代入している。

次の`try:`セクションでは、条件式`job.Resource_List["walltime"] == None`を評価する。ここで真（ジョブスクリプトに`walltime`を指定しなかった）ならば、関数`e.reject()`をコールしてジョブを拒否（リジェクト）し、偽ならばなにもしない。

もし`j.Resource_List["walltime"]`がセットされていない場合には、例外`pbs.UnsetResourceNameError`が投げられて、最後の2行が実行されてジョブは拒否される。

フックのスクリプトでは、拒否されずにプログラム終端に来た場合は、受理（アクセプト）されたものとして扱われる。

`pbs.event().job.Resource_List`のオブジェクトには、ジョブスクリプトのヘッダー`#PBS`で指示された様々な値が格納されている。詳しくは参考文献3を参照して欲しい。

なお、`pbs.event().accept()`と`pbs.event().reject()`はどちらも、`SystemExit`例外を投げてフックの実行を終了する。引数の無い`try except`はすべての例外をキャッチするが、フックの内容自体が`try except`である場合には、`SystemExit`を通常に発生するものとして扱うために、次のコードを追加するべきである。

```python
except SystemExit:
   pass
```

ただし既存のコードに対して、特定の例外のみをキャッチする場合は、これを追加する必要はない。

### フックの導入

フックの導入には`qmgr`コマンドを使用するが、これを実行するには管理者権限が必要である。

```shell
% sudo qmgr -c 'create hook RequireWalltime event=queuejob'
% sudo qmgr -c 'import hook RequireWalltime application/x-python default RequireWalltime.py'
% sudo /etc/init.d/pbs restart
```

まず最初のコマンドで、`RequireWalltime`という名前のフックオブジェクト（この時点では空のオブジェクト）を作成している。このときフックの`event`属性を`queuejob`に指定している。

次のコマンドで、先に作成したフックオブジェクトに対して実際にPythonコードを読み込ませている。ここで`default`とは文字のエンコードを指定しており、他に`base64`を指定することもできる。コマンドの最後にPythonコードのファイル名を指定する。

フックを導入した後はデーモンを再起動して変更を反映させる必要がある。この例ではフックタイプが`queuejob`（ジョブの提出時に実行される）なので、サーバーノードだけでよい。

### フックの削除

フックの削除は、以下のように`qmgr`のコマンドを実行する。その後、デーモンを再起動する。

```shell
% sudo qmgr -c 'delete hook RequireWalltime'
% sudo /etc/init.d/pbs restart
```

## トラブルシューティング

### Dataserviceがすぐ終了してしまう

PBSのPostgreSQLのプロセスが起動していない場合は、サーバー（PBS_SERVERデーモン）の正常な起動に失敗している。

```
% ps aux | grep '^postgres' | grep pbs
postgres    1578  0.0  0.3 230412 28672 ?        Ss   15:46   0:00 /usr/lib/postgresql/14/bin/postgres -D /var/spool/pbs/datastore -p 15007
postgres    1722  0.0  0.2 232828 20060 ?        Ss   15:46   0:00 postgres: postgres pbs_datastore 
```

上の2つのプロセスが起動しているかを調べるとよい。

PBSのデータサービスが正しく起動しない問題は、デーモンの初回の起動に`/etc/init.d/pbs start`ではなく、`systemctl start pbs`としてしまった場合や、`/etc/hosts`に`127.0.0.1 kanri`などと記述された状態で`pbs_postinstall`を実行した場合に生じるようだ（経験的に）。この状態では、`/var/spool/pbs`以下のなにかのファイルが正しく生成されないためか、PBSサーバーの起動に失敗する。

これに対する処方は、`/var/spool/pbs`以下を一旦削除し、上記の点を修正した上で`pbs_postinstall`スクリプトを再度実行することである。その後、改めて`/etc/init.d/pbs start`を実行する。もっと良い解決策があるかもしれないが、これについてはわからない。

## あとがき

本稿ではソースコードからビルドしてインストールする方法を紹介したが、Ubuntu 20.04ではOpenPBSのバイナリが提供されているらしいので、そちらを使うほうがやや楽ではある。

[OpenPBSのコミュニティ掲示板](https://community.openpbs.org/)もあるようなので、クラスターの構成で困った場合は参照するとよいだろう。

## 参考文献

[Altair Community - Documentation](https://community.altair.com/community?id=altair_product_documentation)より

1. "Altair PBS Professional 2022.1 - Administrator's Guide"

2. "Altair PBS Professional 2024.1 - User's Guide"

3. "Altair PBS Professional 2021.1.3 - Plugins (Hooks) Guide"

その他のウェブサイト・ページ

4\. [自作クラスタ計算機のpbs_proの基本設定](http://www2.yukawa.kyoto-u.ac.jp/~koudai.sugimoto/dokuwiki/doku.php?id=%E8%87%AA%E4%BD%9C%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF%E8%A8%88%E7%AE%97%E6%A9%9F:pbs_pro%E3%81%AE%E5%9F%BA%E6%9C%AC%E8%A8%AD%E5%AE%9A)

5\. [ジョブスケジューラPBSProでGPU計算クラスタを組みAIを効率的に学習させる方法 (前編)](https://qiita.com/MasafumiTsuyuki/items/acf4b19b54937496e32b)

6\. [ジョブスケジューラPBSProでGPU計算クラスタを組みAIを効率的に学習させる方法 (後編)](https://qiita.com/MasafumiTsuyuki/items/a3674c2654fc50477d0b)
   - この記事はGPUを計算リソースとしてジョブスケジューリングする方法について詳しい。