一身上の都合によりsrvdというデーモンを書いた。
これは何?
DNSのSRVレコードをバックエンドにしたconfdみたいなものです。
SRVレコードの値に合わせてミドルウェアの設定ファイルを書き換えて、設定ファイルが変更されたらミドルウェアをリロードする、みたいな。
ことの発端
某所ではMySQLのスレーブへのロードバランサーとして、Railsサーバに同居しているHAProxyを使ってるんですよ。 中央集権的ロードバランサーに比べて、スループットがよいとか、大量のコネクションが一カ所に集中しないとか性能的にはいいんですが、いかんせん設定ファイルをRailsサーバにばらまくのがめんどくさい。設定ファイルをばらまいた後は大量のHAProxyのリロードとRailsのリロード。
それをなんとかしたいと思ったので、いろいろ試してみたんですよ。
- NLB
- スループットが結構落ちる
- DNS RR
- コネクションが一部のMySQLスレーブに偏る
- DNS RR(複数IPアドレスを返すのではなくRoute53のWeighted Routingを使う)
- やっぱりコネクションが偏る
- CLB
- 論外なスループット
四苦八苦していたら同僚がHAProxyのserver-template(とそのデメリット)を教えてくれたので、さらに検証。
HAProxy 1.8だと
server-template db 10 _mysql._tcp.example.com:3306 check port 3306 resolvers dns
みたいな設定を書くと、SRVレコードの値に合わせてバックエンドを設定してくれるんですよね。
一件、なかなかよいのですが
- バックエンドのサーバ数が固定
- 減ったサーバはメンテ状態になって残る
- SRVレコードがdb-001・db-002と返していたのが、db-002だけになると、db-001のバックエンドは「メンテ状態」のステータスで残り続ける
バックエンドが大きく変わるような場合だと、指定したサーバ数からあふれたり、残り続けたバックエンドがどうなるのかがよく分からない…なんとなーく、よくない匂いを感じる…
それで、consul-template使うか、いやconsulのクラスタを管理したいわけじゃないんだよな、もっとシンプルにやりたいんだよ、そういえばconfdってSRVレコード対応してたっけ…と調べてみると
confd/dns-srv-records.md at master · kelseyhightower/confd · GitHub
etcdやconsulのノードを見つけるためにSRVレコードは使えるけど、バックエンドとしては使えない。 たしかに、DNSはKVSじゃないしね…
confdのバックエンドに追加する修正を投げようかと思ったけれど、なんとなくポリシーが違いそうだし、confdのソースをざっと眺めた感じ、これくらいなら実装できるか、と思って作った次第です。
使い方
srvdの設定ファイルがこんな感じ。
src = "/etc/haproxy/haproxy.cfg.tmpl" dest = "/etc/haproxy/haproxy.cfg" domain = "_mysql._tcp.example.com" reload_cmd = "/bin/systemctl reload haproxy.service" check_cmd = "/usr/sbin/haproxy -c -V -f {{ .src }}" interval = 1 timeout = 3 #resolv_conf = "/etc/resolv.conf" cooldown = 60 #status_port = 8080
haproxy.cfgのテンプレートはこんな感じ。
backend nodes mode tcp # see https://godoc.org/github.com/miekg/dns#SRV {{ range .srvs }} server {{ .Target }} {{ .Target }}:{{ .Port }} check {{ end }}
SRVレコードを
10 10 3306 db-001.example.com.
と設定して、srvdを起動すると、以下のようなhaproxy.cfgが作成される。
backend nodes mode tcp # see https://godoc.org/github.com/miekg/dns#SRV server db-001.example.com. db-001.example.com.:3306 check
RaisのUnitファイルはこんな感じ。
[Unit] Description=Rails After=network.target ReloadPropagatedFrom=haproxy.service [Service] User=ubuntu WorkingDirectory=/home/ubuntu/hello ExecStart=/usr/local/bin/bundle exec puma ExecReload=/bin/kill -s USR2 $MAINPID [Install] WantedBy=multi-user.target
RailsはlocalhostのHAProxyを参照するように設定。
default: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: scott password: tiger host: 127.0.0.1
コントローラで接続先が分かるようにして
def index render plain: Item.connection.execute("show variables like 'hostname'").first.last + "\n" ActiveRecord::Base.clear_all_connections! end
curlで叩き続けると
$ while true; do curl localhost:3000; sleep 1; done db-001 db-001 db-001
db-001に接続していることが分かる。
そうしたらSRVレコードにdb-001を追加。
10 10 3306 db-001.example.com. 10 10 3306 db-002.example.com.
変更して30秒〜1分ぐらい待つと、haproxy.cfgが書き換わってHAProxyとRailsがリロード。
Aug 03 02:35:01 app-101 srvd[20298]: 2018/08/03 02:35:01 The configuration has been changed. Update /etc/haproxy/haproxy.cfg Aug 03 02:35:01 app-101 srvd[20298]: 2018/08/03 02:35:01 Run '/usr/sbin/haproxy -c -V -f {{ .src }}' for checking Aug 03 02:35:01 app-101 srvd[20298]: 2018/08/03 02:35:01 /usr/sbin/haproxy: stdout: Configuration file is valid Aug 03 02:35:01 app-101 srvd[20298]: 2018/08/03 02:35:01 Run '/bin/systemctl reload haproxy.service' for reloading Aug 03 02:35:01 app-101 systemd[1]: Reloading HAProxy Load Balancer. Aug 03 02:35:01 app-101 systemd[1]: Reloading Rails. Aug 03 02:35:01 app-101 bundle[22391]: * Restarting... Aug 03 02:35:01 app-101 haproxy[30792]: Configuration file is valid Aug 03 02:35:01 app-101 haproxy-systemd-wrapper[21157]: haproxy-systemd-wrapper: re-executing Aug 03 02:35:01 app-101 systemd[1]: Reloaded HAProxy Load Balancer. Aug 03 02:35:01 app-101 haproxy-systemd-wrapper[21157]: haproxy-systemd-wrapper: executing /usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -Ds -sf 30587 Aug 03 02:35:01 app-101 systemd[1]: Reloaded Rails. Aug 03 02:35:01 app-101 haproxy[30802]: Proxy localnodes started. Aug 03 02:35:01 app-101 haproxy[30802]: Proxy localnodes started. Aug 03 02:35:01 app-101 haproxy[30802]: Proxy nodes started. Aug 03 02:35:01 app-101 haproxy[30802]: Proxy nodes started. Aug 03 02:35:01 app-101 bundle[22391]: Puma starting in single mode... Aug 03 02:35:01 app-101 bundle[22391]: * Version 3.12.0 (ruby 2.3.1-p112), codename: Llamas in Pajamas Aug 03 02:35:01 app-101 bundle[22391]: * Min threads: 5, max threads: 5 Aug 03 02:35:01 app-101 bundle[22391]: * Environment: development Aug 03 02:35:02 app-101 bundle[22391]: * Inherited tcp://0.0.0.0:3000 Aug 03 02:35:02 app-101 bundle[22391]: Use Ctrl-C to stop
新しいDBに接続するようになる。
backend nodes mode tcp # see https://godoc.org/github.com/miekg/dns#SRV server db-001.example.com. db-001.example.com.:3306 check server db-002.example.com. db-002.example.com.:3306 check
$ while true; do curl localhost:3000; sleep 1; done ... db-001 db-001 db-001 db-002 db-001 db-002 db-001 db-002
その他
まあまあ使えるかな、と思いつつまだ実践に投入できていないのでなんともかんとも。 Dockerコンテナ内でのHAProxyのリロードも考えねば。