pacemakerでapacheを冗長化してみた on Cent OS 7

pacemakerでapacheを冗長化してみた on Cent OS 7

# 前提

mac 10.15.3

まず３台のVMを作ります。
- n1: apache + VIP
- n2: apache + VIP
- n3: 何もしない人。n1/n2 へのアクセスをしてみる傍観者

n1, n2 両方に pacemaker を通して共通のVIPを割り当てます。

共通のVIPに対するアクセスが、n1/n2 どちらかが止まっても通ることを確認します

referrence to https://www.digitalocean.com/community/tutorials/how-to-set-up-an-apache-active-passive-cluster-using-pacemaker-on-centos-7


# 最初にひとこと

困ったらreboot! 困ったら cleanup！！

```ruby
reboot
pcs resource cleanup
```

# 1. VMを作る

vagrant + virtualboxを使います

1. virtual box をinstall
2. vagrantでvmを作る

それぞれのVMのIPは↓を見てください。

```ruby
brew install vagrant
mkdir ~/vagrant
cd ~/vagrant

# Vagrantfile を作る
cat > Vagrantfile << EOL
Vagrant.configure("2") do |config|
  config.vm.box = "bento/centos-7"

  config.vm.provision "shell" do |s|
    s.inline = "yum install -y vim"
  end

  config.vm.define "n1" do |server|
    server.vm.hostname = "n1"
    server.vm.network "private_network", ip: "192.168.33.11"
  end

  config.vm.define "n2" do |server|
    server.vm.hostname = "n2"
    server.vm.network "private_network", ip: "192.168.33.12"
  end

  config.vm.define "n3" do |server|
    server.vm.hostname = "n3"
    server.vm.network "private_network", ip: "192.168.33.13"
  end
end
EOL

# vm x 3 を起動する
vagrant up
```


VM　ができたら、shellを３つ開きます

```ruby
# shell 1
vagrant ssh n1

# shell 2
vagrant ssh n2

# shell 3
vagrant ssh n3
```

全部で実施

```ruby
# rootになる
vagrant$ sudo su -

# hostnameでアクセスできるようにしておく。pacemakerで必要っぽい。
cat <<EOL >> /etc/hosts

192.168.33.11 n1
192.168.33.12 n2
192.168.33.13 n3
192.168.33.99 vip
EOL
cat /etc/hosts   

# access check. 全部通れば ok
ping -c1 n1
ping -c1 n2
ping -c1 n3

# vipは通らなくて ok
ping -c1 vip
```


# 2. apacheを入れる

n1, n2 で実行します。

```ruby
# rootになる
vagrant$ sudo su -

# install apache
sudo yum install -y httpd
systemctl enable httpd

# /server-statusを有効にする。pacemakerのhealth checkのdefaultがここになっているためです。
echo -e "\n\
<Location /server-status>\n\
   SetHandler server-status\n\
   Require all granted\n\
</Location>" > /etc/httpd/conf.d/status.conf
sudo systemctl restart httpd
sudo systemctl status httpd

# nodeがわかるようにする
sudo hostname > /var/www/html/index.html

# アクセスチェック
curl -s localhost         # それぞれのhostnameが返ればok

curl -s localhost/server-status | grep title
# これが返ればok → <title>Apache Status</title>
```


# 3. pacemaker する！

n1, n2 で実行します。

```ruby
# rootになる
vagrant$ sudo su -

# install pacemaker etc
sudo yum install -y pacemaker pcs
sudo systemctl enable pcsd.service
sudo systemctl status pcsd.service
sudo systemctl enable corosync.service
sudo systemctl enable pacemaker.service
sudo systemctl restart corosync.service
sudo systemctl restart pacemaker.service
sudo systemctl status corosync.service
sudo systemctl status pacemaker.service
```

## 3-1. cluster auth <ハマりpoint>
clusterにnodeを登録する。 n1, n2 を参加させる.


```ruby
pcs cluster auth n1 n2
Username: hacluster   <--- pacemakerが作るこのユーザじゃないとできなかった。ハマった


# パスワードがわからないのでやり直します
sudo passwd hacluster

# firewall止めます
sudo systemctl stop firewalld.service
sudo systemctl mask firewalld.service

再度トライ
pcs cluster auth n1 n2
# しかしどうしても通らないのでrebootしたら通った。

pcs status nodes

Pacemaker Nodes:
 Online: n1
 Standby:
 Standby with resource(s) running:
 Maintenance:
 Offline: 
Pacemaker Remote Nodes:
 Online:
 Standby:
 Standby with resource(s) running:
 Maintenance:
 Offline:
```

hacluster ユーザじゃないとダメなのは、おそらく group = haclient か nologin が原因というのを想像しています
今回は深堀りしない. PEMのなにかかもしれない.

```ruby
groups hacluster
hacluster : haclient

grep hac /etc/passwd
hacluster:x:189:189:cluster user:/home/hacluster:/sbin/nologin
```

ちなみに vagrant userは

```php
[root@n1 ~]# groups vagrant
vagrant : vagrant
```

## 2-2. clusetr setup

n1 だけで実行します


```ruby
[root@n1 ~]# pcs cluster setup --name webcluster n1 n2

成功すると

cat /etc/corosync/corosync.conf

ここに設定が入ります。

/etc/corosync/corosync.conf がない、というエラーが出たときはrebootしたら直りました。


# start cluster
sudo pcs cluster start --all

全部で
pcs status

結果は

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

disabledのものがあったらrebootすればいいっぽいです。


# 不要な設定を除外します。後でやろうとしたらうまくいかずハマったのでやっぱり先にやったほうがよさそう。
sudo pcs property set stonith-enabled=false
sudo pcs property set no-quorum-policy=ignore

```

## 2-3. VIP 

ここで、３台のどこにも所属しないVIP `192.168.33.99` を作ります。

n1 だけで実行します。ここでハマったら `ocf:heartbeat:IPaddr2` のドキュメントを参照すればいいんですが、 linux-ha.org が大本らしいがサービスダウン？終了？していてろくに情報を集められませんでいsた。google力のなさ。

とりあえず、 `ocf:heartbeat:IPaddr2` は

Open Cluster Framework の heartbeat の中の IPaddr2 というモジュール？を使うということのようです。

```ruby

# nicの指定が大切です。nicを指定しないと、成功するのにNICにVIPが作られませんでした。192.168.33.0/24にいるnicを指定しましょう。
sudo pcs resource create Cluster_VIP ocf:heartbeat:IPaddr2 nic=eth1 ip=192.168.33.99 cidr_netmask=24 op monitor interval=20s

pcs status
成功してるけどVIPがなかったのでcleanupしたら・・
pcs resource cleanup

ｷﾀ―――(ﾟ∀ﾟ)―――― !!

[root@n1 ~]# ip a | grep 33
    inet 192.168.33.11/24 brd 192.168.33.255 scope global noprefixroute eth1

[root@n2 ~]# ip a | grep 33
    inet6 fe80::e233:1e8a:be94:91d9/64 scope link noprefixroute
    inet 192.168.33.12/24 brd 192.168.33.255 scope global noprefixroute eth1
    inet 192.168.33.99/24 brd 192.168.33.255 scope global secondary eth1

今回は n2 が VIPを取得しました。


# 接続テスト
3台から ping が通るか確認しましょう。

ping -c1 192.168.33.99

# このアドレスは誰が持ってるの？

対向の n1 から、arp で .99 の Mac Address を見てみましょう。

[root@n1 ~]# arp | grep 99
192.168.33.99            ether   08:00:27:8d:10:4f   C                     eth1

これは n2 の eth1 が持っている mac address と一致していますね！

[root@n2 ~]# ifconfig eth1 | grep ether
        ether 08:00:27:8d:10:4f  txqueuelen 1000  (Ethernet)
```

これで VIP の設定は終わりです。便利だ。


# 2-4. apacheを冗長化する

正直、VIPさえ冗長化できればapacheはいらないのではとも思ったりするけど
pacemakerはセットで冗長化できるのでやってみましょう。
これにより、 VIP: active, Apache: 落ちてる　みたいな悲しい状況を防ぐことができます。


n1 だけで実行します

```ruby
# apacheをclusterに登録
sudo pcs resource create WebServer ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf statusurl="http://127.0.0.1/server-status" op monitor interval=20s


# VIPとapacheがセットで切り替わるようにする
sudo pcs constraint colocation add WebServer Cluster_VIP INFINITY


# pcs status

 Cluster_VIP   (ocf::heartbeat:IPaddr2):  Started n1
 WebServer  (ocf::heartbeat:apache):   Stopped

Failed Resource Actions:
* WebServer_start_0 on n1 'unknown error' (1): call=12, status=Timed Out, exitreason='',
    last-rc-change='Sun Sep 12 01:39:36 2021', queued=0ms, exec=40004ms
* WebServer_start_0 on n2 'unknown error' (1): call=10, status=Timed Out, exitreason='',
    last-rc-change='Sun Sep 12 01:40:17 2021', queued=0ms, exec=40005ms


# エラーが出たら、cleanupしたら直りました

pcs resource cleanup
```

これで、apacheが落ちてるサーバに VIP がつくことが防げました！

この `ocf:heartbeat:apache` は `リソースクラス:ネームスペース:リソースエージェント名` ということです

> The first field is the resource class, which is the standard the resource agent conforms to. It also tells Pacemaker where to find the script. The IPaddr2 resource agent conforms to the OCF (Open Cluster Framework) standard.
The second field depends on the standard. OCF resources use the second field for the OCF namespace.
The third field is the name of the resource agent.

OCF = Open Cluster Framework

つまり

Open Cluster Framework の heartbeat の中の apache という設定を使うということですね

### Resource cluster list

> OCF
LSB
Upstart
Systemd
Service
Fencing
Nagios Plugins
https://clusterlabs.org/pacemaker/doc/deprecated/en-US/Pacemaker/1.1/html/Pacemaker_Explained/s-resource-supported.html

しかし、残念ながら  linux-ha.org が落ちていて、マニュアルは確認できず。

http://www.linux-ha.org/wiki/OCF_Resource_Agents



# 4. httpアクセスしてみよう！ 片方落としてみよう！

早速やってみよう！ VIPにアクセスしてみます

```ruby
[root@n1 ~]# curl -s 192.168.33.99
n2
```

2号機がactiveです。2号機を止めてみます。

```ruby
n2# systemctl stop httpd
```

でも、pacemakerが自動で起動するようです。止まりませんでした。

pacemakerが触らなそうな、nicを止めてしまいます。通常であればsshも切れてしまいますが、このvagrant(virtualbox)では eth0 の 10.0.2.15 で ssh 通信しているので 192系の eth1 は止めても大丈夫です。

```ruby
n2# ifdown eth1
```

すると、見事にsplit brainにできました。

```ruby
# n2の死亡に気づいて、自分をactiveにしているn1くん。優秀。
[root@n1 ~]# pcs status
 WebServer  (ocf::heartbeat:apache):   Started n1
 Cluster_VIP   (ocf::heartbeat:IPaddr2):  Started n1

# 自分の死亡に気付かず、サービス提供し続けるn2。やめてくれ！！
[root@n2 ~]# pcs status
 WebServer  (ocf::heartbeat:apache):   Started n2
 Cluster_VIP   (ocf::heartbeat:IPaddr2):  Started n2
```

このとき、n2がもっていた .99 のVIPは ifdown とともに死にます。それを検知して n1 が引き継ぎます。

```ruby
[root@n1 ~]# ip a | grep 99
    inet 192.168.33.99/24 brd 192.168.33.255 scope global secondary eth1

[root@n2 ~]# ip a | grep 99
   なし
```

ということで、どこからでも 今度は curl が n1 に行きます。

```ruby
[root@n1 ~]# curl -s 192.168.33.99
n1
[root@n2 ~]# curl -s 192.168.33.99
n1
[root@n3 ~]# curl -s 192.168.33.99
n1
```

このとき、standby -> active に変わった n1 ではこんな `/var/log/messages` が出ています。

```ruby
Sep 12 02:19:04 n1 corosync[989]: [TOTEM ] A processor failed, forming new configuration.
Sep 12 02:19:05 n1 corosync[989]: [TOTEM ] A new membership (192.168.33.11:70) was formed. Members left: 2
Sep 12 02:19:05 n1 corosync[989]: [TOTEM ] Failed to receive the leave message. failed: 2
Sep 12 02:19:05 n1 corosync[989]: [CPG   ] downlist left_list: 1 received
Sep 12 02:19:05 n1 corosync[989]: [QUORUM] Members[1]: 1
Sep 12 02:19:05 n1 corosync[989]: [MAIN  ] Completed service synchronization, ready to provide service.
Sep 12 02:19:05 n1 pacemakerd[1038]:  notice: Node n2 state is now lost
Sep 12 02:19:05 n1 crmd[1095]:  notice: Node n2 state is now lost
Sep 12 02:19:05 n1 crmd[1095]: warning: No reason to expect node 2 to be down
Sep 12 02:19:05 n1 crmd[1095]:  notice: Stonith/shutdown of n2 not matched
Sep 12 02:19:05 n1 crmd[1095]:  notice: State transition S_IDLE -> S_POLICY_ENGINE
Sep 12 02:19:05 n1 crmd[1095]: warning: No reason to expect node 2 to be down
Sep 12 02:19:05 n1 crmd[1095]:  notice: Stonith/shutdown of n2 not matched
Sep 12 02:19:05 n1 attrd[1093]:  notice: Node n2 state is now lost
Sep 12 02:19:05 n1 attrd[1093]:  notice: Removing all n2 attributes for peer loss
Sep 12 02:19:05 n1 attrd[1093]:  notice: Purged 1 peer with id=2 and/or uname=n2 from the membership cache
Sep 12 02:19:05 n1 cib[1085]:  notice: Node n2 state is now lost
Sep 12 02:19:05 n1 cib[1085]:  notice: Purged 1 peer with id=2 and/or uname=n2 from the membership cache
Sep 12 02:19:05 n1 stonith-ng[1086]:  notice: Node n2 state is now lost
Sep 12 02:19:05 n1 stonith-ng[1086]:  notice: Purged 1 peer with id=2 and/or uname=n2 from the membership cache
Sep 12 02:19:06 n1 pengine[1094]:  notice:  * Start      WebServer     (       n1 )
Sep 12 02:19:06 n1 pengine[1094]:  notice:  * Start      Cluster_VIP   (       n1 )
Sep 12 02:19:06 n1 pengine[1094]:  notice: Calculated transition 6, saving inputs in /var/lib/pacemaker/pengine/pe-input-22.bz2
Sep 12 02:19:06 n1 crmd[1095]:  notice: Initiating start operation WebServer_start_0 locally on n1
Sep 12 02:19:06 n1 crmd[1095]:  notice: Initiating start operation Cluster_VIP_start_0 locally on n1
Sep 12 02:19:06 n1 IPaddr2(Cluster_VIP)[4191]: INFO: Adding inet address 192.168.33.99/24 with broadcast address 192.168.33.255 to device eth1
Sep 12 02:19:06 n1 IPaddr2(Cluster_VIP)[4191]: INFO: Bringing device eth1 up
Sep 12 02:19:06 n1 apache(WebServer)[4190]: INFO: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 192.168.33.11. Set the 'ServerName' directive globally to suppress this message
Sep 12 02:19:06 n1 IPaddr2(Cluster_VIP)[4191]: INFO: /usr/libexec/heartbeat/send_arp  -i 200 -r 5 -p /var/run/resource-agents/send_arp-192.168.33.99 eth1 192.168.33.99 auto not_used not_used
Sep 12 02:19:06 n1 crmd[1095]:  notice: Result of start operation for Cluster_VIP on n1: 0 (ok)
Sep 12 02:19:06 n1 crmd[1095]:  notice: Initiating monitor operation Cluster_VIP_monitor_3000 locally on n1
Sep 12 02:19:06 n1 crmd[1095]:  notice: Result of start operation for WebServer on n1: 0 (ok)
Sep 12 02:19:06 n1 crmd[1095]:  notice: Initiating monitor operation WebServer_monitor_20000 locally on n1
Sep 12 02:19:06 n1 crmd[1095]:  notice: Transition 6 (Complete=4, Pending=0, Fired=0, Skipped=0, Incomplete=0, Source=/var/lib/pacemaker/pengine/pe-input-22.bz2): Complete
Sep 12 02:19:06 n1 crmd[1095]:  notice: State transition S_TRANSITION_ENGINE -> S_IDLE
Sep 12 02:19:10 n1 IPaddr2(Cluster_VIP)[4191]: INFO: ARPING 192.168.33.99 from 192.168.33.99 eth1#012Sent 5 probes (5 broadcast(s))#012Received 0 response(s)
```

ざっと解説

```ruby
crmdというデーモンがチェックしてるみたい

n2 死亡を確認
notice: Node n2 state is now lost

メンバーから n2 を除外
notice: Purged 1 peer with id=2 and/or uname=n2 from the membership cache

サービスを有効にする
 notice: Initiating start operation WebServer_start_0 locally on n1
 notice: Initiating start operation Cluster_VIP_start_0 locally on n1

ここで IPaddr2 が VIP を作る
IPaddr2(Cluster_VIP)[4191]: INFO: Adding inet address 192.168.33.99/24 with broadcast address 192.168.33.255 to device eth1

VIP宛の通信が n2 にいかないように、arp を `not_used` で更新してくれるみたい！　優秀。
IPaddr2(Cluster_VIP)[4191]: INFO: /usr/libexec/heartbeat/send_arp  -i 200 -r 5 -p /var/run/resource-agents/send_arp-192.168.33.99 eth1 192.168.33.99 auto not_used not_used

VIP宛のarpを自分で更新した？　時間差があるから外からのrequestによって？
IPaddr2(Cluster_VIP)[4191]: INFO: ARPING 192.168.33.99 from 192.168.33.99 eth1#012Sent 5 probes (5 broadcast(s))#012Received 0 response(s)
```

素晴らしい！！！ VIPだけじゃなくて apacheのstateもセットで管理できるのすごいわ。

pacemaker完全に理解した！