リソース制限の設計 — 1プロセスの暴走で全体を巻き込まないために

本番環境で複数のサービスやジョブを同居させるとき、リソース制限を設けないまま運用を始めると、1プロセスの暴走が隣のサービスを道連れにする。設計判断の核心は「制限するかどうか」ではなく、「どの単位で・どこまで絞るか・超過時に何を起こさせるか」という三つの軸を先に決めることにある。この三軸を整理しないまま制限を後付けすると、本番稼働後に二重投資の設計変更を迫られる結果になりやすい。

制限をかけずに同居させて共倒れする構造

「まず動かす」が招く多重障害の連鎖

初期構築では「まず動けばよい」という判断が優先され、リソース制限が後回しになりやすい。メモリを食い尽くすバッチ処理がOOMKillerを起動し、同一ホスト上のWebサーバーがプロセスごと落ちる、という障害は珍しくない。5〜10サービスが同居するアプリサーバーでは、1つのジョブが引き金になった連鎖停止を何十分も把握できないまま放置するケースが報告されている。初期の「とりあえず動く」状態がそのまま本番に移行するとき、リソース制限の欠如は時限爆弾として埋め込まれる形になる。

ノイジーネイバー問題と隠れたリソース競合

クラウド上の仮想マシンやコンテナ環境では、同じ物理ホストに相乗りする他テナントがCPUやディスクIOを独占すると性能が劣化する。オンプレミスのベアメタルサーバーに複数サービスを同居させる場合、この競合は自分たちの設計次第で発生する。特にI/O待ちが支配的なDBと、CPUバウンドなバッチ処理を同一ホストに置く構成は危険で、どちらかが高負荷になった瞬間にもう片方のレイテンシが急騰する。隠れた競合の大半は通常時には表面化しないため、「設計の問題」と認識されないまま長期間放置される。

「問題は起きていない」が最も危険な状態

リソース制限がなくても、普段は問題が起きない。月末バッチや突発的なアクセス集中でリソースが枯渇して初めて、制限の欠如が露わになる。本番稼働から3〜6ヶ月以内に「想定外の多重障害」として顕在化するケースが多く、そのタイミングで設計を見直すのは工数の二重投資になる。「問題が起きてから設計する」という順序は、安定稼働の期間が長いほど見直しの影響範囲が広がる構造になっている。

CPU/メモリ/IOをどこまで制限するかの判断

ピーク値ではなく「許容できる上限」を起点にする

制限値の設定でよくある誤りは、観測されたピーク使用量をそのまま上限にすることだ。ピーク値を上限にすると、正常なピーク時に制限に引っかかりサービスが異常終了してしまう。正しい起点は「このプロセスが消費してよい最大量」であり、ホスト全体のリソースを先に割り振ってから逆算する。たとえば32GBメモリのホストに3サービスを同居させるなら、OSとカーネルが常時消費する2〜4GBを除いた残りを、役割と優先度に応じて分配する設計になる。割り振り後の余白をどれだけ残すかが、突発的な負荷への耐性を左右する。

CPUはソフトリミット・メモリはハードリミットで考える

CPUはバーストを許容しつつ平均を絞るソフトリミット的な設計が有効で、一時的な高負荷は許してもスロットリングで長期占有を防ぐ方向が主流だ。一方メモリはスワップを含めたハードリミットが現実的で、上限を超えた瞬間にプロセスを強制終了させる挙動を前提に設計する。ディスクIOは帯域幅の上限(BPS/IOPS)をサービスの特性に合わせて決める。書き込みが激しいログ収集プロセスが同一ホストのDBのIOを圧迫するという問題は、IO制限なしの環境で繰り返されるアンチパターンであり、構築済みの環境でも見直しを求められることが多い。

制限値を決めるためのデータ収集の期間設計

制限値を決める前に最低2〜4週間は無制限でメトリクスを収集するのが現実的な手順だ。その期間にCPU・メモリ・IOそれぞれのp99値を把握し、「通常ピークの1.5〜2倍」を上限の仮値として設定する。実運用のデータが根拠になるため、推測で決めた制限値よりはるかに精度が高い。仮値を設定した後は、アラートを先行させてから制限を段階的に締めていくフローが、設計の安全弁として機能する。この順序を守らずに本番で一気に制限を適用すると、正常な処理を誤って殺すリスクが高まる。

比較表: cgroup/ulimit/ディスククォータの守備範囲

手段 適用単位 制限できるリソース 主な用途 設計上の注意点
cgroup v2 プロセスグループ(サービス・コンテナ) CPU・メモリ・IO帯域幅 同居サービスの隔離、コンテナリソース管理、systemdスライス 子プロセスも一括管理できる半面、設定の階層が複雑になる
ulimit(rlimit) プロセス単体・ユーザー全体 FD数・プロセス数・最大ファイルサイズ・CPU時間 フォークボム防止、FD不足対策、共有開発サーバーの制御 子プロセスのグループを束ねられない。サービスアカウントの分離が前提
ディスククォータ ユーザー・グループ・プロジェクト ブロック使用量・inode数 マルチユーザー・マルチテナント環境での容量配分 IO速度ではなく使用量の総量を制限する。IO帯域はcgroupで別途管理

cgroupはプロセスグループ全体を束ねる

Linux cgroupはプロセスの集合体に対してCPU・メモリ・IOのリソース制限をカーネルレベルで適用する仕組みだ。コンテナランタイム(Docker/containerd)は内部でcgroupを使っており、Kubernetesのrequestsとlimitsもcgroupのラッパーとして機能している。ベアメタルやVM上で複数サービスを同居させる場合も、systemdのスライスとしてcgroupを直接使うことで、プロセスが子プロセスを生成してもグループ全体に制限を適用できる。この「グループ全体への適用」がcgroupの最大の強みであり、プロセス単体にしか効かないulimitとの決定的な違いになる。

ulimitはプロセス単体・ユーザー単体の上限

ulimitはLinuxカーネルのrlimitを設定するインターフェースで、オープンできるファイルディスクリプタ数・生成できるプロセス数・最大ファイルサイズなどを1プロセスまたはそのユーザー全体に適用する。cgroupと異なり子プロセスの集合を束ねることはできないが、「1サービスが同時に開けるFD数を65536以内に収める」「フォークボムによるプロセス爆発を防ぐ」といった用途では今でも現役だ。ApacheやNginxのようなforkモデルのサービスでは、ファイルディスクリプタ不足が障害として顕在化するため、ulimitの設定はサービスの起動定義ファイルに明示しておく必要がある。

ディスククォータはストレージの公平配分に使う

ディスクのブロック使用量とinode使用量を、ユーザー・グループ・プロジェクト単位で上限付きで管理するのがディスククォータだ。Webホスティング環境やマルチテナントのファイルサーバーで、特定ユーザーがディスクを使い切って他ユーザーの書き込みができなくなるのを防ぐ目的で使われる。cgroupのIO制限が「帯域幅の速度」を制限するのに対し、ディスククォータは「使用量の総量」を制限するという守備範囲の違いを理解してから使い分けを決めることが重要だ。この二つを混同すると、どちらかが欠けた設計になる。

プロセス・ユーザー・コンテナのどの単位で絞るか

単位の選択がトラブル時の影響範囲を決める

制限をかける単位の選択は、超過が起きたときに「どの範囲が影響を受けるか」を直接決定する。プロセス単位の制限は精度が高いが、子プロセスや孫プロセスの集合に適用しにくい。ユーザー単位の制限は管理が単純になる半面、同一ユーザーで動く複数サービスが一括で影響を受ける。コンテナ単位はサービス境界に最も近く、オーケストレーション下では標準的な設計単位になっている。単位の選択ミスは、制限が機能しないか、逆に広すぎる範囲を巻き込む二方向の問題を生む。

同居構成ごとの推奨単位

ベアメタルに直接複数サービスを並べる構成では、systemdサービスごとにcgroupスライスを切り、CPU・メモリをサービス単位で制限するのが管理しやすい設計だ。10〜20サービスが同居するような環境では、サービスが増えるたびに手動でulimitを設定する方法では設定漏れが生じるため、systemdのUnitファイルにリソース制限を記述する形で宣言的に管理することが重要になる。KubernetesやECSのコンテナ環境では、コンテナ単位のrequests/limitsを設計のデフォルトにし、NamespaceやResourceQuotaで上位のガードレールを設ける二段構えが定石とされている。

ユーザー単位制限が有効な場面

複数のユーザーがログインしてコマンドを実行する共有の開発サーバーや、マルチユーザー環境のバッチ実行基盤では、ユーザー単位のulimitが現実的な選択肢になる。ユーザーごとにCPU時間・最大プロセス数・メモリを制限しておくと、1人の作業者が重い処理を走らせても他のユーザーへの影響を抑えられる。ただし、システムサービスも同一ユーザーで動いていると制限が想定外のプロセスに適用される。サービスアカウントと人間のユーザーを分離するという基本設計が、ユーザー単位制限の前提条件になる点は見落とされやすい。

制限に引っかかった時の挙動をどう設計するか

「静かに失敗する」か「明示的に落ちる」かを先に決める

制限超過時の挙動は、サービス特性によって設計が変わる。バッチ処理なら、メモリ上限に達したらOOMKillerが強制終了させ、ジョブキューが再試行する設計が成立する。一方、リクエストを受けるAPIサーバーでは、OOMで強制終了が起きるとリクエストが失われるため、制限に達する前にアプリケーション側でメモリを解放するか、上位のロードバランサーがヘルスチェックで切り離す設計が必要になる。「どう落ちるか」ではなく「どう落とすか」を先に設計する、という順序の違いが品質を分ける。超過時の挙動を後から付け足すと、上位設計との整合が取れないケースが出やすい。

CPU制限のスロットリングとプロセス終了の違い

CPUの制限超過は、多くの場合プロセスを終了させずにスロットリング(処理速度を落とす)で対応する。これはメモリ超過時のOOMKillとは挙動が根本的に異なる。スロットリングによる遅延はモニタリングで観測しにくく、「レスポンスが遅い」という症状だけが表面に出るため、原因特定が遅れることがある。設計段階でCPUスロットリングを可視化するメトリクスをダッシュボードに組み込んでおくと、制限設定の妥当性を運用中に検証できる体制が整う。スロットリングが常態化している場合は、制限値を緩めるかサービスを分割するかという判断に進む。

超過時のアラート設計と制限値の段階的な締め方

制限に引っかかった後の検知が遅れると、ユーザー影響が長期化する。制限値の80〜90%に達した時点でアラートを出し、実際の制限超過よりも前に対処が始まるよう設計する。段階的な締め方として、最初は余裕を持たせた値を設定し、モニタリングで安定を確認してから10〜20%ずつ削っていく方法が、突然の正常処理停止を防ぐ上で有効だ。このフローを取り入れると、制限値の最終調整に要する期間はおよそ2〜3週間かかることが多い。その期間を見込まずに「最初から最適値を設定する」という進め方をとると、後から修正工数が発生する。

締めすぎて正常な処理まで殺す境界の見極め

過小な制限が生む「原因不明の障害」

制限を厳しくしすぎると、正常なリクエストや定期処理がエラーになる。特にメモリ制限を厳密に設定したコンテナ環境では、JVMやPHPのメモリアロケータが確保しようとするヒープ量が制限値を超え、アプリが起動直後に落ちるケースがある。構築を任されたある担当者が、デプロイ直後にコンテナが繰り返し再起動する症状の原因をアプリのバグと誤認し、2〜3日間調査した末にメモリ制限の過小設定と判明した、という話は現場でよく聞く。過小な制限は見えにくい障害を生むという点で、無制限と同程度のリスクを持つアンチパターンだ。

定常処理と突発処理のメモリフットプリントの差

Webサービスでは、通常のリクエストに使うメモリと、キャッシュウォームアップ・起動時のクラスローディング・ガベージコレクション実行時のヒープ膨張で使うメモリが大きく異なる。制限値を「定常のピーク値」で決めると、起動時やGCサイクルで上限に触れてプロセスが強制終了される。この境界の見極めには、起動直後30分間を含むメモリのタイムラインを取得し、最大フットプリントを把握してから制限値を設定するという手順が現実的だ。起動時の最大値が定常の1.5〜2倍になるケースはJVMベースのサービスで特に多く、この差を無視した制限値は機能しない。

バースト許容とソフト制限の組み合わせ

長期の平均は低いが短時間のバーストが多いワークロードには、cgroupのCPU制限をソフトリミット(CPUシェアやクォータ)として設計し、リソースが空いている時間帯はバーストを許容する方向が有効だ。ホスト上でそのプロセスだけが動いているなら、割り当てられたシェアを超えて使わせてよい。隣のサービスが負荷を上げてきたときに初めてシェアに応じてスロットリングが入る仕組みを使うと、使われていないリソースを無駄にしない設計ができる。この仕組みを理解しないまま全サービスに固定のCPUハードリミットを設定すると、静かなホストでも人為的なボトルネックを自ら作り出してしまう。

リソース制限設計チェックリスト

以下のチェックリストは、本番環境への適用前に設計の抜け漏れを確認するために使う。各項目が「決まっていない」なら、そこが設計上の空白になっている。手を動かす前に確認し、決まっていない項目を明示してから実装フェーズに移ることが重要だ。

  • 制限をかける単位(プロセス・ユーザー・コンテナ・systemdサービス)を決め、サービスごとに記録してあるか
  • CPU・メモリ・IOそれぞれの制限値を、実測のp99値または2〜4週間分のメトリクスから算出してあるか
  • 制限値がピーク使用量ではなく「ホスト全体の割り振りからの逆算値」として設定されているか
  • メモリ制限において、起動時・GCサイクル時のフットプリントを含めた最大値を考慮しているか
  • CPU制限をソフトリミット(シェア方式)とハードリミット(クォータ方式)のどちらにするか判断してあるか
  • 制限超過時の挙動(OOMKill・スロットリング・エラー返却)をサービスごとに把握し、ジョブキューやロードバランサーのヘルスチェックと整合しているか
  • アラートを制限値の80〜90%で設定し、超過前に検知できる体制になっているか
  • ulimitで設定すべき項目(FD数・最大プロセス数)が、サービスの起動定義ファイルに明記されているか
  • ディスクのブロック使用量制限(クォータ)が必要なマルチユーザー・マルチテナント環境かどうかを判断してあるか
  • 制限値を段階的に締める計画(初期値→モニタリング→10〜20%ずつ削減)があり、担当者に共有されているか
  • コンテナ環境の場合、Namespace/ResourceQuotaなど上位のガードレールが設定されているか
  • サービスアカウントと人間のユーザーが分離されており、ユーザー単位の制限が想定外のプロセスに適用されない構成になっているか

▶ 手を動かして本番まで到達する

その設計判断を、RHEL10の本番環境で手を動かして固めませんか

ここまでの設計判断は、読めば腹に落ちます。ですが「自分の手で本番まで通しきる自信」は、それとは別の話になります。

最大6名の少人数で、構築実務者の到達点まで引き上げます。座学で終わらせず、本番相当の環境で判断を検証する2日間です。

リソース制限の設計をRHEL10本番で検証する上級講座へ →

Linuxセミナー上級編(RHEL10)|最大6名の少人数ハンズオン