Apache mod_heartbeat を使ってみる

CentOS 7 の Apache 2.4 に `mod_heartbeat` とか `mod_watchdog` という変なモジュールがあったので興味本位で使ってみました。

使っただけなので詳しいことは調べていません。

## 概要

Apache の `mod_proxy_balancer` で負荷分散するときにリアルサーバからのハートビートを元にバランス先を判断するもののようです。

以下の３つのモジュールを組み合わせて使います。

- `mod_watchdog`
    - 他のモジュールで定期的なタスクを実行するためのモジュール
    - `mod_heartbeat` や `mod_heartmonitor` で定期的なタスクを実行するために必要
    - リバースプロキシとリアルサーバの両方で有効にする
    - いわゆる software watchdog のことではない
    - 下記のページが詳しい
    - [mod_watchdog | しんじつのなみだ][しんじつのなみだ]
- `mod_heartbeat`
    - リアルサーバからハートビートを送信するためのモジュール
    - リアルサーバで有効にする
    - ハートビートはマルチキャストで送信される
    - いわゆる Linux-HA の heartbeat とは関係ない
- `mod_heartmonitor`
    - ハートビートを受信してバランス先を管理するためのモジュール
    - リバースプロキシで有効にする

[しんじつのなみだ]: http://apache24.jugem.jp/?eid=10 "mod_watchdog | しんじつのなみだ"

## Vagrant

Vagrant で環境を作ります。

```ruby
Vagrant.configure(2) do |config|

  config.vm.box = "ngyuki/centos-7"

  config.vm.define "rev" do |cfg|
    cfg.vm.hostname = "rev"
    cfg.vm.network "forwarded_port", guest: 80, host: 8080
    cfg.vm.network "private_network", ip: "192.168.33.10", virtualbox__intnet: "apache-hb"
  end

  config.vm.define "sv1" do |cfg|
    cfg.vm.hostname = "sv1"
    cfg.vm.network "forwarded_port", guest: 80, host: 8081
    cfg.vm.network "private_network", ip: "192.168.33.11", virtualbox__intnet: "apache-hb"
  end

  config.vm.define "sv2" do |cfg|
    cfg.vm.hostname = "sv2"
    cfg.vm.network "forwarded_port", guest: 80, host: 8082
    cfg.vm.network "private_network", ip: "192.168.33.12", virtualbox__intnet: "apache-hb"
  end

  config.vm.provision "shell", inline: <<-SHELL
    yum -y install httpd
    uname -n > /var/www/html/index.html
  SHELL
end
```

rev がいわゆるリバースプロキシ型の L7 ロードバランサで、sv1 と sv2 がリアルサーバです。

使用している Box はプライベートな Box なのでわたし以外使えませんが、epel-release とか git とかを追加しているだけなので、普通の CentOS 7 と特に変わりありません。

それでは起動します。

```
vagrant up
```

## mod_heartbeat を使わずに分散

リアルサーバ(sv1/sv2)の Apache を開始します。

```
systemctl start httpd.service
```

リバースプロキシ(rev)で次のように設定します。

```sh
cat <<'EOS'> /etc/httpd/conf.d/rev.conf
<VirtualHost *:80>
    ServerName localhost
    <Location />
        ProxyPass balancer://app/
        ProxyPassReverse balancer://app/
    </Location>
    <Location /balancer-manager>
        ProxyPass !
        SetHandler balancer-manager
    </Location>
    <Proxy balancer://app/>
        BalancerMember http://192.168.33.11
        BalancerMember http://192.168.33.12
    </Proxy>
</VirtualHost>
EOS
```

Apache を開始します。

```
systemctl start httpd.service
```

この状態でリバースプロキシ(rev)にアクセスすると sv1 と sv2 とに分散されているのがわかります。

Vagrant のホスト側から sv2 を強制終了させます。

```
vagrant halt -f sv2
```

もう一度 rev を開くと最初の１回目か２回目で超待たされると思います。

これはリバースプロキシが sv2 にリクエストしてタイムアウトを待っているためです。

一度タイムアウトすると、それ以降はしばらく sv1 にのみ振り分けられますが、60 秒ごとに sv2 にリクエストするので、その時にまたタイムアウトを待たされます。sv2 の復帰を確認する必要があるためです。

sv2 を再起動します。

```
vagrant up sv2
vagrant ssh sv2
```

sv2 の Apache を開始します。

```
systemctl start httpd.service
```

しばらく（最大で 60 秒ぐらい）すると、sv2 にもバランスされるように戻ります。

## mod_heartbeat を使ってバランス

すべてのサーバ(rev/sv1/sv2)で、次の通りルーティングを設定します。

```sh
ip route add 239.0.0.0/24 dev enp0s8 proto kernel scope link
```

`mod_heartbeat` はマルチキャストでハートビートを送るのですが、送信先の設定で I/F を指定することができないっぽいので、ルーティングでどの I/F から出ていくかを指定する必要があります（普通はこういうときは I/F も指定するもののような気がするけど）。

I/F が一つしかないなら指定する必要はありませんが、Vagrant なので NAT と PrivateNetwork で２つあります。NAT の方から出て行ってもゲスト間で通信できないので、PrivateNetwork から出て行くようにルーティングを設定しています。

### リアルサーバ(sv1/sv2)の設定

リアルサーバ(sv1/sv2)で `mod_watchdog` と `mod_heartbeat` を有効にします。

```sh
vim /etc/httpd/conf.modules.d/00-base.conf
```

次のようにコメントアウトを解除します。

```diff
 #LoadModule buffer_module modules/mod_buffer.so
-#LoadModule watchdog_module modules/mod_watchdog.so
+LoadModule watchdog_module modules/mod_watchdog.so
-#LoadModule heartbeat_module modules/mod_heartbeat.so
+LoadModule heartbeat_module modules/mod_heartbeat.so
 #LoadModule heartmonitor_module modules/mod_heartmonitor.so
 #LoadModule usertrack_module modules/mod_usertrack.so
```

ハートビートのマルチキャストアドレスを指定します（ここで I/F 名を指定できても良さそうな気がするのですが・・・）。

```sh
cat <<EOS> /etc/httpd/conf.d/heartbeat.conf
HeartbeatAddress 239.0.0.1:27999
EOS
```

Apache を再起動します。

```sh
systemctl restart httpd.service
```

tcpdump するとマルチキャストパケットが流れているのが判ります。

```sh
tcpdump -nn -i enp0s8 port 27999
```

### リバースプロキシ(rev)の設定

リバースプロキシ(rev)で `mod_watchdog` と `mod_heartmonitor` を有効にします。

```sh
vim /etc/httpd/conf.modules.d/00-base.conf
```

次のようにコメントアウトを解除します。

```diff
 #LoadModule buffer_module modules/mod_buffer.so
-#LoadModule watchdog_module modules/mod_watchdog.so
+LoadModule watchdog_module modules/mod_watchdog.so
 #LoadModule heartbeat_module modules/mod_heartbeat.so
-#LoadModule heartmonitor_module modules/mod_heartmonitor.so
+LoadModule heartmonitor_module modules/mod_heartmonitor.so
 #LoadModule usertrack_module modules/mod_usertrack.so
```

マルチキャストの受信の設定と、状態を保存するファイル名を設定します。

```sh
cat <<EOS> /etc/httpd/conf.d/heartbeat.conf
HeartbeatListen 239.0.0.1:27999
HeartbeatStorage /tmp/hb.dat
EOS
```

プロキシの設定で `lbmethod=heartbeat` を追記します。

```sh
cat <<'EOS'> /etc/httpd/conf.d/rev.conf
<VirtualHost *:80>
    ServerName localhost
    <Location />
        ProxyPass balancer://app/ lbmethod=heartbeat
        ProxyPassReverse balancer://app/
    </Location>
    <Location /balancer-manager>
        ProxyPass !
        SetHandler balancer-manager
    </Location>
    <Proxy balancer://app/>
        BalancerMember http://192.168.33.11
        BalancerMember http://192.168.33.12
    </Proxy>
</VirtualHost>
EOS
```

Apache を再起動します。

```sh
systemctl restart httpd.service
```

リバースプロキシ(rev)にアクセスして sv1 と sv2 に分散されていることを確認しておきます。

確認できたら、おもむろに sv2 を停止します。

```sh
vagrant halt -f sv2
```

少し待ってからもう一度リバースプロキシを開くと、先ほどのようにタイムアウトを待たされることはありません。

sv2 からのハートビートが切れたことで、バランス先から取り外されたからです（多分）。

もう一度 sv2 を再起動します。

```sh
vagrant up sv2
vagrant ssh sv2
```

ルーティングを設定して Apache を起動します（本来なら自動で設定されるようにしておくべきです）。

```sh
ip route add 239.0.0.0/24 dev enp0s8 proto kernel scope link
systemctl start httpd.service
```

10 秒ぐらいで sv2 にもバランスされるようになりました。

## さいごに

L7 ロードバランサとか、別に Apache でやる必要は全くないと思うし、HAProxy とかでいいと思うし、というか AWS に乗っけて ELB でいいんじゃないって思うので、実際に運用で使うことはないと思います。