keepalivedが異常終了したらvipを手動で消す必要がある

keepalivedでvrrpを通してVIPを持つ構成にした時に、keepalivedの障害試験をしていて発覚したことのメモです。

# 概要

* keepalivedは、MASTER STATEの時に終了すると、自身のVIPをinterfaceから解放する
* 異常終了時(`kill -9`で強引に殺したり、サーバがダウンしたりなど)は、正常な終了処理ではないので、VIPを解放しないで持ち続ける
* 次にkeepalivedを起動したときは、起動時のIPを正しいものとして認識してしまう
* 手動でVIPを解放しない限り、keepalivedはVIPを解放しない

# 設定

確認してみます。`keepalived`のバージョンは以下です。

```
Keepalived v1.2.13 (03/19,2015)
```

* `192.168.33.11` : ノード1: keepalivedを構成。MASTER STATE
* `192.168.33.12` : ノード2: keepalivedを構成。BACKUP STATE
* `192.168.33.20` : vrrp VIP

```:/etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
    state BACKUP
    interface eth1
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.33.20
    }
    notify_master "/tmp/master.sh"
    notify_backup "/tmp/backup.sh"
    unicast_peer {
        192.168.33.11
        192.168.33.12
    }
}
```

MASTER STATE側は、VIPが見えるようになります。

```bash:192.168.33.11
$ ip addr show eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:4e:29:d9 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.11/24 brd 192.168.33.255 scope global eth1
    inet 192.168.33.20/32 scope global eth1 # ← VIP
    inet6 fe80::a00:27ff:fe4e:29d9/64 scope link
       valid_lft forever preferred_lft forever
```

# keepalived正常終了時

この状態で、`keepalived`を正常に停止します。

```bash:192.168.33.11
$ sudo service keepalived stop
```

ログには以下のようなメッセージが出ます。

```:/var/log/messages
Aug 16 06:36:06 centos66-1 Keepalived[2785]: Stopping Keepalived v1.2.13 (03/19,2015)
Aug 16 06:36:06 centos66-1 Keepalived_vrrp[2788]: VRRP_Instance(VI_1) sending 0 priority
Aug 16 06:36:06 centos66-1 Keepalived_healthcheckers[2787]: Removing service [192.168.33.11]:80 from VS [192.168.33.20]:80
Aug 16 06:36:06 centos66-1 Keepalived_healthcheckers[2787]: Removing service [192.168.33.12]:80 from VS [192.168.33.20]:80
Aug 16 06:36:06 centos66-1 kernel: IPVS: __ip_vs_del_service: enter
Aug 16 06:36:06 centos66-1 Keepalived_vrrp[2788]: VRRP_Instance(VI_1) removing protocol VIPs. # ← VIP削除
```

VIPが取り除かれているようです。(VIPはnode2側に移動しています)

```bash:192.168.33.11
$ ip addr show eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:4e:29:d9 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.11/24 brd 192.168.33.255 scope global eth1
    inet6 fe80::a00:27ff:fe4e:29d9/64 scope link
       valid_lft forever preferred_lft forever
```

この後、`keepalived`を起動しても、当然正常に組み込まれます。

# keepalived異常終了時

同じシチュエーションで、今度は`kill -9`で強引にプロセスを殺します。

```bash:192.168.33.11
$ sudo pkill -9 -f keepalived
```

強引に殺しているので、syslogにも何も出ませんし、終了処理としてのVIPの解放も行われません。

```bash:192.168.33.11
$ ip addr show eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:4e:29:d9 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.11/24 brd 192.168.33.255 scope global eth1
    inet 192.168.33.20/32 scope global eth1 # ← VIPが残っている
    inet6 fe80::a00:27ff:fe4e:29d9/64 scope link
       valid_lft forever preferred_lft forever
```

node2側は、node1のkeepalivedのダウンを検知してMASTER STATEになり、VIPを持ち始めます。

```bash:192.168.33.12
$ ip addr show eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:75:a7:36 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.12/24 brd 192.168.33.255 scope global eth1
    inet 192.168.33.20/32 scope global eth1 # ← VIPが増えている
    inet6 fe80::a00:27ff:fe75:a736/64 scope link
       valid_lft forever preferred_lft forever
```

この時点で、VIPである `192.168.33.20` は競合しているので、アプリケーションは不具合を起こす可能性があります。

復旧するには、VIPを手動で削除します。

```bash:192.168.33.11
$ sudo ip addr del 192.168.33.20/32 dev eth1
```

# keepalived異常終了後、復旧操作を行わないままkeepalivedを起動するとどうなるか

上記のように `ip addr del` コマンドを実行せずに、またkeepalivedを起動するとどうなるかも確認しました。起動時にいい感じでBACKUP STATEに移行し、VIPを取り除いてくれるかを期待しつつ…

```bash:192.168.33.11
$ sudo service keepalived start
```

```:/var/log/messages
Aug 16 07:06:12 centos66-1 Keepalived[3476]: Starting Keepalived v1.2.13 (03/19,2015)
(snip)
Aug 16 07:06:12 centos66-1 Keepalived_vrrp[3480]: Netlink reflector reports IP 192.168.33.11 added
Aug 16 07:06:12 centos66-1 Keepalived_vrrp[3480]: Netlink reflector reports IP 192.168.33.20 added # ← VIPが登録されている
(snip)
Aug 16 07:06:12 centos66-1 Keepalived_vrrp[3480]: Registering Kernel netlink reflector
Aug 16 07:06:12 centos66-1 Keepalived_vrrp[3480]: Registering Kernel netlink command channel
(snip)
Aug 16 07:06:12 centos66-1 Keepalived_healthcheckers[3479]: Netlink reflector reports IP 192.168.33.11 added
Aug 16 07:06:12 centos66-1 Keepalived_healthcheckers[3479]: Netlink reflector reports IP 192.168.33.20 added # ← VIPが登録されている
(snip)
Aug 16 07:06:12 centos66-1 Keepalived_vrrp[3480]: Using LinkWatch kernel netlink reflector...
Aug 16 07:06:12 centos66-1 Keepalived_vrrp[3480]: VRRP_Instance(VI_1) Entering BACKUP STATE
Aug 16 07:06:12 centos66-1 Keepalived_vrrp[3480]: VRRP sockpool: [ifindex(3), proto(112), unicast(1), fd(10,11)]
Aug 16 07:06:12 centos66-1 Keepalived_healthcheckers[3479]: IPVS: Service already exists
Aug 16 07:06:12 centos66-1 Keepalived_healthcheckers[3479]: IPVS: Destination already exists
Aug 16 07:06:12 centos66-1 Keepalived_healthcheckers[3479]: IPVS: Destination already exists
Aug 16 07:06:12 centos66-1 Keepalived_healthcheckers[3479]: Using LinkWatch kernel netlink reflector...
Aug 16 07:06:12 centos66-1 Keepalived_healthcheckers[3479]: Activating healthchecker for service [192.168.33.11]:80
Aug 16 07:06:12 centos66-1 Keepalived_healthcheckers[3479]: Activating healthchecker for service [192.168.33.12]:80
```

いろいろと端折りましたが、正常終了時に出力されたような「`removing protocol VIPs.`」のようなメッセージが出ません。むしろ、VIPである`192.168.33.20`を登録したようなメッセージが出ています(`Netlink reflector reports IP 192.168.33.20 added`)。

ということで、BACKUP STATEに移行したにも関わらず、VIPは解放されませんでした。

```bash:192.168.33.11
$ ip addr show eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:4e:29:d9 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.11/24 brd 192.168.33.255 scope global eth1
    inet 192.168.33.20/32 scope global eth1 # ← VIPが残っている
    inet6 fe80::a00:27ff:fe4e:29d9/64 scope link
       valid_lft forever preferred_lft forever
```

こうなってしまうと、次に正常終了しても一緒。

```bash:192.168.33.11
$ sudo service keepalived stop
```

```:/var/log/messages
Aug 16 07:13:53 centos66-1 Keepalived[3477]: Stopping Keepalived v1.2.13 (03/19,2015)
Aug 16 07:13:53 centos66-1 Keepalived_healthcheckers[3479]: Removing service [192.168.33.11]:80 from VS [192.168.33.20]:80
Aug 16 07:13:53 centos66-1 Keepalived_healthcheckers[3479]: Removing service [192.168.33.12]:80 from VS [192.168.33.20]:80
Aug 16 07:13:53 centos66-1 kernel: IPVS: __ip_vs_del_service: enter
```

もうVIPを解放してくれません…。

```bash:192.168.33.11
$ ip addr show eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:4e:29:d9 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.11/24 brd 192.168.33.255 scope global eth1
    inet 192.168.33.20/32 scope global eth1 # ← VIPが残っている
    inet6 fe80::a00:27ff:fe4e:29d9/64 scope link
       valid_lft forever preferred_lft forever
```

# まとめ

最初の概要に書いたとおりですが、`keepalived`が異常終了すると、VIPを自動で解放してくれないようです。対処としては、以下くらいかなと思います。

* 発覚したら速やかに`ip addr del`コマンドでIPを削除する
* 自動化するには、`keepalived.conf`中の`notify_backup`中のスクリプトで、強引にVIPを解放する仕組みを仕込む
* あるいは、一度MASTER STATEに戻してしまえば、次にはちゃんと解放してくれます。

`keepalived`は結構歴史が古いソフトなので、本当にこんな対処しかないのかは不明です…。