cgroupv2でsystemdサービスのCPU・メモリを制限する方法|プロセス暴走をシステムレベルで防ぐ実践手順

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Linuxtips, Linuxトラブルシューティング > cgroupv2でsystemdサービスのCPU・メモリを制限する方法|プロセス暴走をシステムレベルで防ぐ実践手順
「特定のサービスがCPUを食いつぶして、他のサービスまで巻き添えで遅くなった」「メモリを大量消費するプロセスを放置したら、夜中にOOMKillerが走ってサービスが落ちた」 — こうした経験は、Linuxサーバー管理の現場で誰もが一度は直面する問題です。

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で設定値と実使用量を確認できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

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

本番環境でUnitファイルを編集せずに動作確認したい場合は 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帯域が特定サービスに独占されると、他のサービスのディスク応答が遅くなります。IOReadBandwidthMaxIOWriteBandwidthMax でデバイスごとに帯域を制限できます。

[Service] ExecStart=/usr/local/bin/backup-tool # デバイスパスと帯域上限(バイト/秒)を指定 IOReadBandwidthMax=/dev/sda 50M IOWriteBandwidthMax=/dev/sda 50M

LVMを使用している場合は /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

② 実測ピーク値の1.5~2倍をMemoryHighに、2倍をMemoryMaxに設定する

③ 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とsystemdの組み合わせは、プロセス暴走を未然に防ぐための強力な仕組みです。Unitファイルに数行追加するだけで本番環境のリソース保護が実現できます。重要なポイントは「MemoryHighとMemoryMaxを両方設定してソフトランディングを設計すること」、そして「設定後にcgroupfsやsystemd-cgtopで実測値を確認して上限値が適切かを検証すること」です。まずは負荷の高いサービス1つから試してみてください。

cgroupv2の設定は、Linuxサーバー設計の「型」を知っていれば迷わない

リソース制限の手順は追えても、「なぜMemoryHighとMemoryMaxを両方設定するのか」「OOMKillerが走る前にどう監視設計するか」を体系的に説明できますか?
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。