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

tkuchikiの日記

Linux やプログラミングについて書きます。

HAProxy の external-check で Read Replica から Master に昇格した Amazon Aurora に接続できないようにする

追記 2016/07/22 19:06

error: 'Lost connection to MySQL server at 'reading initial communication packet', system error: 0' というエラーがでて接続できなくなる現象が発生するようなので、改良が必要なようです。
エラーの検証をしようとしたら再現できなかったので、とりあえず気にしなくても良さそうです。

追記 2016/07/19 18:48

ブックマークにもコメントいただきましたが、
SHOW GLOBAL VARIABLES LIKE 'innodb_read_only'; を実行して、
OFF なら master、ON なら Read Replica という判断方法もあります。
Amazon Aurora を使用する際のベストプラクティス に書いてあります。
なぜ、これを使わなかったかというと、検証したときは知らなかったからです...

検証した環境は以下の通りです。

$ haproxy -v
HA-Proxy version 1.6.6 2016/06/26
Copyright 2000-2016 Willy Tarreau <willy@haproxy.org>

$  cat /etc/system-release
Amazon Linux AMI release 2016.03

RDS for MySQL は Multi-AZ 構成にした場合、スタンバイ用のインスタンスにアクセスできません。
Amazon Aurora(以降、Aurora) はスタンバイインスタンスが Read Replica となっているため参照することができます。
Aurora を HAProxy で参照分散するとき、Read Replica のいずれかが Master に昇格するため、
Read Replica のつもりで参照していたインスタンスが Master になっている可能性があります。
Slave 参照しない Read Replica を用意し、Master に昇格させるインスタンスの優先順位を調整することで、 HAProxy からは Master に参照することがないように調整することも可能ですが、
一台は余剰なインスタンスとなってしまいますし、3台構成の場合に Master と Read Replica 1台が同時にダウンした場合どうするかなど、 考慮しなくてはならないことがたくさんあります。
もちろん、Master を参照しても負荷的に問題なければ良いですが、Slave 参照する場合は Master への負荷を軽減したい場合でしょうから問題になるケースが多いのではないでしょうか。
以上のことから、HAProxy の mysql-check では意図しない挙動をしてしまいます。 そこで、HAProxy の external-check という、任意のコマンドでヘルスチェックを行う機能を利用できないか検証しました。

haproxy.conf

使用した設定ファイルです。

global
   external-check

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

defaults
    log             127.0.0.1 local1 debug
    retries 2
    timeout connect 3000
    timeout server 5000
    timeout client 5000

frontend f_mysql_slave
    bind            127.0.0.1:3307
    default_backend b_mysql_slave

backend b_mysql_slave
    log             127.0.0.1 local1 debug
    mode tcp
    option external-check
    external-check path "usr/bin:/bin:/usr/local/bin"
    external-check command /usr/local/bin/check_mysql_slave.sh
    balance         roundrobin
    server          slave test-db01.xxxxxx.ap-northeast-1.rds.amazonaws.com:3306 check resolvers mydns
    server          master test-db.cluster-xxxxxx.ap-northeast-1.rds.amazonaws.com:3306 check resolvers mydns backup

external-check を使うためには、

global
   external-check

backend b_mysql_slave
        option external-check
        external-check path "usr/bin:/bin:/usr/local/bin"
        external-check command /usr/local/bin/check_mysql_slave.sh

が必要です。

server master ... backup は Read Replica が全部ダウンしたときのみ Master を参照するための設定です。

external-check command

HAProxy がコマンドを実行する際に環境変数を設定するようになっています。
環境変数は以下のとおりです。

  • HAPROXY_PROXY_ADDR
    • backend の bind IP
    • backend に定義した bind は無視されるので実質使用しない(はず)
  • HAPROXY_PROXY_ID
    • backend ID.
  • HAPROXY_PROXY_NAME
    • backend 名
  • HAPROXY_PROXY_PORT
    • backend の bind port
    • backend に定義した bind は無視されるので実質使用しない(はず)
  • HAPROXY_SERVER_ADDR
    • server の IP
  • HAPROXY_SERVER_CURCONN
    • server のカレントコネクション数
  • HAPROXY_SERVER_ID
    • server の ID
  • HAPROXY_SERVER_MAXCONN
    • server の 最大コネクション数
  • HAPROXY_SERVER_NAME
    • server の設定名
  • HAPROXY_SERVER_PORT
    • server で設定したホストの port
  • PATH
    • external-check path で設定した PATH

詳細は external-check command を参照してください。 環境変数を上から順に出力した例は以下のとおりです。

 
2
b_mysql_slave

10.206.1.20
0
2
0
slave
3306
usr/bin:/bin:/usr/local/bin

check_mysql_slave.sh

ヘルスチェックを行うスクリプトです。

#!/bin/bash

BACKUP="master"
MASTER_IP=$(dig +short test-db.cluster-cnttkfxtxwao.ap-northeast-1.rds.amazonaws.com | tail -n 1)
[ "${MASTER_IP}" = "" ] && exit 1
[ "${HAPROXY_SERVER_NAME}" = "${BACKUP}" ] && exit 0
[ "${HAPROXY_SERVER_ADDR}" != "${MASTER_IP}" ]
  • ${MASTER_IP} が取得できなければ fail
  • ${HAPROXY_SERVER_NAME} = ${BACKUP} の場合、backup 以外のインスタンスはダウンしているので Master を参照するようにする
  • ${HAPROXY_SERVER_ADDR} != ${MASTER_IP} の場合、Read Replica を参照しているのでヘルスチェックを success にする
追記: 2016-07-25 16:05

SHOW GLOBAL VARIABLES LIKE 'innodb_read_only'; を使う場合は以下のようになります。

#!/bin/bash

BACKUP="master"
READ_ONLY=$(mysql -u haproxy -h ${HAPROXY_SERVER_ADDR} -P ${HAPROXY_SERVER_PORT} -BN -e "SHOW GLOBAL VARIABLES LIKE 'innodb_read_only';" --connect-timeout=5 | awk '{print $2}')

[ "${HAPROXY_SERVER_NAME}" = "${BACKUP}" ] && exit 0
[ "${READ_ONLY}" = "ON" ]

DNSを引く場合と違って、BACKUP 以外は環境ごとに変わる要素がなくなったので使いやすくなっています。

検証結果

Master と Read Replica それぞれ 1 台ずつで 2 種類の検証を行いました。

Read Replica が Master に昇格した場合

backup の設定は削除した状態です。
server slave = Master なのでヘルスチェックに失敗しています。
Read Replica が 1 台なので no server available になっていますが、Read Replica が 2台以上ある場合は、
Read Replica には接続できて、Master には接続できない状態になります。

Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1032ms, status: 2/3 UP.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1032ms, status: 2/3 UP.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1007ms, status: 1/3 UP.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1007ms, status: 1/3 UP.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1016ms, status: 0/2 DOWN.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1016ms, status: 0/2 DOWN.
Server b_mysql_slave/slave is DOWN. 0 active and 0 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue.
Server b_mysql_slave/slave is DOWN. 0 active and 0 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue.
backend b_mysql_slave has no server available!
backend b_mysql_slave has no server available!

backup を設定して、Read Replica が Master に昇格した場合

backup 以外の server のヘルスチェックに失敗して、backup のみヘルスチェックに成功した場合のログです。
Master 以外ダウンしているときは Master に接続できるようになっていることがわかります。

Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1014ms, status: 2/3 UP.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1014ms, status: 2/3 UP.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1007ms, status: 1/3 UP.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1007ms, status: 1/3 UP.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1014ms, status: 0/2 DOWN.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1014ms, status: 0/2 DOWN.
Server b_mysql_slave/slave is DOWN. 0 active and 1 backup servers left. Running on backup. 0 sessions active, 0 requeued, 0 remaining in queue.
Server b_mysql_slave/slave is DOWN. 0 active and 1 backup servers left. Running on backup. 0 sessions active, 0 requeued, 0 remaining in queue.
b_mysql_slave/master changed its IP from 10.2.0.10 to 10.2.1.20 by mydns/dns1.
b_mysql_slave/master changed its IP from 10.2.0.10 to 10.2.1.20 by mydns/dns1.
Health check for server b_mysql_slave/slave succeeded, reason: External check passed, code: 0, check duration: 1015ms, status: 1/2 DOWN.
Health check for server b_mysql_slave/slave succeeded, reason: External check passed, code: 0, check duration: 1015ms, status: 1/2 DOWN.
Health check for server b_mysql_slave/slave succeeded, reason: External check passed, code: 0, check duration: 1007ms, status: 3/3 UP.
Health check for server b_mysql_slave/slave succeeded, reason: External check passed, code: 0, check duration: 1007ms, status: 3/3 UP.
Server b_mysql_slave/slave is UP. 1 active and 1 backup servers online. 0 sessions requeued, 0 total in queue.
Server b_mysql_slave/slave is UP. 1 active and 1 backup servers online. 0 sessions requeued, 0 total in queue.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1014ms, status: 2/3 UP.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1014ms, status: 2/3 UP.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1007ms, status: 1/3 UP.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1007ms, status: 1/3 UP.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1016ms, status: 0/2 DOWN.
Health check for server b_mysql_slave/slave failed, reason: External check error, code: 1, check duration: 1016ms, status: 0/2 DOWN.
Server b_mysql_slave/slave is DOWN. 0 active and 1 backup servers left. Running on backup. 0 sessions active, 0 requeued, 0 remaining in queue.
Server b_mysql_slave/slave is DOWN. 0 active and 1 backup servers left. Running on backup. 0 sessions active, 0 requeued, 0 remaining in queue.

まとめ

HAProxy の external-check で、Aurora の Read Replica が Master に昇格したときに参照させないようにすることができました。
また、Read Replica が全台ダウンしたときだけ Master を参照できることも確認できました。
独自のヘルスチェックが行えるので、他にも応用ができそうですね。