LVSの高負荷対策 その1 ~障害発生~
こんにちは。インフラ担当の岡村です。 昨年、あるサービスで使用中のロードバランサが停止してしまうという事件が起こりました。 事の顛末を、数回に分けて紹介していきたいと思います。 もし同様の事象にお困りの場合は、役立てて頂ければと思います。
システム構成
KLabのDSASでは、ロードバランサにLVS (Linux Virtual Server) を使用しています。 ロードバランサはマスタ-バックアップ構成になっており、マスタ側が停止してしまっても、通常はバックアップ側がマスタに昇格し、サービスを継続できるようになっています。 おおまかな構成は下図のようになります。
障害発生
ある日の晩、突然ロードバランサ(マスタ側)の死活監視のアラート通知が届きます。 (なんだろう..。電源障害? その他HW障害? もしくはカーネルのバグを踏んだ?) 原因調査・復旧はもちろん必要ですが、冗長構成のため、とりあえずサービスは継続可能です...。そのはずでした。 数分後、今度は昇格した新マスタの死活監視アラートが届きます。 (2台揃って落ちた? 208.5日問題のようなバグ? 一体何が起こってる??) 緊張が走ります。 その後も何度かアラートが飛びましたが、サーバにログインする頃には止んでおり、ロードバランサは起動している状態でした。
調査どうやら、マスタ側のロードバランサで再起動が発生し、バックアップ側がマスタに昇格するのですが、その昇格後にまた再起動が発生する、という事象が繰り返されていたようです。 ロードバランサのログを調査したところ、oom-killerが動いていたりでメモリ周りで問題が起こっていそうでした。
(ロードバランサログ) ・kernel: keepalived invoked oom-killer: gfp_mask=0xd0, order=0, oomkilladj=0 ・kernel: IPVS: ip_vs_conn_new: no memory available.
もしメモリの枯渇が起きていたとすれば、32bitカーネルを使用していることが原因の一つである可能性がありました。32bitカーネルでは、カーネルが使用するLowメモリが制限されるためです。 再起動が起こる前に、Webサーバに対するヘルスチェックのエラーが頻発していることもわかりました。
(ロードバランサログ) ・Keepalived_healthcheckers: Timeout connect, timeout server [“WebサーバIP”:”ポート ”].
同時刻に、Webサーバでは次のログが出ていました。
(Webサーバログ) ・kernel : TCP: TCP: Possible SYN flooding on port "ポート番号". Sending cookies. Check SNMP counters.SYNフラッディングで攻撃された?と頭をよぎりますが、断定はできません。 このログは、正常の通信でもWebサーバが捌ききれない量の通信が来れば出力されます。 「パケットキャプチャが欲しいね」 インフラチーム内からそんな声が聞こえてきました。 再発
> 「パケットキャプチャが欲しいね」 この願いはすぐに叶いました...。 障害の調査中に、またロードバランサが再起動を繰り返し始めたのです。
パケットの特徴
すかさずtcpdumpでパケットを観測したところ、以下の特徴を持つパケットが大半を占めていることがわかりました。
・synパケット ・tcp window size が0 ・オプションなし正常な通信においては、synパケットが大半を占めるのは考えにくく、tcp windows size が0かつオプションがない、というパケットもありえなさそうです。 観測できた範囲では、頻度は1秒間におよそ10万パケットで、送り元のIPアドレスはばらけていました。
緊急の対策
対策を行うために、検証環境でロードバランサに負荷をかけるなどして、ロードバランサが落ちた原因を究明したいところでした。しかし、さらなる再発に備えて、取り急ぎ対応を行う必要がありました。 Amazon CloudFront の利用
KLabではオンプレ環境だけではなく、AWSを使用してのサービス提供も行っており、クラウドチームから、CloudFront を間に挟んでみてはどうか、という提案をもらいました。 (DDoSに対するAWSのベストプラクティス) Amazon CloudFront も既に運用の実績があったため、スムーズに実現できました。
対策の効果CloudFrontを挟んで以降、しばらく経過を観察したのですが、上で観測したような大量のパケットはLVSに届かなくなりました。観察は、特徴に当てはまるパケットが来たらカウントするように、ロードバランサに以下の設定を投入して行いました。
# iptables -t mangle -A PREROUTING -p tcp -m u32 --u32 "0x20&0xffff=0x0" -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m length --length 40tcp window size が0という特徴は、u32モジュール(参考)を使用し、オプションがないという特徴は、--length 40 とすることで、マッチさせています。CloudFrontを挟んでもなおパケットが届く場合は、更にDROPターゲットを指定すれば、netfilterでのフィルタリングもできそうです。 今回の場合、CloudFrontによってフィルタリングされているのか、そもそも観測したようなパケットがもう飛んできていないのか、その判断はできないのですが、以後ロードバランサが落ちることはありませんでした。 めでたし、めでたし! ...ではなく、 次回は、ロードバランサ再起動の原因と、その対策をご紹介します!