このクソ API の運搬作業員がまたやってきた、= = 今日は Supervisor
の隠れた設定パラメータのせいで、重要なプロジェクトがオンラインでクラッシュしました。= = シェアする価値があると思ったので、このクソ記事を書きました。
起因#
コードを書いている最中に、突然大量の警告メールが届き、世界がそんなに美しくないと感じました。
そして、例外情報をじっくり見てみると?なんと?新しい接続を作成すると [Errno 24] Too many open files
というやつが出る??これは何だよ、やってやるぜ。
バグの調査#
まず、周知の通り、Linux ではすべてがファイル = = なので、ネットワーク接続の操作も実は File Descriptor
の操作になります = = まあ、とにかく Too many open files
なら、まずはシステムの閾値が小さすぎるのかどうかを考えてみましょう、だから ulimit -a
で確認してみます。
え?open files
の数字は小さくない?十分じゃん?じゃあこれは一体なんなんだよ?
まあ、とりあえずネットワーク接続を調べてみましょう、一発で netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
で各状態の接続数を統計してみましょう。
え?面白いことに、TIME_WAIT の数が多い?え?面白いことに、じゃあ俺のカーネルネットワークパラメータの半端ない努力を出して、ちょっと改造してみましょう?
# パラメータの最適化
# これは、ソケットがローカル側からクローズを要求した場合、FIN-WAIT-2 状態で保持される時間を決定します
net.ipv4.tcp_fin_timeout = 30
# これは、keepalive が有効になっている場合に、TCP が keepalive メッセージを送信する頻度を指定します。デフォルトは2時間ですが、300秒に変更します
net.ipv4.tcp_keepalive_time = 300
# これは、SYN Cookies を有効にします。SYN 待ちキューがオーバーフローした場合、クッキーを使用して処理します。デフォルトは0で、無効を意味します
net.ipv4.tcp_syncookies = 1
# これは、ソケットがローカル側からクローズを要求した場合、FIN-WAIT-2 状態で保持される時間を決定します
net.ipv4.tcp_tw_reuse = 1
# これは、TCP 接続の TIME-WAIT ソケットのクイックリサイクルを有効にします。デフォルトは0で、無効を意味します。現在、TIME-WAIT が多すぎるため、クイックリサイクルを有効にします
net.ipv4.tcp_tw_recycle = 1
net.ipv4.ip_local_port_range = 5000 65000
上記の設定項目を /etc/sysctl.conf
に追加します。そして、sysctl -p
で有効にします。効果を見てみましょう。
え!エラーの数が減っているし、TIME_WAIT の数も徐々に正常になっている。
え???ちょっと待って??他のマシンには TIME_WAIT の問題がないのに、これは一体なんなんだ?しかも、TIME_WAIT の接続をクイックに回収することは他の副作用ももたらします(後の章で説明します)
= = まあいい、今疑っているのは Supervisor
の問題かどうかですね、いいです、ドキュメントを見てみましょう。
うん、半日かけて調べた結果、minfds
というパラメータに関連していることがわかりました。
説明は以下の通りです。
supervisord が正常に起動するために利用可能な最小のファイルディスクリプタの数。minfds を満たすために、setrlimit の呼び出しが行われ、supervisord プロセスのソフトリミットとハードリミットが上げられます。ハードリミットは、supervisord が root として実行されている場合にのみ上げることができます。supervisord はファイルディスクリプタを自由に使用し、OS から取得できない場合には失敗モードに入るため、実行中にそれらが不足することを防ぐために、最小値を指定することは有用です。これらの制限は、管理されたサブプロセスにも継承されます。このオプションは、デフォルトではファイルディスクリプタの制限が低い Solaris で特に有用です。
要するに、Supervisor が起動する際に、minfds の値に基づいてシステムに十分な空き fd があるかどうかを判断します。また、Supervisor で実行されるサービスはすべて Supervisord によってフォークされるため、親子関係でありながら安全性を確保するため、個々のプロセスで開かれるディスクリプタの最大数は、minfds で設定された値を超えないようにします。デフォルトでは 1024 です。そして、彼らは別のトリックを教えてくれました、root ユーザーで実行するなら、最大の値をデフォルトで提供します!
なんだよ。。。そういうことか、誰がサービスを root で実行するんだよ = = まったく。。。
まあ、パラメータを変更しましょう、パラメータを変更しましょう
その後#
最後に、この問題はこれで終わりです、雷を踏んだ一件で、カーネルのネットワークパラメータの復習もできました、気持ちいいですが、Supervisor
さん、薬を飲んでください!