Linuxのプロセス管理とリソース制御: nice、renice、cgroups、systemdでサーバーパフォーマンスを最適化する

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

クイックスタート: 暴走プロセスの制御 (5分)

Linuxサーバーが重くなり、インタラクティブセッションが遅延したり、Webサービスが応答しなくなったりした経験はありませんか?多くの場合、その原因は、CPUやメモリなどのシステムリソースを大量に消費している単一のプロセス、またはプロセス群です。Linuxはマルチタスクに優れていますが、時にはタスクがリソースを過度に消費してしまうことがあります。

多くの場合、問題は適切に制御されていないプロセスに起因します。Linuxプロセスは通常、リソースを公平に共有しますが、単一のCPU集約的な計算やメモリを大量に消費するアプリケーションは、すぐに利用可能なすべての容量を奪い去ってしまう可能性があります。

迅速な解決策として、プロセスの優先度をその場で調整できます。Linuxは「nice値」を使用します(-20から19の範囲で、数値が小さいほど優先度が高くなります)。niceコマンドは指定されたnice値で新しいプロセスを起動し、reniceはすでに実行中のプロセスの優先度を変更します。

低い優先度でプロセスを起動する

プライマリサービスに干渉させたくない、長時間実行されるバックアップタスクがあるとします。これをより高い(より低い優先度)nice値で開始できます。

nice -n 10 tar -zcf /tmp/website_backup.tar.gz /var/www/html

ここで、-n 10niceコマンドに、tarコマンドをnice値10で開始するように指示します。これは実行されますが、他のより優先度の高いプロセスに対してCPU時間をより簡単に譲るようになります。

実行中のプロセスの優先度を調整する

もしプロセスがすでに暴走している場合はどうでしょうか?まず、そのプロセスID(PID)を特定します。例えば、Pythonスクリプトが過度にCPUを消費している場合:

ps aux | grep my_data_processor.py

PIDが分かったら、reniceを使用してnice値を変更できます。優先度を下げる(nice値を上げる)には:

sudo renice -n 15 -p <PID>

いくつか理由があってsudoが必要です。プロセスを「よりniceにする」(より高いnice値を割り当てる)場合や、自身のプロセスに負の値(より高い優先度)のnice値を設定したい場合に必要です。他のユーザーが所有するプロセスに負のnice値を割り当てることができるのは、rootユーザーのみであることに注意してください。

systemdによって管理されるサービスの場合、ユニットファイル内でデフォルトのnice値を設定することもできます。サービスファイル(例:sudo systemctl edit --full my-service.service)を編集して、以下を追加します。

[Service]
Nice=10

その後、サービスをリロードして再起動します:sudo systemctl daemon-reload && sudo systemctl restart my-service.service

詳細解説: 優先度とリソース制限の理解

nicereniceはCPUスケジューリングの優先度を管理する上で素晴らしいツールですが、プロセスが消費するCPU量を実際に制限するわけではありません。さらに重要なことに、これらはメモリ、I/O、ネットワーク帯域幅を管理しません。まさにここでControl Groups、つまりcgroupsが不可欠になります。

niceとreniceの役割

「nice値」はLinuxスケジューラに影響を与えます。nice値が低いプロセス(例:-10)は「あまりniceではない」と見なされ、より多くのCPU時間を得ます。逆に、nice値が高いプロセス(例:19)は「非常にniceである」ことを意味し、より少ないCPU時間を得ます。これは協力的なメカニズムとして機能し、「niceな」プロセスは、より高い優先度の他のプロセスが必要とする場合、喜んでCPUサイクルを譲ります。

  • 範囲: -20(最高の優先度)から19(最低の優先度)。
  • デフォルト: 0。
  • 影響: 主にリソースを競合するプロセス間でのCPU時間割り当てに影響します。

これらのコマンドは、重要ではないバックグラウンドタスクに最適であり、フォアグラウンドのアクティビティに大きな影響を与えることなくプロセスを実行できます。最終的に、nicereniceは厳格なリソース制限を強制するのではなく、CPU共有における公平性を促進します。

Control Groups (cgroups)の導入

niceが完全に解決できない問題は、リソースの*制限*です。重要なサービスに一定量のCPUを保証したい、あるいは開発ビルドが利用可能なすべてのRAMを消費するのを防ぎたい、といった場合があります。これこそがcgroupsが対応するものです。

Control Groups (cgroups)を使用すると、プロセスを階層的なグループに整理できます。そして、CPU、メモリ、I/O帯域幅、ネットワークなど、特定のシステムリソースを各グループに割り当てることができます。これは、プロセス用の仮想コンテナを作成するようなもので、それぞれが厳格なリソース制約のセットを持っています。このコアメカニズムは、Dockerなどのコンテナ技術の基盤でもあります。

主なバージョンはcgroups v1とcgroups v2の2つです。cgroups v1はまだ広く使用されていますが、cgroups v2はより統一され、合理化されたアプローチを提供します。最新のLinuxシステムでは、systemdcgroupsに大きく依存しています。これはサービスやユーザーセッションを管理するためにcgroupsを使用しており、その採用と使用を大幅に簡素化します。

systemdとcgroups: 強力な連携

ほとんどのモダンなLinuxディストリビューションにおけるinitシステムおよびサービスマネージャーであるsystemdは、起動するプロセスを管理するために本質的にcgroupsを使用します。systemd内の各サービス、スライス、スコープ、およびユーザーセッションは、独自のcgroupを取得します。この設計により、systemdユニットファイル内で直接リソース制限を適用することが簡単になります。

この統合は画期的なものです。生のcgroupファイルを手動で操作する(これは複雑になる可能性があります)代わりに、systemdユニットファイルでリソース制約を宣言的に記述できます。例えば、サービスのCPUとメモリを制限するには:

# /etc/systemd/system/my-cpu-intensive-service.service

[Unit]
Description=CPUを大量に消費するサービス
After=network.target

[Service]
ExecStart=/usr/local/bin/my_long_running_calc.sh
CPUQuota=50%
MemoryLimit=1G

[Install]
WantedBy=multi-user.target

ここで:

  • CPUQuota=50%は、このサービスが1つのCPUコアの時間の50%以上を消費しないことを保証します。システムがアイドル状態であっても、これを超えることはありません。これは厳格な制限です。
  • MemoryLimit=1Gは、サービスを最大1ギガバイトのRAMに制限します。もしそれ以上を割り当てようとすると、Out-Of-Memory(OOM)キラーが介入する可能性があります。

サービスファイルを作成または変更した後、常にsystemdをリロードしてサービスを再起動してください:

sudo systemctl daemon-reload
sudo systemctl enable my-cpu-intensive-service.service
sudo systemctl restart my-cpu-intensive-service.service

この方法は、きめ細やかな制御を提供します。これにより、重要なサービスが必要なリソースを確実に得られるようになり、他のアプリケーションが貴重なシステム容量を占有するのを防ぎます。

高度な使用法: きめ細やかなリソース制御

基本的なCPUおよびメモリ制限を超えて、systemdcgroupsの統合は、さまざまなシステムリソースに対してさらにきめ細やかな制御を提供します。これは、複雑なサーバー環境や特定のリソース要求を持つアプリケーションを扱う場合に特に役立ちます。

IOWeightによるディスクI/Oの制御

ディスクI/Oはしばしばボトルネックとなることがあります。IOWeightディレクティブ(またはcgroups v1の場合はBlockIOWeight)を使用すると、他のサービスと比較してサービスに対するI/O優先度を設定できます。これはNiceに似ていますが、ブロックデバイスアクセス用です。

# /etc/systemd/system/my-backup-service.service

[Unit]
Description=私のバックグラウンドバックアップサービス

[Service]
ExecStart=/usr/local/bin/run_heavy_backup.sh
IOWeight=10     # ウェイトが低いほどI/Oの優先度が低いことを意味します
CPUQuota=30%
MemoryLimit=512M

[Install]
WantedBy=multi-user.target

10という値は非常に低い優先度です。デフォルトは1000です。

TasksMaxによるプロセス生成の制限

一部のアプリケーションは多くの子プロセスをフォークし、システムを圧倒する可能性があります。TasksMaxを使用すると、cgroup内で作成できるタスク(プロセスとスレッド)の数に上限を設定できます。

# /etc/systemd/system/my-app-server.service

[Unit]
Description=私のアプリケーションサーバー

[Service]
ExecStart=/usr/bin/my_app_server
TasksMax=100    # 100プロセス/スレッドに制限
CPUQuota=70%
MemoryLimit=2G

systemd-runによる一時的なサービス

ワンオフのコマンドや一時的なテストのために完全なサービスファイルを作成したくない場合はどうでしょうか?systemd-runがその答えです。これは、cgroupリソース制御が即座に適用された一時的なサービスまたはスコープとして任意のコマンドを実行することを可能にします。

例えば、CPUとメモリのストレステストを60秒間実行し、CPUを20%、RAMを256MBに制限するには:

sudo systemd-run --scope -p CPUQuota=20% -p MemoryLimit=256M stress --cpu 4 --timeout 60s

--scopeフラグは、それが「スコープ」ユニットとして実行されることを保証し、短命のコマンドに理想的です。通常のサービスファイルが受け入れる任意のリソースディレクティブを適用できます。

別の一般的なシナリオ:専用のユニットファイルなしで、低い優先度とメモリ制限でバッチジョブを実行する:

sudo systemd-run --scope -p Nice=10 -p MemoryLimit=1G /usr/local/bin/long_batch_job.sh

この機能は、インタラクティブセッションや自動化されたスクリプトに対して計り知れない柔軟性を提供します。特に、一時的でありながら制御された実行が必要な場合に役立ちます。

cgroupsの監視

cgroupsがどのように構成され、どのようなリソースを消費しているかを確認するには、systemd-cglssystemd-cgtopのようなツールを使用できます(これらは、例えばDebian/Ubuntuでapt install systemd-containerとしてインストールする必要があるかもしれません)。

systemd-cgls

このコマンドは、cgroupの階層全体を表示します。cgroupリソース使用量をリアルタイムで監視するには:

systemd-cgtop

これらのツールは、設定したリソース制限が期待通りに機能しているかどうかの洞察を与え、競合を診断するのに役立ちます。

実践的なヒント: いつ、どのように制御を適用するか

ツールを知っていることと、それらを効果的に適用することは別物です。ここでは、これらの概念を日常のサーバー管理に統合する方法について考えます。

nice/renice vs. cgroups/systemd リソース制御

  • nice/reniceを使用する場合:
    • 単一プロセスのCPU優先度をすばやく一時的に調整する必要があるとき。
    • タスクが重要ではなく、他のプロセスに対して「よりniceに」振る舞ってほしいだけの場合。
    • インタラクティブなコマンドや、一度限りのスクリプトを扱っている場合。
  • systemdを介してcgroups(CPUQuota、MemoryLimitなど)を使用する場合:
    • 重要なサービスに厳格なリソース制限や保証された割り当てが必要なとき。
    • 長時間実行されるバックグラウンドタスクやサービスを管理するとき。
    • 特定のアプリケーションで予測可能なパフォーマンスが必要なとき。
    • リソース分離が鍵となるマルチユーザーまたはマルチアプリケーションサーバー環境で運用しているとき。
    • サービスファイルで永続的なリソースポリシーを定義したいとき。

リソースを大量消費するプロセスを特定する

リソースを制御する前に、誰がそれらを使用しているかを知る必要があります。不可欠なツールには以下が含まれます:

  • tophtop: CPU、メモリ、プロセスのリアルタイム概要。
  • ps aux: 詳細なプロセス一覧。
  • pidstat -u 1 (sysstatパッケージより): プロセスごとのCPU使用率。
  • pidstat -r 1: プロセスごとのメモリ使用率。
  • pidstat -d 1: プロセスごとのI/O統計。

私の経験: 実世界での例

4GB RAMを搭載した本番のUbuntu 22.04サーバーで、かつて厄介な問題に遭遇しました。毎晩実行されるデータ処理スクリプトが時折CPUとメモリの使用量を急増させ、Webサービスが応答しなくなることがありました。当初、私はそのスクリプトのnice値を調整しようと試みました。これはCPUをより頻繁に譲るようにすることでいくらか役立ちましたが、特にスクリプトが非常に大きなデータセットを処理する際には、リソース競合を完全に防ぐことはできませんでした。

スクリプトのsystemdユニットファイル内でCPUQuotaMemoryLimitを直接適用したことで、状況は一変しました。そのバッチジョブに対してCPUQuota=70%MemoryLimit=2Gを設定しました。これにより、スクリプトが最も集中的に動作するフェーズでも、Webサーバーを完全にリソース枯渇状態に陥らせないことが保証されました。

この方法は、他の重要なサービスが常に十分なリソースを確保できるようにすることで、それらの処理時間を劇的に短縮しました。結果として、システムははるかに安定し、応答性が向上しました。かつては毎日の頭痛の種だったものが、ほとんど意識することのない静かなバックグラウンドプロセスになったのです。

小さく始めて、徹底的にテストする

cgroupの制限、特にメモリやCPUの割り当てを適用する際は、控えめな値から始めてシステムを監視してください。誤った制限設定は、サービスを予期せずクラッシュさせる可能性があります。実際の使用状況とパフォーマンステストに基づいて、徐々に制限を調整してください。

過度な最適化を避ける

強力ではありますが、すべてのプロセスが明示的なリソース制御を必要とするわけではありません。パフォーマンスの問題を実際に引き起こしている、または運用にとって重要であるアプリケーションやサービスに焦点を当ててください。不必要な制限は、大きなメリットをもたらすことなく、かえって複雑さを増すことがあります。

nicerenicecgroups、そしてsystemdを真に理解し活用することで、堅牢なツールを手に入れることができます。これらは、重いワークロードに直面しても、Linuxサーバーのパフォーマンスを正確に管理し、安定性と応答性を保証することを可能にします。

Share: