tkuchikiの日記

新ブログ https://blog.tkuchiki.net

HAPrxoy 1.6.0 で導入された DNS の動的名前解決の検証結果

追記(2016-07-13 16:40)

Changelog に書いてあるとおり(BUG/MEDIUM: dns: unbreak DNS resolver after header fix)、
動的名前解決ができないバグが修正されました。

詳細は省きますが、本記事と同様の検証を行い、動的名前解決ができることを確認しました。

追記(2016-06-24 12:34)

HAProxy 1.6.5 は、DNS の動的名前解決が動作しないようです。
ご注意ください。

検証環境は以下のとおりです。

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 の通信をキャプチャすると確認することができます。

追記(2015-10-30 14:57)

http://b.hatena.ne.jp/mapk0y/20151030#bookmark-270133853
ブックマークにコメントを頂いたとおり、
A レコードを動的に変えるものとして RDS を使いましたが、
例ですのでこの構成に意味はありません。
RDS の前段に HAProxy を置く場合は、
Slave(Read Replica) を複数台立てて参照を分散する、
といった使い方になると思います。
強引にメリットを上げるとすれば、
最初は1台でスタートして、
あとから複数台の DB を立てて参照分散したいとしたときに、
アプリの修正が必要ないことでしょうか。
コメントありがとうございました!

動的名前解決しないとどうなるか

動的名前解決機能を説明する前に、
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 から操作する場合は、
チェックボックスにチェックを入れて再起動します。

f:id:tkuchiki:20151030115951p:plain

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 を省略するのは甘え