GeekFactory

int128.hatenablog.com

TCPカーネルパラメータによる障害復旧時間の短縮

クラスタ構成のサーバでは、障害発生後にクライアントがすぐに復旧しない場合があります。サーバ側がフェイルオーバした後にクライアント側が再接続するまでの時間を短くする方法を紹介します。

クライアントからサーバに接続するとソケットはESTABLISHEDになります。もしESTABLISHEDになったソケットで正しくパケットが送信されなかった場合、OSは再送を試みます。再送に失敗してソケットをクローズするまでの時間はOSの設定によります。

OSがTCP接続の異常を検知してからクローズするまでの時間を短くするには3つの方法があります。

  1. パケットの再送回数を少なくする。
  2. TCPレイヤでKeep Aliveパケットを送信する。この方法はTCP Keep Aliveに対応しているアプリのみ可能。
  3. アプリケーションレイヤでKeep Aliveパケットを送信する。この方法はNullパケットを投げる等に対応しているアプリのみ可能。

例えばSSHでは上記3つの設定が可能ですが、Keep Aliveに対応していないアプリもあります。ここでは、1を適用した場合のTCPの挙動について考察します。

検証

SSHクライアントとSSHサーバの間のパケットが欠落した場合の挙動を確認してみます。障害試験は、SSH接続中にファイアウォールでパケットをドロップすることで実施します。

SSHクライアント ファイアウォール SSHサーバ
172.16.35.1 172.16.35.254 - 172.16.36.254 172.16.36.1

何も設定していない場合

約15分でソケットがクローズされる結果となりました。

時刻 作業手順 SSHクライアントの
ソケットステータス
SSHサーバの
ソケットステータス
02:48:10 SSHクライアントからSSHサーバに接続し、1秒毎に日時を表示する。 ESTABLISHED ESTABLISHED
02:50:30 ファイアウォールSSHをドロップするようポリシーを変更する。 ESTABLISHED ESTABLISHED
03:05:56 - ESTABLISHED CLOSED
03:06:20 SSHクライアントでEnterキーを押す ESTABLISHED CLOSED
03:21:48 SSHクライアントが異常終了 CLOSED CLOSED

パケットログを見てみましょう。再送パケットを時系列に並べてみました。ちなみに初回はパケットログを取り忘れたのでもう一回やってます。

# 時刻 経過秒
0 03:53:10 0
1 03:53:11 1
2 03:53:11 1
3 03:53:13 3
4 03:53:16 6
5 03:53:23 13
6 03:53:36 26
7 03:54:01 51
8 03:54:53 103
9 03:56:36 206
10 03:58:36 326
11 04:00:36 446
12 04:02:36 566
13 04:04:36 686
14 04:06:36 806
15 04:08:36 926
CLOSED 04:10:36 1046(17分26秒)

間隔を2倍ずつ大きくして再送していることが分かります。Linuxカーネルでは再送回数のデフォルト値が15回に設定されています。これは /proc/sys/net/ipv4/tcp_retries2 で確認できます。

TCP再送回数を少なくする

再送回数を7回に設定してみましょう。/etc/sysctl.confに以下を追加してsysctl -pを実行します。

# /etc/sysctl.conf
net.ipv4.tcp_retries2 = 7

すると、約50秒でソケットがクローズされる結果となりました。

時刻 作業手順 SSHクライアントの
ソケットステータス
SSHサーバの
ソケットステータス
04:21:00 SSHクライアントからSSHサーバに接続し、1秒毎に日時を表示する。 ESTABLISHED ESTABLISHED
04:21:20 ファイアウォールSSHをドロップするようポリシーを変更する。 ESTABLISHED ESTABLISHED
04:22:11 - ESTABLISHED CLOSED

パケットログを見ても、7回の再送でタイムアウトしていることが分かります。

# 時刻 経過秒
0 04:21:19 0
1 04:21:19 0
2 04:21:19 0
3 04:21:20 1
4 04:21:22 3
5 04:21:25 6
6 04:21:31 12
7 04:21:44 25
CLOSED 04:22:11 52

再送回数をあまりにも少なくすると、ネットワークが瞬断しただけでTCP接続が切れてしまいます。ファイアウォールのフェイルオーバ時間より長い時間であれば問題ないでしょう。

NFSのようにTCP Keep Aliveを設定できないプロトコルでは、再送回数をデフォルトより少なくすることで障害復旧時の立ち直りが早くなるはずです。他ミドルへの影響を検討してからお試しください。

おまけ

1秒毎にソケットステータスをログるスクリプトです。

#!/bin/bash
PORT=22
while true; do
    netstat -an --inet 2> /dev/null | grep ":$PORT" | sed -e "s,^,$(date +%Y-%m-%dT%H:%M:%S)\t,g"
    sleep 1
done