一身上の都合によりsrvdというデーモンを書いた。
github.com
これは何?
DNSのSRVレコードをバックエンドにしたconfdみたいなものです。
SRVレコードの値に合わせてミドルウェアの設定ファイルを書き換えて、設定ファイルが変更されたらミドルウェアをリロードする、みたいな。
ことの発端
某所ではMySQLのスレーブへのロードバランサーとして、Railsサーバに同居しているHAProxyを使ってるんですよ。
中央集権的ロードバランサーに比べて、スループットがよいとか、大量のコネクションが一カ所に集中しないとか性能的にはいいんですが、いかんせん設定ファイルをRailsサーバにばらまくのがめんどくさい。設定ファイルをばらまいた後は大量のHAProxyのリロードとRailsのリロード。
それをなんとかしたいと思ったので、いろいろ試してみたんですよ。
- NLB
- DNS RR
- DNS RR(複数IPアドレスを返すのではなくRoute53のWeighted Routingを使う)
- CLB
四苦八苦していたら同僚がHAProxyのserver-template(とそのデメリット)を教えてくれたので、さらに検証。
www.haproxy.com
HAProxy 1.8だと
server-template db 10 _mysql._tcp.example.com:3306 check port 3306 resolvers dns
みたいな設定を書くと、SRVレコードの値に合わせてバックエンドを設定してくれるんですよね。
一件、なかなかよいのですが
- バックエンドのサーバ数が固定
- 例えば、
server-template db 10
と書くとSRVレコードの返す値が2個でもdb1〜db10までのバックエンドが作られて、db1・db2以外は「ヘルスチェック失敗」というステータスになる
- 減ったサーバはメンテ状態になって残る
- 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のリロードも考えねば。