LVS+DSRでMySQLサーバの参照を分散してRailsから接続してみたところ、
デプロイごとにMySQLのコネクションが増えていくという事象が発生してハマりました。
※Railsの持続的なコネクションだとそもそもLVSで分散できねーだろ、ってツッコミは今回は無しでお願いします・・・
結論としてはLVS が FIN を落とすに書いてるとおり、LVSサーバの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 が FIN を落とす
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の挙動を考えると、なるほどなという感じですね。
ついでに
Railsではなくmysqlコマンドで接続、切断すると、
$ 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はハマりどころ多いですねぇ。