めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

Control Groups (cgroups)

LXC の解説に必要なので、ちょいと cgroups についてまとめておきます。RHEL6.0 前提です。

参考資料:
RHEL6 リソース管理ガイド
Control Groups(cgroups) の概要

cgroups とは

最近の Linux は、あるプロセスの実行を許可する CPU コアの指定など、プロセスごとにさまざまなリソース制御ができるようになっています。このような制御を複数のプロセスをグループ化したグループ単位でできると便利な場合があります。cgroups は、グループ単位でリソース制御を行うためのユーザ・インターフェースを特殊ファイルシステムの形式で提供します。

cgroups 自体がリソース制御の機能を提供するわけではありません。既存のリソース制御機能(cgroups の用語で『コントローラ』もしくは『サブシステム』と言います)を cgroups のインターフェースを利用するように改変することで、cgroups によるリソース制御が可能になります。

RHEL6.0 で利用できる主要なサブシステムにはつぎのようなものがあります。

サブシステム 説明
cpuacct CPU 使用時間を確認します。値を見るだけで何かの制御を行うものではありません。
cpuset グループ内のプロセスが利用可能な CPU コアを指定します。
cpu 複数グループのプロセスが同一の CPU コアで実行される場合の優先順位(CPU 時間配分の割合)を制御します。
memory 使用可能なメモリの上限を制限します。実メモリの上限に加えて、実メモリ + Swap の合計値の上限を指定することも可能です。
blkio 物理 IO の優先順位を制御します。
devices /dev/ 以下のデバイスファイルのアクセス制御を行います。
net_cls 送出するネットワークパケットに『帯域クラスラベル』を付与します。帯域クラスは、別途 tc コマンドで設定しておく必要があります。

これらのサブシステムは、cgroups のインターフェースを利用するという点は共通ですが、実際の中身は個別に開発されています。それぞれのサブシステムの機能や利用方法には、とくに統一性はありません。

cgroups の基本的な考え方

cgroups では、ツリー構造でグループを作成することができます。cgroups は特殊ファイルシステムの形式でインターフェースを提供しますが、1つのグループは1つのディレクトリに対応しており、ディレクトリのツリー構造がグループのツリー構造に対応します。次は、親子関係を持った5つのグループを作成した例です。 は、デフォルトで存在するもので、すべてのグループの親となる『ルート・グループ』です。(実際には、cgroup 特殊ファイルシステムのマウントポイントにあたります。)

<root>/group01/subgroup01/subsubgroup01
      |       |          |
      |       |          /subusbgroup02
      |       /subgroup02
      /group02

このようなディレクトリ・ツリーは、サブシステムごとに個別に作成することもできますし、すべてのサブシステムで共通のディレクトリ・ツリーを使用することもできます。RHEL6.0 では、cgconfig サービスを起動すると cgroups 用のディレクトリ・ツリーが /cgroups にマウントされますが、デフォルトでは、サブシステムごとに個別のツリーが用意されます。次の例のように、/cgroup/<サブシステム>/ 以下が該当のサブシステム用のツリーになります。

# ls -l /cgroup
合計 0
drwxr-xr-x. 4 root root 0  4月 22 17:42 2011 blkio
drwxr-xr-x. 5 root root 0  4月 22 17:42 2011 cpu
drwxr-xr-x. 3 root root 0  4月 22 17:42 2011 cpuacct
drwxr-xr-x. 5 root root 0  6月  2 08:54 2011 cpuset
drwxr-xr-x. 3 root root 0  4月 22 17:42 2011 devices
drwxr-xr-x. 3 root root 0  4月 22 17:42 2011 freezer
drwxr-xr-x. 3 root root 0  4月 22 17:42 2011 memory
drwxr-xr-x. 2 root root 0  4月 22 17:42 2011 net_cls

例えば、/cgroup/cpu は、cpu サブシステムのルート・グループです。この下にサブディレクトリを作成することで、cpu サブシステム用の新しいグループが作成されます。なお、LXC から利用する cgroups では、すべてのサブシステムに対して共通の 1 つのツリーを使用します。LXC で作成したコンテナごとにグループが作成されます。

なお、cgroups の機能としては、ツリー構造のグループを定義することができますが、実際のリソース制御がツリー構造に対応しているかどうかは、それぞれのサブシステムによって異なります。ツリー構造に対応していないサブシステムのディレクトリでは、1 階層目のグループ(先の例では、group01, group02)のみが作成可能です。

グループに対応するディレクトリの下にある tasks ファイルに PID の数値を echo で出力すると、そのプロセスは該当グループに属することになります。これ以降、このプロセスから作成される子プロセスもすべて同じグループに属します。同じディレクトリの cgroup.procs ファイルを cat で表示すると、子プロセスを含めて、現在、そのグループに属しているすべてのプロセスの PID が表示されます。

ちょっとした小技ですが、シェルプロンプトから新しく実行するコマンドを特定のグループに入れたい場合は、次のように新しい親シェルを作って、それをグループに入れた後に、子プロセスとしてコマンドを実行する方法が使えます。子プロセスができた後に、親プロセスを特定のグループに入れ直しても子プロセスのグループは変わらないので注意して下さい。

# sh -c "echo \$$ > /cgroup/blkio/<group>/tasks && <Command>"

また、プロセスをグループからはずす場合は、ルートグループの tasks に該当 PID を echo します。

各ディレクトリの下にあるその他の特殊ファイルが実際のリソース制御を行うためのものになります。次は、RHEL6.0 の KVM ホスト環境での例でが、cpu サブシステムに対して、libvirt グループがあり、さらにその子グループとして、lxc や qemu があります。詳細は後で説明しますが、例えば、cpu.shares は、このグループに対する CPU コアの時間配分を調整するパラメータです。

# ls -l /cgroup/cpu/libvirt/
合計 0
-r--r--r--. 1 root root 0  4月 22 17:43 2011 cgroup.procs
-rw-r--r--. 1 root root 0  4月 22 17:43 2011 cpu.rt_period_us
-rw-r--r--. 1 root root 0  4月 22 17:43 2011 cpu.rt_runtime_us
-rw-r--r--. 1 root root 0  4月 22 17:43 2011 cpu.shares
drwxr-xr-x. 2 root root 0  4月 22 17:43 2011 lxc
-rw-r--r--. 1 root root 0  4月 22 17:43 2011 notify_on_release
drwxr-xr-x. 3 root root 0  5月 31 15:51 2011 qemu
-rw-r--r--. 1 root root 0  4月 22 17:43 2011 tasks

libcgroup について

libcgroup は、さまざまなプロセスが属するグループを管理するためのツールです。いろいろな管理コマンドを通して、プロセスが属するグループを管理することができます。ただし、cgconfig サービスの利用が前提となります。ここでは libcgroup については説明しません。

主要なサブシステムの使い方

ここでは、主要なサブシステムについて、実際に利用することが多いと思われる機能と使い方を説明します。ここで説明しない機能もあります。以下に登場するパラメータ(cpuset.cpus など)は、該当グループのディレクトリ内にある特殊ファイルです。値をセットする際は、echo で値を出力して、現在の設定を確認する時は、cat で値を表示します。

(1) cpuset サブシステム
グループ内のプロセスが実行される CPU コアを指定します。サーバ仮想化などで CPU Pinning と呼ばれる機能です。次のパラメータを利用します。

パラメータ 説明
cpuset.cpus プロセスが実行される CPU コアを指定します。"0-2,7" は、0,1,2,7 という意味になります。
cpuset.cpu_exclusive 1 をセットすると、このグループが指定する CPU コアを他のグループでは指定できないようになります。デフォルトは 0 です。
cpuset.mems NUMA 構成のサーバにおいて、プロセスが利用するメモリの位置を指定します。

cpuset.mems は NUMA のチューニングを行う時以外はあまり気にする必要はありませんが、この値がセットされていないと、該当グループを使用する事ができません。新しいグループを作成したら、基本的にはルートグループと同じ値をセットしておきます。

cpuset は、ツリー構造のグループに対応しています。サブグループは、親グループが利用可能な CPU コアの中から、さらに利用可能な CPU コアを選択して制限します。

(2) cpu サブシステム
CPU コアの時間配分を指定します。

パラメータ 説明
cpu.shares 該当グループの CPU コア占有率を 2 〜 262144 の数値で指定します。この値に比例した CPU コア使用時間が割り当てられます。(デフォルト値は 1024)

『CPU コア』という表現をしている理由は次のとおりです。例えば、2 コアのサーバ上で 1 スレッドで動作するプロセスを 2 個起動した場合、それぞれのプロセスには 1 コアずつアサインされます。この場合、cpu.shares の値に関係なく、どちらのプロセスも各コアを 100% 使用することができます。cpu.shares は、同じ cpuset を共有するグループ間での時間配分を決定します。

cpuset は、ツリー構造のグループに対応しています。サブグループは、親グループに割り当てられた CPU コア時間をさらに細分します。

(3) memory サブシステム
メモリ使用量の上限を指定します。

パラメータ 説明
memory.limit_in_bytes 該当グループが利用可能な物理メモリの上限を byte 単位で指定します。
memory.memsw.limit_in_bytes 該当グループが利用可能な『物理メモリ + Swap』の上限を byte 単位で指定します。
memory.use_hierarchy 1 をセットするとツリー構造のグループの使用が可能になります。デフォルトは 0 です。

(4) blkio サブシステム
ディスク IO の優先順位を指定します。全ブロックデバイスに共通の設定とデバイス個別の設定が可能です。

パラメータ 説明
blkio.weight 全ブロックデバイスに共通の優先順位を 100〜1000 の値で指定します。この値に比例した IO 処理が割り当てられます。
blkio.weight_device 特定のデバイスに対する値を指定します。"8:0 500" のように "Major:Minor 値" の形式で指定します。

Linux カーネルは、複数の IO スケジューラを選択して使用することができますが、この機能を利用する場合は(デフォルトの IO スケジューラである)CFQ スケジューラを使用する必要があります。

(5) devices サブシステム
デバイスに対するアクセス制御を行います。

パラメータ 説明
devices.list アクセスが許可されているデバイスのリスト(アクセスリスト)を表示します。
devices.allow アクセスリストにデバイスを追加します。
devices.deny アクセスリストからエントリを削除します。

アクセスリストは "(a|b|c) major:minor [rwm]" の形式で指定します。頭の a, b, c は、ブロックデバイス(b)、キャラクタデバイス(c)、両方(a)で、rwm は、読み取り(r)、書き込み(w)、作成(m)の指定です。major:minor にはワイルドカード(*)が指定できます。

デフォルトでは、"a *:* rwm" (任意のデバイスへのアクセスを許可)が指定されていますので、一般には、これを削除した上で必要なデバイスのみを許可していきます。最低限のアクセス許可としては、このぐらいでしょうか。

# echo "a *:* rwm" > devices.deny
# echo "c 1:3 rwm" > devices.allow     # null
# echo "c 1:5 rwm" > devices.allow     # zero
# echo "c 5:1 rwm" > devices.allow     # console
# echo "c 5:0 rwm" > devices.allow     # tty
# echo "c 4:* rwm" > devices.allow     # tty*
# echo "c 5:2 rwm" > devices.allow     # ptmx
# echo "c 136:* rwm" > devices.allow   # pts/*
# echo "c 1:8 rwm" > devices.allow     # random
# echo "c 1:9 rwm" > devices.allow     # urandom
# echo "c 254:0 rwm" > devices.allow   # rtc0

(6) net_cls サブシステム
送出するネットワークパケットに『帯域クラスラベル』を付与します。帯域クラスは、別途 tc コマンドで設定しておく必要があります。tc コマンドは、送出パケットに対するネットワーク帯域を制御する機能を提供します。tc コマンドの詳細は省略します・・・。なお、tc コマンドは受信パケットに対する帯域制御はできません。