AWSではじめるOpen OnDemand

# 本記事の目的
前記事の[さくらのVPSではじめるOpen OnDemand](https://qiita.com/mnakao/items/a3ad18d845c209395aa2)では、1台の計算機を使ったOpen OnDemandの構築方法を紹介しました。しかし、実環境では複数台の計算機で構成されたクラスタ環境が用いられます。

そこで本記事では、クラスタ環境を用いたOpen OnDemandの構築方法を紹介します。具体的には、AWS (Amazon Web Services) 上にクラスタ環境を構築し、そのヘッドノードにOpen OnDemandをインストールします[^1]。AWS上にクラスタ環境を構築する方法として、AWS公式ツールである[AWS ParallelCluster](https://aws.amazon.com/jp/hpc/parallelcluster/)を用います。本記事で用いるOpen OnDemandのバージョンは3.1.1、AWS ParallelClusterのバージョンは3.8.0です。

# クラスタ環境の構築
## AWS ParallelClusterとは
AWS上でクラスタ環境を構築するには、AWSが提供している様々なサービスを組み合わせる必要があります。具体的には、Amazon EC2（Elastic Compute Cloud）、Amazon VPC（Virtual Private Cloud）、NFS（Network File System）などの共有ファイルシステム、Slurmなどのジョブスケジューラ、AWS Auto Scalingを用いた計算ノードの自動増減などです。

AWS ParallelClusterは、それらの設定を自動化し、統一的な管理を可能にします。AWS ParallelClusterにはWeb上で操作するGUIバージョン（AWS ParallelCluster UI）とコマンドラインで操作するCUIバージョンがあり、本記事では簡単なGUIバージョンを用います。

## クラスタ環境の概念図
今回、AWS上で構築するクラスタ環境の概念図は下記の通りです。
![gainen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/5d9304ff-03a4-da8c-daed-38eff53c8e4f.png)


Open OnDemandはヘッドノードにインストールし、ユーザはヘッドノードにWebブラウザを使ってログインします。そして、ヘッドノードからSlurmを通して計算ノードにジョブを投入します。必要に応じて、AWS Auto Scalingにより計算ノードの数を増減させます。計算ノードはヘッドノードにNFSマウントしており、ヘッドノードのファイルシステムの一部を共有化します。

## AWS ParallelCluster UIのインストール
[AWS ParallelCluster UI クイック作成リンク](https://docs.aws.amazon.com/ja_jp/parallelcluster/latest/ug/install-pcui-v3.html)の中からリージョンを選択します。今回は「ap-northeast-1（アジアパシフィック：東京）」を選択しました。

![UI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/1b52de35-2f03-083b-0a99-e9277f869ec5.png)

次のスタック作成の画面では、スタック名に適当な名前（デフォルトのparallelcluster-uiのままでもOK）、Admin's Emailに自身のメールアドレス、機能の項目をすべてチェックした後、「スタックの作成」を実行します（15分程度かかります）。入力したメールアドレスに一時パスワードが送信されます。

![CloudFormation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/12afc8b8-af61-c772-f1cb-e46811966a8b.png)

![function.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/a3504819-9de2-32ae-8460-ce4e2832054e.png)

次に表示されるAWS CloudFormationで、スタックの名前のリンクを選択し、出力タブを選択します。

![output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/e27e3c88-c9fb-5970-3caa-3d6f2a962bb5.png)

その中の「ParallelClusterUIUrl」のURLをクリックすると、Emailとパスワードの画面が現れるので、スタック作成時に入力したAdmin's Emailと一時パスワードを入力します。その次の画面で正式なパスワードを設定します。

以上の操作で、AWS ParallelCluster UIのホームページが表示されます。

![ParallelCluster.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/f3d81e17-7a2f-dc67-cd41-d064f3bec811.png)

次にクラスタ環境を作成します。早速「Create cluster」のボタンをクリックしてもよいのですが、その前に左の「Images」をクリックください。AWS ParallelClusterが対応しているAMI（Amazon Machine Image）の一覧が表示されます。

![officialimages.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/ea3667be-be07-aab3-2422-9926a4eaa9a5.png)

クラスタ環境は基本的にこれらのAMIを用いて構築されますが、この一覧にないカスタムイメージを作成することもできます。そこで、今回はRocky Linux 8.9（arm64アーキテクチャ）のカスタムイメージを作成したいと思います[^2]。さらに、そのカスタムイメージには、HPC (High Performance Computing) 向けのコンテナプラットフォームであるSingularityをインストールしておきます。

「Custom」タブを選択し、「Build image」をクリックします。
![customimages.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/f518e6b0-2d4e-a375-0493-531f01056d16.png)

Image IDは適当な名前を入力ください。RegionとParenetImageは[https://rockylinux.org/cloud-images/](https://rockylinux.org/cloud-images/)から選択しています。Nameは適当なAMIの名前です。InstanceTypeはarm64アーキテクチャのAWS Graviton 3を選択しています。ComponentsのValueにはSingularityをインストールするため、`dnf install -y singularity-ce`と記載したスクリプトを適当なWebサイトに保存し、そのURLを入力します[^3]。

![customimage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/82f23aae-8d0b-35e3-7a29-e279c64c7aeb.png)

```YAML
Region: ap-northeast-1
Image:
  Name: Rocky Linux 8.9 Custom AMI for PCluster 3.8.0
Build:
  InstanceType: c6g.large
  ParentImage: ami-0e0226a3734dea3e4
  Components:
    - Type: script
      Value: https://mnakao.net/singularity.sh
```

最後に「Build Image」をクリックします。カスタムイメージの完成までに1時間程度かかります。完成すると、カスタムイメージのAMI IDが表示されるので、それをコピーしておきます。

AWS ParallelCluster UIのトップページに戻り、「Create cluster」をクリックします。Nameはクラスタの名前を設定します。ここでは「Rokkosan」にしています。Operating Systemでは「Use a custom Amazon Machine Image (AMI)」を選択し、コピーしておいたカスタムイメージのAMI IDを入力します。VPCはデフォルトVPCなどを選択します。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/ca85ef06-465e-fa88-bb03-06e2937c2129.png)

ヘッドノードの設定を行います。Instance typeはカスタムイメージを作成したときに指定したのと同じ「c6g.large」にしました。また、AWSのコンソールからヘッドノードにログインするために「Add SSM session」をチェックします。「Set IMDS secured」はデフォルトでチェックされていたので、そのままです。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/52aefc5d-f4b3-3e4b-39b1-327006593be0.png)

計算ノード設定を行います。Instance typeはヘッドノードと同じにしました（CPUコア数やメモリ量を大きくしてもOKです）。Static nodesは1、Dynamic nodesは4にしました。この設定では、計算ノードは常に1台立ち上がっており、ユーザの要望に応じて最大4台の計算ノードが自動的に起動・停止します。

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/f37643d2-79c6-20ff-264b-0a656d1314b3.png)

これまで設定した情報がYAML形式で閲覧できます。Osの行を「Os: rocky8」と変更してください。「Dry run」を実行し、問題がなければ、「Create」をクリックすると、10分程度でクラスタ環境が構築できます。

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/aa9b24a8-13b2-b947-a961-badec509acd1.png)

# Open OnDemandの設定
## Open OnDemandのインストール
完成したクラスタ名をチェックし、「Shell」をクリックするとそのヘッドノードにログインできます。
![top.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/c2ab79d1-e580-a376-4ec7-060121cb4146.png)

Open OnDemandのインストールを行います。
```SHELL
sudo dnf -y config-manager --set-enabled powertools
sudo dnf -y install epel-release
sudo dnf -y module enable ruby:3.1 nodejs:18
sudo dnf -y install https://yum.osc.edu/ondemand/3.1/ondemand-release-web-3.1-1.el8.noarch.rpm
sudo dnf -y install ondemand
sudo systemctl start httpd
sudo systemctl enable httpd
```

## 認証の設定
Open OnDemandではApache2.4でサポートしている認証を利用できます。詳細は[こちら](https://osc.github.io/ood-documentation/latest/authentication.html)を参照ください。今回は簡単なため[PAM認証](https://osc.github.io/ood-documentation/release-1.8/authentication/pam.html)の設定を行いますが、この認証は推奨されていないことに注意ください。

```SHELL
sudo dnf -y install mod_authnz_pam
echo "LoadModule authnz_pam_module modules/mod_authnz_pam.so" | sudo tee /etc/httpd/conf.modules.d/55-authnz_pam.conf
sudo cp /etc/pam.d/sshd /etc/pam.d/ood
sudo chmod 640 /etc/shadow
sudo chgrp apache /etc/shadow
```

`/etc/ood/config/ood_portal.yml`に下記を追記します。
```
auth:
  - 'AuthType Basic'
  - 'AuthName "Open OnDemand"'
  - 'AuthBasicProvider PAM'
  - 'AuthPAMService ood'
  - 'Require valid-user'
# Capture system user name from authenticated user name
user_map_cmd: "/opt/ood/ood_auth_map/bin/ood_auth_map.regex"
node_uri: '/node'
rnode_uri: '/rnode'
```

`/opt/ood/ood_auth_map/bin/ood_auth_map.regex`を下記のように作成します。
```SHELL
#!/bin/bash

REX="([^@]+)"
INPUT_USER="$1"

if [[ $INPUT_USER =~ $REX ]]; then
  MATCH="${BASH_REMATCH[1]}"
  echo "$MATCH" | tr '[:upper:]' '[:lower:]'
else
  # can't write to standard out or error, so let's use syslog
  logger -t 'ood-mapping' "cannot map $INPUT_USER"

  # and exit 1
  exit 1
fi
```

実行権限をつけます。
```SHELL
sudo chmod +x /opt/ood/ood_auth_map/bin/ood_auth_map.regex
```

## サーバ証明書の作成
前記事の[さくらのVPSではじめるOpen OnDemand](https://qiita.com/mnakao/items/a3ad18d845c209395aa2)ではLet's Encryptを使ってサーバ証明書を作成しましたが、AWS上で同様のことを行う場合は独自のドメインが必要です。そこで、今回は自己署名証明書を代わりに用います。信頼された第三者機関（CA）による認証が行われていないため、実運用する場合は正式なサーバ証明書をご利用ください。

```SHELL
# 秘密鍵の作成
openssl genrsa 2048 > server.key

# CSR（Certificate Signing Request）の作成
openssl req -new -key server.key > server.csr
### すべてエンター ###

# 有効期限を365日にしてサーバ証明書（server.crt）を作成
openssl x509 -days 365 -req -sha256 -signkey server.key < server.csr > server.crt

sudo mkdir -p /etc/httpd/conf.d/ssl
sudo mv server.key server.csr server.crt /etc/httpd/conf.d/ssl/
```

作成したサーバ証明書などの情報を`/etc/ood/config/ood_portal.yml`に追記します。また、ヘッドノードのドメイン名を「servername:」に記載します。この情報は「Instances」の「HeadNode」の行にあるIDをクリックしたページの「パブリック IPv4 DNS」に記載されています。
```
servername: ec2-XXX.ap-northeast-1.compute.amazonaws.com
ssl:
  - 'SSLCertificateKeyFile "/etc/httpd/conf.d/ssl/server.key"'
  - 'SSLCertificateFile "/etc/httpd/conf.d/ssl/server.crt"'
```

Open OnDemandの機能を使って、Apacheの設定ファイルを自動生成し、apacheの設定をリロードします。
```
sudo /opt/ood/ood-portal-generator/sbin/update_ood_portal
sudo systemctl reload httpd
```

## ヘッドノードの設定
デフォルトのユーザであるrockyにパスワードを設定します。
```SHELL
sudo passwd rocky
```

Open OnDemandのWebサーバにアクセスするため、ヘッドノードの443ポートを開けます。
![head.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/b0e70720-b31e-352d-6db5-b5761f3c2e2d.png)

「Instances」の「HeadNode」の行にあるIDをクリックします。「セキュリティ」タブの「セキュリティグループ」をクリックすると、インバウンドルールの設定項目画面が表示されます。
![inbound.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/1f608c3a-0321-d4ee-e881-b2986020ebc7.png)

「インバウンドのルールを設定」をクリックした後、「ルールの追加」をクリックします。「タイプ」にHTTPSを設定し、接続元の範囲を0.0.0.0/0（どこからでもアクセス可能）に設定し、「ルールを保存」をクリックします。
![rule.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/8dc12c46-c8ba-8708-ed28-8e5c0acf89e9.png)

この時点で、Webブラウザに「https://（ドメイン名）」を入力すると、ユーザ認証後（ユーザ名はrocky）に下記のような画面が表示されます。
![dashboard.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/b4c52731-6c0b-15b7-7857-1f17d83ff598.png)

## Singularityのコンテナイメージの作成
計算ノードで何らかのアプリケーションを動作させるためには、すべての計算ノードにそのアプリケーションの設定が必要であり、非常に煩雑な作業になります。そこで、必要なアプリケーションをインストールしたSingularityコンテナイメージを、全計算ノードが参照できる共有ファイルシステム（`/opt/parallelcluster/shared`）に保存することにします[^4]。

今回はリモートデスクトップをインストールしたコンテナイメージを構築するため、下記のSingularityの定義ファイルを作成します。
```
# rokko.def
Bootstrap: docker
From: rockylinux:8.9

%post
  dnf -y install https://yum.osc.edu/ondemand/latest/compute/el8/aarch64/python3-websockify-0.10.0-1.el8.noarch.rpm
  dnf -y install https://yum.osc.edu/ondemand/latest/compute/el8/aarch64/turbovnc-2.2.5-1.el8.aarch64.rpm
  dnf -y install epel-release
  dnf -y group install Xfce
  dnf -y install dbus-x11 python3-numpy
  #
  dnf clean all
```

コンテナイメージを作成して、共有ファイルシステムに保存します。
```
singularity build --fakeroot rokko.sif rokko.def
sudo mv rokko.sif /opt/parallelcluster/shared/
```

## クラスタの設定
Open OnDemandで用いるクラスタの設定を行います。クラスタの名前を「rokkosan」とします。
```
sudo mkdir -p /etc/ood/config/clusters.d
```
`/etc/ood/config/clusters.d/rokkosan.yml`を下記のように作成します。hostにはヘッドノードのドメイン名を設定してください。
```
---
v2:
  metadata:
    title: "Rokkosan"
  login:
    host: "ec2-XXX.ap-northeast-1.compute.amazonaws.com"
  job:
    adapter: "slurm"
    bin: "/opt/slurm/bin/"
  batch_connect:
    vnc:
      script_wrapper: |
        export PATH="/opt/TurboVNC/bin:$PATH"
        export WEBSOCKIFY_CMD="/usr/bin/websockify"
        %s
```

リモートデスクトップのWebフォームの設定ファイル`/var/www/ood/apps/sys/bc_desktop/form.yml`を下記のように編集します。
```
---
attributes:
  bc_vnc_idle: 0
  bc_vnc_resolution:
    required: true
  bc_num_slots: 1
  desktop: "xfce"
  cluster: "rokkosan"

form:
  - bc_num_hours
  - bc_vnc_idle
  - bc_vnc_resolution
  - bc_num_slots
  - desktop
  - cluster
```

リモートデスクトップの起動スクリプト`/var/www/ood/apps/sys/bc_desktop/submit.yml.erb`を下記のように編集します。
```
---
batch_connect:
  template: vnc
  websockify_cmd: '/usr/bin/websockify'
  script_wrapper: |
    cat << "CTRSCRIPT" > container.sh
    export PATH="$PATH:/opt/TurboVNC/bin"
    %s
    CTRSCRIPT

    singularity run /opt/parallelcluster/shared/rokko.sif bash ./container.sh
```

以上で設定は完了です。Webフォームの変更を反映させるため、ナビゲーションバーからRestart Web Serverをクリックください。
![restart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/022c57bd-87ea-9390-1d56-2c8f7b1df46d.png)

## リモートデスクトップの起動
ナビゲーションバーの`Interactive Apps`の`Desktop`をクリックします。
![desktop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/b6f2297a-b30d-1a71-4dad-de5c22b43ecb.png)

下記の画面が表示されるので、Launchをクリックすると、リモートデスクトップが起動します。
![form.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/841be2ef-0f7b-ec22-8b32-f5c0492588a7.png)

お疲れ様でした。
![xfce.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/f59f932f-d490-42ae-dc53-5ceee46bfca6.png)
![xfce2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43789/940223a6-835d-9f9b-26fb-39df90277a2c.png)

# 補足
2つ上の図に「>_queue-1-st-queue-1-cr-1-1」というリンクがあります。このリンクをクリックすると、該当のノードにSSHログインを行えるのですが、SSHログインは許可されたノードとのみ可能になっています。その許可を行う方法を説明します。

ノード名を`sinfo`コマンドで調べます。ノード名はAWS ParallelClusterの設定によって異なる可能性があります。
```
sinfo
PARTITION AVAIL  TIMELIMIT  NODES  STATE NODELIST
queue-1*     up   infinite      4  idle~ queue-1-dy-queue-1-cr-1-[1-4]
queue-1*     up   infinite      1    mix queue-1-st-queue-1-cr-1-1
```

上記のノード名をSSHを許可するノードとして`/etc/ood/config/apps/shell/env`に記載します。
```
OOD_SSHHOST_ALLOWLIST="queue-1-st-queue-1-cr-1-1:queue-1-dy-queue-1-cr-1-[1-4]"
```

[^1]: [Deploy Open OnDemand on AWS](https://catalog.us-east-1.prod.workshops.aws/workshops/c4d84bde-e2c5-44ed-a7ee-1a27e90e2cb2/en-US)に、AWS CloudFormationのテンプレートを利用した本記事よりも簡単な方法が説明されていますが、なぜか正常に動作しませんでした。
[^2]: 2023年12月19日にリリースされたAWS ParallelCluster 3.8.0からRocky Linux 8に対応しました。また、2024年2月20日にリリースされたOpen OnDemand 3.1.0からarm64アーキテクチャに対応しました。
[^3]: S3で指定することもできます。
[^4]: 共有ファイルシステムの場所は`sudo exportfs`コマンドでわかります。