VPNリレーがSSHを受け付けなくなる:ファイルディスクリプタの上限
トンネルの通信は流れ続けているのに、背後のsocatリレーが新しいセッションを拒否する。経路をたどり、稼働中のPIDで継承された上限を読む。
WireGuardのハンドシェイクは今も完了し、ピアは最近のエンドポイントを示しているのに、背後にあるリレー経由の新しいSSHセッションはバナーも出さずにハングする。この組み合わせはトンネルではなく、ソケットを受け付けるプロセスを指している。socatのようなリレーは、独自のディスクリプタ予算、独自の子プロセス、独自の継承された上限を持ってアプリケーションの経路に座っている。以下の道筋はクライアントから始まり、これ以上ソケットを開けない最初の構成要素で止まる。
数値を変える前に経路をたどる
トンネル全体を論じるのではなく、失敗した一つのリクエストを追跡する。クライアントはトンネルに書き込み、VPNデーモンが復号してルーティングし、リレーが反対側で受け付ける。各段階はディスクリプタを消費し、たいてい枯渇するのは最後の段階だ。
再現できるリクエストから始めて、その時間を測る。TCPハンドシェイクが完了するのにアプリケーションのバナーが届かないなら、リレーがソケットを引き受けた後、子プロセスか上流側の脚で止まっている。ハンドシェイク自体が完了しないなら、原因はルーティング、ファイアウォール、リスナーの状態であり、ディスクリプタの天井ではない。観察したのがどちらの形かを記録する。それぞれ別のプロセスを指すからだ。
作業を二重に行うホップも数える。フォークするリスナーは受け付けた各ソケットを、独自のコピーを保持する子へ渡すので、実際の合計は親だけでなくツリー全体の性質だ。
リレーが継承した上限を読む
意味のある上限は稼働中のプロセスのものであり、そのプロセスを起動したものが決めている。シェルでulimit -nと打っても、説明できるのはシェルだけだ。サービスマネージャーが何を適用したかを尋ね、次にPIDで確認する。
systemctl show -p MainPID -p LimitNOFILE vpn-relay.service
cat /proc/$(systemctl show -p MainPID --value vpn-relay.service)/limits
limitsファイルは、プロセスが見ているソフト上限とハード上限を出力する。寛大なハード値の下でソフト値が低ければ、デーモンは自身の天井を上げてよいことになり、起動時にそうするものもある。ソフト値が等しく低ければ、unitファイルが発生源だ。
drop-inは辞書順に読み込まれ、後のファイルが勝つので、systemctl cat vpn-relay.serviceが何が適用されるかの正式な見方になる。マネージャーが決して読まないファイルを編集することが、上げた数値が効かない最も一般的な理由だ。
定常的な同時接続と保持された状態を分ける
二つのプロセスが無関係な理由で同じ天井に達することがあり、対処法は異なる。有用な同時セッションに追従する需要は容量の問題だ。作業が終わった後も続く増加は、リークか誰も閉じなかったセッションの集合だ。
アイドル時、通常のピーク時、負荷が止まって数分後に測る。ls /proc/PID/fd | wc -lで開いているエントリをソフト上限と比べて数えれば差が分かり、天井の一つか二つ下の数は遅い経路ではなく確実な停止だ。
数が下がらないときは、合計ではなくソケットの状態を見る。CLOSE_WAITに留まるディスクリプタは、ピアが閉じたのにこちら側が閉じていないということで、より大きな上限では先送りにしかならないアプリケーションのライフサイクル欠陥だ。子のモデルも制限する。同時に動く子に上限のないフォーク型リレーは、ディスクリプタ、プロセス枠、メモリを同時に使い果たす。
稼働中のプロセスで新しい値を確認する
unitを編集した後は、マネージャーを再読み込みしてサービスを再起動する。再読み込みだけでは、稼働中のプロセスがすでに継承した上限は決して置き換わらない。実際に通信を処理しているプロセスで確認する。
systemctl daemon-reload
systemctl restart vpn-relay.service
awk '/Max open files/ {print}' /proc/$(systemctl show -p MainPID --value vpn-relay.service)/limits
ss -tnp state established '( sport = :2222 )' | wc -l
awkの行が新しい組を返す。まだ古い組が出るなら、編集したdrop-inは読み込まれているものではない。ssの数が負荷の確認なので、想定する数のセッションを開いて増えるのを見る。それらのセッションを閉じた後も増え続けるなら、上限は問題の全部ではなかったということだ。
大きな引き上げには必ず注意点が伴う。select()でポーリングするアプリケーションはFD_SETSIZEを超えるディスクリプタを監視できず、これは一般的なビルドでは1024なので、それを超えて上げた天井は無関係に見える失敗を生み得る。systemdの実行リファレンスも、起動するサービスについて同じ警告を載せている。
再起動をトンネルの可用性から遠ざける
リレーを再起動するとアクティブなセッションが落ちるが、それはVPNの停止とは別の出来事だ。リレーが入れ替わる間もトンネルが上がったままになるように変更を順序立て、長期セッションがそれにまたがる人には警告する。
切り戻しは前のunitファイルともう一度の再起動であり、そのファイルは新しい値が一営業日を生き延びるまで残す。その後、最初に失敗したリクエスト——リレー経由の新しいSSHセッション一つ、意味のある長さの転送一つ、アイドル時の水準に戻るディスクリプタ数——でループを閉じる。