💫

クラウドに再現する従来型ストレージ【AWS構築編】

2023/11/05に公開

はじめに

以下の記事の構築編です。クラウド (AWS) に従来型ストレージを構築していきます。
AWS固有機能は使用していませんのでAWS以外でも複数マシン間のディスク共有が設定できる環境 (他クラウド、VirtualBoxなど) で同様に構築可能です。

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

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

クラウド利用にあたっては料金がかかります。 未使用時にはEC2インスタンスを停止する、不要になった際には速やかにリソースを削除することで料金を抑えられます。

構成概要

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

スター?

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


AWSに作るストレージのイメージ

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

ストレージの構築

AWSが既に使える状態からの手順です。
コマンド操作はコピー&ペーストができますので実際の手数はそれほど多くありません。

1. コントローラ作成 (新規EC2インスタンス2台)

以下オプションを指定して新規EC2インスタンスを2台作成します。

項目 設定値 備考
リージョン 東京 (ap-northeast-1) io1ボリューム利用時は制限あり
名前[1] star0001-ctl1 2台目はstar0001-ctl2とする
AMI Rocky Linux 8 (Official)
インスタンスタイプ t3a.medium
キーペア名 (任意の名前可) gemini 1台目で新しく作成
サブネット ap-northeast-1d
セキュリティグループ名 STAR 1台目で新しく作成
- 説明 STAR security group
セキュリティグループルールを追加 (クリックして1つ追加) 1台目で作成するグループに追加
- タイプ All traffic
- ソースタイプ カスタム
- ソース 172.31.0.0/16 デフォルト VPC
高度な詳細 - ホスト名のタイプ リソース名
その他 デフォルト指定 パブリックIPが必要 (SSH、dnf)

2. 共有ディスク割り当て (EBSマルチアタッチ)

Elastic Block Store - ボリュームをクリックし、ボリュームの作成をクリックします。

項目 設定値 備考
ボリュームタイプ プロビジョンドIOPS SSD (io2) io1またはio2である必要あり
サイズ 10GiB 2つ作成
アベイラビリティーゾーン ap-northeast-1d インスタンスと同じ
マルチアタッチを有効化 チェックする

作成したボリュームを識別しやすくするために名前を付けることをお勧めします。

作成したボリュームを1つずつ選択、アクションからボリュームのアタッチをクリックし、インスタンスにアタッチします (ボリューム2つ×インスタンス2つで4回の操作)。

制約を含む詳細は以下に情報があります。
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ebs-volumes-multi.html

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

コントローラ (EC2インスタンス) にSSHでログインします (ユーザ名はrocky)。
EC2 Instance Connectは使えないのでSSHクライアントで公開鍵認証 (キーペア名geminiで作成した秘密鍵を使用) で接続します。

sudo su - コマンドでrootユーザになります。

コントローラ1,2で以下のコマンドを実行し、ホスト名・hosts設定を行います。

コピー&ペースト可 (★アドレス書換え要)
## 変数設定
ARRAY_NAME="star0001"
NODE1_NAME="${ARRAY_NAME}-ctl1"   # ctl1ではない場合は書換え必要
NODE2_NAME="${ARRAY_NAME}-ctl2"   # ctl2ではない場合は書換え必要
NODE1_ADDR="XXX.XXX.XXX.XXX"      # ctl1のプライベート IPv4 アドレスに書換え必要
NODE2_ADDR="YYY.YYY.YYY.YYY"      # ctl2のプライベート IPv4 アドレスに書換え必要

## ホスト名設定
NODE_ADDR=`curl http://169.254.169.254/latest/meta-data/local-ipv4`
NODE_NAME=$NODE1_NAME
if [ $NODE_ADDR != $NODE1_ADDR ];
then
  NODE_NAME=$NODE2_NAME
fi
hostnamectl set-hostname $NODE_NAME

## hosts設定
echo $NODE1_ADDR $NODE1_NAME >> /etc/hosts
echo $NODE2_ADDR $NODE2_NAME >> /etc/hosts

## プロンプトにホスト名反映
exec bash

echo '( ´∀`)σσ それイイ!'

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

コピー&ペースト可
## タイムゾーン設定
timedatectl set-timezone Asia/Tokyo ; timedatectl

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

## selinux無効
grubby --update-kernel ALL --args selinux=0

## サービス起動設定
systemctl start vsftpd   # ftpはSSH設定まで利用、enableしない
systemctl enable --now pcsd iscsid firewalld
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|nvme-Amazon_Elastic_Block_Store_.*|'
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
#!/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有効化と追記
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="3072M"
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. サーバ作成 (新規EC2インスタンス1台)

以下オプションを指定して新規EC2インスタンスを1台作成します。

項目 設定値 備考
リージョン 東京 (ap-northeast-1)
名前 instance-1
AMI Rocky Linux 8 (Official)
インスタンスタイプ t3a.medium
キーペア名 gemini 新しいキーペアでも可
ネットワーク設定 HTTP トラフィックを許可 性能情報のWeb参照用
サブネット ap-northeast-1d ストレージと同じもの
その他 デフォルト指定 パブリックIPが必要 (SSH、dnf、apache)

2. サーバ設定

サーバ (EC2インスタンス) にSSHでログインします (ユーザ名はrocky)。
EC2 Instance Connectは使えないのでSSHクライアントで公開鍵認証で接続します。

sudo su - コマンドでrootユーザになります。

以下のコマンドを実行し、hosts設定を行います。

コピー&ペースト可 (★アドレス書換え要)
## 変数設定
ARRAY_NAME="star0001"
NODE1_NAME="${ARRAY_NAME}-ctl1"   # ctl1ではない場合は書換え必要
NODE2_NAME="${ARRAY_NAME}-ctl2"   # ctl2ではない場合は書換え必要
NODE1_ADDR="XXX.XXX.XXX.XXX"      # ctl1のプライベート IPv4 アドレスに書換え必要
NODE2_ADDR="YYY.YYY.YYY.YYY"      # ctl2のプライベート IPv4 アドレスに書換え必要
SERVER_NAME="instance-1"

## ホスト名設定
hostnamectl set-hostname $SERVER_NAME

## hosts設定
echo $NODE1_ADDR $NODE1_NAME >> /etc/hosts
echo $NODE2_ADDR $NODE2_NAME >> /etc/hosts

## プロンプトにホスト名反映
exec bash

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

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

コピー&ペースト可
## 変数設定
ARRAY_NAME="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
yum update -y

## selinux無効
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 '(。・ω・)ノ゙ ありがとう'

EC2インスタンスの一覧画面からサーバのパブリック IPv4 アドレスにWeb接続 (http://アドレス) します。

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

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

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

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


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

07:08:24頃からIOPSが2,000程度に制限されていることがわかります。
これはストレージを構成するEC2インスタンスタイプをt3a.mediumにした影響 (ベースライン IOPSに低下) と考えられます。

アスタリスク (*) が付いているインスタンスでは、最大パフォーマンスを 24 時間につき少なくとも 30 分間維持することができます。その後、ベースラインの性能に戻ります。


「Amazon EBS 最適化インスタンスを使用する」より引用

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ebs-optimized.html

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

while :
do
dd if=/dev/zero of=/dev/mapper/mpathf bs=8192 oflag=direct
done
ddは8KBずつの指定 (bs=8192) なのにグラフは4KBずつのI/Oに見える?

multipath設定 (/etc/multipath.conf) にmax_sectors_kb 256を追加 (2023.11.6) しましたのでddの指定通りに8KBでI/Oされるようになりました。

あとがき

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

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

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

  3. EBSで冗長化のためにRAIDを構成する必要はありませんが、これを作るモチベーションの従来型ストレージを再現する、という観点での比較です ↩︎

Discussion