こんにちは!国内外のデータセンターで、日々ネットワークの安定稼働を見守っているNOC(ネットワークオペレーションセンター)エンジニアの私です。
突然ですが、皆さんはWebサイトやアプリを運用していて、「なぜか徐々にサーバーの動きが重くなり、最終的に全く繋がらなくなってしまった……」という原因不明のトラブルに遭遇したことはありませんか?
サーバーを再起動すれば一時的に直るけれど、しばらく経つとまた同じ現象が起きる。そんな不気味な現象の裏で、ひっそりと牙を剥いているのが、今回ご紹介する「CLOSE_WAIT(クローズ・ウェイト)状態の滞留」です。
一見すると難しそうな専門用語に見えますが、仕組みを紐解いていけば、私たちが日常生活で行っている「あるやり取り」と全く同じです。今回は、ネットワークやインフラに初めて触れるエンジニアの皆さんに向けて、パケットの細かいビット数などの難しい話は一旦脇に置いて、身近な例え話でどこよりも分かりやすく解説します。一歩ずつ、一緒に理解していきましょう!
—
1. TCPの「接続を閉じる」流れを、電話の会話で例えてみよう
インターネットでデータをやり取りする際、多くの場合は「TCP(ティー・シー・ピー)」という、お互いに確認を取り合いながら確実につなぐ仕組みが使われています。
このTCPで接続を終了するとき、実は「4ステップのやり取り(4ウェイ・クローズ)」が行われています。これを、私たちは日常の「電話の終わり際」に例えて考えることができます。
今回は、スマホでWebサイトを見ている「あなた(クライアント)」と、「Webサーバー」の電話をイメージしてみてください。
【電話でのクローズのやり取り】
あなた:「用事は済んだので、そろそろ切りますね!」(FINパケットを送信)
│
▼
サーバー:「あ、了解しました!ちょっと待ってくださいね」(ACKパケットを送信)
│
├─★ この瞬間、サーバーは「CLOSE_WAIT」という状態になります!
├─★ 「相手が電話を切るって言っているから、こっちも切る準備をしなきゃな」と待機している状態です。
│
▼(サーバー側で、やり残したデータ処理や片付けを行う)
│
サーバー:「お待たせしました!こちらも準備ができたので、切りますね!」(FINパケットを送信)
│
▼
あなた:「はーい、お疲れ様でした!」(ACKパケットを送信)⇒【完全に通話終了】
この流れの中で、注目してほしいのが「サーバーが『了解しました!ちょっと待ってくださいね』と言ってから、『こちらも準備ができたので切りますね!』と言うまでの間」です。
この、「相手から切断の申し出(FIN)を受け取ったけれど、自分自身が電話を切る宣言(FIN)を返すまでの待ち時間」のことこそが、ネットワーク用語でいう CLOSE_WAIT なのです。
—
2. なぜ CLOSE_WAIT が消えずに溜まってしまうのか?
通常であれば、CLOSE_WAIT 状態は一瞬(コンマ数秒)で通り過ぎます。サーバー側のプログラムが「あ、相手が切るんだな。じゃあこっちもプログラム上で close() という命令を実行して、受話器を置こう」と、すぐに次のアクションを起こすからです。
しかし、もしサーバーのプログラム(アプリケーション)に不具合(バグ)があると、どうなるでしょうか?
あなた:「そろそろ切りますね!」(FIN)
サーバー:「了解です!」(ACK) ⇒ 【CLOSE_WAIT状態に突入】
(……ここから、サーバー側のプログラムがフリーズ、または close() を呼び出すのを忘れる……)
サーバー:「(無言で受話器を持ったまま放置)」
あなた:「(えっ、切ってくれないの……?)」
これこそが、CLOSE_WAIT の滞留(居座り)です。
サーバー側のプログラムが、相手が切ったことに気づいているのに、自分から「受話器を置く(close() を呼ぶ)」という処理を忘れてしまっている状態です。
溜まりすぎると、なぜサーバーが動かなくなるの?
サーバーが同時に持てる「受話器(これを『ソケット』や『ファイル記述子(ファイルディスクリプタ)』と呼びます)」の数には、上限(限界)があります。
受話器を持ったまま放置された CLOSE_WAIT の接続が100個、1000個、10000個……と増えていくと、サーバー内の受話器が全て塞がってしまいます。その結果、新しく電話をかけてきた別のお客さんに対して、「もう使える受話器がありません!」と接続を拒否せざるを得なくなってしまうのです。これが、サーバーが沈黙する原因です。
—
3. 現場のCLIコマンドで CLOSE_WAIT を検知・特定しよう!
「最近、サーバーの調子が悪いな」と思ったら、まずはサーバーにログインして、この CLOSE_WAIT が大量に発生していないか確認してみましょう。NOCの現場でも、障害発生時に真っ先に叩くコマンド群です。
現代のLinuxサーバーで最も推奨されるのが、ネットワークの接続状態を表示する ss コマンド(または従来の netstat コマンド)です。
① CLOSE_WAITの状態にある接続を一覧表示する
以下のコマンドを実行すると、現在 CLOSE_WAIT になっている接続だけを綺麗に絞り込んで表示できます。
# ssコマンドを使って、CLOSE_WAIT状態のTCP接続のみを表示します
ss -ant state close-wait
もし、このコマンドを実行したときに、画面を埋め尽くすほどの大量の行が表示されたら、システムで「切断漏れ(クローズ漏れ)」が発生している証拠です。
② 犯人(プログラム)を特定する
「どのプログラムが受話器を置き忘れているのか」を特定するために、プロセスID(PID)とプログラム名も一緒に表示してみましょう。これには -p オプションを付けます(実行には管理者権限 sudo が必要です)。
# -p オプションを付与して、プロセス名とプロセスID(PID)を紐付けて表示します
sudo ss -antp state close-wait
出力例のイメージ:
“`text
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
CLOSE-WAIT 1 0 192.168.1.10:8080 192.16
コメント