HAProxy で MySQL のヘルスチェックをちょっと便利にする

MySQL で slave を複数台立てて参照分散するには、HAProxy を利用してロードバランスと切り離しを行うと手軽に使えて便利です。
option mysql-check という設定で、HAProxy 自身が mysqld に接続してヘルスチェックが可能です。

listen mysql-slave
       bind     127.0.0.1:3307
       mode     tcp
       option   mysql-check user haproxy
       balance  roundrobin
       server   slave1  192.168.1.11 check
       server   slave2  192.168.1.12 check
       server   slave3  192.168.1.13 check

なのですが、この設定だと以下のように少々不便なことがあります。

  • mysqldに接続できるかどうかのみを死活の判断にしているので、レプリケーションが停止しているような場合にも構わず振り分けてしまう
    • 結果、ユーザに古い情報が見え続けてしまう事故が起きうる
  • 作業をするので一時的に特定のホストだけ外したい、という場合に切り離し作業が面倒

ということで、Having HAProxy check mysql status through a xinetd script | Sysbible を参考にして、HTTP経由での死活監視をしてみました。

HTTPリクエストを受けて、localhost の mysqld に接続して 200 / 500 を返すような daemon を実装します。

  • 接続できなければ Status 500
  • SHOW SLAVE STATUS の結果が得られなければ Status 500
  • SHOW SLAVE STATUS の結果を Status 200、JSON で返す

という挙動をします。

#!/usr/bin/env perl
use strict;
use Plack::Runner;
use DBI;
use Try::Tiny;
use JSON;

sub response {
    my ($code, $message) = @_;
    my $content = encode_json({ message => $message });
    my $header = [
        "Content-Type"   => "application/json",
        "Content-Length" => length($content),
    ];
    return [ $code, $header, [ $content ] ];
}

sub app {
    my $env = shift;
    my $result;
    my $res = try {
        my $dbh = DBI->connect(
            'dbi:mysql:database=test;host=127.0.0.1;port=3306',
            'haproxy', '',
            { RaiseError => 1, AutoCommit => 1 },
        );
        $dbh->ping;
        $result = $dbh->selectrow_hashref("SHOW SLAVE STATUS");
        $dbh->disconnect;
        undef;
    }
    catch {
        my $e = $_;
        response 500, $e;
    };
    return $res if defined $res;
    if (ref $result) {
        response 200, $result;
    }
    else {
        response 500, "This mysqld is not a slave.";
    }
}

my $runner = Plack::Runner->new;
$runner->parse_options(@ARGV);
$runner->run(\&app);

これを slave が動作しているホストそれぞれで動作させ、そこに対して HAProxy が HTTP でのヘルスチェックを行うように設定します。死活監視用のユーザには REPLICATION CLIENT の権限を与えておきます。

listen mysql
       bind     127.0.0.1:3307
       mode     tcp
       balance  roundrobin
       option   httpchk
       server   slave1 192.168.1.11:3306 check port 5000
       server   slave2 192.168.1.12:3306 check port 5000
       server   slave3 192.168.1.13:3306 check port 5000

これで接続できなかったり、master になっていたりした場合には HTTP 500 が返るので HAProxy がダウン検知して切り離されます。MHA で slave が master に昇格しても大丈夫。

特定のローカルファイルが存在していたら 503 を返すようにすれば、一時的に切り離したいときには touch で行えます。レプリケーションの状態を見て一定以上遅れていたら切り離しなど、アプリケーションの都合により柔軟に切り離し条件を設定できるので、便利になりますね。

もちろん死活監視用の daemon が落ちたら mysqld が生きていても切り離されてしまうので、万が一落ちても再度起動できるように daemontools 等から起動するとよいかと思います。

[追記]

ヘルスチェック用 httpd の Go 版も書きました。 fujiwara/mysql-slave-healthcheck-agent · GitHub