ulimitによる制限はシェルセッション単位でしか機能しませんが、Linux標準の仕組みである cgroup(Control Group) を使えば、OSレベルでサービスごとにCPU・メモリ・I/Oの使用量に上限を設けることができます。RHEL 9 / Rocky Linux 9 / Ubuntu 22.04以降では cgroupv2(cgroup v2)が標準となり、systemdと深く統合されているため、Unitファイルに数行追加するだけで設定できます。
この記事では、systemd Unitファイルを使ったcgroupv2リソース制限の設定方法を、RHEL 9.4の実機出力を交えて解説します。動作確認環境はRHEL 9.4 / Rocky Linux 9.4 / Ubuntu 22.04 LTSです。対象はサービス単位でリソースを管理したいすべてのLinuxサーバー管理者です。
この記事のポイント
・cgroupv2はsystemdと統合済み — UnitファイルでCPU・メモリを制限できる
・CPUQuota=50% でCPU使用率を1コアの50%に上限設定できる
・MemoryMaxを超えたプロセスはOOMKillerによって強制終了される
・systemctl showとsystemd-cgtopで設定値と実使用量を確認できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
cgroupv2とは何か — なぜsystemdとの連携が重要なのか
cgroup(Control Group)はLinuxカーネルの機能で、プロセスのグループに対してCPU・メモリ・I/O・ネットワーク帯域などのリソースを制限・計測できます。cgroupv1とcgroupv2の2バージョンがありますが、RHEL 8 / Ubuntu 21.10以降のモダンなLinuxディストリビューションではcgroupv2が既定です。cgroupv1との最大の違いは 単一の統合ツリー構造 にあります。v1ではcpu・memory・blkioなどコントローラーごとに別々のcgroupツリーが存在し、管理が煩雑でした。v2では単一の階層ツリーに統合されたため、一元管理が容易です。また、メモリ制限の精度が向上し、I/Oスロットリングがより細かく制御できるようになりました。
systemdはcgroupv2を積極的に活用しており、各サービス(Unitファイル)はそれぞれ固有のcgroupスコープを自動的に持ちます。これは Unitファイルに設定を追加するだけで、cgroupv2のリソース制限が即座に反映される ことを意味します。複雑なcgroupfsへの直接操作は不要です。
まず現在のシステムがcgroupv2を使っているか確認しましょう。
# cgroupv2の確認(出力が "cgroup2" であればv2が有効) [server01 ~]$ stat -fc %T /sys/fs/cgroup/ cgroup2 # mountコマンドでも確認できる [server01 ~]$ mount | grep cgroup cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
cgroup2 と表示されれば、cgroupv2が利用可能です。cgroup(v1)のみ表示される場合はカーネルバージョンが古いか、systemdのcgroup統合設定が必要です。systemd Unitファイルでリソース制限を設定する方法
systemdはcgroupv2のリソース制限を、Unitファイルの[Service] セクションのディレクティブで指定します。設定変更は daemon-reload 後にサービスを再起動することで反映されます。1. CPUQuotaでCPU使用率の上限を設定する
CPUQuota は、サービスが使用できるCPU時間の割合を指定します。100%が1コア分相当です。4コアのサーバーで200%と指定すれば、2コア分まで利用できます。# /etc/systemd/system/myapp.service の [Service] セクションに追加 [Service] ExecStart=/usr/local/bin/myapp CPUQuota=50%
# Unitファイルの再読み込みとサービスの再起動 [server01 ~]$ sudo systemctl daemon-reload [server01 ~]$ sudo systemctl restart myapp.service
systemctl set-property が便利です。# 一時的にnginxのCPU使用率を80%に制限(--runtimeを付けると再起動で元に戻る) [server01 ~]$ sudo systemctl set-property --runtime nginx.service CPUQuota=80%
2. MemoryMax・MemoryHighでメモリ上限を設定する
メモリ制限には主に2つのディレクティブがあります。・MemoryMax: 絶対上限。これを超えると、そのcgroup内のプロセスがOOMKillerによって強制終了される
・MemoryHigh: ソフト上限。超えるとカーネルが積極的なページ回収やスワップアウトを行うが、プロセスは生き続ける
実務では両方を組み合わせて「徐々に制約をかけて、最悪の場合にのみ終了させる」設計が安全です。
[Service] ExecStart=/usr/local/bin/myapp MemoryHigh=400M MemoryMax=512M
K(キロバイト)、M(メガバイト)、G(ギガバイト)で指定します。パーセント指定(例: MemoryMax=25%)も可能で、システム搭載メモリの25%が上限になります。3. IOReadBandwidthMax・IOWriteBandwidthMaxでI/O帯域を制限する
ストレージへのI/O帯域が特定サービスに独占されると、他のサービスのディスク応答が遅くなります。IOReadBandwidthMax と IOWriteBandwidthMax でデバイスごとに帯域を制限できます。[Service] ExecStart=/usr/local/bin/backup-tool # デバイスパスと帯域上限(バイト/秒)を指定 IOReadBandwidthMax=/dev/sda 50M IOWriteBandwidthMax=/dev/sda 50M
/dev/mapper/rl-root のようにLV名を指定します。以上の設定をまとめた実践的なUnitファイル例を示します。
# /etc/systemd/system/myapp.service [Unit] Description=My Application Service After=network.target [Service] Type=simple User=myapp ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml Restart=on-failure # --- リソース制限 --- CPUQuota=50% MemoryHigh=400M MemoryMax=512M IOWriteBandwidthMax=/dev/sda 50M [Install] WantedBy=multi-user.target
4. 既存サービスはDropInファイルで安全に上書きする
nginx.serviceなど既存パッケージが提供するUnitファイルを直接変更すると、パッケージ更新で設定が上書きされます。DropInファイルを使えば元のUnitファイルに手を加えずに設定を追加できます。# systemctl edit コマンドでDropInファイルを自動作成・編集する [server01 ~]$ sudo systemctl edit nginx.service # エディタが開くので以下を入力して保存する [Service] CPUQuota=80% MemoryMax=1G # daemon-reloadとrestart(systemctl editは自動でdaemon-reloadを実行するが明示的に) [server01 ~]$ sudo systemctl daemon-reload [server01 ~]$ sudo systemctl restart nginx.service
systemctl edit を実行すると /etc/systemd/system/nginx.service.d/override.conf が作成されます。DropInファイルはパッケージ更新の影響を受けません。cgroup設定の確認と実測値の見方
設定が意図通りに適用されているか、必ず確認作業を行いましょう。1. systemctl showで設定値を確認する
[server01 ~]$ sudo systemctl show myapp.service | grep -E 'CPUQuota|MemoryMax|MemoryHigh|IOWrite' CPUQuotaPerSecUSec=500ms MemoryHigh=419430400 MemoryMax=536870912 IOWriteBandwidthMax=8:0 52428800
CPUQuotaPerSecUSec=500ms は50%をカーネル内部単位に換算した値(1秒あたり500ミリ秒のCPU時間)です。MemoryMax=536870912 は512MBのバイト換算値です。正しい値が反映されているか目視で確認してください。2. cgroupfsでリアルタイムの使用量を確認する
実際にどれだけリソースを消費しているかはcgroupfsのファイルから直接確認できます。# サービスのcgroupパスを確認 [server01 ~]$ sudo systemctl show myapp.service -p ControlGroup ControlGroup=/system.slice/myapp.service # 現在のメモリ使用量(バイト単位) [server01 ~]$ cat /sys/fs/cgroup/system.slice/myapp.service/memory.current 47513600 # CPU使用量の統計情報 [server01 ~]$ cat /sys/fs/cgroup/system.slice/myapp.service/cpu.stat usage_usec 3821043 user_usec 3124891 system_usec 696152 nr_periods 382 nr_throttled 12 throttled_usec 241837
nr_throttled が0より大きい場合、CPUQuotaによる絞り込みが実際に発生していることを意味します。値が増え続けている場合は上限値の見直しが必要です。3. systemd-cgtopでcgroup全体をリアルタイム監視する
systemd-cgtop コマンドを使うと、cgroup単位のリソース使用状況をtopコマンドのように一覧できます。[server01 ~]$ sudo systemd-cgtop Control Group Tasks %CPU Memory Input/s Output/s / 92 3.5 1.8G - - /system.slice - 2.1 956.4M - - /system.slice/myapp.service 3 1.8 453.2M - - /system.slice/nginx.service 4 0.2 45.3M - - /system.slice/sshd.service 2 0.0 12.1M - -
トラブルシュート — よくある問題と対処法
1. CPUQuotaを設定しても制限が効かない
原因: cgroupv2ではなくcgroupv1が使われている、またはsystemdのバージョンが古い場合に発生します。# cgroupのバージョンを確認 [server01 ~]$ stat -fc %T /sys/fs/cgroup/ cgroup2 # systemdのバージョン確認(245以上を推奨) [server01 ~]$ systemd --version | head -1 systemd 252 (252-46.el9_5)
cgroup(v1)と表示される場合は、GRUBのカーネルオプションに systemd.unified_cgroup_hierarchy=1 を追加してcgroupv2を有効化してください。2. MemoryMaxを設定したらプロセスがすぐ落ちる
原因: MemoryMaxがアプリケーションの実際の必要メモリより小さく設定されており、起動直後にOOMKillerが動作しています。対処手順:
① まず制限なしで起動し、ピーク時のメモリ使用量を計測する
# MemoryMaxを設定していない状態で起動後、使用量を確認 [server01 ~]$ cat /sys/fs/cgroup/system.slice/myapp.service/memory.peak 234881024 # バイト → MB換算 [server01 ~]$ echo "234881024 / 1024 / 1024" | bc 224
③ journalctlでOOMのログを確認して原因を特定する
# OOMKillerのログを確認(-1は前回起動分まで遡る場合に使用) [server01 ~]$ sudo journalctl -b -p err | grep -i 'oom\|killed process\|out of memory' Aug 23 10:15:22 server01 kernel: myapp invoked oom-killer: gfp_mask=0x400dc0, order=0 Aug 23 10:15:22 server01 kernel: Memory cgroup out of memory: Killed process 12345 (myapp)
3. daemon-reloadを忘れてUnitファイルの変更が反映されない
Unitファイルを直接編集した場合は 必ずsystemctl daemon-reload を実行してから systemctl restart してください。この順番を守らないと、古いUnitファイルが使われ続けます。# 正しい適用順序 [server01 ~]$ sudo systemctl daemon-reload # 1. ファイル再読み込み [server01 ~]$ sudo systemctl restart myapp # 2. サービス再起動 # 変更が反映されているか確認 [server01 ~]$ sudo systemctl show myapp.service | grep CPUQuota CPUQuotaPerSecUSec=500ms
4. コンテナ(Docker)内のサービスにはUnitファイルが使えない
Docker・Podmanコンテナ内でsystemdが動いていない場合、本記事のUnitファイル設定は機能しません。コンテナのリソース制限はdocker run --cpus や --memory オプションでDockerレイヤーで行うか、コンテナを管理するsystemdサービス(podman-auto-update等)のUnitファイルで制限をかけてください。本記事のまとめ
| やりたいこと | 設定ディレクティブ | 例 |
|---|---|---|
| CPU使用率に上限を設ける | CPUQuota= |
CPUQuota=50% |
| メモリのソフト上限(スワップ促進) | MemoryHigh= |
MemoryHigh=400M |
| メモリの絶対上限(超過でOOM) | MemoryMax= |
MemoryMax=512M |
| ディスクI/O書き込み帯域を制限する | IOWriteBandwidthMax= |
IOWriteBandwidthMax=/dev/sda 50M |
| 設定値を確認する | systemctl show |
systemctl show myapp.service | grep CPU |
| cgroup全体をリアルタイム監視する | systemd-cgtop |
sudo systemd-cgtop |
| 既存サービスへ安全に設定を追加する | systemctl edit |
sudo systemctl edit nginx.service |
cgroupv2の設定は、Linuxサーバー設計の「型」を知っていれば迷わない
リソース制限の手順は追えても、「なぜMemoryHighとMemoryMaxを両方設定するのか」「OOMKillerが走る前にどう監視設計するか」を体系的に説明できますか?
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:LinuxでToo many open filesエラーが出た時の対処法|ファイルディスクリプタ枯渇の調査からulimit・sysctl設定まで
- この記事の属するカテゴリ:Linuxtips・Linuxトラブルシューティングへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます