本番環境での教訓:Linuxリソース制限の拡張
私たちのチームは最近、毎秒約15,000リクエストを処理するマイクロサービスクラスターを移行しました。最初の数ヶ月間は安定していましたが、ある金曜日の午前2時、トラフィックが40%急増した際に監視アラートが鳴り響きました。
ログには java.io.IOException: Too many open files が溢れ返りました。Nginxは新しいTCP接続の受け入れを停止し、PostgreSQLデータベースはクエリのドロップを開始しました。Node.js、Go、Elasticsearchなどの高コンカレンシーなスタックを運用している場合、このボトルネックはほぼ避けられません。
Linuxのアーキテクチャは、「ほぼすべてがファイルである」という哲学に基づいています。ネットワークソケット、システムログ、データベースのインデックスはすべて、ファイル記述子(FD)を消費します。
デフォルトでは、多くのLinuxディストリビューションは、暴走したスクリプトからシステムリソースを保護するために、1プロセスあたりわずか1,024 FDという保守的な制限を設定しています。現代の本番環境では、これらのデフォルト設定はリスクとなります。12の異なるクラスターで数ヶ月にわたり微調整を繰り返した結果、脆弱なデフォルト設定から堅牢で高パフォーマンスな構成へと移行するための正確なワークフローを導き出しました。
リソース管理の3つのレイヤー
トラブルシューティングの結果、リソース制限を処理するための3つの異なる方法があることが分かりました。間違ったレイヤーを選択することが、システムのリブートやサービスの再起動後に変更が「消えて」しまう主な原因です。
1. シェルセッション(ulimitコマンド)
これは多くのフォーラムで見つかる応急処置的な方法です。ターミナルで ulimit -n 65535 を実行すれば即座に反映されます。しかし、この変更はその特定のシェルセッションとその子プロセスにのみ適用されます。ログアウトしたりサーバーを再起動したりすると、制限はデフォルトの1024に戻ります。クイックテストには最適ですが、永続的な本番環境の修正には役に立ちません。
2. ユーザーレベルの設定(/etc/security/limits.conf)
これは、特定のユーザーまたはグループに対してハード制限(hard limit)とソフト制限(soft limit)を定義する従来のアプローチです。対話型のSSHセッションには有効ですが、よくある落とし穴があります。systemd によって管理される現代のサービスは、limits.conf を完全に無視します。このファイルを編集してNginxサービスを拡張しようとしても、その変更が反映されることはありません。
3. サービスレベルのオーバーライド(systemdユニットファイル)
Ubuntu 22.04+、Debian、AlmaLinuxなどの現代的なディストリビューションでは、systemdが「信頼できる唯一の情報源(Source of Truth)」です。NginxやMySQLなどのサービス制限を変更するには、サービスオーバーライドを使用する必要があります。これが本番環境において最もピンポイントで信頼性の高い方法です。なぜなら、サービスの起動方法に関わらず、そのサービスに設定が紐付けられるからです。
管理方法の比較
| 方法 | メリット | デメリット |
|---|---|---|
| ulimitコマンド | 即座に反映、制限を下げるにはroot不要。 | ログアウト時に消失. 永続性なし。 |
| limits.conf | マルチユーザー環境や開発者に適している。 | systemdに無視される。新しいログインセッションが必要。 |
| systemdオーバーライド | 現代的なサービスの標準。再起動後も維持。 | daemon-reloadが必要。サービスごとに設定。 |
| sysctl (カーネル) | OS全体の絶対的な上限を設定。 | プロセスごとの制限が低いままだと効果がない。 |
実証済みの本番環境戦略
最も安定した戦略は、ハイブリッドアプローチです。まずカーネルレベルの上限を引き上げ、一般ユーザー向けに適切なデフォルト値を設定し、その上で高負荷なアプリケーションに対して明示的に高い制限を与えます。50以上のVPSインスタンスを管理してきた本番環境での経験から、実行中のプロセスの視点から常に制限を確認することを学びました。かつて「ハード」制限が「ソフト」制限より低く設定されていたために、OSが設定全体を密かに破棄し、3時間のダウンタイムが発生したことがあります。
推奨設定:
- カーネル: システム全体の負荷をハードウェアが処理できるように、
fs.file-maxを2,097,152(200万)に設定します。 - Systemd: すべての高トラフィックなサービスユニットに
LimitNOFILE=65535を適用します。 - ユーザー: 1つのバグだらけのスクリプトがノード全体をクラッシュさせるのを防ぐため、開発者向けに
limits.confで10,000 FDの制限を設定します。
実装ガイド
ステップ1:診断コマンド
推測せず、/proc ファイルシステムを確認しましょう。Nginxが動作していれば、設定ファイルの内容に関わらず、ライブの制限値を確認できます。
# Nginxプロセスのライブ制限を取得する
cat /proc/$(pidof nginx | awk '{print $1}')/limits | grep "Max open files"
プロセスが現在いくつのファイルを開いているかを確認するには、lsof を使用します。この数値が1024に近い場合は、危険信号です:
# 特定のPIDのアクティブなファイル記述子をカウントする
lsof -n -p <PID> | wc -l
ステップ2:カーネル制限の引き上げ
システム全体の制限が低すぎると、プロセスレベルのチューニングは機能しません。現在のグローバルな最大値を確認してください:
cat /proc/sys/fs/file-max
これを永続的に200万に増やすには、sysctl.conf を編集します:
# /etc/sysctl.conf に追加
fs.file-max = 2097152
# 新しい設定を即座に読み込む
sysctl -p
ステップ3:systemdオーバーライド(現代の標準)
NginxやRedisのようなサービスには、オーバーライドファイルを作成します。これはメインのサービスファイルを直接編集するよりもクリーンで、パッケージのアップデート時に上書きされる心配もありません。
# Nginxのオーバーライドエディタを開く
systemctl edit nginx
以下のブロックを貼り付けます:
[Service]
LimitNOFILE=65535
デーモンをリロードしてサービスを再起動し、変更を適用します:
systemctl daemon-reload
systemctl restart nginx
ステップ4:スクリプト用のユーザー制限
手動で移行スクリプトを実行する deploy ユーザーがいる場合は、/etc/security/limits.conf を更新します:
# /etc/security/limits.conf
deploy soft nofile 10000
deploy hard nofile 65535
**注:** ユーザーは自分の「ソフト」制限を「ハード」制限まで自分で引き上げることができます。「ハード」制限はrootのみが変更できる天井として機能します。
まとめ
効果的なリソース管理とは、可視化することです。これらの最適化された設定を半年間運用した後、「Too many open files」エラーは完全に解消されました。最大の教訓は何でしょうか?設定ファイルを盲信せず、/proc/[PID]/limits の出力を信頼することです。そのファイルが1024を示していれば、プロセスは1024で止まっています。カーネルレベルのチューニングとsystemdオーバーライドを組み合わせることで、急激なトラフィック急増時でも稼働し続けるサーバー環境を構築できます。

