ども、cloudpackかっぱ (@inokara) です。

追記(2014/12/12)

HAProxy のドキュメントを眺めていたら mysql-check について以下のように書かれていました。

If you specify a username, the check consists of sending two MySQL packet,
one Client Authentication packet, and one QUIT packet, to correctly close
MySQL session. We then parse the MySQL Handshake Initialisation packet and/or
Error packet. It is a basic but useful test which does not produce error nor
aborted connect on the server.

MySQL の死活監視について RDS 側で max_connect_errors999999999 に設定して接続エラーを回避するのではなくてチェック用のユーザーを作成する方が良いようです。

GRANT USAGE ON *.* TO monitor@'%';

上記のように最低限の権限で監視用のユーザーを作成し、haproxy.cfg では以下のように設定します。

mode    tcp
option  mysql-check user monitor
balance leastconn

mysql-check オプションに user ${ユーザー名} を付加します。

はじめに

Amazon RDS のリードレプリカが複数台存在する場合に HAProxy を RDS とアプリケーションの間においてアクセスを分散させる…というのは既に以下の記事で紹介されています。

HAProxy を利用することで RDS のヘルスチェックは HAProxy に任せることが出来るのでカジュアルに導入出来ると思いますが,,,では、その HAProxy をどうやって冗長化しましょうか…という疑問に基づいた答えを探したのが今回の記事です。

参考

概要

環境

  • アプリケーション・サーバー ✕ 2
  • RDS(Master + Read Replica x 2)
  • データベースの読みと書きの区別はアプリケーション側で実装する

やりたいこと

  • Read Replica へのアクセスは一意の名前でアクセス出来るようにする(Route53 の Private DNS での出来そう)
  • Read Replica へのアクセスは HAProxy で分散させる
  • 一台の HAProxy が死んでも RDS への接続が保証される

ツール類の整理

ツール 用途(実装)
HAProxy Read Replica へのアクセスを分散
consul HAProxy の死活を監視してクラスタを構成、管理する
consul-temlate Consul に登録された情報を利用して /etc/hosts ファイルを生成する

構成図

RDS のリードレプリカへのアクセスを HAProxy で分散しつつ consul と consul-template で冗長化: 構成図

実装(という程ではないけど)

Read Replica の分散

HAProxy の設定は以下のように。

global
  log 127.0.0.1 local0
  log 127.0.0.1 local1 notice
  maxconn 4096
  daemon

defaults
  log   global
  option        dontlognull
  retries       3
  maxconn       2000
  timeout connect 5000ms
  timeout client 50000ms
  timeout server 50000ms

listen mysql
  bind    0.0.0.0:3306
  mode    tcp
  option  mysql-check user monitor
  balance leastconn
  server  read01 hogehoge-replica.xxxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:3306 check port 3306 inter 10000 fall 2
  server  read02 hogehoge-replica02.xxxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:3306 check port 3306 inter 10000 fall 2
  server  master hogehoge.xxxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:3306 check port 3306 backup

listen haproxy_stats
  bind 127.0.0.1:1919
  mode http
  stats enable
  stats uri /stats

ポイントは…

  • RDS のマスターは backup に指定しており Read Replica が全死亡でも最悪は Master からデータを取得することが出来る
  • stats enable を設定することで curl で HAProxy の死活をチェックすることが出来る

注意点としては…こちらの記事「HAProxyを用いたRead Replica(RDS)の振り分け」にも書かれているように RDS のパラメータグループで max_connect_errors999999999 にしておく必要があります。これは HAProxy からのヘルスチェックで RDS から接続をブロックしてしまうことを回避しています。これの設定を怠ると以下のようなエラーが出て接続が出来なくなります…orz

ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 0

consul の起動

両方ともに Server モードにて consul を起動します。
まずは一台目を以下のように起動します。

consul agent -data-dir=/tmp/consul -server -bootstrap-expect 2 &

さらに二台目を以下のように起動します。尚、consul は 0.4.1 を利用しており /usr/local/bin/ 以下に展開済みです。

consul agent -data-dir=/tmp/consul -server -join ${一台目の consul ノードの IP} &

-bootstrap-expect で指定した数の台数が揃ったタイミングで Bootstrap が走ります。

Bootstrap が終了したら consul members でクラスタの状態を確認します。

# consul members
    2014/12/12 19:13:53 [INFO] agent.rpc: Accepted client: 127.0.0.1:37823
Node              Address             Status  Type    Build  Protocol
ip-xxx-xxx-xxx-1  xxx.xxx.xxx.1:8301  alive   server  0.4.1  2
ip-xxx-xxx-xxx-2  xxx.xxx.xxx.2:8301  alive   server  0.4.1  2

consul へのサーヴィス登録と HAProxy の監視

既に HAProxy は起動している状態で consul にサービスを以下のように登録します。

cat << EOT | curl -XPUT http://127.0.0.1:8500/v1/agent/service/register -d @-
{
    "name": "rdsproxy",
    "port": 3306,
    "check": {
      "script": "/path/to/bin/check_haproxy.sh",
      "interval": "10s"
    }
}
EOT

この操作は両方のノードで行います。
サービスの登録が終わったら登録されているサービスを確認します。

# curl -s http://127.0.0.1:8500/v1/catalog/service/rdsproxy | jq .
[
  {
    "Node": "ip-xxx-xxx-xxx-2",
    "Address": "xxx.xxx.xxx.2",
    "ServiceID": "rdsproxy",
    "ServiceName": "rdsproxy",
    "ServiceTags": null,
    "ServicePort": 3306
  },
  {
    "Node": "ip-xxx-xxx-xxx-1",
    "Address": "xxx.xxx.xxx.1",
    "ServiceID": "rdsproxy",
    "ServiceName": "rdsproxy",
    "ServiceTags": null,
    "ServicePort": 3306
  }
]

また、HAProxy の監視スクリプト(/path/to/bin/check_haproxy.sh)は以下のような内容です。

#!/bin/sh

r=`curl -LI 127.0.0.1:1919/stats -o /dev/null -w '%{http_code}n' -s`

if [ ${r} = "200" ];then
  exit 0
else
  exit 1
fi

監視スクリプトはドキュメントによると sensu や Nagios と同様に…

  • Exit code 0 – Check is passing
  • Exit code 1 – Check is warning
  • ny other code – Check is failing

正常終了は 0 で終わらせることが重要なようです。

consul-template の起動と設定

次に consul 上のデータを利用して /etc/hosts に HAProxy の IP アドレスを設定する為に consul-template を利用します。consul-template についてはこちら「consul-template/README.md at master · hashicorp/consul-template」を御覧ください。

まず hosts ファイルの元になるテンプレートファイルを以下のように作成します。

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
{{range service "rdsproxy"}}
{{.Address}} rdsproxy {{.Node}}{{end}}

{{range service "rdsproxy"}} から {{end}} までの間は Go のテンプレートになっており consul のサービス定義の中から AddressNode の値が埋め込まれます。

consul-template はサービスとして起動させることで consul を監視して、consul に登録されているデータに変化があれば consul-template が実行されて該当のファイルを更新しますので以下のように consul-template を両方のノードで起動しておきます。

consul-template   -consul 127.0.0.1:8500   -template /etc/hosts.ctmpl:/etc/hosts &

はい。これで、準備完了です。

デモ

2 ノードの状態

/etc/hosts を覗いてみます。

# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

xxx.xxx.xxx.1 rdsproxy ip-xxx-xxx-xxx-1
xxx.xxx.xxx.2 rdsproxy ip-xxx-xxx-xxx-2

おお…rdsproxy として 2 つのノードが登録されています。
rdsproxy という名前を利用して RDS につないでみます。

# mysql -uhoge -h rdsproxy -P 3306 -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or g.
Your MySQL connection id is 34826
Server version: 5.6.19-log MySQL Community Server (GPL)

Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.

mysql>
mysql> show variables like 'hostname%';
+---------------+---------------+
| Variable_name | Value         |
+---------------+---------------+
| hostname      | ip-10-7-1-205 |
+---------------+---------------+
1 row in set (0.01 sec)

mysql> exit

おお。

HAProxy の一台に障害やー

# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

xxx.xxx.xxx.2 rdsproxy ip-xxx-xxx-xxx-2

おお。consul-tempalte が /etc/hosts を書き換えて rdsproxy が一台になっています。
RDS につないでみます。

# mysql -uhoge -h rdsproxy -P 3306 -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or g.
Your MySQL connection id is 5730
Server version: 5.6.19 MySQL Community Server (GPL)

Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.

mysql>
mysql> show variables like 'hostname%';
+---------------+--------------+
| Variable_name | Value        |
+---------------+--------------+
| hostname      | ip-10-7-1-56 |
+---------------+--------------+
1 row in set (0.00 sec)

mysql>

難なく RDS には接続出来ました。

ということで…

HAProxy の冗長化を図ろうとすると Corosync + Pacemaker か Heartbeat を選択しがちですが、consul と consul-template を利用すれば consul 自体の監視やプロセス管理方法等については引続き詰める必要はあると思いますが、カジュアルとモダン(自分比)に構築することが出来たような気がします。

元記事はこちらです。
RDS のリードレプリカへのアクセスを HAProxy で分散しつつ consul と consul-template で冗長化するメモ