💫

デスクトップに再現する従来型ストレージ

2024/02/11に公開

はじめに

以下の記事の構築編です。
Windowsデスクトップ上 (VirtualBox) に従来型ストレージを構築していきます。

https://zenn.dev/lightgreenface/articles/0b0188ffdc30fd

GCP編、AWS編の記事は以下にあります。
クラウドに再現する従来型ストレージ【GCP構築編】
クラウドに再現する従来型ストレージ【AWS構築編】

構成概要

以下のような構成で「star0001」というストレージを構築していきます。

スター?

中身が見えるストレージを目標に
see‐through array (シースルー アレイ)
という名前をつけました。
中心的な役割を果たすinitスクリプト名はSTARorbit (star orbit、星の軌道) です。

ソフトウェアはdnf (yumの後継パッケージ管理コマンド) で入手できる一般的なLinuxソフトウェアと100行未満の自前スクリプト (STARorbit) 1個から構成されます。

ストレージの構築

VirtualBox (バージョン6.1.50と7.0.142024.2.20追加で動作確認) が既に使える状態からの手順です。
コマンド操作はコピー&ペーストができますので実際の手数はそれほど多くありません。

筆者のマシン環境は以下の通りです。

  • Surface Pro 3
  • Windows 10 Pro (64ビット)
  • Intel Core i5-4300U CPU 1.90GHz 2コア 4スレッド
  • 8GB RAM

1. コントローラ作成 (新規仮想マシン2台)

以下設定にて新規仮想マシンを1台作成します (2台目は後で複製します)。

項目 設定値 備考
名前[1] star0001-ctl1
OSイメージ Rocky-8.9-x86_64-minimal.iso Download Rocky
タイプ / バージョン Linux / Red Hat (64-bit)
メモリーサイズ 1,536MB
仮想ハードディスク 10GB
プロセッサー 2 CPU
ネットワーク - アダプター1 ブリッジアダプターまたはNAT2024.2.20追加 準仮想化ネットワーク
ネットワーク - アダプター2 内部ネットワーク 準仮想化ネットワーク
ブリッジアダプターを使わない構成 (アダプター1にNATを選択) の場合

ブリッジアダプターはVirtualBoxホストマシンと同じネットワークセグメントのIPアドレスを割り当てますが、都合が悪い場合 (典型例はVirtualBoxホストマシンが社内ネットワークに接続するPCの場合) はアダプター1にNATを選択し、以下の通りポートフォワーディングを設定します。

名前 プロトコル ホストポート ゲストポート
SSH TCP 10022 22

ゲストマシンへの接続はlocalhostのホストポート (10022) を指定します。

仮想マシンを起動し、OSをインストールします (詳細な手順は省略)。

  • English (United States)

  • KeyboardにJapaneseを追加、^でEnglish (US)より優先 (Japaneseを一番上に)

  • Time & DateはAsia / Tokyo

  • User Creationでrockyユーザ作成 (パスワードは任意のもの)

    • Make this user administratorにチェックを入れます
  • Software SelectionはMinimal Installを指定

  • Network & Host NameではEthernet (enp0s3)のIPv6を無効化してからONにします

    • Configure - IPv6 SettingsからMethodをDisabledにしてSaveします
    • OFF → ONにします (IPアドレスが設定されConnectedになる)
  • Installation DestinationはCustom指定で非LVM構成にします

    1. Storage ConfigurationをCustomにしてDoneします
    2. LVMをStandard Partitionに変更、Click here to create them automatically.をクリック
    3. 再度DoneするとSUMMARY OF CHANGESが出るのでAccept Changesをクリック
  • INSTALLATION SUMMARYが以下になっていることを確認し、Begin Installationをクリック

OSインストールの完了後、SSHから[2][3]rockyユーザログイン、sudo su -コマンドでrootユーザにスイッチしてパッケージをアップデート、仮想マシンをシャットダウンします。

コピー&ペースト可
dnf update -y
sleep 30 && shutdown -h now

VirtualBoxマネージャー画面から作成したstar0001-ctl1を複製します。
電源オフ状態のstar0001-ctl1を右クリックしてクローンします。

項目 設定値
名前 star0001-ctl2
MACアドレスのポリシー すべてのネットワークアダプターでMACアドレスを生成
クローンのタイプ すべてをクローン

後でサーバ (作成するストレージを利用する) を立てる際にも使うので名前をinstance-1に指定してもう1台複製します。

ブリッジアダプターを使わない構成 (アダプター1にNATを選択) の場合

ポートフォワーディングの設定をホストポートが重複しないように変更します。
以下のようにstar0001-ctl2は20000番台、instance-1は30000番台とするとわかりやすいです。

star0001-ctl2設定

名前 プロトコル ホストポート ゲストポート
SSH TCP 20022 22

instance-1設定 (SSHに加えHTTPが必要)

名前 プロトコル ホストポート ゲストポート
SSH TCP 30022 22
HTTP TCP 30080 80

2. 共有ディスク作成・割り当て

VirtualBoxマネージャー画面のファイルから仮想メディアマネージャーを開いて仮想ハードディスクを2つ作成します。

項目 設定値 備考
ファイルタイプ VDI
可変 or 固定 固定サイズ 可変では共有可能にできない
ファイルの場所 VirtualBox VMs\star0001\star0001-disk-1.vdi 2つ目はstar0001-disk-2.vdi
サイズ 4GB

作成したらタイプを通常から共有可能に変更します。

2台のコントローラ (star0001-ctl1、star0001-ctl2) に作成した共有ディスクを割り当てます。
コントローラ SATA配下に追加します。

3-1. コントローラホスト名設定

2台のコントローラを起動し、SSHからrockyユーザログイン、sudo su -コマンドでrootユーザにスイッチしてホスト名を設定します。

コピー&ペースト可 (コントローラ1で実施)
hostnamectl set-hostname "star0001-ctl1"   # 任意の名前を指定する場合は書換え必要
exec bash   # プロンプトにホスト名反映

コピー&ペースト可 (コントローラ2で実施)
hostnamectl set-hostname "star0001-ctl2"   # 任意の名前を指定する場合は書換え必要
exec bash   # プロンプトにホスト名反映

3-2. コントローラ共通設定 (両コントローラに実施)

コントローラ1,2で以下のコマンドを実行し、ネットワーク設定を行います。

コピー&ペースト可
## 変数設定
ARRAY_NAME="star0001"     # 任意の筐体名を指定可
NODE1_ADDR="172.16.0.1"   # 任意のアドレスを指定可 (コントローラ1)
NODE2_ADDR="172.16.0.2"   # 任意のアドレスを指定可 (コントローラ2)
CIDR="24"                 # 任意のアドレスを指定可

## hosts設定
grep -qF "$ARRAY_NAME" /etc/hosts || {
  NODE_BASENAME="$(uname -n | sed 's/\(.\+\).$/\1/g')"
  echo $NODE1_ADDR ${NODE_BASENAME}1 >> /etc/hosts
  echo $NODE2_ADDR ${NODE_BASENAME}2 >> /etc/hosts
}

## ネットワーク設定
NODE_ADDR=$(getent ahostsv4 $(uname -n) | head -1 | awk '{ print $1 }')
nmcli c mod enp0s8 ipv4.method manual ipv4.addresses ${NODE_ADDR}/${CIDR} \
    ipv6.method disabled connection.autoconnect yes 802-3-ethernet.mtu 9000
nmcli c up enp0s8 ; nmcli c

コントローラ1,2で以下のコマンドを実行し、ソフトウェアインストール・設定を行います。

コピー&ペースト可
## パッケージインストール、アップデート
dnf -y install pcs pacemaker targetcli iscsi-initiator-utils \
    sysstat vsftpd lvm2 rsyslog jq --enablerepo=ha
dnf update -y

## selinux無効 (2024.4.21追加)
grubby --update-kernel ALL --args selinux=0

## サービス起動設定
systemctl start vsftpd   # ftpはSSH設定まで利用、enableしない
systemctl enable --now pcsd iscsid
systemctl disable --now target iscsi-onboot iscsi
systemctl --no-pager status pcsd iscsid target iscsi-onboot iscsi vsftpd

## firewall設定
firewall-cmd --permanent --add-service=high-availability
firewall-cmd --permanent --add-port=3260/tcp   # iscsi target
firewall-cmd --reload
firewall-cmd --add-service=ftp   # ftpはSSH設定まで利用、--permanentしない

## ユーザ、パスワード設定
echo hacluster | passwd --stdin hacluster
useradd -u 1979 alien && echo alien | passwd --stdin alien

## SSHサーバ設定 (rootログインを許可、rootのパスワードログインは不要)
sed -i 's/^PermitRootLogin no/PermitRootLogin yes/g' /etc/ssh/sshd_config
systemctl restart sshd

## LVM環境設定
FILTER='a|scsi-SATA_VBOX_.*|'
grep -qF "$FILTER" /etc/lvm/lvm.conf || {
  sed -Ei -e 's/# system_id_source = "none"/system_id_source = "uname"/' \
      -e "/# Run vgscan aft.+cache/a \        filter = [ \"$FILTER\", \"r|.*|\" ]" \
          /etc/lvm/lvm.conf ; }
             # STANDBYに見えるiSCSIデバイス拒否、failoverにてSTANDBY->ACTIVE昇格後に
	     # 不通のiSCSIデバイスを走査してSTARpool活性T.O.から切替失敗するのを防ぐ

echo '(=・ω・)ノ ぃょぅ'

コントローラ1,2で以下のコマンドを実行し、iSCSI target起動スクリプトの設定を行います。

コピー&ペースト可
## ファイル配置
cat << "__EOF__" > /etc/init.d/STARorbit   # 2024.4.21コード修正
#!/bin/bash
# chkconfig: 345 99 1
# description: STAR iSCSI target control script

NODE_BASENAME="$(uname -n | sed 's/\(.\+\).$/\1/g')"
NODE_NUM="$(uname -n | sed 's/.\+\(.\)$/\1/g')"
PARTNER_NODE="${NODE_BASENAME}$(( 3 - $NODE_NUM ))"

test -f /etc/star/config && . /etc/star/config

is_active_node() { pcs resource status STARpool | egrep -q "Started +$(uname -n)"; }

start_as_active() {
  iscsiadm -m node --logout

  cp /etc/target/{saveconfig.json.active,saveconfig.json}
  sed -i -e "s/node._tg_pt_gp/node${NODE_NUM}_tg_pt_gp/g" \
      -e "s/\"tg_pt_gp_id\": [12]/\"tg_pt_gp_id\": ${NODE_NUM}/g" \
          /etc/target/saveconfig.json
  systemctl restart target

  ssh -n -o ConnectTimeout=10 $PARTNER_NODE "/etc/init.d/STARorbit start" &
}

start_as_standby() {
  for I in $(seq 120); do
    is_active_node && break
       # This function may run for up to 10 minutes, with the possibility of
       # parallel switching to ACTIVE. If it detects ACTIVE, it exits.

    pcs resource status STARorbit | egrep -q "Started +${PARTNER_NODE}" || \
       { sleep 5 ; continue ; }

    iscsiadm -m discovery -t sendtargets -p $PARTNER_NODE
    iscsiadm -m node --login

    for ISCSI_HOST in $(ls -1 /sys/class/iscsi_host) ; do
      echo '- - -' > /sys/class/scsi_host/$ISCSI_HOST/scan ; done

    sleep 5 ; lsscsi | fgrep -q -e STAR-LUN -e STAR-HD140283 || continue

    cp /etc/target/{saveconfig.json.standby,saveconfig.json}
    sed -i -e "s/node._tg_pt_gp/node${NODE_NUM}_tg_pt_gp/g" \
        -e "s/\"tg_pt_gp_id\": [12]/\"tg_pt_gp_id\": ${NODE_NUM}/g" \
            /etc/target/saveconfig.json
    if type jq >/dev/null 2>&1; then
      jq '(.storage_objects[].alua_tpgs[] | select(.name | test("node._tg_pt_gp"))
          | .alua_access_type) |=1' /etc/target/saveconfig.json \
          | jq "(.storage_objects[].alua_tpgs[]
              | select(.name | test(\"node._tg_pt_gp\")) | .alua_access_state) \
                  |=${STANDBY_NON_OPTIMIZED:-0}" > /etc/target/saveconfig.json.$$ \
                      && mv /etc/target/{saveconfig.json.$$,saveconfig.json}; fi
    systemctl start target
    break
  done
}

stop_as_active() {
  ssh -n -o ConnectTimeout=10 $PARTNER_NODE "/etc/init.d/STARorbit stop"
  systemctl stop target
}

stop_as_standby() { systemctl stop target ; }

start() {
  echo -n "Starting STAR iSCSI target:"

  is_active_node && start_as_active
  is_active_node || start_as_standby

  return 0
}

stop() {
  echo -n "Stopping STAR iSCSI target:"

  systemctl is-active target || return 0
  is_active_node && stop_as_active
  is_active_node || stop_as_standby

  return 0
}

status() {
  is_active_node || return 3
     # STANDBY returns 'stopped' to avoid 2-nodes-active detection by Pacemaker.
  systemctl status target
}

case $1 in
  start)  start  ;;
  stop)   stop   ;;
  status) status ;;
esac
__EOF__

## パーミッション設定
chmod 755 /etc/init.d/STARorbit

## rc.local有効化と追記
grep -qF '/etc/init.d/STARorbit' /etc/rc.d/rc.local \
    || { echo "/etc/init.d/STARorbit start" >> /etc/rc.d/rc.local ; }
chmod 755 /etc/rc.d/rc.local

echo 'ヽ(^O^)ノ いらっしゃいませー'

4. SSH公開鍵認証設定

コントローラ1,2の順に以下のコマンドを実行し、SSHの公開鍵認証設定を実施します。

コピー&ペースト可
NODE_BASENAME="$(uname -n | sed 's/\(.\+\).$/\1/g')"
NODE_NUM="$(uname -n | sed 's/.\+\(.\)$/\1/g')"
PARTNER_NODE="${NODE_BASENAME}$(( 3 - $NODE_NUM ))"

test -f /root/.ssh/id_rsa || {
  if [ $NODE_NUM -eq 1 ]; then
    ssh-keygen -t rsa -N "" -f /root/.ssh/id_rsa   # 公開鍵作成 (両ノード共通)

    ## 鍵転送 (コントローラ2に向けて)
    curl -T "/root/.ssh/{id_rsa.pub,id_rsa}" -u alien:alien ftp://${PARTNER_NODE}/
       # コントローラ2を再起動した場合にはvsftpdが起動していません
       # 先に`systemctl start vsftpd ; firewall-cmd --add-service=ftp`が必要です
  else
    ## 鍵配置 (コントローラ1から転送されたもの)
    test -d /root/.ssh || { mkdir /root/.ssh ; chmod 700 /root/.ssh ; }
    mv /home/alien/{id_rsa.pub,id_rsa} /root/.ssh
    chown root:root /root/.ssh/{id_rsa.pub,id_rsa}
    chmod 600 /root/.ssh/id_rsa
  fi
  cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys   # ログイン許可
    
  echo '( ´ー`)フゥー...'
}

コントローラ1,2で以下のコマンドを実行し、SSH動作確認をします。

コピー&ペースト可
ssh -n -o StrictHostKeyChecking=no $PARTNER_NODE "date ; uname -n"
   # 表示例 (初回は`Warning: Permanently added ...`が出ます):
   # Sun Oct 15 12:37:27 JST 2023
   # star0001-ctl2

echo '∩( ・ω・)∩'

5. ストレージプール、LUN作成

コントローラ1 (star0001-ctl1) にログイン、sudo su - コマンドでrootユーザになります。

コントローラ1で以下のコマンドを実行し、ストレージプール、LUN作成を実施します。

コピー&ペースト可
POOL_NAME="STARpool"
DIR="/etc/star" ; mkdir -p $DIR
LUN_SIZE="1024M"
NODE_NUM="$(uname -n | sed 's/.\+\(.\)$/\1/g')"

if [ $NODE_NUM -eq 1 ]; then
  vgs -q "$POOL_NAME" || {
    ## ストレージプール (Volume Group) 作成
    for disk in $(pvs --all | grep -v '\-part[1-9]' | awk '/dev/{ print $1 }'); do
      pvcreate $disk ; done
    vgcreate --setautoactivation n $POOL_NAME $(pvs | awk '/dev/{ print $1 }')
    vgs -o+systemid ; pvs --all

    lvcreate -n HD140283 -l 1 $POOL_NAME
       # Pacemakerで制御するにあたりLogical Volumeが0個になる状況を避けるため

    ## LUN (Logical Volume) 作成
    for I in $(seq 0 5); do
      LUN_NAME="LUN${I}" ; LUN_WWN="$(uuidgen)"
      lvcreate -n $LUN_NAME -L $LUN_SIZE $POOL_NAME
      echo "$LUN_NAME $LUN_WWN $LUN_SIZE" >> $DIR/lunlist
    done
  }
  echo '>゜)))彡 サカナ'
else
  echo '(#゚Д゚) ゴルァ!!'   # 誤ってコントローラ2で実行した場合
fi

6. iSCSIターゲット設定

コントローラ1 (star0001-ctl1) にログイン、sudo su - コマンドでrootユーザになります。

コントローラ1で以下のコマンドを実行し、iSCSIターゲット設定を実施します。

コピー&ペースト可
## 変数設定
ARRAY_NAME="star0001"     # 任意の筐体名を指定可

DIR="/etc/star"
ARRAY_VERSION="0.5"
STANDBY_NON_OPTIMIZED=0
NUM_HOSTS=2
NODE_BASENAME="$(uname -n | sed 's/\(.\+\).$/\1/g')"
NODE_NUM="$(uname -n | sed 's/.\+\(.\)$/\1/g')"
PARTNER_NODE="${NODE_BASENAME}$(( 3 - $NODE_NUM ))"

if [ $NODE_NUM -eq 1 ]; then
  test -f /etc/target/saveconfig.json.active || {
    ## 筐体情報作成
    echo $ARRAY_NAME > $DIR/starname
    echo $ARRAY_VERSION > $DIR/version
    echo "STANDBY_NON_OPTIMIZED=${STANDBY_NON_OPTIMIZED}" > $DIR/config

    ## iSCSIターゲット設定 (ACTIVEコントローラ用)
    ARRAY_IQN=iqn.2023-09.test.use.only:${ARRAY_NAME}-target
    targetcli /iscsi create $ARRAY_IQN

    while read LUN_INFO ; do
      LUN_NAME="$(echo $LUN_INFO | awk '{ print $1 }')"
      LUN_WWN="$(echo $LUN_INFO | awk '{ print $2 }')"
      targetcli /backstores/block create name=STAR-${LUN_NAME} \
          dev=/dev/STARpool/$LUN_NAME wwn=${LUN_WWN}
      targetcli /backstores/block/STAR-${LUN_NAME}/alua \
          create node${NODE_NUM}_tg_pt_gp ${NODE_NUM}
      targetcli /iscsi/$ARRAY_IQN/tpg1/luns create /backstores/block/STAR-${LUN_NAME}
    done < $DIR/lunlist

    for lunN in \
        $(targetcli ls /iscsi/$ARRAY_IQN/tpg1/luns 1 | awk '/STAR/{ print $2 }') ; do
      targetcli /iscsi/$ARRAY_IQN/tpg1/luns/$lunN \
          set alua alua_tg_pt_gp_name=node${NODE_NUM}_tg_pt_gp ; done

    for I in $(seq $NUM_HOSTS); do
      HOST_NAME="Host_${I}"
      HOST_IQN="iqn.2023-09.com.example:host${I}-initiator"    
      echo "$HOST_NAME $HOST_IQN LUN0,LUN1,LUN2,LUN3,LUN4,LUN5" >> $DIR/hostlist
      targetcli /iscsi/$ARRAY_IQN/tpg1/acls create $HOST_IQN
    done

    targetcli /iscsi/$ARRAY_IQN/tpg1/acls \
        create iqn.2023-09.test.use.only:${ARRAY_NAME}-initiator
    targetcli saveconfig /etc/target/saveconfig.json.active

    ## コントローラ2に構成情報を転送
    ssh -n $PARTNER_NODE "mkdir -p $DIR"
    scp $DIR/* ${PARTNER_NODE}:${DIR}
    scp /etc/target/saveconfig.json.active ${PARTNER_NODE}:/etc/target

    ## iSCSIイニシエータ設定 (コントローラ間iSCSIアクセス用)
    echo InitiatorName=iqn.2023-09.test.use.only:${ARRAY_NAME}-initiator \
        > /etc/iscsi/initiatorname.iscsi
    systemctl restart iscsid
  }
  echo '▼・ェ・▼'
else
  echo '(#゚Д゚) ゴルァ!!'   # 誤ってコントローラ2で実行した場合
fi

コントローラ2 (star0001-ctl2) にログイン、sudo su - コマンドでrootユーザになります。

コントローラ2で以下のコマンドを実行し、iSCSIターゲット設定を実施します。

コピー&ペースト可
DIR="/etc/star"
ARRAY_NAME=$(cat $DIR/starname)
NODE_BASENAME="$(uname -n | sed 's/\(.\+\).$/\1/g')"
NODE_NUM="$(uname -n | sed 's/.\+\(.\)$/\1/g')"
PARTNER_NODE="${NODE_BASENAME}$(( 3 - $NODE_NUM ))"

if [ $NODE_NUM -eq 2 ]; then
  test -f /etc/target/saveconfig.json.standby || {

    ## iSCSIイニシエータ設定 (コントローラ間iSCSIアクセス用)
    echo InitiatorName=iqn.2023-09.test.use.only:${ARRAY_NAME}-initiator \
        > /etc/iscsi/initiatorname.iscsi
    systemctl restart iscsid

    ## LUN認識
    iscsiadm -m discovery -t sendtargets -p $PARTNER_NODE
    iscsiadm -m node -o show ; iscsiadm -m node --login
    iscsiadm -m session -o show

    sleep 10 ; lsscsi

    ## iSCSIターゲット設定 (STANDBYコントローラ用)
    ARRAY_IQN=iqn.2023-09.test.use.only:${ARRAY_NAME}-target
    targetcli /iscsi create $ARRAY_IQN

    while read LUN_INFO ; do
      LUN_NAME="$(echo $LUN_INFO | awk '{ print $1 }')"
      LUN_WWN="$(echo $LUN_INFO | awk '{ print $2 }')"
      targetcli /backstores/block create name=STAR-${LUN_NAME} \
          dev=/dev/disk/by-id/scsi-36001405$(echo ${LUN_WWN//-/} | cut -c 1-25) \
              wwn=${LUN_WWN}
      targetcli /backstores/block/STAR-${LUN_NAME}/alua \
          create node${NODE_NUM}_tg_pt_gp ${NODE_NUM}
      targetcli /iscsi/$ARRAY_IQN/tpg1/luns create /backstores/block/STAR-${LUN_NAME}
    done < $DIR/lunlist

    for lunN in \
        $(targetcli ls /iscsi/$ARRAY_IQN/tpg1/luns 1 | awk '/STAR/{ print $2 }') ; do
      targetcli /iscsi/$ARRAY_IQN/tpg1/luns/$lunN \
          set alua alua_tg_pt_gp_name=node${NODE_NUM}_tg_pt_gp ; done

    for HOST_IQN in $(cat $DIR/hostlist | awk '{ print $2 }'); do
      targetcli /iscsi/$ARRAY_IQN/tpg1/acls create $HOST_IQN ; done

    targetcli saveconfig /etc/target/saveconfig.json.standby

    ## コントローラ1に構成情報を転送
    scp /etc/target/saveconfig.json.standby ${PARTNER_NODE}:/etc/target
  }
  echo '( ゚Д゚).。oO(もうひと息)'
else
  echo '(#゚Д゚) ゴルァ!!'   # 誤ってコントローラ1で実行した場合
fi

7. クラスタ構築

コントローラ1 (star0001-ctl1) にログイン、sudo su - コマンドでrootユーザになります。

コントローラ1で以下のコマンドを実行し、クラスタ構築を実施します。

コピー&ペースト可
DIR="/etc/star"
ARRAY_NAME=$(cat $DIR/starname)
POOL_NAME="STARpool"
NODE_NAME=`uname -n`
NODE_BASENAME="$(uname -n | sed 's/\(.\+\).$/\1/g')"
NODE_NUM="$(uname -n | sed 's/.\+\(.\)$/\1/g')"
PARTNER_NODE="${NODE_BASENAME}$(( 3 - $NODE_NUM ))"

if [ $NODE_NUM -eq 1 ]; then
  ## クラスタ作成
  NODE_ADDR=`getent ahostsv4 ${NODE_NAME} | head -1 | awk '{ print $1 }'`
  PARTNER_ADDR=`getent ahostsv4 ${PARTNER_NODE} | head -1 | awk '{ print $1 }'`

  pcs host auth ${NODE_NAME} addr=${NODE_ADDR} ${PARTNER_NODE} \
      addr=${PARTNER_ADDR} -u hacluster -p hacluster
  pcs cluster setup ${ARRAY_NAME} ${NODE_NAME} addr=${NODE_ADDR} \
      ${PARTNER_NODE} addr=${PARTNER_ADDR}

  ## クラスタ起動
  pcs cluster start --all ; pcs cluster enable --all
  pcs cluster status ; pcs status corosync

  ## STONITH無効化 (スプリットブレインでデータ破壊の可能性!!実験用のため)
  pcs property set stonith-enabled=false no-quorum-policy=ignore
  pcs property
  
  ## リソース作成
  pcs resource create ${POOL_NAME} ocf:heartbeat:LVM-activate \
      vgname=${POOL_NAME} vg_access_mode=system_id --group star_group
  pcs resource create STARorbit lsb:STARorbit --group star_group \
      op monitor enabled=false force-reload timeout=60 restart timeout=60 \
          start timeout=60 stop timeout=60

  sleep 60 ; pcs status

  echo 'キタ━(゚∀゚)━!!'
else
  echo '(#゚Д゚) ゴルァ!!'   # 誤ってコントローラ2で実行した場合
fi

8. 1台ずつコントローラリブート (クラスタ動作確認)

コントローラ2→コントローラ1の順で1台ずつリブートします。
1台リブートしたらpcs statusコマンドの出力のNode Listが2台ともOnlineになるまで待ってから2台目をリブートします。

コントローラ2リブート直後 (コントローラ1リブート不可)
Node List:
  * Online: [ star0001-ctl1 ]
  * OFFLINE: [ star0001-ctl2 ]
コントローラ2クラスタ復帰 (コントローラ1リブート可能)
Node List:
  * Online: [ star0001-ctl1 star0001-ctl2 ]

サーバの構築

1. サーバ作成

instance-1として複製済のものをそのまま使うので作成は不要です。

2. サーバ設定

instance-1を起動し、SSHからrockyユーザログイン、sudo su -コマンドでrootユーザになります。

以下のコマンドを実行し、ネットワーク設定を行います。

コピー&ペースト可
## 変数設定
ARRAY_NAME="star0001"             # star0001でない場合は書換え必要
NODE1_NAME="${ARRAY_NAME}-ctl1"   # ctl1でない場合は書換え必要
NODE2_NAME="${ARRAY_NAME}-ctl2"   # ctl2でない場合は書換え必要
NODE1_ADDR="172.16.0.1"           # コントローラ1のアドレスを指定
NODE2_ADDR="172.16.0.2"           # コントローラ2のアドレスを指定
SERVER_NAME="instance-1"          # 任意のサーバ名を指定可
SERVER_ADDR="172.16.0.3"          # 任意のアドレスを指定可
CIDR="24"                         # 任意のアドレスを指定可

## hosts設定
grep -qF "$ARRAY_NAME" /etc/hosts || {
  echo $NODE1_ADDR $NODE1_NAME >> /etc/hosts
  echo $NODE2_ADDR $NODE2_NAME >> /etc/hosts
}

## ネットワーク設定
nmcli c mod enp0s8 ipv4.method manual ipv4.addresses ${SERVER_ADDR}/${CIDR} \
    ipv6.method disabled connection.autoconnect yes 802-3-ethernet.mtu 9000
nmcli c up enp0s8 ; nmcli c

## ホスト名設定
hostnamectl set-hostname $SERVER_NAME
exec bash   # プロンプトにホスト名反映

echo '_〆(・_・。)^ カキカキ'

以下のコマンドを実行し、ソフトウェアインストール・設定を行います。

コピー&ペースト可
## 変数設定
ARRAY_NAME="star0001"             # star0001でない場合は書換え必要
NODE1_NAME="${ARRAY_NAME}-ctl1"   # ctl1でない場合は書換え必要
NODE2_NAME="${ARRAY_NAME}-ctl2"   # ctl2でない場合は書換え必要

## タイムゾーン設定
timedatectl set-timezone Asia/Tokyo ; timedatectl

## パッケージインストール、アップデート
dnf -y install iscsi-initiator-utils device-mapper-multipath sysstat rsyslog
   # minimalではsyslogが入らない
yum update -y

## selinux無効 (2024.4.21追加)
grubby --update-kernel ALL --args selinux=0

## iSCSIイニシエータ設定
echo InitiatorName=iqn.2023-09.com.example:host1-initiator \
    > /etc/iscsi/initiatorname.iscsi

## multipath設定
mpathconf --enable --user_friendly_names y
cat << "__EOF__" >> /etc/multipath.conf

devices {
        device {
                vendor "LIO-ORG"
                product "STAR-.+"
                path_grouping_policy "group_by_prio"
                path_selector "round-robin 0"
                hardware_handler "1 alua"
                prio "alua"
                failback "immediate"
                no_path_retry 10
                detect_prio no
                max_sectors_kb 256
        }
}
__EOF__

systemctl restart multipathd

## ディスク認識
iscsiadm -m discovery -t sendtargets -p $NODE1_NAME
iscsiadm -m discovery -t sendtargets -p $NODE2_NAME
iscsiadm -m node -o show ; iscsiadm -m node --login
iscsiadm -m session -o show

sleep 10 ; lsscsi ; ll /dev/sd* /dev/disk/by-id

## マルチパス確認
multipath -ll

echo 'キタ━━(゚∀゚)━( ゚∀)━( ゚)━( )━( )━(・ )━(∀・ )━(・∀・)━イイ!!!!'

この段階で1度サーバリブートします。

ここまででサーバにストレージが見えましたので、ファイルシステムを作成する、読み書きをするなど試行することができます。

3. サーバ追加設定 (性能情報取得インターバル変更とWeb表示)

サーバにログイン、sudo su - コマンドでrootユーザになります。

以下のコマンドを実行し、ブラウザからsarグラフが見えるように設定します。

コピー&ペースト可
## パッケージインストール
dnf -y install httpd expect firewalld

## digest認証設定
cat << "__EOF__" > /etc/httpd/conf.d/digest.conf
<Directory "/var/www/html/">
    AuthType Digest
    AuthName "milky way"
    AUthUserFile "/etc/httpd/.htdigest"
    Require valid-user
</Directory>
__EOF__

M=$(date '+%m') ; D=$(date '+%d')
X=$([ $((D % 2)) -eq 1 ] && echo "-" || echo "=")   # 奇数日なら"-"、偶数日なら"="

expect -c "
spawn htdigest -c /etc/httpd/.htdigest \"milky way\" perfweb
expect \"New password:\"
send -- \"${M}Kinp@kU${D}${X}\n\"
expect \"Re-type new password:\"
send -- \"${M}Kinp@kU${D}${X}\n\"
wait"

systemctl enable --now httpd   # apache起動

## sar記録インターバル変更 (30秒)
DIR="/etc/systemd/system/sysstat-collect.timer.d/"
mkdir -p $DIR
cat << "__EOF__" > $DIR/override.conf
[Timer]
OnCalendar=*:00/1
__EOF__

DIR="/etc/systemd/system/sysstat-collect.service.d/"
mkdir -p $DIR
cat << "__EOF__" > $DIR/override.conf
[Service]
Type=oneshot
User=root
ExecStart=/usr/lib64/sa/sa1 30 2
__EOF__

systemctl enable --now sysstat
systemctl daemon-reload

sleep 90   # データ生成待ち

## sarデータWeb化スクリプト配置・設定
cat << "__EOF__" > /usr/bin/perfweb
#!/bin/bash

DIR="/var/www/html"
METRIC=("disk" "cpu" "net")
OPTION=("-d -p" "-P ALL" "-n DEV")
INTERVAL=1
CMD="nice -n 10 sadf -Tg -O showinfo"
ENDTIME="$(date '+%H:%M')"
PREVDAY="$(date '+%d' -d '-1 day')"

sleep 3 ; cd $DIR || exit 1

draw_graph() {
  STARTTIME="$(date -d "-${1} minutes" '+%H:%M')"
  SADIR="/var/log/sa"
  [ $2 -lt 30 ] && SADIR="/var/log/sa.short.5"
  [ $2 -lt 5 ]  && SADIR="/var/log/sa.short.1"

  for I in $(seq 0 $(( ${#METRIC[@]} - 1 ))); do
    if [ ${STARTTIME:0:2} -gt ${ENDTIME:0:2} ]; then
      $CMD -s $STARTTIME -- ${OPTION[$I]} $2 $SADIR/sa$PREVDAY \
          > ${METRIC[$I]}.${1}m.1.svg.$$
      sleep $INTERVAL
      $CMD -e $ENDTIME -- ${OPTION[$I]} $2 $SADIR > ${METRIC[$I]}.${1}m.2.svg.$$
      rm -f ${METRIC[$I]}.${1}m.svg
    else
      $CMD -s $STARTTIME -e $ENDTIME -- ${OPTION[$I]} $2 $SADIR \
          > ${METRIC[$I]}.${1}m.svg.$$
      rm -f ${METRIC[$I]}.${1}m.{1,2}.svg
    fi
    rename .$$ '' *.svg.$$
    sleep $INTERVAL
  done
}

draw_graph 1 1   ; draw_graph 5 5    ; draw_graph 15 5   ; draw_graph 30 5
draw_graph 60 30 ; draw_graph 180 30 ; draw_graph 360 60

cat << _eof_ > index.html.$$
<html lang=en><title>$(uname -n) - perfweb</title><body>
<h2 style="background:#88ee88;padding:5px">$(uname -n)</h2>
<h2>Performance Statistics Graphs</h2>
<table border=1 cellpadding=3>
_eof_

for I in $(seq 0 $(( ${#METRIC[@]} - 1 ))); do
  echo '<td>'
  for SVG in $(ls -1 ${METRIC[$I]}.*.svg | sort -t . -k 1,1 -k 2n); do
    echo "<a href=${SVG} target=_blank>${SVG}</a><br>" ; done
  echo '</td>'
done >> index.html.$$

echo "</table><br>Last Updated on $(date)</body></html>" >> index.html.$$
mv index.html.$$ index.html

exit 0
__EOF__

chmod 700 /usr/bin/perfweb

mkdir /var/log/sa.short.{1,5}   # 1秒、5秒間隔のsar取得ディレクトリ作成

## cronに1秒、5秒間隔のsar取得とsarデータWeb化スクリプト登録
cat << "__EOF__" > /var/spool/cron/root
MAILTO=""
S1=/var/log/sa.short.1
S5=/var/log/sa.short.5
* * * * * cd $S1 && sar -o sa$(date +\%d\%H\%M) 1 60 >/dev/null
* * * * * cd $S1 && ln -sf sa$(date +\%d\%H\%M -d '-1 min') sa$(date +\%d)
* * * * * cd $S5 && sar -o sa$(date +\%d\%H00) 5 12 >/dev/null
* * * * * cd $S5 && ln -s sa$(date +\%d\%H00) sa$(date +\%d) 2>/dev/null
* 1-23 * * * cd $S5 && sar -o sa$(date +\%d\%H30 -d '-30 min') 5 12 >/dev/null
0,30 1-23 * * * cd $S5 && ln -sf sa$(date +\%d\%H\%M -d '-30 min') sa$(date +\%d)
* * * * * find $S1 $S5 -xtype l -delete -o -type f -name 'sa*' -mmin +30 -delete
* * * * * /usr/bin/perfweb
__EOF__

chmod 600 /var/spool/cron/root
systemctl reload crond

sleep 90   # データ生成待ち

## firewalld起動・設定
systemctl enable --now firewalld
firewall-cmd --permanent --add-service=http
firewall-cmd --reload

echo -e "Username: perfweb\nPassword: ${M}Kinp@kU${D}${X}"
echo '(。・ω・)ノ゙ ありがとう'

enp0s3のアドレスにブラウザで接続 (http://アドレス) [4]します。

認証ダイアログが出るのでユーザ名とパスワードを入力してログインします。

  • ユーザ名はperfwebです
  • パスワードは3. サーバ追加設定を実施した日付によって変化します
    • パスワードは2桁月 + Kinp@kU + 2桁日 + -または=です
      • Kinp@kUは「金箔」のローマ字を一部変更、末尾は奇数日が-で偶数日が=です
      • 例えば2月29日の場合には02Kinp@kU29-、2月28日の場合には02Kinp@kU28=です

ログインすると、sarをSVG形式にグラフ化したファイルへのリンクページが表示されます。
クリックするとグラフ化されたsarが表示されます[5]

例えば以下のグラフ (disk.15m.svg) はI/O負荷を入れながら動かしたものです。


サーバから見たI/O状況 (mpathf)

170 IOPS程度しか出ないのはTTLがクラウド環境に比べ大きい[6]ことが影響しているようです。

クラウド環境とのTTL差異

VirtualBox環境は以下の通り、クラウド環境 (後述) のTTLに比べると大きいです。

[root@instance-1 ~]# ping -c 60 star0001-ctl1
PING star0001-ctl1 (172.16.0.1) 56(84) bytes of data.
64 bytes from star0001-ctl1 (172.16.0.1): icmp_seq=1 ttl=64 time=3.78 ms
64 bytes from star0001-ctl1 (172.16.0.1): icmp_seq=2 ttl=64 time=2.71 ms
(省略)
64 bytes from star0001-ctl1 (172.16.0.1): icmp_seq=60 ttl=64 time=3.36 ms

--- star0001-ctl1 ping statistics ---
60 packets transmitted, 60 received, 0% packet loss, time 59475ms
rtt min/avg/max/mdev = 0.972/2.007/7.301/1.138 ms
[root@instance-1 ~]# 

クラウド (GCP) 環境では以下の通り、非常にTTLは小さいです。

[root@instance-1 ~]# ping -c 60 star0001-ctl1
PING star0001-ctl1.us-XXXXX (10.128.0.27) 56(84) bytes of data.
64 bytes from star0001-ctl1.us-XXXXX (10.128.0.27): icmp_seq=1 ttl=64 time=0.773 ms
64 bytes from star0001-ctl1.us-XXXXX (10.128.0.27): icmp_seq=2 ttl=64 time=0.247 ms
(省略)
64 bytes from star0001-ctl1.us-XXXXX (10.128.0.27): icmp_seq=60 ttl=64 time=0.247 ms

--- star0001-ctl1.us-XXXXX ping statistics ---
60 packets transmitted, 60 received, 0% packet loss, time 60427ms
rtt min/avg/max/mdev = 0.216/0.262/0.773/0.070 ms
[root@instance-1 ~]# 

I/O負荷は以下コマンドで入れています。

while :
do
dd if=/dev/zero of=/dev/mapper/mpathf bs=8192 oflag=direct
done

あとがき

今回作成したストレージを初期構成から変更するツールの導入は別記事を参照下さい。通常ストレージ製品には固有のコマンドやWebツールが提供されており、それらを利用して変更をします。
また、このストレージはRAIDを構成していませんが、LVMでのRAIDやmdadm (ソフトウェアRAID) をPacemakerで管理することでこれも実現できると思います (未検証)。

脚注
  1. 名前は任意のものでも構いませんが末尾は1台目「1」、2台目「2」とする必要があり、また、末尾の直前までは2台で全く同じである必要があります ↩︎

  2. SSHログインのためのIPアドレスはVirtualBoxウィンドウから一度ログインしてip addr showコマンドで確認します (enp0s3デバイス) ↩︎

  3. ブリッジアダプターを使わない構成 (アダプター1にNATを選択) の場合のゲストマシンへの接続はlocalhostのポートフォワーディング設定したホストポート (例:10022) を指定します ↩︎

  4. ブリッジアダプターを使わない構成 (アダプター1にNATを選択) の場合はhttp://localhost:30080を指定します ↩︎

  5. 日付をまたぐ期間は日付をまたぐ前後の2つのファイルが生成されます、例えば0時20分時点での*.30m.svgは前日分*.30m.1.svg、当日分*.30m.2.svgが生成されます ↩︎

  6. Windows上のVirualBoxにおける一般的な特性ではなく、筆者のマシン環境 (VirtualBox 6.1.50) における結果です ↩︎

Discussion