FreeBSD 上に carp(4), relayd(8) を使って、pgpool-II の可用性を高める実験です。
Linux の場合は pgpool-HA を使うのが正しい選択でしょう。
今回試したのは以下の構成。
論理構成は多少ややこしいのですが、実ハードウェアは 2 台で済みます。左側の図の A 系統で 1 台、B 系統で 1 台です。物理構成は右図のように単純です。
まず普通に 2 台構成で pgpool-II を replication_mode = true で準備。この時、health check 用に listen_address = '*' としておく。pcp_recovery_node もちゃんと動くようにしておきましょう。
クライアントのアクセス先 IP として、carp(4) による仮想 IP (Virtual IP, VIP) を用います。GENERIC, およびそのカーネルモジュールでは提供されていないので、潔く別カーネルを sudo make {build,install}kernel しましょう。後で pf(4) も使うので、特に KLD にする必要がなければそのあたりも一緒に入れておくといいでしょう。
Server-A の /etc/rc.conf に以下を追加。
ifconfig_em0="192.168.0.11/24" cloned_interfaces="carp0" ifconfig_carp0="192.168.0.99/24 vhid 99 pass secret advskew 10"
Server-B の /etc/rc.conf に以下を追加。
ifconfig_em0="192.168.0.12/24" cloned_interfaces="carp0" ifconfig_carp0="192.168.0.99/24 vhid 99 pass secret advskew 20"
ここで 192.168.0.99 が仮想 IP。pass, advskew は各自適当に設定。
これで、仮想 IP に飛んできたパケットは、carp の MASTER となっているノードに配送されます。
後で relayd(8) が VIP の tcp/5432 を処理するように設定します。
carp ノードが MASTER/BACKUP のどちらの状態になっているか、は ifconfig で確認できます。
% ifconfig carp0
carp0: flags=49<UP,LOOPBACK,RUNNING> metric 0 mtu 1500
inet 192.168.0.99 netmask 0xffffff00
carp: MASTER vhid 99 advbase 1 advskew 10
relayd(8) は pf(4) の redirect 機能を使うので、そのための設定を加えます。
また、carp MASTER ノード上で動いている relayd の health check スクリプトが、carp BACKUP ノードの pgpool-II のチェックを行う際に、 パケット今回はpf(4)を使うので、組み込んだカーネルを再構築するか、/boot/loader.conf で pf をロードしておく。/etc/rc.conf には以下を追加。
pf_enable="YES" pf_rules="/etc/pf.conf.local"
/etc/pf.conf.local として以下を設置。
ext_if = "em0"
vip = "192.168.0.99"
table <checker> const { 192.168.0.11, 192.168.0.12 }
qpass = "pass in quick"
rdr-anchor "relayd/*"
pass all
主に L3 ロードバランサーとして利用されている relayd(8) を、ここでは health check 機能と failover 機能を利用するために使います。
relayd が想定している用途がロードバランスなので、ちょっと変な使い方として、pgpool の Active ノード一台を正常系のロードバランスプールに追加し、StandBy ノードを sorry host プール(正常系がすべて落ちていた場合に向けられる)に入れます。
こうすることで、Active pgpool が落ちたら StandBy 側にパケットが流れるようになります。ただ、このままでは一度落ちた Active pgpool が立ち上がるとすぐにそちらが Active に戻ってしまうので、health check スクリプトに一工夫加えます(往々にして接続を再度受け付ける前に pgpool が把握している PostgreSQL ノードの状態を確認し、必要なら pcp_{attach,detach}_node するため)。
/etc/rc.conf に以下を追加します。relayd_flags は、ログが肥大化しますが、入れておいた方が状況が把握しやすいのでおすすめです。
relayd_enable="YES" relayd_flags="-v"
/usr/local/etc/relayd.conf として以下を設置。ここの interval 5 が生死チェックの間隔で、単位は秒です。値を小さくすれば pgpool の障害検出までの平均時間が短くなりますが、その分サーバの負荷も上がります。
db1 = "192.168.0.11"
db2 = "192.168.0.12"
public_ip = "192.168.0.99"
interval 5
prefork 1
log all
table <active> { $db1 }
table <standby> { $db2 }
redirect "db" {
listen on $public_ip port 5432
forward to <active> port 9999 timeout 300 \
check script "/usr/bin/check_pgpool.sh"
forward to <standby> port 9999 timeout 300 \
check script "/usr/bin/check_pgpool.sh"
}
ここで使っている check_pgpool.sh は以下のようなもの。check_pgsql は自作してもいいくらいのシンプルなものですが、手を抜くために ports/net-mgmt/nagios-plugins に含まれるものを使っています。頻繁に走るので、スクリプト言語で接続させるよりは C で書いたプログラムの方がいいでしょう。
#!/bin/sh [ -r /usr/local/pgsql/.pgpool-maint-$1 ] && exit 0 /usr/local/libexec/nagios/check_pgsql -t 5 -H $1 \ -P 9999 -d test -l test >/dev/null [ $? -ne 0 ] && exit 0 exit 1
ここではデータベースユーザー test で、test というデータベースにアクセスできるかどうかをチェックしています。データベース側に許可設定をしておく必要があります。
また、check_pgsql コマンドは正常時に exit 0 し、異常時にそれ以外の値を返す、といういたって普通の挙動をしますが、relayd の check script ディレクティブが想定しているのが正常時に正の値、異常時に 0 を返す、という振る舞いなので、それに合わせて最後に返り値を調整しています。
サーバ全体が綺麗に落ちてくれた場合は、carp の advbase がデフォルトの 1 のままの場合は、2 秒以内にBACKUP ノードが MASTER になります。carp VIP 宛に送られた tcp/5432 パケットは、その carp MASTER ノード上の relayd に渡されます。
クライアントからの接続が維持されるかどうかは、その時どの pgpool が Active になっていたか、に依存します。落ちた方の pgpool だった場合はおそらく切断されますが、生きている方なら接続は維持されるはずです。
サーバに PostgreSQL ノードもいる場合は、生きている pgpool-II から切り離された状態になるので、復旧後にノードのリカバリーが必要です。
ここが現状の問題点です。carp MASTER ノードとなっているサーバ上の relayd が落ちた場合、carp VIP へのパケットを受け取るプロセスがいなくなってしまい、クライアントからは結果としてアクセスができなくなります。
別途 relayd のプロセス生死を監視して、必要に応じて relayd を手動で復旧する、となってしまいます。(ただ、今のところ relayd が落ちた、という経験はありませんが)
relayd が Active として扱っている pgpool が落ちた場合、relayd のチェックが今回は 5 秒ごとなので、その周期で検出次第 StandBy 側の pgpool にパケットが向けられます。
この場合、クライアントから見ると処理する pgpool が変わるので、一旦切断されることになると思われます。
これは通常の pgpool の利用パターンと同様です。ここでは replication_mode を想定しているので、片方が落ちたら縮退運転に入ってサービスを続けます。クライアントからの接続は維持されます。