2018年7月3日火曜日

AWSとVIP

ご無沙汰しております。
最近、AWSの仕事が多くなってきました。
こちら((番外編) AWSに冗長化NFSサーバ)にも追記で少し書きましたが、去年と今年の新サービスで、かなりの弱点がなくなり、オンプレからクラウドへの移行がお勧めできる状態になりました。私が書く記事は冗長化に関するものが多いですが、今回もご多分に漏れず、この方向です。

AWSでHA構成を組みたい、という要望があります。HA構成のために必要となる要素をあげると、VIPと共有ストレージが問題となります。共有ストレージについては、LIO Cluster [LIO, DRBD, Pacemaker による冗長化 iSCSI Target]をAWSに移植すればよいです。この連載は書いている途中でかなりの時間がたってしまい恐縮ですが、取り合えず続きを書く前に、AWS編を書こうかと思っております。本稿では、その移植でも問題となるVIPについての情報をまとめていきたいと思います。

AWSでは、オンプレと同じ方法でVIPを扱うことができません。AWSのAPI呼出しと連携する必要があります。具体的な方法としては、以下の4つのうちのどれかを選択することになります。

(a) EIPを移動する
(b) ルートテーブルを書き換える
(c) セカンダリプライベートIPアドレスを移動する
(d) ENIを移動する

どれも一長一短があって、要件に合わせて選択する必要があります。


【特徴】

(a) EIPを移動する

この方法は、AWSのグローバルIPアドレスであるEIPがVIPの代わりとなります。特徴は以下の通りです。

・AZ(Availability Zone)をまたがったHA構成可能
・インターネットからの通信を受付
・NAT経由

NAT経由の通信を許容しないプロトコルでは利用できません。EC2のOS内部では、EIPを持っているかどうか関知していません。クラスタソフトでは、VIPリソースの代わりにEIPリソースをフェイルオーバーさせることになります。
グローバルIPアドレス経由でしか通信できません。こちらは、HA構成をとっているサーバ側だけでなく、サービスを利用するクライアント側もインターネット経由でのアクセスが必須である点にも注意が必要です。Direct Connect経由で通信する場合、通信業者とよく相談しておくことをお勧めします。特に通信経路の暗号化がどうなっているのかについて吟味する必要があるのではないでしょうか。
(b),(C)案ではどうしても要件を満たせない場合に検討するのがこの(a)案です。

(b) ルートテーブルを書き換える

VIP宛ての通信を、Active機のENI(EC2等に紐づける仮想NIC)宛てに届ける指示となるようにルートテーブルを書き換えます。

・AZ(Availability Zone)をまたがったHA構成可能
・VIPはVPCのCIDR外のIPアドレスを指定
・VPC外からの通信は不能
・HAを構成するEC2で「Change Source/Dest Check」をオフに設定
・EC2のOS側でVIP宛て通信を受け入れるように設定
・切り替え時間が短い

VIP宛てDirect Connect経由での通信、マネージドVPN接続経由での通信、VPCピアリング経由での通信がない場合、一番よい方法と思われます。
本来はNATサーバを構築するための仕組みを利用しています。

(c) セカンダリプライベートIPアドレスを移動する

VIPをEC2のセカンダリプライベートIPアドレスとして設定し、OS側でもVIPを設定します。

・単一のAZ(Availability Zone)内(単一のサブネット内)でのみHA構成可能
・VPC外からの通信を受付可能

マルチAZ構成は取らないが、冗長化はしたいという場合の最適解です。オンプレでHA構成を扱っていたのであれば、一番わかりやすい構成なのではないかと思われます。Direct Connect経由での通信、マネージドVPN接続経由での通信、VPCピアリング経由での通信にも対応しています。

(d) ENIを移動する

仮想NICをEC2間で移動してしまおうという構成です。

・単一のAZ(Availability Zone)内(単一のサブネット内)でのみHA構成可能
・VPC外からの通信を受付可能
・MACアドレスも移動可能
・切り替え時間が長い

仮想環境ならではの発想です。メリットはMACアドレスも移動可能なことくらいで、それを除けば、(c)案を採用すべきです。その理由は、同一セグメントでのマルチホーミングとなるからです。Linuxであれば、iproute2を利用することで対応可能ですが、Windowsだと非常に難しいのではないかと思われます。何を言っているのか分からない方は、無条件でこの方法は却下してください。一部通信が思うようにできない場合、マルチホーミングの対応が誤っている可能性が高いです。
MACアドレスを移動しなければならない要件というのはまず無いはずなので、この案のことは忘れましょう。私は忘れましたので、実装例は他にあたってください。

(番外)VIPを移動させたように見せかけるフェイク

「DRBD_EC2_HA_VIP_White_Paper.pdf」というドキュメントに掲載されている方法で、OS上ではVIPを移動させますが、ルートテーブル等のAWSリソースを動的に書き換えることはせずに固定しておきます。AZ aにあるActive機に対して、AZ aにあるクライアントαがVIP宛て通信できますが、AZ cにあるクライアントβは通信不能です。AZ cにあるStand-by機が昇格してVIPを持つと、AZ cにあるクライアントβがVIP宛て通信可能ですが、AZ aにあるクライアントαは通信できません。これがHA構成のVIPとして有効なのかは非常に疑問で、このような動作を望んでいる読者は皆無に近いのではないかと思います。しかも、このドキュメントにはこのような制限が存在していることを全く述べておりません。有効なケースが全くないとまでは言いませんが、読者を迷宮に誘い込んで、問い合わせを増やそうという目論見以外の何物でもありません。ググるとよく引っかかるドキュメントなので注意が必要です。この会社のドキュメントは、肝心なことをわざと書かないことが多いので、気を付けましょう。


【CentOS7での実装】

AWS CLIコマンドの実行例を示した上で、Pacemakerのリソースエージェントを作ります。
VIPリソースを持たせたいEC2にロールを紐づけて権限を付与済みの前提です。AWS CLIの実行環境もインストール済みとします。

(a) EIPを移動する

EIPを取得します。


export AWS_DEFAULT_REGION=ap-northeast-1

aws ec2 allocate-address --domain vpc
{
    "PublicIp": "52.194.126.86",
    "Domain": "vpc",
    "AllocationId": "eipalloc-01f097befc3a79b1c"
}

aws ec2 create-tags --resources eipalloc-01f097befc3a79b1c --tags Key=Name,Value="vip1"


名前を付けると後で便利です。常に名前を付ける習慣にしましょう。

EIPをEC2に紐づけます。


INS_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null)
echo $INS_ID
i-035c6ee3958191ef7

aws ec2 associate-address --instance-id $INS_ID --public-ip 52.194.126.86 --allow-reassociation
{
    "AssociationId": "eipassoc-077d7bb670348f076"
}


EIPの状態を確認します。


aws ec2 describe-addresses --public-ips 52.194.126.86
{
    "Addresses": [
        {
            "Domain": "vpc",
            "Tags": [
                {
                    "Value": "vip1",
                    "Key": "Name"
                }
            ],
            "InstanceId": "i-035c6ee3958191ef7",
            "NetworkInterfaceId": "eni-0a8728301ed37d7f4",
            "AssociationId": "eipassoc-0559555f1e5bcf7f2",
            "NetworkInterfaceOwnerId": "869741784757",
            "PublicIp": "52.194.126.86",
            "AllocationId": "eipalloc-01f097befc3a79b1c",
            "PrivateIpAddress": "100.65.50.228"
        }
    ]
}


ちなみに、EIPがどのEC2とも紐づいていない場合は以下のようになります。


aws ec2 describe-addresses --public-ips 52.194.126.86
{
    "Addresses": [
        {
            "PublicIp": "52.194.126.86",
            "Domain": "vpc",
            "AllocationId": "eipalloc-01f097befc3a79b1c",
            "Tags": [
                {
                    "Value": "vip1",
                    "Key": "Name"
                }
            ]
        }
    ]
}


「InstanceId」の値が自己のインスタンスIDと一致すれば当該EIPを持っている、そうでなければ持っていない、と判断してよいことが分かります。


if [ "$(aws ec2 describe-addresses --public-ips 52.194.126.86 | grep InstanceId | awk -F\" '{print $4}')" = $(curl http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null) ]; then
>   echo active
> else
>   echo not active
> fi
active


以下のようにメタデータを取得した場合、反映に数秒かかるようなので、EIPの状態確認目的で利用するのはやめましょう。


curl http://169.254.169.254/latest/meta-data/public-ipv4
52.194.126.86

curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/ipv4-associations/
52.194.126.86


EIPの紐づけを解除します。


aws ec2 disassociate-address --public-ip 52.194.126.86


リソースエージェントは以下のようになります。


cat << 'EOF' | sudo tee /usr/lib/ocf/resource.d/heartbeat/EIP
#!/bin/bash
#
#     EIP OCF RA. manages AWS EIP.
#
#   (c) 2009-2010 Florian Haas, Dejan Muhamedagic,
#                 and Linux-HA contributors
#
#      modified by Katsuaki Hamada (hamada@pc-office.net), 1 July 2018
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it would be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Further, this software is distributed without any warranty that it is
# free of the rightful claim of any third person regarding infringement
# or the like.  Any license provided herein, whether implied or
# otherwise, applies only to this software file.  Patent licenses, if
# any, provided herein do not apply to combinations of this program with
# other software, or any other product whatsoever.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
#

#######################################################################
# Initialization:
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs

# Lockfile, used for selecting a target ID
LOCKFILE=${HA_RSCTMP}/eip.lock
#######################################################################

meta_data() {
  cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="EIP" version="1.0">
<version>1.0</version>

<longdesc lang="en">
Manages AWS EIP.
</longdesc>
<shortdesc lang="en">AWS EIP agent</shortdesc>

<parameters>
<parameter name="vip" required="1" unique="1">
<longdesc lang="en">
AWS EIP. Specify EIP to substitute for VIP.
</longdesc>
<shortdesc lang="en">EIP</shortdesc>
<content type="string" />
</parameter>
</parameters>

<actions>
<action name="start" timeout="10" />
<action name="stop" timeout="10" />
<action name="status" timeout="10" interval="30" depth="0" />
<action name="monitor" timeout="10" interval="30" depth="0" />
<action name="meta-data" timeout="5" />
<action name="validate-all" timeout="10" />
</actions>
</resource-agent>
END
}

#######################################################################

export AWS_DEFAULT_REGION=$(curl http://169.254.169.254/latest/dynamic/instance-identity/document 2> /dev/null|grep region|awk -F\" '{print $4}')

EIP_usage() {
  cat <<END
usage: $0 {start|stop|status|monitor|validate-all|meta-data}

Expects to have a fully populated OCF RA-compliant environment set.
END
}

EIP_start() {
  if aws ec2 associate-address --instance-id $(curl http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null) --public-ip ${OCF_RESKEY_vip} --allow-reassociation; then
    return $OCF_SUCCESS
  else
    return $OCF_ERR_GENERIC
  fi
}

EIP_stop() {
  if aws ec2 disassociate-address --public-ip ${OCF_RESKEY_vip}; then
    return $OCF_SUCCESS
  else
    return $OCF_ERR_GENERIC
  fi
}

EIP_monitor() {
  if [ "$(aws ec2 describe-addresses --public-ips ${OCF_RESKEY_vip} | grep InstanceId | awk -F\" '{print $4}')" = $(curl http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null) ]; then
    return $OCF_SUCCESS
  else
    return $OCF_NOT_RUNNING
  fi
}

EIP_validate() {
  if ! ocf_is_probe; then
    # Do we have all required binaries?
    check_binary aws
  fi
  return $OCF_SUCCESS
}

case $1 in
  meta-data)  meta_data; exit $OCF_SUCCESS;;
  usage|help) EIP_usage; exit $OCF_SUCCESS;;
esac

# Everything except usage and meta-data must pass the validate test
EIP_validate

case $__OCF_ACTION in
  start)          EIP_start;;
  stop)           EIP_stop;;
  monitor|status) EIP_monitor;;
  reload)         ocf_log err "Reloading..."; EIP_start;;
  validate-all)   ;;
  *)              EIP_usage; exit $OCF_ERR_UNIMPLEMENTED;;
esac
rc=$?
ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
exit $rc
EOF
sudo chmod 755 /usr/lib/ocf/resource.d/heartbeat/EIP


(b) ルートテーブルを書き換える

VIP宛てに通信するクライアントが所属しているサブネットに紐づけられているルートテーブルのIDを取得します。名前をキーにして検索してみます。


export AWS_DEFAULT_REGION=ap-northeast-1

RTB_NAME=rtb-private-vpc
aws ec2 describe-route-tables --filters "Name=tag:Name,Values=${RTB_NAME}" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq
rtb-061bbb47b6e9db5bc


VIP宛てに通信するクライアントが増えて、別のサブネットからも通信してくることがあるたびに、クラスタのリソース設定を変更するのはよくないので、名前で検索するのではなく、特定のタグが付いているルートテーブルのID一覧を取得してみます。値は「true」固定とします。


TAG_NAME=VIP10.0.0.1
aws ec2 describe-route-tables --filters "Name=tag:${TAG_NAME},Values=true" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq
rtb-0446b0cfad48c9634
rtb-061bbb47b6e9db5bc


VPC内の全てのルートテーブルを書き換える場合は、以下のコマンドでルートテーブルのID一覧を取得可能です。該当するタグが一つも見つからなかった場合はこちらを利用することにします。


aws ec2 describe-route-tables --filters Name=vpc-id,Values=$(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/vpc-id 2> /dev/null) | grep RouteTableId | awk -F\" '{print $4}' | sort | uniq


取得したルートテーブルが持っているルート一覧を表示してみます。


for i in $(aws ec2 describe-route-tables --filters "Name=tag:${TAG_NAME},Values=true" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq)
> do
>   echo $i
>   aws ec2 describe-route-tables --route-table-ids $i | grep DestinationCidrBlock
>   echo
> done
rtb-0446b0cfad48c9634
                    "DestinationCidrBlock": "100.65.0.0/16",

rtb-061bbb47b6e9db5bc
                    "DestinationCidrBlock": "100.65.0.0/16",


VIPへのルートを追加します。


for i in $(aws ec2 describe-route-tables --region ${AWS_DEFAULT_REGION} --filters "Name=tag:${TAG_NAME},Values=true" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq)
> do
>   echo $i
>   aws ec2 create-route --route-table-id $i --destination-cidr-block 10.0.0.1/32 --instance-id $(curl http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null)
>   echo
> done
rtb-0446b0cfad48c9634
{
    "Return": true
}

rtb-061bbb47b6e9db5bc
{
    "Return": true
}


ルート一覧を再度表示してみます。


for i in $(aws ec2 describe-route-tables --region ${AWS_DEFAULT_REGION} --filters "Name=tag:${TAG_NAME},Values=true" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq)
> do
>   echo $i
>   aws ec2 describe-route-tables --route-table-ids $i | grep DestinationCidrBlock
>   echo
> done
rtb-0446b0cfad48c9634
                    "DestinationCidrBlock": "10.0.0.1/32",
                    "DestinationCidrBlock": "100.65.0.0/16",

rtb-061bbb47b6e9db5bc
                    "DestinationCidrBlock": "10.0.0.1/32",
                    "DestinationCidrBlock": "100.65.0.0/16",



VIPへのルートを削除します。


for i in $(aws ec2 describe-route-tables --region ${AWS_DEFAULT_REGION} --filters "Name=tag:${TAG_NAME},Values=true" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq)
> do
>   aws ec2 delete-route --route-table-id $i --destination-cidr-block 10.0.0.1/32
> done


リソースエージェントは以下のようになります。


cat << 'EOF' | sudo tee /usr/lib/ocf/resource.d/heartbeat/ART
#!/bin/bash
#
#     ART OCF RA. adjust AWS route table so that VIP works.
#
#   (c) 2009-2010 Florian Haas, Dejan Muhamedagic,
#                 and Linux-HA contributors
#
#      modified by Katsuaki Hamada (hamada@pc-office.net), 1 July 2018
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it would be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Further, this software is distributed without any warranty that it is
# free of the rightful claim of any third person regarding infringement
# or the like.  Any license provided herein, whether implied or
# otherwise, applies only to this software file.  Patent licenses, if
# any, provided herein do not apply to combinations of this program with
# other software, or any other product whatsoever.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
#

#######################################################################
# Initialization:
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs

# Lockfile, used for selecting a target ID
LOCKFILE=${HA_RSCTMP}/art.lock
#######################################################################

meta_data() {
  cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="ART" version="1.0">
<version>1.0</version>

<longdesc lang="en">
Adjust AWS route table so that VIP works.
</longdesc>
<shortdesc lang="en">ART agent</shortdesc>

<parameters>
<parameter name="vip" required="1" unique="1">
<longdesc lang="en">
VIP.
</longdesc>
<shortdesc lang="en">VIP</shortdesc>
<content type="string" />
</parameter>
</parameters>

<actions>
<action name="start" timeout="10" />
<action name="stop" timeout="10" />
<action name="status" timeout="10" interval="30" depth="0" />
<action name="monitor" timeout="10" interval="30" depth="0" />
<action name="meta-data" timeout="5" />
<action name="validate-all" timeout="10" />
</actions>
</resource-agent>
END
}

#######################################################################

export AWS_DEFAULT_REGION=$(curl http://169.254.169.254/latest/dynamic/instance-identity/document 2> /dev/null|grep region|awk -F\" '{print $4}')

ART_usage() {
  cat <<END
usage: $0 {start|stop|status|monitor|validate-all|meta-data}

Expects to have a fully populated OCF RA-compliant environment set.
END
}

ART_start() {
  IDs=$(aws ec2 describe-route-tables --filters "Name=tag:VIP${OCF_RESKEY_vip},Values=true" | grep '"RouteTableId":' | awk -F\" '{print $4}' | sort | uniq)
  if [ "$IDs" = "" ]; then
    IDs=$(aws ec2 describe-route-tables --filters Name=vpc-id,Values=$(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/vpc-id 2> /dev/null) | grep RouteTableId | awk -F\" '{print $4}' | sort | uniq)
  fi
  rc=$OCF_SUCCESS
  for i in $IDs
  do
    aws ec2 create-route --route-table-id $i --destination-cidr-block ${OCF_RESKEY_vip}/32 --instance-id $(curl http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null)
    if [ $? -ne 0 ]; then
      rc=$OCF_ERR_GENERIC
    fi
  done
  return $rc
}

ART_stop() {
  IDs=$(aws ec2 describe-route-tables --filters Name=vpc-id,Values=$(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/vpc-id 2> /dev/null) | grep RouteTableId | awk -F\" '{print $4}' | sort | uniq)
  for i in $IDs
  do
    aws ec2 delete-route --route-table-id $i --destination-cidr-block ${OCF_RESKEY_vip}/32
  done
  return $OCF_SUCCESS
}

ART_monitor() {
  if aws ec2 describe-route-tables --filters Name=vpc-id,Values=$(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/vpc-id 2> /dev/null) | grep -q "DestinationCidrBlock.*[^0-9]$(echo ${OCF_RESKEY_vip} | sed -e 's/\./\\./g')/32"; then
    return $OCF_SUCCESS
  else
    return $OCF_NOT_RUNNING
  fi
}

ART_validate() {
  if ! ocf_is_probe; then
    # Do we have all required binaries?
    check_binary aws
  fi
  return $OCF_SUCCESS
}

case $1 in
  meta-data)  meta_data; exit $OCF_SUCCESS;;
  usage|help) ART_usage; exit $OCF_SUCCESS;;
esac

# Everything except usage and meta-data must pass the validate test
ART_validate

case $__OCF_ACTION in
  start)          ART_start;;
  stop)           ART_stop;;
  monitor|status) ART_monitor;;
  reload)         ocf_log err "Reloading..."; ART_start;;
  validate-all)   ;;
  *)              ART_usage; exit $OCF_ERR_UNIMPLEMENTED;;
esac
rc=$?
ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
exit $rc
EOF
sudo chmod 755 /usr/lib/ocf/resource.d/heartbeat/ART


(c) セカンダリプライベートIPアドレスを移動する

EC2に紐づいているENIは1つしかない前提で話を進めます。1つのEC2に多くのIPアドレスをアサインしたい場合には、複数のENIを持つ必要が出てくる場合がありますが、本稿では対応しておりません。

セカンダリプライベートIPアドレスを付与します。


VIP=100.65.50.220
aws ec2 assign-private-ip-addresses --network-interface-id $(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/interface-id 2> /dev/null|head -1) --private-ip-addresses $VIP --allow-reassignment


セカンダリプライベートIPアドレスが付与されていることを確認します。


if aws ec2 describe-network-interfaces --network-interface-ids $(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/interface-id 2> /dev/null|head -1) | grep -q $(echo $VIP | sed -e 's/\./\\./g'); then
>   echo active
> else
>   echo not active
> fi
active


セカンダリプライベートIPアドレスを削除します。


aws ec2 unassign-private-ip-addresses --network-interface-id $(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/interface-id 2> /dev/null|head -1) --private-ip-addresses $VIP


リソースエージェントは以下のようになります。


cat << 'EOF' | sudo tee /usr/lib/ocf/resource.d/heartbeat/SIP
#!/bin/bash
#
#     SIP OCF RA. compliant AWS secondary private ip address script.
#
#   (c) 2009-2010 Florian Haas, Dejan Muhamedagic,
#                 and Linux-HA contributors
#
#      modified by Katsuaki Hamada (hamada@pc-office.net), 1 July 2018
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it would be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Further, this software is distributed without any warranty that it is
# free of the rightful claim of any third person regarding infringement
# or the like.  Any license provided herein, whether implied or
# otherwise, applies only to this software file.  Patent licenses, if
# any, provided herein do not apply to combinations of this program with
# other software, or any other product whatsoever.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
#

#######################################################################
# Initialization:
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs

# Lockfile, used for selecting a target ID
LOCKFILE=${HA_RSCTMP}/sip.lock
#######################################################################

meta_data() {
  cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="SIP" version="1.0">
<version>1.0</version>

<longdesc lang="en">
Compliant AWS secondary private ip address script.
</longdesc>
<shortdesc lang="en">SIP agent</shortdesc>

<parameters>
<parameter name="vip" required="1" unique="1">
<longdesc lang="en">
VIP.
</longdesc>
<shortdesc lang="en">VIP</shortdesc>
<content type="string" />
</parameter>
</parameters>

<actions>
<action name="start" timeout="10" />
<action name="stop" timeout="10" />
<action name="status" timeout="10" interval="30" depth="0" />
<action name="monitor" timeout="10" interval="30" depth="0" />
<action name="meta-data" timeout="5" />
<action name="validate-all" timeout="10" />
</actions>
</resource-agent>
END
}

#######################################################################

export AWS_DEFAULT_REGION=$(curl http://169.254.169.254/latest/dynamic/instance-identity/document 2> /dev/null|grep region|awk -F\" '{print $4}')

SIP_usage() {
  cat <<END
usage: $0 {start|stop|status|monitor|validate-all|meta-data}

Expects to have a fully populated OCF RA-compliant environment set.
END
}

SIP_start() {
  if aws ec2 assign-private-ip-addresses --network-interface-id $(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/interface-id 2> /dev/null|head -1) --private-ip-addresses $OCF_RESKEY_vip --allow-reassignment; then
    return $OCF_SUCCESS
  else
    return $OCF_ERR_GENERIC
  fi
}

SIP_stop() {
  if aws ec2 unassign-private-ip-addresses --network-interface-id $(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/interface-id 2> /dev/null|head -1) --private-ip-addresses $OCF_RESKEY_vip; then
    return $OCF_SUCCESS
  else
    return $OCF_ERR_GENERIC
  fi
}

SIP_monitor() {
  if aws ec2 describe-network-interfaces --network-interface-ids $(curl http://169.254.169.254/latest/meta-data/network/interfaces/macs/$(curl http://169.254.169.254/latest/meta-data/mac 2> /dev/null|head -1)/interface-id 2> /dev/null|head -1) | grep -q $(echo $OCF_RESKEY_vip | sed -e 's/\./\\./g'); then
    return $OCF_SUCCESS
  else
    return $OCF_NOT_RUNNING
  fi
}

SIP_validate() {
  if ! ocf_is_probe; then
    # Do we have all required binaries?
    check_binary aws
  fi
  return $OCF_SUCCESS
}

case $1 in
  meta-data)  meta_data; exit $OCF_SUCCESS;;
  usage|help) SIP_usage; exit $OCF_SUCCESS;;
esac

# Everything except usage and meta-data must pass the validate test
SIP_validate

case $__OCF_ACTION in
  start)          SIP_start;;
  stop)           SIP_stop;;
  monitor|status) SIP_monitor;;
  reload)         ocf_log err "Reloading..."; SIP_start;;
  validate-all)   ;;
  *)              SIP_usage; exit $OCF_ERR_UNIMPLEMENTED;;
esac
rc=$?
ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
exit $rc
EOF
sudo chmod 755 /usr/lib/ocf/resource.d/heartbeat/SIP



【ロール】

EC2に紐づけるロールの作成例は以下の通りです。Actionは3つのリソースエージェントのどれを選んでも問題ないようにリストアップしているので、必要に応じて削っていただければと思います。


cat << 'EOF' > vip-role.json
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

cat << 'EOF' >vip-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "ec2:DescribeAddresses",
        "ec2:AssociateAddress",
        "ec2:DisassociateAddress",
        "ec2:DescribeRouteTables",
        "ec2:CreateRoute",
        "ec2:DeleteRoute",
        "ec2:DescribeNetworkInterfaces",
        "ec2:AssignPrivateIpAddresses",
        "ec2:UnassignPrivateIpAddresses"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
EOF

aws iam create-role --role-name vip-role --assume-role-policy-document file://vip-role.json
{
    "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2008-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Principal": {
                        "Service": "ec2.amazonaws.com"
                    },
                    "Effect": "Allow",
                    "Sid": ""
                }
            ]
        },
        "RoleId": "AROAIUYAU2DBXLFEDX3WG",
        "CreateDate": "2018-07-01T03:39:06.243Z",
        "RoleName": "vip-role",
        "Path": "/",
        "Arn": "arn:aws:iam::999999999999:role/vip-role"
    }
}

ret=$(aws iam create-policy --policy-name vip-policy --policy-document file://vip-policy.json)
echo "$ret"
{
    "Policy": {
        "PolicyName": "vip-policy",
        "CreateDate": "2018-07-01T03:39:27.195Z",
        "AttachmentCount": 0,
        "IsAttachable": true,
        "PolicyId": "ANPAJ2TASVJGLJF3EOLBI",
        "DefaultVersionId": "v1",
        "Path": "/",
        "Arn": "arn:aws:iam::999999999999:policy/vip-policy",
        "UpdateDate": "2018-07-01T03:39:27.195Z"
    }
}

aws iam attach-role-policy --role-name vip-role --policy-arn $(echo "$ret" | grep arn:aws:iam | awk -F\" '{print $4}')

aws iam create-instance-profile --instance-profile-name vip-role --path /
{
    "InstanceProfile": {
        "InstanceProfileId": "AIPAII33IEQ6L5DRU7K6W",
        "Roles": [],
        "CreateDate": "2018-07-01T03:40:22.450Z",
        "InstanceProfileName": "vip-role",
        "Path": "/",
        "Arn": "arn:aws:iam::999999999999:instance-profile/vip-role"
    }
}

aws iam add-role-to-instance-profile --instance-profile-name vip-role --role-name vip-role


2016年12月20日火曜日

LIO Cluster [LIO, DRBD, Pacemaker による冗長化 iSCSI Target] (その16)障害テスト計画


障害テストを実施するにあたり、その計画を立てようという話です。関連する運用設計についても触れてみたいと思います。

今回のような HA クラスタの場合、フェイルオーバが発生し、障害原因が取り除かれた後、スイッチバックさせるべきかどうか、という問題があります。
できるだけサービスを停止させたくない、ということであればスイッチバックさせたくない、という方向に話が進みます。
運用が楽な方がいいということであれば、1号機が常に Active 機であるという運用設計を採用し、スイッチバックさせておくべきであるということに落ち着きやすいです。分かりやすい(常に1号機でサービスが稼働している)ことが大切で、未然に事故を防ぐことにつながります。テスト工数も単純に半分で済みます。
どちらがいい、悪いではなく、どちらに決めるか、という問題です。
スイッチバックさせるためには、スイッチバック中のサービス停止が問題となります。
今回の構成では、2秒ほどでスイッチバックできるので、サービス利用が一番少ない時間帯にスイッチバックさせれば影響はほとんどない、ということでいいと思います。
2秒ほどのサービス停止に関する調整が簡単に許されないのであれば、もっともっと、ずーと高価なストレージを採用すべきです。コントローラのスイッチオーバが2秒で完了するストレージがあればいいですが、よく知りません。

今回は、1号機が常に Active 機である、という運用設計を採用したとして話を進めます。
2号機でサービスが提供されている状態は、仮復旧しているだけで障害が継続している状態ということになります。
構築編では特に話を出しませんでしたが、1号機(iscsitgt01a)で優先的にリソースが起動される設定を入れてあります。

1号機(iscsitgt01a)の障害が回復した場合に自動フェイルバックする設定を入れるべきかどうかについては、入れないこととします。
障害の原因が分からないままに、正しい対応ができている確信がないままに自動復旧させるのは危険だからです。状態によってはデータを失ってしまいます。
ボンディングの設定については、プライマリスレーブが有効なときはいつでもアクティブなスレーブとなる設定となっています。Red Hat 社のマニュアルにあるとおりの設定です。何度もフェイルオーバが発生する状況(いわゆる往復ビンタ状態)に陥った場合、サービス遅延やサービス停止にはなってもデータロストにはならないという判断が働いているのだと思います。テストで問題が発生しない限りは、Red Hat 社の判断を尊重し、ボンディングについては自動フェイルバックする設定のままとします。
自動復旧は期待しない、という運用に決めたので、OS 起動時に pacemaker.service を自動起動させる設定も入れないこととします。
クラスタの起動は、オペレータが明示的にサービス起動させる運用とします。
自動復旧させないということは、障害が発生して以降は必ず冗長性が失われているということになりますので、障害発生時には直ちに駆けつけて復旧を急ぐ必要があります。
運用ポリシーとしては、4時間以内に駆けつけなのか、翌営業日対応なのか、というあたりが選択肢となるのだと思われます。

運用設計として、定期点検を実施するかどうか、という話があります。
スタンバイ機が故障していることに気づかないまま運用を続けていると危険です。
スタンバイ機が故障していないことをどのように確認するかですが、スイッチオーバさせてみるのが手っ取り早く、確実な方法です。
スイッチオーバ中のサービス停止が問題となるのは障害復旧時と同じです。
スイッチオーバさせてしまったがゆえに障害が発生してしまった、という事態も想定されます。
コントロールされている時間帯に発生させてしまった方がマシだと考えるべきか、寝ている子は起こすべきでないと考えるべきか、難しい問題です。
スイッチオーバさせないで点検する方法があるかどうかですが、どこまでやれて、どこまで妥協するのか、という話になるものと思います。
定期点検はしないケースの方が多いと思われるので、一旦この問題は保留します。

運用設計として、パッチ適用をどうするのか、という問題もあります。
こちらは、ストレージという点から考えると、閉じたネットワーク空間内に配置されるということもあり、問題が発生しない限り適用しない、ということになると思われます。

データのバックアップについては、今回は上位層で考えるということで割り切りたいと思います。もう1セット LIO Cluster を構築し、バックアップ領域とするなどの対応を考えていただければと思います。仮想環境であれば、Active 機の仮想マシンを丸ごとバックアップすればいいです。
LIO Cluster を3セット用意して Oracle RAC の ASM 用ストレージとするというのはよい考えだと思います。それを更に2セット用意して一方をデータベース用、もう一方をバックアップ用という構成も素敵です。本当に大切なデータについては十二重化くらいまでは考えておくべきです。今回構築した LIO Cluster は非常にシンプルな機能しか実装しておらず、スナップショット機能やバックアップ機能などはあえて実装していません。これらの実装を始めると、汎用性が無くなっていくこともありますが、フェイルオーバ時間が2秒に収まらなくなっていく方が問題と捉えています。高級ストレージは当然これらの機能を有していますが、それらに依存するとベンダーロックインな実装になってしまう点も考慮しておく必要があります。バックアップ対象がある程度の大きさを超えた場合、ストレージの機能を利用しないとバックアップが一定時間内に終了しない、といったことになりますが、そうでない限りは、できるだけストレージの機能に依存する実装は避けるべきだと考えております。
クラウド時代を見越した実装を考えると、ストレージについては、今までの常識にとらわれない考え方をしなければならないケースが増えると思います。スケールアップ型のストレージは高機能ですが、半端なく高価です。スケールアウト型は、安い機材を多数組み合わせることで信頼性と性能を向上させます。レイテンシの向上は最初から諦めたスループット重視のスケールアウト型 (AWS の S3 等) の方が普及していますが、これらは高価なストレージの代替にはなりえない点が残念です。今回紹介する構成が、レイテンシの向上も見越したスケールアウト型ストレージの土台となれば幸いです。iSCSI イニシエータ側で LVM を使って束ねて使う、というのがもう1つのおすすめ構成となります。

OS 部分のバックアップについては、取得しておくことにします。バックアップが使えることの確認、リストア・リカバリ手順を整えておく必要があります。バックアップの有効期限についても考慮する必要があり、バックアップ後の設定変更に対応できるのかどうかも問題となります。
お奨めはできませんが、今回の構成程度であれば再構築でも十分短時間でできる、という判断もありだと思います。初期構築とスタンバイ機のみの復旧構築では手順が異なる部分も存在するので、事前に確認しておく必要があります。


どのような障害試験を行うかですが、全ての障害ケースをテストするのは実質的に不可能です。そこで、ある程度の絞り込みを行う必要があります。

一つ目の原則として、二重障害は考慮しない、というものがあります。
二重障害、三重障害等を考え始めると、テストケースが爆発的に増えていきます。
今回は HA 構成ということで2ノードしか存在しておらず、2ノードとも障害が発生してしまえば無力ですので、この原則は比較的お客様の了解を得やすいものと思われます。

ハードウェアを破壊してしまう可能性のあるテストは実施しない、という原則もお客様の了解を得やすい部類だと思います。わざと壊したらサポートを受けられないのでコストに響きます。

ソフト的に破壊してしまう可能性のあるテストは実施しない、という原則はどうでしょうか。バックアップ、リカバリ試験を兼ねて、実施すべきと考える場合と、テスト工数削減のために実施しないケースがあると思います。
実施すべきだと思います。

全損からの復旧試験は必ず実施し、部分破損からの復旧試験はある程度妥協する、という考え方があります。
障害対応として一番困惑するのは、部分的に壊れているケースです。一部が壊れた場合であっても、全部が壊れたものとみなして対応する、というのは整理としては非常に分かりやすいです。Stonith などは、下手に壊れた状態で放置するよりも積極的に壊れた側を強制停止させてしまおうという発送の下で考えだされた技術です。
LIO のリソースエージェントを改修しました(2016/12/10) が、LIO はカーネルモジュールとして実装されている関係で LIO のサービスを kill するといった対応ができないため、制御不能に陥って kill したい場合には、カーネルを停止させる処理を追加しました。OS 丸ごと停止させるのは乱暴な処置ですので、/etc/ha.d/noreboot というファイルが存在している場合には、単にエラーとなるようにしています。今回の運用であれば、2号機側に /etc/ha.d/noreboot ファイルを作成しておきます。

障害の原因として考えられるのは、ハードウェア故障、操作ミス、ソフトウェア・バグ、誤作動などが挙げられます。
ソフトウェアのバグや誤作動をソフトウェアでカバーするのは非常に難しいですが、カーネルパニックが発生した場合や、ビジーループによりひどい処理遅延が発生した場合にフェイルオーバさせることで対処できる場合もあります。
今回の構成では、ソフトウェア watchdog を仕込んでいるので、運が良ければ自動的にフェイルオーバします。運悪くフェイルオーバしない場合でも、別途監視していて障害を検知できた場合は、手動でフェイルオーバさせることで仮復旧できることもあります。これらの事象をわざと発生させることや再現させることは非常に難しいのでテスト範囲から除外します。
ハードウェア故障については、ハードウェア固有の検知機構が必要です。ハードウェア故障により、後述する障害ケースが引き起こされる場合についてのみテストすることとします。
操作ミスについては、やりがちなミスはテストしておくべきだと思いますが、「rm /」等を実行してしまった、というケースまでやるのはやりすぎだと思います。全損からの復旧試験に含まれているという整理でいいと思います。
全損からの復旧が必要とならないケースについて、クラスタ操作時にやりがちなミスをテストしておけば十分と考えます。

※ うっかり「rm /」を実行してしまうことがあるのか、という問いに対し、「ある」と答えておきます。過去に実施した数千行の手順の中からできるだけ単純化して説明すると、VAR1VAR2 がうっかり未定義の状態で「rm $VAR1/$VAR2」を貼り付けて実行してしまうということをやったことがあります。手順書を作ってその確認をしているときでしたが、開発機で、かつ、バックアップが存在したので最悪の事態に発展するところまではいきませんでした。お客様からは「次からは気を付けてね」と言われるレベルで済みました。今考えても背筋が凍りつく思いがします。この日以来、「su」コマンドは使わずに、できるだけ「sudo」コマンドを使うようになりました。スクリプト内でrm $VAR1/$VAR2」という表現になりそうなときはrm $VAR1$VAR2」に変更し、VAR1 の定義で末尾に「/」を含ませるように心がけています。


具体的な障害ケースは以下の分類で考えることにします。Active 機側で発生する場合、Stand-by 機側で発生する場合、等の分類は省略しています。

◆ノード障害
・シャットダウン
・カーネルパニック
・電源断
・DRBD 特有のスプリットブレイン障害

◆プロセス障害
・Corosync プロセス障害
・Pacemaker プロセス障害
・DRBD プロセス障害 (カーネルモジュールなので難しい)
・LIO プロセス障害 (カーネルモジュールなので難しい)
・Corosync 誤操作
・Pacemaker 誤操作
・LVM 誤操作
・DRBD 誤操作
・LIO 誤操作
・Corosync ポート閉塞
・DRBD ポート閉塞
・LIO ポート閉塞

◆ネットワーク障害
・スイッチ障害
・ポート障害
・インターコネクト障害

◆ストレージ障害
・ローカルハードディスク障害

※ ローカルストレージの RAID 障害は試験範囲外とします。ハードウェアに適した方法で実行しておいた方が良いですが、サポートとの関係で難しい場合もあるかと思われます。

※ ストレージ障害については、共有ディスクに対するパス障害などもありますが、今回の構成では共有ストレージが存在しません。ローカルストレージを読み書きしている DRBD が疑似的な共有ストレージとなっています。

2016年12月19日月曜日

VMware NSX @SoftLayer - その16 【ESXi 6.5 と NSX 6.2.4】


ESXi 6.5 が登場したので、NSX 6.2.4 をインストールして動かしてみようとしましたが、インストールに失敗します。同じ設計で、6.0u2 にすると動きます。
Host Client の完成度もまだまだで、仮想マシンの DVD ドライブのメディアを入れ替えただけで Reload となってしまいます。

Nested 環境も試してみましたが、NSX がインストールできないのはもとより、VMwareTools すら動きません。こちらはサポート範囲外なので文句は言えませんが。

NSX は、VMware 社の主力製品の1つであり、万全のテストを実施してくれているはずですが、ESXi 6.5 にインストールすらできないというのは…。

とりあえず、6.5u1 待ちです。

2016年12月18日日曜日

LIO Cluster [LIO, DRBD, Pacemaker による冗長化 iSCSI Target] (その15)障害テスト環境


LIO Cluster の障害テストを始めるに当たり、どのような環境でテストすべきかについて、思いつくままに述べていきます。

当然のことながら、今後一番利用されるケースを想定した環境を準備したいと思います。

ストレージに関する来年のトレンドは、オールフラッシュ化だと勝手に判断しています。
HDD と SSD の性能差は歴然としており、SSD の信頼性が上がってきたことから、オールフラッシュ化に取り組まれている方も多いと思います。一度オールフラッシュ・ストレージを利用すると、HDD には戻れなくなります。ただ、いかんせん、価格がまだまだ高すぎます。ストレージ専用機でオールフラッシュ化すると、すぐに億単位の見積もりができてしまいます。20~30TB 位の容量を確保し、バックアップを取得し、バックアップを遠隔地にレプリケーションしたい、等ということを考えるとそうなります。本番環境やステージング環境であれば、このような額の投資もありうるものと思われますが、開発環境や検証環境では、なかなか難しいものと思われます。

そこで、LIO Cluster の登場です。
ESXi の共有ディスクとして利用できます。VAAI がサポートされています。弊社の ESXi の共有ディスクは、LIO Cluster にしています。WSFC の共有ディスクとしても利用できます。SCSI3 Persistent Reservation 機能がサポートされています。データベースを冗長化する際の共有ディスクとしても利用できます。


検証環境は機能を満たす範囲で、安ければ安いほどいいということで、弊社の検証環境は、以下のような構成にしています。

サーバ: Fujitsu Primergy TX1310 M1、3万円ほど
CPU: Pentium G3420 (2C2T)
Memory: 4GB
RAID コントローラ: Adaptec、3万円ほど
SSD: Crucial 525GB × 4台、5万円ほど
NIC: X540-T2 (10Gbps 2ポート)、3万円ほど

計 14万円ほど ×2

このスペックのサーバを2台用意し、LIO Cluster にしています。

ESXi をインストールするサーバは以下のスペックを8台用意しています。

サーバ: Fujitsu Primergy TX1310 M1、3万円ほど
CPU: Xeon E3-1231V3 (4C8T)、3万円ほど  (ESXi インストール時にのみグラフィックカードを別途利用)
Memory: 32GB、3万円ほど
NIC: X540-T2 (1Gbps 4ポート)、1万円ほど

計 10万円ほど ×8

スイッチは、10Gbps ポートが4個ついている NetGear の S3300-28X を2台、ダイレクトアタッチケーブル2本でつなぎスタックを構成します。計15万円ほどです。

メーカのサポートは一切ありませんが、それなりに満足して使っています。
ESXi のメモリが 32GB というのは時々つらいので、64GB 搭載可能なサーバに置き換えたい、という考えは持っていますが、もうしばらく我慢するつもりです。VCSA が 16GB も使うので、そのうち 32GB 要求してくるのではないかと危惧しています。



上記の ESXi 8台のうちの4台に、SSD を4台ずつ追加し、10Gbps NIC を追加し、オールフラッシュの VSAN 検証を始めました。10Gbps NIC は、NetGear XS708E に接続しています。


LIO Cluster  上にある仮想サーバ (A) と、VSAN 上にある仮想サーバ (B) で、fio によるベンチマークを取得してみました。

4K Block Random read, 32 Job, 100MB x32 data
 (A) 89.4 MB/s, 22465 iops
 (B) 92.1 MB/s, 23151 iops

4K Block Random write, 32 Job, 100MB x32 data
 (A) 98.5 MB/s, 24638 iops
 (B) 94.8 MB/s, 23718 iops

4K ブロックサイズのランダムアクセス、32個のジョブが同時実行されます。それぞれのジョブは 100 MB のファイルを持っていて、そのファイル内をランダムアクセスします。

SSD: Crucial 525GB の単体性能が、カタログスペックで以下のようになっています。
シーケンシャル読み取り : 530MB/s
シーケンシャル書き込み : 510MB/s
4KBランダム読み取り : 92,000 IOPS
4KBランダム書き込み : 83,000 IOPS

HDD でテストした時の100倍をはるかに超える性能が出ています。
ボトルネックは、サーバの PCIe バス (CPU のバス) の性能だと判断しております。Xeon 2ソケットのサーバでテストすると、もう1桁性能が向上すると考えています。
性能を求めると、コストも簡単に1桁、2桁上昇してしまうので、検証環境としては十分な性能が出ている、ということにします。

ESXi on ESXi の上で VCSA を利用してみましたが、物理サーバ HDD 上の Windows vCenter よりも快適に利用できました。HDD の利用をやめて SSD にした場合、仮想化のオーバヘッドはとるに足りないものであることが実感できます。


この環境に関する LIO Cluster 障害試験をブログに連載することに意味があるかどうかですが、サポートが存在しない、このような環境を利用する人はほとんどいないと思うので、今のところ意義を見出せていません。VSAN の障害試験についてであれば、それなりの意義があると思いますが、別途気が向いたら (NSX に関する連載の続きとして) 掲載することにします。
IBM Bluemix (SoftLayer) の共有ディスクとしての LIO Cluster であれば、今すぐにでも利用したい人はいると思うのですが、いかんせん、現在私がテスト環境を有していないため、スポンサーが出てくるのを待つことにします。Bluemix では、非常に高くついたとしても、QuantaStor を利用して Bluemix のサポートを受けた方がいいのかもしれません。QuantaStor も DRBD による冗長化をサポートしているはずです。


予算が許せば、以下の構成で LIO Cluster を構築したいと考えています。資材だけで2台計300万円です。

サーバ: Fujitsu Primergy TX1330 M2 (5インチベイ搭載、冗長化電源、ラックマウント可能)
5インチベイ1つに8台の SSD を搭載できる HDD ケース 2個
RAID コントローラ: MegaRAID 16ポート
SSD: Crucial 2TB × 16台
NIC: X540-T2 (10Gbps 2ポート)

同じような構成でサポートがあるメーカ製サーバにすると、CE費、SI費抜き、2台で2000万円ほどになります。ストレージ専用機で同容量を確保すると大台に乗ると思います。この価格構造はどうしようもないです。Google 社などは、安い機材を集めて独自にサーバを組み立てているので、AWS が SSD を積極的に利用している現状が理解できます。


オンプレミス環境における現時点のオールフラッシュ・ストレージの本命は、コスト対効果からすると、VSAN だと考えています。ただし、VSAN では、ESXi の共有ディスクにはなり得ても、WSFC の共有ディスクにはならないので、その隙間を埋めるものとして、仮想サーバで構築する LIO Cluster が必要となるのではないかと考えています。データベースを冗長化する際の共有ディスクについても同様です。
共有ディスクのバックアップを考えた場合、ある程度の大きさを超えるとストレージのスナップショット機能が必須となります。更に差分バックアップができないと、バックアップ時間が許容範囲を超えてしまいます。遠隔地へのレプリケーションでは、重複排除、圧縮機能が必須だと思われます。これらの機能を求めると、ストレージ専用機が必須となってしまうのですが、ストレージ専用機のオールフラッシュ構成は、あまりにも高価すぎます。
VMware の VSAN、VDP、Replication を利用する前提で考えると、LIO Cluster を追加することで、ストレージの仮想化というシナリオが完成します。LIO Cluster のバックアップは、VDP で取得することができます。VDP でバックアップすれば、重複排除、圧縮がなされます。バックアップをレプリケーションする機能もあります。VDP によるバックアップは差分バックアップなので、上記のニーズを一通り満たします。VDP のサポートはあれなので、同等以上の機能を有する別のソリューションに置き換えると、それなりの性能をそれなりの価格で、それなりのサポート付きで享受できます。
バックアップを積極利用できるデータの仮想化という観点で、Actifio が面白いと考えています。仮想環境に最高性能を求めてはいけませんが、便利な機能は追及すべきです。


思いつくままにつらつらと述べてきましたが、仮想環境の LIO Cluster を利用する WSFC を構築し、障害試験を実施することとします。これらの仮想マシンをオールフラッシュの VSAN 上で動かします。このシナリオが、LIO Cluster を生かす最善のものだと考えています。ただし、VSAN については本連載の範囲外と考えていますので、本連載の障害テストのシナリオ内には登場しない想定です。
Oracle での利用については、Oracle 用のクラスタと、LIO Cluster を動かすための VSAN クラスタに分けます。Oracle のライセンス費用は、ストレージ専用機よりもはるかに割高なのですが、オールフラッシュのストレージ専用機利用を考えるのであれば、コストが均衡してきますので、そのコストダウンを図る目的としての VSAN 導入はありうると思います。少なくとも、HDD ストレージを利用しているのであれば、すぐにでも置き換えを検討すべきだと考えられます。置き換えたら、Oracle を動かしているサーバの CPU コア数を半減させても、それまで以上にきびきびと動作してくれるものと思われます。時間が許せば、こちらの障害テスト環境も準備したいと思います。

2016年12月17日土曜日

LIO Cluster [LIO, DRBD, Pacemaker による冗長化 iSCSI Target] (その5)DRBD リソース設定


DRBD 設定ファイルを作成し、リソースを設定します。

DRBD が何者であるのかについては各自調べていただくとして、今回は、「snapshot-resync-target-lvm.sh」が自動的に呼び出される設定を追加しました。「fencing」設定が「resource-only」となっている点にも注意が必要です。これらの挙動は、「障害テスト編」で述べたいと思います。


cat /etc/drbd.conf
# You can find an example in  /usr/share/doc/drbd.../drbd.conf.example

include "drbd.d/global_common.conf";
include "drbd.d/*.res";

cat << 'EOF' | sudo tee /etc/drbd.d/global_common.conf
global {
  usage-count no;
}
common {
  handlers {
    pri-on-incon-degr "/usr/lib/drbd/notify-pri-on-incon-degr.sh; /usr/lib/drbd/notify-emergency-reboot.sh; echo 1 > /proc/sys/kernel/sysrq; echo b > /proc/sysrq-trigger; reboot -f";
    local-io-error "/usr/lib/drbd/notify-io-error.sh; /usr/lib/drbd/notify-emergency-shutdown.sh; echo 1 > /proc/sys/kernel/sysrq; echo o > /proc/sysrq-trigger; halt -f";
    fence-peer "/usr/lib/drbd/crm-fence-peer.sh";
    before-resync-target "/usr/lib/drbd/snapshot-resync-target-lvm.sh -p 4";
    after-resync-target "/usr/lib/drbd/unsnapshot-resync-target-lvm.sh; /usr/lib/drbd/crm-unfence-peer.sh";
  }
  startup {
#wfc# wfc-timeout 10;
#wfc# degr-wfc-timeout 10;
#wfc# outdated-wfc-timeout 10;
  }
  disk {
    on-io-error detach;
    fencing resource-only;
    al-extents 6433;
    c-plan-ahead 20;
    c-delay-target 100;
    c-fill-target 0;
    c-max-rate 100M;
    c-min-rate 1M;
  }
  net {
    protocol C;
    max-buffers 128k;
    sndbuf-size 0;
    rcvbuf-size 0;
    cram-hmac-alg sha1;
    shared-secret "password";
    congestion-fill 100M;
    congestion-extents 2000;
    csums-alg md5;
    verify-alg md5;
    use-rle yes;
  }
}
EOF

cat << 'EOF' | sudo tee /etc/drbd.d/r0.res
resource r0 {
  volume 0 {
    device /dev/drbd0;
    disk /dev/vg0/lv-drbd0;
    meta-disk internal;
  }
  on iscsitgt01a.example.com {
    address 192.168.1.2:7788;
  }
  on iscsitgt01s.example.com {
    address 192.168.1.3:7788;
  }
}
EOF


DRBD リソースを初期化します。


sudo drbdadm create-md r0
initializing activity log
NOT initializing bitmap
Writing meta data...
New drbd meta data block successfully created.


続いて、drbd.service を起動して初期同期を実行という手順ですが、サーバ間にまたがった作業は後でまとめて実行したいと思います。

2016年11月30日水曜日

LIO Cluster [LIO, DRBD, Pacemaker による冗長化 iSCSI Target] (その14)Munin インストール、設定

Munin をインストールし、必要な設定を入れます。
Active 機と Stand-by 機の両方で実行します。
LIO プラグインの設定時には、リソースが稼働している(LIO が LUN をエクスポートしている)側で操作する必要があるので、スイッチオーバさせて順次実行します。

インターネット接続可能な端末で以下のコマンドを実行する等して、必要なパッケージを収集します。


curl -O http://yum.oracle.com/repo/OracleLinux/OL7/optional/latest/x86_64/getPackage/perl-Crypt-DES-2.05-20.el7.x86_64.rpm
curl -O http://yum.oracle.com/repo/OracleLinux/OL7/optional/latest/x86_64/getPackage/perl-File-Copy-Recursive-0.38-14.el7.noarch.rpm
curl -O http://yum.oracle.com/repo/OracleLinux/OL7/optional/latest/x86_64/getPackage/perl-Taint-Runtime-0.03-19.el7.x86_64.rpm
curl -O http://yum.oracle.com/repo/OracleLinux/OL7/optional/latest/x86_64/getPackage/perl-XML-DOM-1.44-19.el7.noarch.rpm
curl -O http://yum.oracle.com/repo/OracleLinux/OL7/optional/latest/x86_64/getPackage/perl-XML-RegExp-0.04-2.el7.noarch.rpm
curl -O http://yum.oracle.com/repo/OracleLinux/OL7/optional/latest/x86_64/getPackage/rrdtool-perl-1.4.8-9.el7.x86_64.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/m/munin-2.0.25-11.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/m/munin-common-2.0.25-11.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/m/munin-node-2.0.25-11.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-Cache-Cache-1.06-12.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-Email-Date-Format-1.002-15.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-HTML-Template-2.95-1.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-IO-Multiplex-1.13-6.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-IPC-ShareLite-0.17-12.el7.x86_64.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-Log-Dispatch-2.41-1.el7.1.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-Log-Dispatch-FileRotate-1.19-13.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-Log-Log4perl-1.42-2.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-MIME-Lite-3.030-1.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-MIME-Types-1.38-2.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-Mail-Sender-0.8.23-1.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-Mail-Sendmail-0.79-21.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-Net-CIDR-0.18-1.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-Net-SNMP-6.0.1-7.el7.noarch.rpm
curl -O https://dl.fedoraproject.org/pub/epel/7/x86_64/p/perl-Net-Server-2.007-2.el7.noarch.rpm


収集したパッケージを管理者用一般ユーザのホームディレクトリにコピーします。

コピーされたファイルを確認します。


ls -l *.rpm
-rw-rw-r-- 1 admin admin 410308 Nov 20 19:02 drbd84-utils-8.9.6-1.el7.elrepo.x86_64.rpm
-rw-rw-r-- 1 admin admin 204328 Nov 20 19:34 munin-2.0.25-11.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  93672 Nov 20 19:34 munin-common-2.0.25-11.el7.noarch.rpm
-rw-rw-r-- 1 admin admin 408204 Nov 20 19:34 munin-node-2.0.25-11.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  93340 Nov 20 19:34 perl-Cache-Cache-1.06-12.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  19920 Nov 20 19:34 perl-Crypt-DES-2.05-20.el7.x86_64.rpm
-rw-rw-r-- 1 admin admin  17524 Nov 20 19:34 perl-Email-Date-Format-1.002-15.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  23164 Nov 20 19:34 perl-File-Copy-Recursive-0.38-14.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  77780 Nov 20 19:34 perl-HTML-Template-2.95-1.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  25616 Nov 20 19:34 perl-IO-Multiplex-1.13-6.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  31004 Nov 20 19:34 perl-IPC-ShareLite-0.17-12.el7.x86_64.rpm
-rw-rw-r-- 1 admin admin  84300 Nov 20 19:34 perl-Log-Dispatch-2.41-1.el7.1.noarch.rpm
-rw-rw-r-- 1 admin admin  25232 Nov 20 19:34 perl-Log-Dispatch-FileRotate-1.19-13.el7.noarch.rpm
-rw-rw-r-- 1 admin admin 433560 Nov 20 19:34 perl-Log-Log4perl-1.42-2.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  60212 Nov 20 19:34 perl-Mail-Sender-0.8.23-1.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  29540 Nov 20 19:35 perl-Mail-Sendmail-0.79-21.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  98316 Nov 20 19:34 perl-MIME-Lite-3.030-1.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  39184 Nov 20 19:34 perl-MIME-Types-1.38-2.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  19640 Nov 20 19:35 perl-Net-CIDR-0.18-1.el7.noarch.rpm
-rw-rw-r-- 1 admin admin 213136 Nov 20 19:35 perl-Net-Server-2.007-2.el7.noarch.rpm
-rw-rw-r-- 1 admin admin 105348 Nov 20 19:35 perl-Net-SNMP-6.0.1-7.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  22496 Nov 20 19:34 perl-Taint-Runtime-0.03-19.el7.x86_64.rpm
-rw-rw-r-- 1 admin admin 141504 Nov 20 19:34 perl-XML-DOM-1.44-19.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  10628 Nov 20 19:34 perl-XML-RegExp-0.04-2.el7.noarch.rpm
-rw-rw-r-- 1 admin admin  42864 Nov 20 19:34 rrdtool-perl-1.4.8-9.el7.x86_64.rpm

file *.rpm
drbd84-utils-8.9.6-1.el7.elrepo.x86_64.rpm:          RPM v3.0 bin i386/x86_64 drbd84-utils-8.9.6-1.el7.elrepo
munin-2.0.25-11.el7.noarch.rpm:                      RPM v3.0 bin noarch munin-2.0.25-11.el7
munin-common-2.0.25-11.el7.noarch.rpm:               RPM v3.0 bin noarch munin-common-2.0.25-11.el7
munin-node-2.0.25-11.el7.noarch.rpm:                 RPM v3.0 bin noarch munin-node-2.0.25-11.el7
perl-Cache-Cache-1.06-12.el7.noarch.rpm:             RPM v3.0 bin noarch perl-Cache-Cache-1.06-12.el7
perl-Crypt-DES-2.05-20.el7.x86_64.rpm:               RPM v3.0 bin i386/x86_64 perl-Crypt-DES-2.05-20.el7
perl-Email-Date-Format-1.002-15.el7.noarch.rpm:      RPM v3.0 bin noarch perl-Email-Date-Format-1.002-15.el7
perl-File-Copy-Recursive-0.38-14.el7.noarch.rpm:     RPM v3.0 bin noarch perl-File-Copy-Recursive-0.38-14.el7
perl-HTML-Template-2.95-1.el7.noarch.rpm:            RPM v3.0 bin noarch perl-HTML-Template-2.95-1.el7
perl-IO-Multiplex-1.13-6.el7.noarch.rpm:             RPM v3.0 bin noarch perl-IO-Multiplex-1.13-6.el7
perl-IPC-ShareLite-0.17-12.el7.x86_64.rpm:           RPM v3.0 bin i386/x86_64 perl-IPC-ShareLite-0.17-12.el7
perl-Log-Dispatch-2.41-1.el7.1.noarch.rpm:           RPM v3.0 bin noarch perl-Log-Dispatch-2.41-1.el7.1
perl-Log-Dispatch-FileRotate-1.19-13.el7.noarch.rpm: RPM v3.0 bin noarch perl-Log-Dispatch-FileRotate-1.19-13.el7
perl-Log-Log4perl-1.42-2.el7.noarch.rpm:             RPM v3.0 bin noarch perl-Log-Log4perl-1.42-2.el7
perl-Mail-Sender-0.8.23-1.el7.noarch.rpm:            RPM v3.0 bin noarch perl-Mail-Sender-0.8.23-1.el7
perl-Mail-Sendmail-0.79-21.el7.noarch.rpm:           RPM v3.0 bin noarch perl-Mail-Sendmail-0.79-21.el7
perl-MIME-Lite-3.030-1.el7.noarch.rpm:               RPM v3.0 bin noarch perl-MIME-Lite-3.030-1.el7
perl-MIME-Types-1.38-2.el7.noarch.rpm:               RPM v3.0 bin noarch perl-MIME-Types-1.38-2.el7
perl-Net-CIDR-0.18-1.el7.noarch.rpm:                 RPM v3.0 bin noarch perl-Net-CIDR-0.18-1.el7
perl-Net-Server-2.007-2.el7.noarch.rpm:              RPM v3.0 bin noarch perl-Net-Server-2.007-2.el7
perl-Net-SNMP-6.0.1-7.el7.noarch.rpm:                RPM v3.0 bin noarch perl-Net-SNMP-6.0.1-7.el7
perl-Taint-Runtime-0.03-19.el7.x86_64.rpm:           RPM v3.0 bin i386/x86_64 perl-Taint-Runtime-0.03-19.el7
perl-XML-DOM-1.44-19.el7.noarch.rpm:                 RPM v3.0 bin noarch perl-XML-DOM-1.44-19.el7
perl-XML-RegExp-0.04-2.el7.noarch.rpm:               RPM v3.0 bin noarch perl-XML-RegExp-0.04-2.el7
rrdtool-perl-1.4.8-9.el7.x86_64.rpm:                 RPM v3.0 bin i386/x86_64 rrdtool-perl-1.4.8-9.el7


インストーラを DVD ドライブにセットし、マウントします。


sudo mount /dev/cdrom /mnt


Munin をインストールします。


sudo yum -y --disablerepo=\* --enablerepo=media install httpd
sudo yum -y --disablerepo=\* --enablerepo=media localinstall perl-*.rpm rrdtool-perl-*.rpm munin-*.rpm


インストーラをアンマウントし、DVD ドライブから取り出します。


sudo umount /mnt


追加インストールしたパッケージの設定をバックアップします。


sudo cp -a /etc{,~}/cron.d/munin
sudo cp -a /etc{,~}/fonts
sudo cp -a /etc{,~}/httpd
sudo cp -a /etc{,~}/logrotate.d/httpd
sudo cp -a /etc{,~}/logrotate.d/munin
sudo cp -a /etc{,~}/logrotate.d/munin-node
sudo cp -a /etc{,~}/munin
sudo cp -a /etc{,~}/sysconfig/htcacheclean
sudo cp -a /etc{,~}/sysconfig/httpd
sudo cp -a /etc/passwd   /etc~/passwd_$(date +%Y%m%d_%H%M%S)
sudo cp -a /etc/passwd-  /etc~/passwd-_$(date +%Y%m%d_%H%M%S)
sudo cp -a /etc/shadow   /etc~/shadow_$(date +%Y%m%d_%H%M%S)
sudo cp -a /etc/shadow-  /etc~/shadow-_$(date +%Y%m%d_%H%M%S)
sudo cp -a /etc/group    /etc~/group_$(date +%Y%m%d_%H%M%S)
sudo cp -a /etc/group-   /etc~/group-_$(date +%Y%m%d_%H%M%S)
sudo cp -a /etc/gshadow  /etc~/gshadow_$(date +%Y%m%d_%H%M%S)
sudo cp -a /etc/gshadow- /etc~/gshadow-_$(date +%Y%m%d_%H%M%S)


前稿、前々稿で紹介した5つのプラグインを /usr/share/munin/plugins/ に配置し、実行権限を付与します。

有効化されている不要なプラグインを削除します。


sudo rm /etc/munin/plugins/postfix_mail*
sudo rm /etc/munin/plugins/fw_packets


Munin の稼働状況をグラフ化するプラグイン用の設定を行います。


cat << 'EOF' | sudo tee -a /etc/munin/plugin-conf.d/munin-node

[munin_*]
user munin
EOF


Munin の稼働状況をグラフ化するプラグインを有効化します。


sudo ln -s '/usr/share/munin/plugins/http_loadtime' '/etc/munin/plugins/http_loadtime'
sudo ln -s '/usr/share/munin/plugins/munin_stats' '/etc/munin/plugins/munin_stats'
sudo ln -s '/usr/share/munin/plugins/munin_update' '/etc/munin/plugins/munin_update'


DRBD の稼働状況をグラフ化するプラグインを有効化します。


sudo ln -s '/usr/share/munin/plugins/drbd' '/etc/munin/plugins/drbd'
sudo ln -s '/usr/share/munin/plugins/drbd_al' '/etc/munin/plugins/drbd_al'
sudo ln -s '/usr/share/munin/plugins/drbd_ext' '/etc/munin/plugins/drbd_ext'


Munin にホスト名を登録します。


sudo sed -i -e "s/^host_name .*\$/host_name $(uname -n)/" /etc/munin/munin-node.conf
sudo sed -i -e "s/^\\[localhost/[$(uname -n)/" /etc/munin/munin.conf


rhel7 の systemd で新設された PrivateTmp 機能を munin-node.service については無効化します。一部のプラグインが対応していないためです。今回利用するプラグインには無関係です。


sudo sed -i -e 's/^PrivateTmp=.*$/PrivateTmp=false/' /usr/lib/systemd/system/munin-node.service
sudo systemctl daemon-reload


ベーシック認証設定を行います。


sudo htpasswd -c -b /etc/munin/munin-htpasswd munin 'password'
sudo htpasswd -b /etc/munin/munin-htpasswd admin 'password'
sudo htpasswd -b /etc/munin/munin-htpasswd monitor 'password'


Active 機で、LIO の稼働状況をグラフ化するプラグインを有効化します。


sudo ln -s '/usr/share/munin/plugins/lio_read' '/etc/munin/plugins/lio_read'
sudo ln -s '/usr/share/munin/plugins/lio_write' '/etc/munin/plugins/lio_write'


Active 機で、Munin 関連サービスを自動起動するように変更し、起動します。


sudo systemctl enable munin-node.service
sudo systemctl enable httpd.service
sudo systemctl start munin-node.service
sudo systemctl start httpd.service


数十分待ってから、ブラウザで http://<ホスト名または IP アドレス>/munin/ へアクセスし、動作確認します。

リソースをスイッチオーバします。


sudo pcs resource move g_tgt; sleep 5; sudo pcs resource clear g_tgt


Stand-by 機で、LIO の稼働状況をグラフ化するプラグインを有効化します。


sudo ln -s '/usr/share/munin/plugins/lio_read' '/etc/munin/plugins/lio_read'
sudo ln -s '/usr/share/munin/plugins/lio_write' '/etc/munin/plugins/lio_write'


Stand-by 機で、Munin 関連サービスを自動起動するように変更し、起動します。


sudo systemctl enable munin-node.service
sudo systemctl enable httpd.service
sudo systemctl start munin-node.service
sudo systemctl start httpd.service


数十分待ってから、ブラウザで http://<ホスト名または IP アドレス>/munin/ へアクセスし、動作確認します。

リソースをスイッチバックします。


sudo pcs resource move g_tgt; sleep 5; sudo pcs resource clear g_tgt


2016年11月29日火曜日

LIO Cluster [LIO, DRBD, Pacemaker による冗長化 iSCSI Target] (その13)Munin用プラグイン(LIO)

今回は、LIO に関するプラグインを紹介します。

元ネタになりそうなものを探したのですが、見つからなかったのでスクラッチで作りました。
perl スクリプトではなく、シェルスクリプトにしました。

Read と Write は別のグラフにしましたが、1つのグラフの中にターゲット、イニシエータ、LUN、の全ての組み合わせを詰め込みましたので、接続数が多くなると見やすくはないと思います。
ターゲット、LUN 別に作るとか、折れ線グラフではなく、塗りつぶしのスタックで表現した方が見やすいとか、いろいろと改造の余地はあると思いますが、とりあえずたたき台としてもらえればと思います。

Read/Write 以外にも、グラフ化した方が有用な情報があるのではないかと思いますが、私のスキルでは選びきれなかったので、とりあえず必要そうなすべての情報をテキストで保存しておけば役に立つのではないか、と考えております。

/sys/kernel/config/target/ 配下にある情報を全て取得すると、私のテスト環境では、仮想サーバで6秒ほど、物理サーバで1秒強かかりました。


sudo mkdir -p /etc/lio
sudo mkdir -p /var/log/lio/

cat << 'EOF' | sudo tee /etc/lio/save
#!/bin/sh
FILE=/dev/shm/lio-$(date +%Y%m%d%H%M)
for i in $(find /sys/kernel/config/target ! -type d | LANG=C sort)
 do echo [$i]; cat $i; echo; done > $FILE 2> /dev/null
gzip $FILE
mv $FILE.gz /var/log/lio/
EOF
sudo chmod 755 /etc/lio/save


これを1分ごとに収集するのは負荷が高すぎると判断しております。多くのデータは値が変化しないので、無駄が多すぎます。1時間に1回取得する方向で考えます。

もう少し絞り込むことにします。

/sys/kernel/config/target/core/*/*/statistics
/sys/kernel/config/target/iscsi/*/fabric_statistics
/sys/kernel/config/target/iscsi/*/tpgt_1/acls/*/fabric_statistics
/sys/kernel/config/target/iscsi/*/tpgt_1/acls/*/*/statistics
/sys/kernel/config/target/iscsi/*/tpgt_1/lun/*/statistics
この配下にある情報を1分ごとに取得する、というあたりが妥協点だと考えました。「statistics (統計)」というディレクトリ配下に有用な統計情報が集約されている、ということにします。
これだけであれば、仮想マシンでも1秒かからずに収集できました。


cat << 'EOF' | sudo tee /etc/lio/statistics
#!/bin/sh
FILE=/dev/shm/lio-statistics-$(date +%Y%m%d%H%M)
YYYYMMDD=$(echo $FILE | sed -e 's/^.*\([0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]\)[0-9][0-9][0-9][0-9]$/\1/')
for i in $(for k in /sys/kernel/config/target/{core/*/*/,iscsi/*/{fabric_,tpgt_1/{acls/*/{fabric_,*/},lun/*/}}}statistics; do echo $k; done | LANG=C sort)
 do for j in $(find $i ! -type d | LANG=C sort); do echo [$j]; cat $j; echo; done; done > $FILE 2> /dev/null
gzip $FILE
mkdir -p /var/log/lio/$YYYYMMDD/
mv $FILE.gz /var/log/lio/$YYYYMMDD/
EOF
sudo chmod 755 /etc/lio/statistics


これらの情報を貯めこむと1年あたり 1GB 程度になる見込みです。1年経過したものは削除する、ということでいいと思います。1つのディレクトリに大量のファイルを置くのはよくないので、毎日別のディレクトリに入れるということで。
タダの HDD の肥やしになりそうな予感がしていますが、取得しておけばよかった、とならないための用心です。


cat << 'EOF' | sudo tee /etc/cron.d/lio
59 * * * * root nice -n 19 /etc/lio/save
* * * * * root nice -n 19 /etc/lio/statistics
58 23 * * * root nice -n 19 /bin/find /var/log/lio -mtime +365 -print0 | xargs -0 rm -rfv 2> /dev/null
EOF




Munin 用プラグインは以下の通りです。GitHubに置きました。

iqn については、「:」以降の文字列のみを切り出して表示しています。


https://raw.githubusercontent.com/blog-pcoffice/public/master/lio_read

cat << 'EOF' | sudo tee /usr/share/munin/plugins/lio_read
#!/bin/sh
#%# family=auto
#%# capabilities=autoconf

if [ "$1" = "autoconf" ]; then
  if [ -d /sys/kernel/config/target/iscsi/iqn.*/tpgt_1 ]; then
    echo yes
  else
    echo 'no (no iscsi target)'
  fi
  exit 0
fi
if [ "$1" = "config" ]; then
  echo 'graph_title LIO (Read)'
  echo 'graph_category LIO'
  echo 'graph_info Graph LIO (Read)'
  echo 'graph_vlabel Graph LIO (Bytes/sec)'
  echo 'graph_scale yes'
  echo 'graph_args --base 1024 --lower-limit 0'
  echo 'graph_period second'
#  echo 'graph_height 200'
#  echo 'graph_width 400'
  echo 'graph_printf %7.2lf'

  TGT_=
  INI_=
  for i in $(echo /sys/kernel/config/target/iscsi/iqn.*/tpgt_1/acls/iqn.*/*/statistics/scsi_auth_intr/read_mbytes | LANG=C sort)
  do
    TGT=$(echo $i | cut -d/ -f7)
    INI=$(echo $i | cut -d/ -f10)
    LUN=$(echo $i | cut -d/ -f11)
    if [ "$TGT_" = "$TGT" ]; then
      if [ "$INI_" = "$INI" ]; then
        :
      else
        INI_=$INI
        INI_F=$(echo $INI | tr "[:upper:]" "[:lower:]" | sed -e 's/-/_/g' -e 's![^a-z0-9_]!!g')
      fi
    else
       TGT_=$TGT
       TGT_F=$(echo $TGT | tr "[:upper:]" "[:lower:]" | sed -e 's/-/_/g' -e 's![^a-z0-9_]!!g')
       INI_=$INI
       INI_F=$(echo $INI | tr "[:upper:]" "[:lower:]" | sed -e 's/-/_/g' -e 's![^a-z0-9_]!!g')
       for j in $(echo /sys/kernel/config/target/iscsi/$TGT/tpgt_1/lun/*/statistics/scsi_tgt_port/read_mbytes | LANG=C sort)
       do
         LUN_=$(echo $j | cut -d/ -f10)
         echo ${TGT_F}$LUN_.label $(echo $TGT | cut -d: -f2) \($LUN_\) Read
         echo ${TGT_F}$LUN_.cdef ${TGT_F}$LUN_,1048576,\*
         echo ${TGT_F}$LUN_.min 0
         echo ${TGT_F}$LUN_.type DERIVE
       done
    fi
    echo ${TGT_F}${INI_F}$LUN.label $(echo $TGT | cut -d: -f2) - $(echo $INI | cut -d: -f2) \($LUN\) Read
    echo ${TGT_F}${INI_F}$LUN.cdef ${TGT_F}${INI_F}$LUN,1048576,\*
    echo ${TGT_F}${INI_F}$LUN.min 0
    echo ${TGT_F}${INI_F}$LUN.type DERIVE
  done
  exit 0
fi

TGT_=
INI_=
for i in $(echo /sys/kernel/config/target/iscsi/iqn.*/tpgt_1/acls/iqn.*/*/statistics/scsi_auth_intr/read_mbytes | LANG=C sort)
do
  TGT=$(echo $i | cut -d/ -f7)
  INI=$(echo $i | cut -d/ -f10)
  LUN=$(echo $i | cut -d/ -f11)
  if [ "$TGT_" = "$TGT" ]; then
    if [ "$INI_" = "$INI" ]; then
      :
    else
      INI_=$INI
      INI_F=$(echo $INI | tr "[:upper:]" "[:lower:]" | sed -e 's/-/_/g' -e 's![^a-z0-9_]!!g')
    fi
  else
     TGT_=$TGT
     TGT_F=$(echo $TGT | tr "[:upper:]" "[:lower:]" | sed -e 's/-/_/g' -e 's![^a-z0-9_]!!g')
     INI_=$INI
     INI_F=$(echo $INI | tr "[:upper:]" "[:lower:]" | sed -e 's/-/_/g' -e 's![^a-z0-9_]!!g')
     for j in $(echo /sys/kernel/config/target/iscsi/$TGT/tpgt_1/lun/*/statistics/scsi_tgt_port/read_mbytes | LANG=C sort)
     do
       LUN_=$(echo $j | cut -d/ -f10)
       echo -n "${TGT_F}$LUN_.value "
       cat $j
     done
  fi
  echo -n "${TGT_F}${INI_F}$LUN.value "
  cat $i
done

exit 0;
EOF
sudo chmod 755 /usr/share/munin/plugins/lio_read



https://raw.githubusercontent.com/blog-pcoffice/public/master/lio_write

cat << 'EOF' | sudo tee /usr/share/munin/plugins/lio_write
#!/bin/sh
#%# family=auto
#%# capabilities=autoconf

if [ "$1" = "autoconf" ]; then
  if [ -d /sys/kernel/config/target/iscsi/iqn.*/tpgt_1 ]; then
    echo yes
  else
    echo 'no (no iscsi target)'
  fi
  exit 0
fi
if [ "$1" = "config" ]; then
  echo 'graph_title LIO (Write)'
  echo 'graph_category LIO'
  echo 'graph_info Graph LIO (Write)'
  echo 'graph_vlabel Graph LIO (Bytes/sec)'
  echo 'graph_scale yes'
  echo 'graph_args --base 1024 --lower-limit 0'
  echo 'graph_period second'
#  echo 'graph_height 200'
#  echo 'graph_width 400'
  echo 'graph_printf %7.2lf'

  TGT_=
  INI_=
  for i in $(echo /sys/kernel/config/target/iscsi/iqn.*/tpgt_1/acls/iqn.*/*/statistics/scsi_auth_intr/write_mbytes | LANG=C sort)
  do
    TGT=$(echo $i | cut -d/ -f7)
    INI=$(echo $i | cut -d/ -f10)
    LUN=$(echo $i | cut -d/ -f11)
    if [ "$TGT_" = "$TGT" ]; then
      if [ "$INI_" = "$INI" ]; then
        :
      else
        INI_=$INI
        INI_F=$(echo $INI | tr "[:upper:]" "[:lower:]" | sed -e 's/-/_/g' -e 's![^a-z0-9_]!!g')
      fi
    else
       TGT_=$TGT
       TGT_F=$(echo $TGT | tr "[:upper:]" "[:lower:]" | sed -e 's/-/_/g' -e 's![^a-z0-9_]!!g')
       INI_=$INI
       INI_F=$(echo $INI | tr "[:upper:]" "[:lower:]" | sed -e 's/-/_/g' -e 's![^a-z0-9_]!!g')
       for j in $(echo /sys/kernel/config/target/iscsi/$TGT/tpgt_1/lun/*/statistics/scsi_tgt_port/write_mbytes | LANG=C sort)
       do
         LUN_=$(echo $j | cut -d/ -f10)
         echo ${TGT_F}$LUN_.label $(echo $TGT | cut -d: -f2) \($LUN_\) Write
         echo ${TGT_F}$LUN_.cdef ${TGT_F}$LUN_,1048576,\*
         echo ${TGT_F}$LUN_.min 0
         echo ${TGT_F}$LUN_.type DERIVE
       done
    fi
    echo ${TGT_F}${INI_F}$LUN.label $(echo $TGT | cut -d: -f2) - $(echo $INI | cut -d: -f2) \($LUN\) Write
    echo ${TGT_F}${INI_F}$LUN.cdef ${TGT_F}${INI_F}$LUN,1048576,\*
    echo ${TGT_F}${INI_F}$LUN.min 0
    echo ${TGT_F}${INI_F}$LUN.type DERIVE
  done
  exit 0
fi

TGT_=
INI_=
for i in $(echo /sys/kernel/config/target/iscsi/iqn.*/tpgt_1/acls/iqn.*/*/statistics/scsi_auth_intr/write_mbytes | LANG=C sort)
do
  TGT=$(echo $i | cut -d/ -f7)
  INI=$(echo $i | cut -d/ -f10)
  LUN=$(echo $i | cut -d/ -f11)
  if [ "$TGT_" = "$TGT" ]; then
    if [ "$INI_" = "$INI" ]; then
      :
    else
      INI_=$INI
      INI_F=$(echo $INI | tr "[:upper:]" "[:lower:]" | sed -e 's/-/_/g' -e 's![^a-z0-9_]!!g')
    fi
  else
     TGT_=$TGT
     TGT_F=$(echo $TGT | tr "[:upper:]" "[:lower:]" | sed -e 's/-/_/g' -e 's![^a-z0-9_]!!g')
     INI_=$INI
     INI_F=$(echo $INI | tr "[:upper:]" "[:lower:]" | sed -e 's/-/_/g' -e 's![^a-z0-9_]!!g')
     for j in $(echo /sys/kernel/config/target/iscsi/$TGT/tpgt_1/lun/*/statistics/scsi_tgt_port/write_mbytes | LANG=C sort)
     do
       LUN_=$(echo $j | cut -d/ -f10)
       echo -n "${TGT_F}$LUN_.value "
       cat $j
     done
  fi
  echo -n "${TGT_F}${INI_F}$LUN.value "
  cat $i
done

exit 0;
EOF
sudo chmod 755 /usr/share/munin/plugins/lio_write




LIO Cluster [LIO, DRBD, Pacemaker による冗長化 iSCSI Target] (その12)Munin用プラグイン(DRBD)

Munin 用のプラグインを作りました。

rhel サーバ個別のリソース監視を考えた場合、ほとんどの環境で有益なものはどれかと考えると、Munin しか選択肢がないのではないかと思います。既存環境では、Zabbix やその他の監視ソフトが既に動いていて、それらの邪魔にならないものでなければなりません。グラフ化が簡単にできるもの、負荷が軽いもの、インストールが容易なもの(本格的な RDBMS 不要は最低条件)、と考えると、RRD(Round Robin Database) Tool を利用している Munin に白羽の矢が立ちます。MRTG は rhel7 の標準パッケージとなっているという魅力がありますが、2値しかグラフ化できないことと、設定ファイルを作成するのが面倒で、候補から外れました。オープンソースを気軽に持ち込めない場合には、MRTG という選択肢は有用です。

Munin は、自動設定が進んでいて、プラグインが用意されているものを利用する限りにおいては、導入が簡単です。デフォルト設定のままだとホスト名が localhost になってしまう等の見た目を気にしなければ、インストールして、認証設定を入れ、サービス起動するだけです。
自動設定されたプラグインを個別に無効化したい場合は、シンボリックリンクを削除してサービスを再起動するだけで済みます。Zabbix 等でグラフ化しているのであれば、重複するものだけを除外するということも簡単にできます。


今回は、DRBD に関するプラグインを紹介します。

元ネタは以下にあります。

https://github.com/munin-monitoring/contrib/tree/master/plugins/drbd

2012年にコミットされてから変更されていないようです。
「drbd-stat」については、dr, dw, nr, ns の値が1024倍されていないです。
「drbd」については、dr, dw, nr, ns の値が1024倍ではなく、819倍されています。理由が分かりませんでした。ご存知の方がいらっしゃいましたら教えてください。
とりあえず、819倍の話は忘れて、以下の3つのプラグインを作りました。

dr(Disk Read), dw(Disk Write), nr(Network Receive), ns(Network Send), oos(Out Of Sync) については、KByte 単位の値が取得できるので、1つのグラフ内にまとめました。
その他の統計値に関するものを1つのグラフにまとめました。ただし、al(Activity Log) に関しては、取得される値が大きかったので、他の値に関するグラフが意味のないものになってしまうのを避けるため、別のグラフにしました。oos に関しても別グラフにした方がよいかもしれませんが、そこまではこだわらないことにします。
以上の3つです。
Disk アクセスと Netwok アクセスは、ほぼ同じ値になるため重なり合ってしまうので、分けてしまう方がいいのかもしれません。Primary と Secondary が入れ替わっても、別の種類でほぼ同じ値になってしまいます。グラフの読み方を理解していれば問題ないとも言えます。

気が向いたら、dr&,dw, nr&ns, oos, al, bm, lo, pe, ua, ap, ep の10個のグラフに分割します。

以下の DRBD8.4 に関するマニュアルを参照しながら作りました。
http://www.drbd.org/en/doc/users-guide-84/ch-admin#s-performance-indicators

ソースコードは GitHub にも置きました。


https://raw.githubusercontent.com/blog-pcoffice/public/master/drbd

cat << 'EOF' | sudo tee /usr/share/munin/plugins/drbd
#!/usr/bin/perl
#%# family=auto
#%# capabilities=autoconf
# http://www.drbd.org/en/doc/users-guide-84/ch-admin#s-performance-indicators

use strict;
my $file="/proc/drbd";
my $store = {};
my $rid;

&crunch;
&display;

sub display{
  if ($ARGV[0] and $ARGV[0] eq "config"){
    print "graph_title DRBD\n";
    print "graph_category DRBD\n";
    print "graph_info Graph DRBD\n";
    print "graph_vlabel Graph DRBD (Bytes/sec)\n";
    print "graph_scale yes\n";
    print "graph_args --base 1024 --lower-limit 0\n";
    print "graph_period second\n";
    print "graph_height 200\n";
    print "graph_width 400\n";
    print "graph_printf %7.2lf\n";
    foreach my $key ( keys %$store ){
      my $drbdname = 'drbd'.$key;
      print $drbdname."dr.label $drbdname Disk Read\n";
      print $drbdname."dw.label $drbdname Disk Write\n";
      print $drbdname."ns.label $drbdname Network Send\n";
      print $drbdname."nr.label $drbdname Network Receive\n";
      print $drbdname."os.label $drbdname Out of Sync\n";
      print $drbdname."dr.cdef ".$drbdname."dr,1024,*\n";
      print $drbdname."dw.cdef ".$drbdname."dw,1024,*\n";
      print $drbdname."ns.cdef ".$drbdname."ns,1024,*\n";
      print $drbdname."nr.cdef ".$drbdname."nr,1024,*\n";
      print $drbdname."os.cdef ".$drbdname."os,1024,*\n";
      print $drbdname."dr.min 0\n";
      print $drbdname."dw.min 0\n";
      print $drbdname."ns.min 0\n";
      print $drbdname."nr.min 0\n";
      print $drbdname."os.min 0\n";
      print $drbdname."dr.type DERIVE\n";
      print $drbdname."dw.type DERIVE\n";
      print $drbdname."ns.type DERIVE\n";
      print $drbdname."nr.type DERIVE\n";
      print $drbdname."os.type DERIVE\n";
    }
    exit 0;
  }
  foreach my $key ( keys %$store ){
    my $drbdname = 'drbd'.$key;
    print $drbdname."dw.value ".$store->{$key}->{'dw'}."\n";
    print $drbdname."dr.value ".$store->{$key}->{'dr'}."\n";
    print $drbdname."ns.value ".$store->{$key}->{'ns'}."\n";
    print $drbdname."nr.value ".$store->{$key}->{'nr'}."\n";
    print $drbdname."os.value ".$store->{$key}->{'os'}."\n";
  }
}

sub crunch{
  open (IN, $file) || die "Could not open $file for reading: $!";
  if ($ARGV[0] and $ARGV[0] eq "autoconf"){
    close (IN);
    print "yes\n";
    exit 0;
  }
  while (<IN>){
    next if /version:|GIT-hash:/;
    chomp;
    my ($drbd) = $_ =~ /^\s+(\d):/;
    $rid = $drbd if $drbd =~ /\d/;
    my ($ns) = $_ =~ /ns:(\d*)/;  $store->{ $rid }->{'ns'} = $ns if $ns ne undef;
    my ($nr) = $_ =~ /nr:(\d*)/;  $store->{ $rid }->{'nr'} = $nr if $ns ne undef;
    my ($dw) = $_ =~ /dw:(\d*)/;  $store->{ $rid }->{'dw'} = $dw if $dw ne undef;
    my ($dr) = $_ =~ /dr:(\d*)/;  $store->{ $rid }->{'dr'} = $dr if $dr ne undef;
    my ($os) = $_ =~ /oos:(\d*)/; $store->{ $rid }->{'os'} = $os if $os ne undef;
  }
  close (IN);
}

exit 0;
EOF
sudo chmod 755 /usr/share/munin/plugins/drbd



https://raw.githubusercontent.com/blog-pcoffice/public/master/drbd_al

cat << 'EOF' | sudo tee /usr/share/munin/plugins/drbd_al
#!/usr/bin/perl
#%# family=auto
#%# capabilities=autoconf
# http://www.drbd.org/en/doc/users-guide-84/ch-admin#s-performance-indicators

use strict;
my $file="/proc/drbd";
my $store = {};
my $rid;

&crunch;
&display;

sub display{
  if ($ARGV[0] and $ARGV[0] eq "config"){
    print "graph_title DRBD (Activity Log)\n";
    print "graph_category DRBD\n";
    print "graph_info Graph DRBD (Activity Log)\n";
    print "graph_vlabel Graph DRBD (Activity Log)\n";
    print "graph_scale yes\n";
    print "graph_args --base 1024 --lower-limit 0\n";
    print "graph_period second\n";
    print "graph_height 200\n";
    print "graph_width 400\n";
    print "graph_printf %7.2lf\n";
    foreach my $key ( keys %$store ){
      my $drbdname = 'drbd'.$key;
      print $drbdname."al.label $drbdname Activity log\n";
      print $drbdname."al.min 0\n";
#      print $drbdname."al.type DERIVE\n";
    }
    exit 0;
  }
  foreach my $key ( keys %$store ){
    my $drbdname = 'drbd'.$key;
    print $drbdname."al.value ".$store->{$key}->{'al'}."\n";
  }
}

sub crunch{
  open (IN, $file ) || die "Could not open $file for reading: $!";
  if ($ARGV[0] and $ARGV[0] eq "autoconf"){
    close (IN);
    print "yes\n";
    exit 0;
  }
  while (<IN>){
    next if /version:|GIT-hash:/;
    chomp;
    my ($drbd) = $_ =~ /^\s+(\d):/;
    $rid = $drbd if $drbd =~ /\d/;
    my ($al) = $_ =~ /al:(\d*)/; $store->{ $rid }->{'al'} = $al if $al ne undef;
  }
  close (IN);
}

exit 0;
EOF
sudo chmod 755 /usr/share/munin/plugins/drbd_al



https://raw.githubusercontent.com/blog-pcoffice/public/master/drbd_ext

cat << 'EOF' | sudo tee /usr/share/munin/plugins/drbd_ext
#!/usr/bin/perl
#%# family=auto
#%# capabilities=autoconf
# http://www.drbd.org/en/doc/users-guide-84/ch-admin#s-performance-indicators

use strict;
my $file="/proc/drbd";
my $store = {};
my $rid;

&crunch;
&display;

sub display{
  if ($ARGV[0] and $ARGV[0] eq "config"){
    print "graph_title DRBD (Ext)\n";
    print "graph_category DRBD\n";
    print "graph_info Graph DRBD (Ext)\n";
    print "graph_vlabel Graph DRBD (Ext)\n";
    print "graph_scale yes\n";
    print "graph_args --base 1024 --lower-limit 0\n";
    print "graph_period second\n";
    print "graph_height 200\n";
    print "graph_width 400\n";
    print "graph_printf %7.2lf\n";
    foreach my $key ( keys %$store ){
      my $drbdname = 'drbd'.$key;
      print $drbdname."bm.label $drbdname Bit Map\n";
      print $drbdname."lo.label $drbdname Local count\n";
      print $drbdname."pe.label $drbdname Pending\n";
      print $drbdname."ua.label $drbdname UnAcknowledged\n";
      print $drbdname."ap.label $drbdname Application Pending\n";
      print $drbdname."ep.label $drbdname Epochs\n";
    }
    exit 0;
  }
  foreach my $key ( keys %$store ){
    my $drbdname = 'drbd'.$key;
    print $drbdname."bm.value ".$store->{$key}->{'bm'}."\n";
    print $drbdname."lo.value ".$store->{$key}->{'lo'}."\n";
    print $drbdname."pe.value ".$store->{$key}->{'pe'}."\n";
    print $drbdname."ua.value ".$store->{$key}->{'ua'}."\n";
    print $drbdname."ap.value ".$store->{$key}->{'ap'}."\n";
    print $drbdname."ep.value ".$store->{$key}->{'ep'}."\n";
  }
}

sub crunch{
  open (IN, $file ) || die "Could not open $file for reading: $!";
  if ($ARGV[0] and $ARGV[0] eq "autoconf"){
    close (IN);
    print "yes\n";
    exit 0;
  }
  while (<IN>){
    next if /version:|GIT-hash:/;
    chomp;
    my ($drbd) = $_ =~ /^\s+(\d):/;
    $rid = $drbd if $drbd =~ /\d/;
    my ($bm) = $_ =~ /bm:(\d*)/; $store->{ $rid }->{'bm'} = $bm if $bm ne undef;
    my ($lo) = $_ =~ /lo:(\d*)/; $store->{ $rid }->{'lo'} = $lo if $lo ne undef;
    my ($pe) = $_ =~ /pe:(\d*)/; $store->{ $rid }->{'pe'} = $pe if $pe ne undef;
    my ($ua) = $_ =~ /ua:(\d*)/; $store->{ $rid }->{'ua'} = $ua if $ua ne undef;
    my ($ap) = $_ =~ /ap:(\d*)/; $store->{ $rid }->{'ap'} = $ap if $ap ne undef;
    my ($ep) = $_ =~ /ep:(\d*)/; $store->{ $rid }->{'ep'} = $ep if $ep ne undef;
  }
  close (IN);
}

exit 0;
EOF
sudo chmod 755 /usr/share/munin/plugins/drbd_ext




2016年11月16日水曜日

LIO Cluster [LIO, DRBD, Pacemaker による冗長化 iSCSI Target] (その11)構築編のまとめ


ここまでの構築手順を PDF にまとめました。

iSCSITarget-Build-OracleLinux7.3-1.20.pdf
iSCSITarget-Build-OracleLinux7.3-1.20.xlsx


多少、手順が入れ替わったりしています。
Oracle Linux 7.3 が出たので、7.3用に書き直しています。

行番号の右隣にチェック欄があります。「a」は Active 機(1号機)のみで実行するコマンドです。「s」は Stand-by 機(2号機)のみで実行するコマンドです。「a,s」は、Active 機と Stand-by 機の両方で実行するコマンドです。「o」は、別の適切な端末で実行するコマンドです。実施したら丸を付ける、といった使い方を想定しています。
罫線を入れるなどの装飾は施していません。手順の追加、削除に関する編集がしやすいように考えた結果です。

2016/11/30 追記
1.10  Munin 関連の手順を追加し、こまごまとバグフィックスしてあります。
元ネタのエクセルファイルも併せて公開しました。
1ページ目の絵を直し、右側欄外の情報を調整すれば、
同一構成の場合、c 列のみをコピーし、テキストエディタに貼り付けることで、
構築に必要なコマンド群が得られます。

2016/12/17 追記
Pacemaker の LIO リソースエージェントを改良版に入れ替え、不要なログ出力を抑制し、こまごまとバグフィックスしてあります。


本連載で紹介した構成で初期構築をご希望の方は、メールにてお問い合わせください。
サーバ1台あたり10万円~(税別)という超特価にてご提供中です。
詳細は、上記PDFの最終頁をご参照ください。

MySQL や PostgreSQL、Oracle の冗長構成構築サービスも鋭意開発中です。商品開発に関するリクエストがあればお知らせください。検討させていただきます。
貴社のアプリケーション、サービス等を冗長化する共同開発も承ります。

2016年11月15日火曜日

LIO Cluster [LIO, DRBD, Pacemaker による冗長化 iSCSI Target] (その10)Pacemaker のリソース設定


Pacemaker にリソースを登録します。

Active 機で、クラスタの状態を確認します。


sudo pcs status
Cluster name: iscsitgt01
WARNING: no stonith devices and stonith-enabled is not false
WARNING: corosync and pacemaker node names do not match (IPs used in setup?)
Last updated: Fri Oct 21 00:28:51 2016          Last change: Fri Oct 21 00:27:20 2016 by hacluster via crmd on iscsitgt01s.example.com
Stack: corosync
Current DC: iscsitgt01s.example.com (version 1.1.13-10.el7-44eb2dd) - partition with quorum
2 nodes and 0 resources configured

Online: [ iscsitgt01a.example.com iscsitgt01s.example.com ]

Full list of resources:

PCSD Status:
  iscsitgt01a.example.com (10.110.88.57): Online
  iscsitgt01s.example.com (10.110.88.58): Online

Daemon Status:
  corosync: active/disabled
  pacemaker: active/disabled
  pcsd: active/enabled


Active 機で、クラスタにリソースを登録します。


sudo /etc/ha.d/crm.sh


Active 機で、状態を確認します。


sudo pcs status
Cluster name: iscsitgt01
WARNING: corosync and pacemaker node names do not match (IPs used in setup?)
Last updated: Sat Oct 29 19:52:34 2016          Last change: Sat Oct 29 19:51:46 2016 by root via cibadmin on iscsitgt01s.example.com
Stack: corosync
Current DC: iscsitgt01s.example.com (version 1.1.13-10.el7-44eb2dd) - partition with quorum
2 nodes and 5 resources configured

Online: [ iscsitgt01a.example.com iscsitgt01s.example.com ]

Full list of resources:

 Master/Slave Set: ms_drbd_r0 [p_drbd_r0]
     Masters: [ iscsitgt01a.example.com ]
     Slaves: [ iscsitgt01s.example.com ]
 Resource Group: g_tgt
     p_lvm      (ocf::heartbeat:LVM):   Started iscsitgt01a.example.com
     p_lio      (ocf::heartbeat:LIO):   Started iscsitgt01a.example.com
     p_vip      (ocf::heartbeat:VIP):   Started iscsitgt01a.example.com

Failed Actions:
* p_lvm_start_0 on iscsitgt01s.example.com 'unknown error' (1): call=16, status=complete, exitreason='Volume group [vg1] does not exist or contains error!   Volume group "vg1" not found',
    last-rc-change='Fri Oct 21 00:32:13 2016', queued=0ms, exec=1095ms
PCSD Status:
  iscsitgt01a.example.com (10.110.88.57): Online
  iscsitgt01s.example.com (10.110.88.58): Online

Daemon Status:
  corosync: active/disabled
  pacemaker: active/disabled
  pcsd: active/enabled


※ ここでのエラー発生は想定の範囲内です。リソースをグループ化するコマンドの前に、各種リソースが Active 機側で起動しようとしたり、Stand-by 機側で起動しようとしたりしてしまうためです。

Active 機で、リソースのエラー状態をクリアします。


sudo pcs resource cleanup
Waiting for 1 replies from the CRMd. OK


Active 機で、状態を確認します。


sudo pcs status
Cluster name: iscsitgt01
WARNING: corosync and pacemaker node names do not match (IPs used in setup?)
Last updated: Sat Oct 29 19:58:35 2016          Last change: Sat Oct 29 19:58:34 2016 by hacluster via crmd on iscsitgt01a.example.com
Stack: corosync
Current DC: iscsitgt01s.example.com (version 1.1.13-10.el7-44eb2dd) - partition with quorum
2 nodes and 5 resources configured

Online: [ iscsitgt01a.example.com iscsitgt01s.example.com ]

Full list of resources:

 Master/Slave Set: ms_drbd_r0 [p_drbd_r0]
     Masters: [ iscsitgt01a.example.com ]
     Slaves: [ iscsitgt01s.example.com ]
 Resource Group: g_tgt
     p_lvm      (ocf::heartbeat:LVM):   Started iscsitgt01a.example.com
     p_lio      (ocf::heartbeat:LIO):   Started iscsitgt01a.example.com
     p_vip      (ocf::heartbeat:VIP):   Started iscsitgt01a.example.com

PCSD Status:
  iscsitgt01a.example.com (10.110.88.57): Online
  iscsitgt01s.example.com (10.110.88.58): Online

Daemon Status:
  corosync: active/disabled
  pacemaker: active/disabled
  pcsd: active/enabled


Active 機と Stand-by 機で、設定情報を保存します。


sudo pcs config | sudo tee /etc/ha.d/crm.conf


Stand-by 機で、クラスタの状態をワッチします。


sudo watch pcs status


Active 機で、スイッチオーバ(手動フェイルオーバ)させてみます。


sudo pcs resource move g_tgt
Warning: Creating location constraint cli-ban-g_tgt-on-iscsitgt01a.example.com with a score of -INFINITY for resource g_tgt on node iscsitgt01a.example.com.
This will prevent g_tgt from running on iscsitgt01a.example.com until the constraint is removed. This will be the case even if iscsitgt01a.example.com is the last node in the cluster.
 

Stand-by 機で、クラスタの状態を確認します。


Cluster name: iscsitgt01
WARNING: corosync and pacemaker node names do not match (IPs used in setup?)
Last updated: Sat Oct 29 19:52:34 2016          Last change: Sat Oct 29 19:51:46 2016 by root via crm_resource on iscsitgt01a.example.com
Stack: corosync
Current DC: iscsitgt01s.example.com (version 1.1.13-10.el7-44eb2dd) - partition with quorum
2 nodes and 5 resources configured

Online: [ iscsitgt01a.example.com iscsitgt01s.example.com ]

Full list of resources:

 Master/Slave Set: ms_drbd_r0 [p_drbd_r0]
     Masters: [ iscsitgt01s.example.com ]
     Slaves: [ iscsitgt01a.example.com ]
 Resource Group: g_tgt
     p_lvm      (ocf::heartbeat:LVM):   Started iscsitgt01s.example.com
     p_lio      (ocf::heartbeat:LIO):   Started iscsitgt01s.example.com
     p_vip      (ocf::heartbeat:VIP):   Started iscsitgt01s.example.com

PCSD Status:
  iscsitgt01a.example.com (10.110.88.57): Online
  iscsitgt01s.example.com (10.110.88.58): Online

Daemon Status:
  corosync: active/disabled
  pacemaker: active/disabled
  pcsd: active/enabled


※ 赤字の行で、VIP を持っているノードが確認できればスイッチオーバ完了です。

※ ログで確認すると、切り替えに2秒かかっていないです。スイッチオーバ操作はこうであってほしいものです。メンテナンスのために切り替え作業を行うことは多いと思いますが、この作業に数十秒以上かかるようであれば、利用者の影響が大きすぎると思われるので、メンテナンス停止に関する利用者との調整が難しくなってしまいます。

Active 機で、設定変更を確認します。


diff <(grep -v last-lrm-refresh /etc/ha.d/crm.conf) <(sudo pcs config | grep -v last-lrm-refresh)
41a42
>     Disabled on: iscsitgt01a.example.com (score:-INFINITY) (role: Started) (id:cli-ban-g_tgt-on-iscsitgt01a.example.com)


Active 機で、設定変更を元に戻します。


sudo pcs resource clear g_tgt
diff <(grep -v last-lrm-refresh /etc/ha.d/crm.conf) <(sudo pcs config | grep -v last-lrm-refresh)


※ pcs では「unmove」ではなく「clear」です。
※ この操作を忘れると、障害発生時にフェイルオーバできません。

Stand-by 機側でリソースが動いている状態の場合、もう一度スイッチオーバさせます。Active 機側でリソースが動いている場合には、その必要はありません。

Active 機と Stand-by 機で、クラスタの状態を記録します。


sudo pcs status | sudo tee /etc/ha.d/crm.status


Active 機で、クラスタを停止します。


sudo pcs cluster stop --all
10.110.88.57: Stopping Cluster (pacemaker)...
10.110.88.58: Stopping Cluster (pacemaker)...
10.110.88.58: Stopping Cluster (corosync)...
10.110.88.57: Stopping Cluster (corosync)...



OS を停止し、バックアップを取得するのは、ここが最適と思われます。


今回は、仮想マシンでも動くことを前提に構築しました。
物理マシンであれば、Stonith 設定を追加したり、物理 watchdog に入れ替えたりといった改良の余地があります。
ただし、ハードウェアに依存する実装となってしまい、都度、調整や挙動の違いを確認する作業が発生しますし、障害試験項目も増えていきます。IPMI 対応を前提とするのであれば、多くのサーバで同じ挙動を期待できるかもしれません。
その価値があると判断した場合には、これらの設定も追加することになります。

LVM のスナップショットを利用したバックアップ機能を実装するのも良い考えだと思います。
Device Mapper Thin-Provisioning を活用すると、ストレージとしての活用範囲が広がります。