LVS+DSRでMySQLサーバの参照を分散してRailsから接続してみたところ、
デプロイごとにMySQLのコネクションが増えていくという事象が発生してハマりました。
※Railsの持続的なコネクションだとそもそもLVSで分散できねーだろ、ってツッコミは今回は無しでお願いします・・・
結論としてはLVS が FIN を落とすに書いてるとおり、LVSサーバのiptables設定がダメだったのですが、いちおう経緯を書いておきます。
iptables無しの状態
通常、MySQLに外部から接続すると
$ mysql -utest -ptest -h192.168.1.111 tmp_app_development
MySQLサーバ上にESTABLISHEDなコネクションが出来て、
[root@mysql01 ~]# netstat -an | grep 3306 tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp 0 0 192.168.1.111:3306 192.168.1.61:49788 ESTABLISHED
MySQLのプロセスリストが1つ増えます。
mysql> show processlist; +----+------+--------------------+---------------------+---------+------+-------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+--------------------+---------------------+---------+------+-------+------------------+ | 34 | test | 192.168.1.61:49805 | tmp_app_development | Sleep | 28 | | NULL | | 37 | root | localhost | NULL | Query | 0 | init | show processlist | +----+------+--------------------+---------------------+---------+------+-------+------------------+ 2 rows in set (0.00 sec)
そして、切断するとどっちも消える。
[root@mysql01 ~]# netstat -an | grep 3306 tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN
mysql> show processlist; +------+------+--------------------+------+---------+------+-------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +------+------+--------------------+------+---------+------+-------+------------------+ | 2648 | test | 192.168.1.61:51120 | NULL | Query | 0 | init | show processlist | +------+------+--------------------+------+---------+------+-------+------------------+ 1 row in set (0.00 sec)
はずなんですが、、、
今回起こった事象
LVSサーバにiptablesを適用することになったので、こんな感じでMySQLの接続を許可する設定をいれました。
[root@lvs01 ~]# iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 3306 -j ACCEPT
そうするとデプロイするたびにMySQLサーバの接続数が増えていく。
APPサーバ側は異常はないんだけど、MySQLサーバで旧プロセスのコネクションが残ったままになってるっぽい。
簡単に確認するためにdatabase.ymlの接続先をLVSのVIPに切り替えて接続、切断してみる。
[mikeda@test01 tmp_app]$ ./bin/rails c irb(main):001:0> User.first irb(main):002:0> exit
切断しても、コネクションがESTABLISHEDのままで残っている・・・
[root@mysql01 ~]# netstat -an | grep 3306 tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp 0 0 192.168.1.111:3306 192.168.1.61:49805 ESTABLISHED
MySQLのプロセスも消えずにどんどん溜まっていく・・・
mysql> show processlist; +----+------+--------------------+------+---------+------+-------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+--------------------+------+---------+------+-------+------------------+ | 34 | test | 192.168.1.61:49805 | tmp | Sleep | 28 | | NULL | | 37 | root | localhost | NULL | Query | 0 | init | show processlist | +----+------+--------------------+------+---------+------+-------+------------------+ 2 rows in set (0.00 sec)
でもMySQLのwait_timeoutは変えたくない・・・
というわけで調査と対応
LVSサーバ、MySQLサーバで切断時のtcpdumpを取ってみると、
LVS側ではえんえんとFINパケットが届いているんだけど、
[root@lvs01 ~]# tcpdump -n port 3306 ... 05:21:47.240436 IP 192.168.1.61.49822 > 192.168.1.111.mysql: Flags [F.], seq 390, ack 1871, win 618, options [nop,nop,TS val 8292014 ecr 8285790], length 0 05:21:47.441272 IP 192.168.1.61.49822 > 192.168.1.111.mysql: Flags [F.], seq 390, ack 1871, win 618, options [nop,nop,TS val 8292215 ecr 8285790], length 0 05:21:47.843234 IP 192.168.1.61.49822 > 192.168.1.111.mysql: Flags [F.], seq 390, ack 1871, win 618, options [nop,nop,TS val 8292617 ecr
MySQL側には何も来ない。
[root@mysql01 ~]# tcpdump -n port 3306 ...
というわけでLVS/DSR + iptablesでググってみるとこういう記事を見つけた。
LVS の DR 構成は、帰りのパケットが LVS を通らないために netfilter(iptables) がコネクションの確立を認識せず、FIN を通さないということが判りました。netfilter で TCP の state を見ないようにすることで、無事解決です。
なるほど。
とりあえず書かれているようにLVSサーバのiptables設定を変更してみると、
[root@lvs01 ~]# iptables -D INPUT -m state --state NEW -m tcp -p tcp --dport 3306 -j ACCEPT [root@lvs01 ~]# iptables -A INPUT -m tcp -p tcp --dport 3306 -j ACCEPT
解決!!!
LVSサーバではクライアントからのFINと(MySQLサーバからのFINに対する)ackのみが確認されて、
[root@lvs01 ~]# tcpdump -n port 3306 ... 05:42:10.944673 IP 192.168.1.61.49851 > 192.168.1.111.mysql: Flags [F.], seq 390, ack 1871, win 618, options [nop,nop,TS val 9515718 ecr 9509710], length 0 05:42:10.945068 IP 192.168.1.61.49851 > 192.168.1.111.mysql: Flags [.], ack 1872, win 618, options [nop,nop,TS val 9515718 ecr 9513806], length 0
MySQLサーバではFIN、FIN/ack、ackの全てが確認できました。
[root@mysql01 ~]# tcpdump -n port 3306 ... 05:42:10.594478 IP 192.168.1.61.49851 > 192.168.1.111.mysql: Flags [F.], seq 391, ack 1871, win 618, options [nop,nop,TS val 9515718 ecr 9509710], length 0 05:42:10.594571 IP 192.168.1.111.mysql > 192.168.1.61.49851: Flags [F.], seq 1871, ack 392, win 243, options [nop,nop,TS val 9513806 ecr 9515718], length 0 05:42:10.594852 IP 192.168.1.61.49851 > 192.168.1.111.mysql: Flags [.], ack 1872, win 618, options [nop,nop,TS val 9515718 ecr 9513806], length 0
DSRの挙動を考えると、なるほどなという感じですね。
ついでに
$ mysql -utest -ptest -h192.168.1.111 mysql> exit Bye $
コネクションはESTABLISHEDではなくFIN_WAIT2になりました。
[root@mysql01 ~]# netstat -an | grep 3306 tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp 0 0 192.168.1.111:3306 192.168.1.61:49793 FIN_WAIT2
これはtcp_fin_timeoutの設定値に従って60秒で消えます。
[root@mysql01 ~]# sysctl -a | grep tcp_fin_timeout net.ipv4.tcp_fin_timeout = 60
ちゃんと調べられてないけど、この違いはなんなんだろう。
まとめ
LVS/DSR + iptablesは初めてで、ちょっとハマりました。
もしかしたら常識なのかもですが。
LVS/DSRはハマりどころ多いですねぇ。