FANCOMI Ad-Tech Blog

株式会社ファンコミュニケーションズ nend・新規事業のエンジニア・技術ブログ

AWSでPacemaker & Corosync

はじめに

はじめまして、nendでインフラ担当をしているn_watanabeです。 今回はAWS EC2でVIP(仮想IP)を使ったActive/Standby構成を組む方法を紹介します。

技術要素としては、AWS EC2(AmazonLinux) + Pacemaker + Corosyncとなります。 Pacemaker構成をオンプレミスで使う場合、IPアドレスはOS上で設定すればいいだけなのですが、 AWSの場合はAWSのシステムに対して設定してあげる必要もあります。 IPアドレスをOSに設定しただけではパケットはインスタンスに到達しません。

注意点として、今回のようにEIPではなくプライベートアドレスを切り替える方式の場合は、 2台が同一Subnetにいる必要があるのでMulitiAZできないという制約があります。

ちなみにちょっとカッコよくいうと、CDP:Floating IPパターンの実装の一つと言えそうです。

Pacemaker,Corosyncとは?

Pacemaker,Corosyncをサポートしている株式会社サードウェアさんのページでは下記のように説明があります。

> Pacemakerは、ハイアベイラビリティ・クラスタ(HAクラスタ)を実現するクラスタ管理システムの主役です。 > これは、HAクラスタ上で動作させるアプリケーションなどのリソースを監視、管理します。 > 一方Heartbeat / Corosyncはクラスタを構成するサーバを監視、管理します。 > 実際のHAクラスタは、PacemakerとHeartbeat、またはPacemakerとCorosyncを組み合わせて実現します。

サーバは必ずいずれは壊れるものです。 したがって、それに備えて2台以上でシステムを組むわけですが、単純に2台置いてもうまく動いてくれません。 システムにもよりますが、一般的に死活監視データの同期IPアドレスのサーバ間の移動ソフトウェアを正しい順番で起動、などを行う必要があります。 当然それを人力でやっているとサービス停止時間が長引いてしまうわけで、このようなソフトウェアで自動化することになります。

もっともクラウド全盛の現在ではサーバ側がImmutableなことが多いので、そういった場合はロードバランサーを置くだけで可能な場合も多いです。 最近ではこのようなソフトを使わないで済む構成にした方がいいのでは?と思うことも多いですが、諸事情あって使う必要に迫られる人もいるとは思います。

今回の検証イメージ

起動時の動作イメージ

ec2_pacemaker_before

まず、先に起動したインスタンスがActive機になろうとします。 そのため、以下の動作を行います。

  • OSにセカンダリIPを付与(vipというresourceで管理)
  • AWSのAPIを通して、AWSインスタンスにセカンダリIPを付与(ec2-vipというresourceで管理)

なお、2つのresourceが2台に分散して配置されないようにvip-groupとグループ化しています。

FailOver時の動作イメージ

ec2_pacemaker_after

Active機で障害が発生した場合の動作です。 やはりOSとAWSの両方の設定を変更しています。

検証環境

今回の具体的な検証環境は以下となります。

  • AMI:amzn-ami-hvm-2014.09.2.x86_64-ebs (ami-18869819)
  • Instance type:t2.micro
  • pacemaker-1.1.12
  • pcs-0.9.90
  • corosync-2.3.4

EC2 Instance のIPアドレス

  • 10.0.250.185  (セカンダリーVIP)
  • 10.0.250.186  (インスタンスA)
  • 10.0.250.187  (インスタンスB)

構築例

yumリポジトリの準備

Linux-HA Japanさんが提供しているyumリポジトリを使わせていただきました。

[shell] $ wget http://jaist.dl.sourceforge.jp/linux-ha/62369/pacemaker-repo-1.1.12-1.1.el6.x86_64.rpm $ sudo rpm -Uvh pacemaker-repo-1.1.12-1.1.el6.x86_64.rpm [/shell]

corosyncとpacemakerとpcsのインストール

corosyncとpacemakerでクラスタリングを組むわけですが、pcsというのも一緒にインストールします。 pcsはpacemakerのCLIフロントエンドツールです。 pcs status~、pcs create~というような使い方をします。

[shell] $ sudo yum -c /etc/yum.repos.d/pacemaker.repo install corosync pacemaker pcs [/shell]

corosyncの設定

/etc/corosync/corosync.conf

[html]

Please read the corosync.conf.5 manual page

totem { version: 2

crypto_cipher: none crypto_hash: none

interface { ringnumber: 0 bindnetaddr: 10.0.250.186  # 2台目は、10.0.250.187 mcastport: 5405 ttl: 1 } transport: udpu }

logging { fileline: off to_logfile: yes to_syslog: yes logfile: /var/log/cluster/corosync.log debug: off timestamp: on logger_subsys { subsys: QUORUM debug: off } }

nodelist { node { ring0_addr: 10.0.250.186 nodeid: 1 }

node { ring0_addr: 10.0.250.187 nodeid: 2 }

}

quorum {

Enable and configure quorum subsystem (default: off)

see also corosync.conf.5 and votequorum.5

provider: corosync_votequorum expected_votes: 2 } [/html]

冗長化プロトコルではよくマルチキャストアドレスが使われますが、VPC内ではマルチキャストは使えない仕様になっています。 今回は、transport: udpu とUDP Unicastで通信する設定になっていますので問題ありません。

リソースエージェントの設置

こちらのブログで紹介されているソースを改造させていただきました。 PacemakerでEIPを付替えるResource Agentを書いてみた https://github.com/moomindani/aws-eip-resource-agent

下記スクリプトを/usr/lib/ocf/resource.d/heartbeat/ec2vipに設置してください。

#!/bin/sh
#
#
#       Resource Agent for AWS EC2 Virtual PrivateIP
#
# Copyright (c) 2014 mooapp All Rights Reserved.
# Copyright (c) 2015 F@N Communications, Inc. All Rights Reserved.
#
# 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

# IAM Roleが設定されていないインスタンスの場合は、なんらかの手段で権限をつけてください。
# 下記をコメントアウトして適切なアクセスキーを設定すれば動きます。
# export AWS_ACCESS_KEY_ID=*****************
# export AWS_SECRET_ACCESS_KEY=*****************
# export AWS_DEFAULT_REGION=*****************

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

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

<longdesc lang="en">
This is a Resource Agent for AWS EC2 Virtual PrivateIP (Not Elastic IP Address).
</longdesc>
<shortdesc lang="en">AWS EC2 VIP resource agent</shortdesc>

<parameters>
<parameter name="ec2_vip" required="1">
<longdesc lang="en">
The Virtual Private IP address
</longdesc>
<shortdesc lang="en">EC2 VIP</shortdesc>
<content type="string" />
</parameter>

</parameters>

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

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

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

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

ec2vip_start() {
    ec2vip_monitor
    if [ $? =  $OCF_SUCCESS ]; then
        return $OCF_SUCCESS
    fi
    /usr/bin/aws ec2 assign-private-ip-addresses --network-interface-id ${eni_id} --private-ip-addresses ${OCF_RESKEY_ec2_vip} --allow-reassignment
}

ec2vip_stop() {
    ec2vip_monitor
    if [ $? =  $OCF_SUCCESS ]; then
        /usr/bin/aws ec2 unassign-private-ip-addresses --network-interface-id ${eni_id} --private-ip-addresses ${OCF_RESKEY_ec2_vip}
    fi
    return $OCF_SUCCESS
}

ec2vip_monitor() {

    mon=`aws ec2 describe-network-interfaces --network-interface-ids ${eni_id} | grep ${OCF_RESKEY_ec2_vip} | grep "PrivateIpAddress" | wc -l`
    if [ $mon -ne 0  ]; then
        return $OCF_SUCCESS
    fi
    return $OCF_NOT_RUNNING
}

ec2vip_validate() {
    return $OCF_SUCCESS
}

instance_id=`wget -q -O - http://169.254.169.254/latest/meta-data/instance-id`
local_ipv4=`wget -q -O -  http://169.254.169.254/latest/meta-data/local-ipv4`
eni_id=`/usr/bin/aws ec2 describe-network-interfaces --output text --filters "Name=addresses.private-ip-address,Values=$local_ipv4" --query "NetworkInterfaces[].NetworkInterfaceId" `

case $__OCF_ACTION in
meta-data)      meta_data
                exit $OCF_SUCCESS
                ;;
start)          ec2vip_start;;
stop)           ec2vip_stop;;
monitor)        ec2vip_monitor;;
validate-all)   ec2vip_validate;;
usage|help)     ec2vip_usage
                exit $OCF_SUCCESS
                ;;
*)              ec2vip_usage
                exit $OCF_ERR_UNIMPLEMENTED
                ;;
esac
rc=$?
ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
exit $rc

実行権限を付与

[shell] $ sudo chmod +x  /usr/lib/ocf/resource.d/heartbeat/ec2vip [/shell]

AWS APIへのアクセス権限の確認

ec2vipはEC2インスタンスからAWS APIに下記の3つのaws ec2コマンドを通してアクセスします。

  • aws ec2 assign-private-ip-addresses
  • aws ec2 unassign-private-ip-addresses
  • aws ec2 describe-network-interfaces

したがって、APIへのアクセス権限が必要です。 対応するIAMポリシーの書き方はこんな感じになります。

 {
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:AssignPrivateIpAddresses",
        "ec2:UnassignPrivateIpAddresses",
        "ec2:DescribeNetworkInterfaces"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

corosyncの起動

さて、サービスを起動しますが順番が重要です。 corosync、pacemakerの順番で起動する必要があります。

[shell] $ sudo service corosync start Starting Corosync Cluster Engine (corosync):               [  OK  ] [/shell]

pacemakerの起動

pacemakerはまだ何も設定していませんが、起動前に設定が入れられないので、まず起動します。

[shell] $ sudo service pacemaker start Starting Pacemaker Cluster Manager                         [  OK  ] [/shell]

現時点のクラスター状態を確認すると以下のようになります。

[shell] $ sudo pcs status Cluster name: WARNING: no stonith devices and stonith-enabled is not false Last updated: Mon Mar 16 01:35:58 2015 Last change: Mon Mar 16 01:35:09 2015 Stack: corosync Current DC: ip-10-0-250-186 (1) - partition with quorum Version: 1.1.12-561c4cf 2 Nodes configured    ← 2台で設定されていればOK 0 Resources configured ← リソースはこれから設定するので0でOK

Online: [ ip-10-0-250-186 ip-10-0-250-187 ] ← 2台ともOnlineになっていればOK

Full list of resources:

PCSD Status: 10.0.250.186: Offline  ←PCSDはOfflineで問題ない 10.0.250.187: Offline [/shell]

Pacemakerの設定

下記は1台で設定すれば大丈夫です。 自動的に設定は同期されます。

まず、全体的な設定です。とりあえず、おまじないと思ってください。

[shell] $ sudo pcs property set \ no-quorum-policy="ignore" \ stonith-enabled="false" \ crmd-transition-delay="0s" [/shell]

 

[shell] $ sudo pcs resource defaults resource-stickiness="INFINITY" migration-threshold="1" [/shell]

ec2vipというリソースを作成しています。 ec2vip="IPアドレス"の部分が前述のec2vipスクリプトに変数として渡されます。 こちらのリソースでAWS側としてのセカンダリーアドレスの設定がされます。

[shell] $ sudo pcs resource create \ ec2vip ocf:heartbeat:ec2vip \ params \ ec2_vip="10.0.250.185" \ op start   timeout="60s" interval="0s"  on-fail="stop" \ op monitor timeout="60s" interval="10s" on-fail="restart" \ op stop    timeout="60s" interval="0s"  on-fail="block" [/shell]

vipというリソースを作成します。 IPaddr2というのは、Pacemaker標準添付のスクリプトです。 ip="IPアドレス"の部分がIPaddr2スクリプトに変数として渡されます。 こちらのリソースでOS側としてのセカンダリーアドレスの設定がされます。

[shell] $ sudo pcs resource create \ vip ocf:heartbeat:IPaddr2 \ params \ ip="10.0.250.185" \ nic="eth0" \ cidr_netmask="24" \ op start timeout="20s" on-fail="restart" \ op stop timeout="20s" on-fail="block" \ op monitor interval="10s" timeout="20s" on-fail="restart" [/shell]

ec2vipとvipがセットでActive/Standbyになるようにグルーピングします。

[shell] $ sudo pcs resource group add vip-group ec2vip vip [/shell]

自動起動設定

[shell] $ sudo chkconfig corosync on $ sudo chkconfig pacemaker on [/shell]

動作確認

[shell] $ sudo pcs status Cluster name: Last updated: Mon Mar 16 02:21:18 2015 Last change: Mon Mar 16 02:03:06 2015 Stack: corosync Current DC: ip-10-0-250-186 (1) - partition with quorum Version: 1.1.12-561c4cf 2 Nodes configured 2 Resources configured

Online: [ ip-10-0-250-186 ip-10-0-250-187 ]

Full list of resources:

Resource Group: vip-group ec2vip     (ocf::heartbeat:ec2vip):        Started ip-10-0-250-186 vip        (ocf::heartbeat:IPaddr2):       Started ip-10-0-250-186

PCSD Status: 10.0.250.186: Offline 10.0.250.187: Offline [/shell]

Active機でsecondaryアドレス見えることを確認します。

[shell] $ sudo ip addr | grep eth0 2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 9001 qdisc pfifo_fast state UP qlen 1000 inet 10.0.250.186/24 brd 10.0.250.255 scope global eth0 inet 10.0.250.185/24 brd 10.0.250.255 scope global secondary eth0 [/shell]

動作テスト

同一SubnetのインスタンスからpingをVIPに打ち続け、Activeインスタンスを再起動したところ。8秒ほどで復旧しました。 pacemakerのパラメータ調整でもっと速くなるかもしれませんが、AWS側の変更をする都合上、どうやっても数秒はかかると思った方が良さそうです。

[shell] $ ping 10.0.250.185 PING 10.0.250.185 (10.0.250.185) 56(84) bytes of data. 64 bytes from 10.0.250.185: icmp_seq=1 ttl=64 time=0.635 ms 64 bytes from 10.0.250.185: icmp_seq=2 ttl=64 time=0.638 ms 64 bytes from 10.0.250.185: icmp_seq=3 ttl=64 time=0.515 ms 64 bytes from 10.0.250.185: icmp_seq=4 ttl=64 time=0.458 ms 64 bytes from 10.0.250.185: icmp_seq=5 ttl=64 time=0.642 ms 64 bytes from 10.0.250.185: icmp_seq=6 ttl=64 time=0.623 ms 64 bytes from 10.0.250.185: icmp_seq=7 ttl=64 time=0.638 ms 64 bytes from 10.0.250.185: icmp_seq=8 ttl=64 time=0.635 ms 64 bytes from 10.0.250.185: icmp_seq=16 ttl=64 time=0.589 ms 64 bytes from 10.0.250.185: icmp_seq=17 ttl=64 time=0.522 ms 64 bytes from 10.0.250.185: icmp_seq=18 ttl=64 time=0.450 ms 64 bytes from 10.0.250.185: icmp_seq=19 ttl=64 time=0.501 ms 64 bytes from 10.0.250.185: icmp_seq=20 ttl=64 time=0.429 ms 64 bytes from 10.0.250.185: icmp_seq=21 ttl=64 time=0.600 ms ^C --- 10.0.250.185 ping statistics --- 21 packets transmitted, 14 received, 33% packet loss, time 20450ms rtt min/avg/max/mdev = 0.429/0.562/0.642/0.080 ms [/shell]

その他、ざっと下記の挙動を確認しました。

Activeインスタンスでsudo service pacemaker stop

FailOver発生します。

Activeインスタンスでpacemakerdをkill -9

FailOver発生しません。 crmdやpengineなどの関連プロセスは生き続けていました。 その後、service pacemaker restartしたところ、何事もなくpacemakerdは起動しました。

Activeインスタンスでをsudo service corosync stop

FailOver発生します。 pacemaker関連のプロセスもいくつか停止してしまいます。 正常にするには、corosyncを起動した後でpacemakerを起動する必要があります。

検証を終えて・・・

実際にここまで動かすまでに1週間かかりましたが、最終的にシンプルな感じにまとまってよかったと思います。

AWSの仕様に驚きました。 aws ec2 assign-private-ip-addresses でOSにも設定されると期待していましたが、そんなこともなく。。。 他のOSならまだしも、AmazonLinuxですら、そこは協調しないんですね。

AWSではセカンダリーIPではなく、EIPを付け替える実装の例をよく見ます。 MultiAZできるという観点では確かにそちらの方がいいように感じますが、その他の点ではどうか気になるところです。

PacemakerとCorosyncの変化にも戸惑いました。 3年ぶりくらいに触ったので。(その時はCorosyncでなく、Heartbeatでしたが。) ただ、文法が変わっただけでやっていることは変化ない気もします。

実際にプロダクション環境で使うには、もう少しパラメータを調整する必要がありそうですが、 ひとまず期待通り動くことが確認できてよかったです。

参考にさせていただいたページ