読者です 読者をやめる 読者になる 読者になる

tail my trail

作るのも使うのも、結局は、人なのだ

HAProxy v1.6を使って複数のRDS Read Replilcaに分散させる

RDS ReadReplicaを立てて、参照クエリを逃がすことを考える。 可用性や拡張性を考えてReadReplicaは複数台構成とした場合、RDSの仕様を考慮して設計しておく必要がある。ポイントは以下。

  • Read Replicaは個々にEndpoint (DNS名) を持つ。
  • 複数Read Replicaに対してバランシングする仕組みは提供していない。
  • ELBは RDS (Read Replica含め) には使えない。ELBにぶら下げられるのはEC2のみ。
  • Read Replica各ノードの死活監視、障害時の切り離し/切り戻しを考慮する必要がある。

ということで、Read Replicaのバランシングを行うなら、自分で仕組みを用意する必要がある。 実現方法はいくつか選択肢があるが、今回はL7のバランサーとして定評のあるHAProxyを使ってみる。

Architecture with HAProxy

ざっくりと、こんな感じ。

  • 各WebサーバでHAProxyを稼働させる
    • localhost で Listen する
    • HAProxyのBackend serverはRead Replicaに向ける
    • backupにMaster RDSを指定し、Read Replica全滅時はMasterに向ける
  • 使い方
    • 参照: localhost:3306 に接続。HAProxy 経由で Read Replica に接続する。
    • 更新: RDS Master の Endpoint

f:id:uorat:20160529171236p:plain

HAProxyが複数Slaveへの振り分けと監視、Read Replica全滅時はMasterに向けるなど耐障害対策を担ってくれるので、アプリケーション側からは参照と更新のEndpointそれぞれ1つずつ記憶しておけば良い。

インストール方法

なお、HAProxy v1.5まではプロセス起動時にEndpointのA recordをキャッシュし、プロセス再起動するまで更新してくれないため、EndpointがDNS名であるAWS系のサービスとは相性が良くない。 (後述)

例えば、RDSのノード障害 ( →Failover) やインスタンスタイプ変更などによりEndpointのIPが変わった場合もHAProxyは古いIPを参照し続け、Backend serverのステータスがいつまで経ってもDOWNのまま変わらない。

2015年10月にリリースされたHAProxy v1.6から resolvers オプションが追加され、A record の変更を追従してくれるようになっているので、必ずv1.6以上を採用しよう。

What’s new in HAProxy 1.6 | HAProxy Technologies – Aloha Load Balancer

Server IP resolution using DNS at runtime In 1.5 and before, HAProxy performed DNS resolution when parsing configuration, in a synchronous mode and using the glibc (hence /etc/resolv.conf file). Now, HAProxy can perform DNS resolution at runtime, in an asynchronous way and update server IP on the fly. This is very convenient in environment like Docker or Amazon Web Service where server IPs can be changed at any time. Configuration example applied to docker. A dnsmasq is used as an interface between /etc/hosts file (where docker stores server IPs) and HAProxy:

なお、現時点でRPMは見当たらなかったので、要ソースコンパイル。 上述のとおり、各Web/APサーバにインストールするので、RPM化しておく。

RPMBuild HAProxy 1.6

検証実施したタイミングでは 1.6.4 が最新版だったので、これを利用した。 http://www.haproxy.org/download/1.6/src/haproxy-1.6.4.tar.gz

SPECファイルの詳細は割愛するが、CentOS 6 標準のYumで入るHAProxyの構成になるべく似せるようにした。 initスクリプトやlogrotateのサンプルファイル、haproxy.confのサンプル設置など。詳しくは文末のGist参照。

SPECさえ用意してしまえば、あとは rpmbuild するだけ。

$ su - rpmbuilder
$ wget http://www.haproxy.org/download/1.6/src/haproxy-1.6.4.tar.gz
$ tar xzvf haproxy-1.6.4.tar.gz

$ vim ~/rpmbuild/SPECS/haproxy.spec

(spec作成 & 必要なファイルの配置)

$ tar zcf ~/rpmbuild/SOURCES/haproxy-1.6.4.tar.gz /haproxy-1.6.4

$ mv haproxy-1.6.4.tar.gz ~/rpmbuild/SOURCES/

$ cd ~/rpmbuild
$ rpmbuild -ba SPECS/haproxy.spec

$ ls -l ~/rpmbuild/RPMS/x86_64/

-rw-rw-r-- 1 rpmbuilder rpmbuilder  746256  4月 26 13:19 2016 haproxy-1.6.4-1.x86_64.rpm
-rw-rw-r-- 1 rpmbuilder rpmbuilder    6916  4月 26 13:19 2016 haproxy-debuginfo-1.6.4-1.x86_64.rpm

出来たRPMをSelf Repositoryに登録してもよいし、直接 yum install haproxy-1.6.4-1.x86_64.rpm しても良い。

HAProxy の設定

HAProxyの設定ファイルはこんな感じ。ポイントは以下。

  • v1.6の resolvers オプションを使ってVPCのnameserverを参照するようにし、A Recordの変更に追従させる。
  • option mysql-check を指定して、 MySQL 層での Healthcheckを行う。
  • backup に RDS Masterのendpointを指定し、Read Replica全滅時はMasterに向けるようにする

rsyslogの設定

HAProxyはsyslogでログを飛ばす。上の設定ファイルでは local2 に飛ばしているので、syslog / rsyslog の設定もあわせて変更する必要がある。 local2 を他の用途で使っている場合は、よしなに変更して下さい。以下、rsyslogの設定例。

$ sudo cp -p /etc/rsyslog.conf{,.bk}
$ sudo vim /etc/rsyslog.conf

$ diff -u /etc/rsyslog.conf{.bk,}
--- /etc/rsyslog.conf.bk 2014-12-10 19:05:22.000000000 +0900
+++ /etc/rsyslog.conf    2016-04-28 13:30:55.150168043 +0900
@@ -10,8 +10,8 @@
 #$ModLoad immark  # provides --MARK-- message capability

 # Provides UDP syslog reception
-#$ModLoad imudp
-#$UDPServerRun 514
+$ModLoad imudp
+$UDPServerRun 514

 # Provides TCP syslog reception
 #$ModLoad imtcp
@@ -59,6 +59,9 @@

 # Save boot messages also to boot.log
 local7.*                                                /var/log/boot.log
+local2.*                                                -/var/log/haproxy.log
+#local2.warn                                             -/var/log/haproxy.log


 # ### begin forwarding rule ###

$ sudo /etc/init.d/rsyslog restart

監視用ユーザの追加

HAProxyの設定で、option mysql-check を指定しているが、これは MySQL 層での Healthcheckを行うためのもの。 このhealthcheckで使用するユーザをMySQLに用意してあげる。接続さえ出来れば良いので、最低限の権限で良い。

$ mysql -u rdsuser -h master.XXXXXXXXXXXX.ap-northeast-1.rds.amazonaws.com -p
...

mysql> grant usage on *.* to 'haproxy'@'%';
mysql> flush privileges;

ここまで来たら、HAProxyを起動する。

$ sudo /etc/init.d/haproxy start

socat インストール

あと、HAProxyのステータス確認用のsocatをインストールしておく。 設定ファイルで指定したUNIX fileに対して標準入力でコマンド投げることでステータスの確認が可能。

$ yum install socat --enablrepo=epel

show stat コマンドでステータス確認できる。

$ echo "show stat" | sudo socat stdio /var/lib/haproxy/stats
# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,
mysql,FRONTEND,,,0,0,512,0,0,0,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,0,,,,,,,,,,,0,0,0,,,0,0,0,0,,,,,,,,
mysql,read01,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,0,0,4512,0,,1,2,1,,0,,2,0,,0,L7OK,0,4,,,,,,,0,,,,0,0,,,,,-1,5.6.19,,0,0,0,0,
mysql,read02,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,0,0,4512,0,,1,2,2,,0,,2,0,,0,L7OK,0,1,,,,,,,0,,,,0,0,,,,,-1,5.6.19,,0,0,0,0,
mysql,master,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,0,1,0,0,4512,0,,1,2,3,,0,,2,0,,0,L7OK,0,1,,,,,,,0,,,,0,0,,,,,-1,5.6.19-log,,0,0,0,0,
mysql,BACKEND,0,0,0,0,52,0,0,0,0,0,,0,0,0,0,UP,2,2,1,,0,4512,0,,1,2,0,,0,,1,0,,0,,,,,,,,,,,,,,0,0,0,0,0,0,-1,,,0,0,0,0,

HAProxy経由でRead Replicaに接続してみる

今回の例ではRead Replicaは2台用意しており、round-robinされます。

$ mysql -u 127.0.0.1 -P 3306 -u recx -e "show variables like '%hostname%';"
+---------------+---------------+
| Variable_name | Value         |
+---------------+---------------+
| hostname      | ip-10-7-0-205 |
+---------------+---------------+

$ mysql -u 127.0.0.1 -P 3306 -u recx -e "show variables like '%hostname%';"
+---------------+--------------+
| Variable_name | Value        |
+---------------+--------------+
| hostname      | ip-10-7-1-66 |
+---------------+--------------

$ mysql -u 127.0.0.1 -P 3306 -u recx -e "show variables like '%hostname%';"
+---------------+---------------+
| Variable_name | Value         |
+---------------+---------------+
| hostname      | ip-10-7-0-205 |
+---------------+---------------+

Read Replicaなので当然read-onlyが有効化されており、更新操作は出来ません。

$ mysql -u 127.0.0.1 -P 3306 -u recx -e "create database cannot_write;"
ERROR 1290 (HY000) at line 1: The MySQL server is running with the --read-only option so it cannot execute this statement

localhost:3306 に繋げば自動的にReadReplicaに刺さることになるので、負荷分散以外にも日頃の運用で重宝する。

v1.6 のresolvers オプションの検証

期待通り A Record の変更に追従してくれるのか確認しておく。

まずは、HAproxyのバージョンが1.6であることを確認。

$ haproxy -v
HA-Proxy version 1.6.4 2016/03/13
Copyright 2000-2016 Willy Tarreau <willy@haproxy.org>

続いてRDS の内部IP を変更する。 RDSのNodeのIP変更が発生する操作を実施すれば良い。今回はRead Replica (read01) の InstanceType の変更を実施してみる。 InstanceType変更操作を実施すると、以下のようにHAProxy上でDOWNステータスに変わったことを確認。

$ echo "show stat" | sudo socat stdio /var/lib/haproxy/stats

# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,
mysql,FRONTEND,,,0,1,512,105,95066,538297,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,2,,,,,,,,,,,0,0,0,,,0,0,0,0,,,,,,,,
mysql,read01,0,0,0,1,,16,3228,4932,,0,,1,0,3,0,DOWN,1,1,0,5,1,398,398,,1,2,1,,13,,2,0,,1,L4TOUT,,2001,,,,,,,0,,,,0,0,,,,,398,,,0,0,0,1,
mysql,read02,0,0,0,1,,92,91838,533365,,0,,0,0,0,0,UP,1,1,0,0,0,501,0,,1,2,2,,92,,2,0,,1,L7OK,0,4,,,,,,,0,,,,0,0,,,,,6,5.6.19-log,,0,1,0,5,
mysql,master,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,0,1,0,0,501,0,,1,2,3,,0,,2,0,,0,L7OK,0,0,,,,,,,0,,,,0,0,,,,,-1,5.6.19-log,,0,0,0,0,
mysql,BACKEND,0,0,0,1,52,105,95066,538297,0,0,,1,0,3,0,UP,1,1,1,,0,501,0,,1,2,0,,105,,1,0,,2,,,,,,,,,,,,,,0,0,0,0,0,0,6,,,0,1,0,6,

しばらくするとRead ReplicaのInstanceType変更が終わり、それに伴いHAProxy上のread01のステータスもOKに戻ったことが確認できた。

$ echo "show stat" |sudo socat stdio /var/lib/haproxy/stats
# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,
mysql,FRONTEND,,,0,0,512,0,0,0,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,0,,,,,,,,,,,0,0,0,,,0,0,0,0,,,,,,,,
mysql,read01,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,8,1,447,191,,1,2,1,,0,,2,0,,0,L7OK,0,4,,,,,,,0,,,,0,0,,,,,-1,5.6.19-log,,0,0,0,0,
mysql,read02,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,0,0,876,0,,1,2,2,,0,,2,0,,0,L7OK,0,0,,,,,,,0,,,,0,0,,,,,-1,5.6.19-log,,0,0,0,0,
mysql,master,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,0,1,0,0,876,0,,1,2,3,,0,,2,0,,0,L7OK,0,4,,,,,,,0,,,,0,0,,,,,-1,5.6.19-log,,0,0,0,0,
mysql,BACKEND,0,0,0,0,52,0,0,0,0,0,,0,0,0,0,UP,2,2,1,,0,876,0,,1,2,0,,0,,1,0,,0,,,,,,,,,,,,,,0,0,0,0,0,0,-1,,,0,0,0,0,

HAProxyのログには、IP変更の検知と、監視成功による切り戻しのログが出力される。

Apr 26 13:51:21 localhost haproxy[22217]: Server mysql/read01 is DOWN, reason: Layer4 connection problem, info: "Connection refused", check duration: 2ms. 1 active and 1 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue.
Apr 26 13:53:42 localhost haproxy[22217]: mysql/read01 changed its IP from 172.31.38.198 to 172.31.35.59 by awsvpc/vpc.
Apr 26 13:54:32 localhost haproxy[22217]: Server mysql/read01 is UP, reason: Layer7 check passed, code: 0, info: "5.6.19", check duration: 4ms. 2 active and 1 backup servers online. 0 sessions requeued, 0 total in queue.

終わりに

MariaDBJDBC Driverを使っても同要件を実現可能だが、実装コストが少なく言語問わず実現できるのでオススメ。 HAProxy自体は汎用的なL7 Balancerなので、何か課題に直面した時の選択肢として抑えておくと良いと思う。

Read Replicaのバランシングに関しては、Read Replica自体や、ELBがEC2以外のサービスに対応してくれる、みたいなリリースが出てくれるといいんだけどなぁ。

Amazon Web Services クラウドデザインパターン設計ガイド 改訂版

Amazon Web Services クラウドデザインパターン設計ガイド 改訂版

Amazon Web Services実践入門 (WEB+DB PRESS plus)

Amazon Web Services実践入門 (WEB+DB PRESS plus)

HAProxyのSPEC

Tarball は公式の HAProxy-1.6.4 から取得して下さい。 SPECにはTarballに含まれていないファイルのインストールも含まれているので、よしなにTarball固め直すなりSPEC編集して使って下さい。

参考