検証環境は以下のとおりです。
http://www.haproxy.org の Quick News に、
dynamic DNS-based server address resolution
と書いてあるとおり、
HAProxy 1.6.0 から DNS の動的名前解決に対応しました。
HAProxy 1.6.0 より前のバージョンではどうだったかというと、
起動時に DNS を参照して、それ以降 DNS を参照することはありません。
この挙動については、ソースコードを見るか、
port 53 の通信をキャプチャすると確認することができます。
動的名前解決しないとどうなるか
動的名前解決機能を説明する前に、
HAProxy 1.5.2 を使い、
動的名前解決しない場合の挙動について説明します。
RDS の準備
サーバがダウンしたらいい感じに A レコードを更新してくれるものとして、
Amazon RDS があります。
Amazon RDS には reboot with failover という、
強制的に failover させる機能がありますので、これを検証に使います。
failover させるために、Multi-AZ を有効にしてインスタンスを作成してください。
MySQL の準備
HAProxy には mysql の health check を行う機能があります。
監視用のユーザが必要ですので作成します。
haproxy ユーザを作るために、以下の SQL を実行します。
grant usage on *.* to 'haproxy'@'%';
ngrep の準備
port 53 への通信をキャプチャするために、
ngrep
をインストールします。
キャプチャできれば良いので、tcpdump
でも良いです。
yum install -y epel-release yum install -y ngrep
HAProxy の準備
amzn-main に HAProxy 1.5.2 があったので、
yum install -y haproxy
してインストールします。
設定ファイルは以下のとおりです。
# /etc/haproxy/haproxy.cfg listen mysql-slave bind 127.0.0.1:3307 mode tcp option mysql-check user haproxy balance roundrobin server master test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:3306 check
検証
準備が整ったので、HAProxy 1.5.2 では動的名前解決しないことを確認します。
RDS が failover する前の IP アドレスを調べます。
$ host test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com test-db01.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com has address 10.1.0.100
ngrep
を実行して、port 53 への通信がないかを確認します。
$ ngrep -d any -W byline port 53 -q
上記のコマンドを実行した状態で、
service haproxy restart
を実行して再起動すると、
$ ngrep -d any -W byline port 53 -q interface: any filter: ( port 53 ) and (ip or ip6) U 10.1.0.2:53 -> 10.1.0.10:42761 .............test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com................. .." U 10.1.0.10:49225 -> 10.1.0.2:53 .............test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com..... U 10.1.0.2:53 -> 10.1.0.10:49225 .............test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com................. .." U 10.1.0.10:34814 -> 10.1.0.2:53 OU...........test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com..... U 10.1.0.2:53 -> 10.1.0.10:34814 OU...........test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com................. .."
のように、DNS を参照していることがわかります。
前述のとおり、HAProxy 1.5.2 は動的名前解決をしないので、
これ以降は ngrep
の様子を気にする必要はありません。
それでは、RDS を failover させます。
AWS CLI では、以下のようにコマンドを実行し、
$ aws rds reboot-db-instance --db-instance-identifier test-db --force-failover
Management Console から操作する場合は、
チェックボックスにチェックを入れて再起動します。
Multi-AZ instance failover completed
というログが出たら failover しています。
$ host test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com test-db01.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com has address 10.2.0.200
IP アドレスが変わっていることを確認できます。
この状態で、HAProxy を通して RDS に接続します。
$ mysqladmin ping -u haproxy -h 127.0.0.1 -P 3307 mysqladmin: connect to server at '127.0.0.1' failed error: 'Lost connection to MySQL server at 'reading initial communication packet', system error: 0'
接続できないことが確認できます。
netstat でも確認してみると、
$ netstat -anp | grep haproxy | grep :3306 tcp 0 1 10.1.0.10:56199 10.1.0.100:3306 SYN_SENT 1553/haproxy
failover 前の IP アドレスに接続しようとしています。
以上のことから、動的名前解決しないと、
A レコードなどを変更しても接続先を変えることができないことがわかります。
動的名前解決を試す
動的名前解決できないとどうなるかがわかったところで、
HAProxy 1.6.1 を使い、
どのように挙動が変わったか確認します。
amzn-main には HAProxy 1.6.1(1.6.0 も)ないので、
適宜ビルドしてください。
HAProxy のログを出力するために、
rsyslog
を設定します。
# /etc/rsyslog.conf + $ModLoad imudp + $UDPServerRun 514
# /etc/rsyslog.d/haproxy.conf + $ModLoad imudp + $UDPServerRun 514 + $template Haproxy,"%msg%\n" + local1.* -/var/log/haproxy.log;Haproxy
service rsyslog restart
で再起動します。
動的名前解決用の設定をします。
# /etc/haproxy/haproxy.cfg resolvers mydns nameserver dns1 10.2.0.2:53 nameserver dns2 8.8.8.8:53 resolve_retries 3 timeout retry 1s hold valid 60s listen mysql-slave bind 127.0.0.1:3307 mode tcp log 127.0.0.1 local1 debug option mysql-check user haproxy balance roundrobin server master test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:3306 check resolvers mydns
resolve_retries 3
, timeout retry 1s
は見たままですが、
リトライする回数とタイムアウトの秒数です。
hold valid 60s
は、名前解決が成功したら、指定秒数名前解決しない設定です。
このオプションは、health check が fail したときは使われません。
nameserver
は DNS サーバを複数設定できます。
http://cbonte.github.io/haproxy-dconv/configuration-1.6.html#5.3.2-nameserver を見ると、
port を省略しても良いという記述はないので、
省略することはできません。
私は、port を省略していたため動的名前解決ができなくて、
2時間くらい無駄にしました...。
設定ができたら、先述の ngrep
を実行しておきます。
service haproxy restart
して準備完了です。
この時点で、port 53 への通信が定期的にあるので、
動的名前解決ができそうな予感がします。
RDS の IP アドレスを確認して、RDS を failover させます。
$ host test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com test-db01.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com has address 10.2.0.200
ngrep
の様子を見ているとhold
の設定とは関係なく、
health check の間隔くらいで名前解決している様子を確認できます。
RDS の A レコードが書き換わり、
HAProxy の health check が成功すると、
mysql-slave/master changed its IP from 10.2.0.200 to 10.1.0.100 by mydns/dns1. Server mysql-slave/master is UP, reason: Layer7 check passed, code: 0, info: "5.6.23", check duration: 1ms. 1 active and 0 backup servers online. 0 sessions requeued, 0 total in queue.
と、接続先が変わったことがログに出力されます。
IP アドレスを確認して、mysqladmin ping
、netstat
でも確認すると、
$ host test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com test-db01.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com has address 10.1.0.100 $ mysqladmin ping -u haproxy -h 127.0.0.1 -P 3307 mysqld is alive $ netstat -anp | grep 3306 tcp 0 0 10.1.0.10:57427 10.1.0.100:3306 TIME_WAIT -
failover 後のインスタンスに接続していることがわかります。
まとめ
HAProxy 1.6.0 から数行設定を追加するだけで動的名前解決できることを示しました。
メンテナンスで DNS 切り替えたら繋がらなくなった...、
みたいなことがなくなるので便利そうですね。
port 問題で消耗したのでソースコードはあまり読めていないので、
詳細な挙動が知りたい方は、ご一読いただくと良いと思います。
※ 検証するときは、以下のことに注意してください。
- ドキュメントをよく読む
- port を省略してはいけない、port を省略するのは甘え