前回は、cgroupを使ってリソース制御を行うのに必要な基礎知識となるcgroupfsの特徴を紹介しました。
今回からは、具体的にどのようなリソースの制御が可能なのかを紹介しながら、それぞれの機能を理解しやすいように簡単な例を挙げていきたいと思います。
サブシステム
名前空間と同様に、cgroupも扱うリソースによって『サブシステム』と呼ばれる独立した機能でリソースを扱います。サブシステムには大きく分けて、数値で制限をかけるような機能と、それ以外のアクセス権やグループ内のプロセスに対する操作を行う機能に分かれます。なお、サブシステムは『コントローラ』と呼ばれることもあります。
サブシステムには以下のようなものがあります。
表1 サブシステムの種類と機能
サブシステム | 機能の概要 | 実装されたバージョン |
cpu | CPUのスケジューリングを制御 | |
相対配分…グループ間のCPU時間の割当を割合で指定 | 2.6.24 |
帯域制御…単位時間内にグループ内のタスクが実行できる合計時間を制限 | 3.2 |
cpuacct | グループ内のタスクが消費するCPU時間をレポート | 2.6.24 |
cpuset | グループへのCPU、メモリノードの割り当て | 2.6.24 |
device | グループ内のタスクのデバイスへのアクセスの許可、禁止の指定 | 2.6.26 |
freezer | グループ内のプロセス全てを同時に一時停止・再開 | 2.6.28 |
memory | グループ内のタスクが消費するメモリリソースのレポートと制限 | 2.6.29 |
net_cls | グループ内のプロセスが発信するパケットの制御。パケットに識別子を付与。 | |
tcで制御 | 2.6.29 |
netfilterで制御 | 3.14 |
blkio | ブロックデバイスに対する制限 | |
重みづけ配分…グループ間のI/Oアクセスの比率を割合で指定 | 2.6.33 |
帯域制限…グループ内のタスクが各デバイスに対して行える操作数の制限 | 2.6.37 |
perf_event | cgroup単位でのperfツールの使用 | 2.6.39 |
net_prio | グループ内のタスクのネットワークの優先度の制御 | 3.3 |
hugetlb | cgroupからのhugetlbの使用 | 3.6 |
表1にある「実装されたバージョン」はカーネルに実装されたバージョンで、ディストリビューションによってはこれよりも古いバージョンのカーネルにバックポートされている場合もあります。また、実装された時点では階層構造がサポートされておらず、後のバージョンで実装されたものもあります。
お使いのシステムで使用可能なサブシステムは、カーネルのバージョンだけでなく、カーネルをどのような設定で作成したかによっても違います。使用可能なサブシステムは以下のように/proc/cgroups
で確認できます。
カーネルによってはサブシステムの一部がモジュールとなっていて、デフォルトではロードされていない場合があります。
前の実行例の環境で新たにnet_clsサブシステムのモジュールをロードすると/proc/cgroups
にnet_clsが現れているのがわかります。
サブシステムのマウント
cgroupfsをマウントする際にはサブシステムごとにマウントできます。前回の説明の際にオプションとして-o cpu
という指定をしました。これはcpuサブシステムをマウントするという意味になります。同様に別のマウントポイントに-o memory
と指定してmemoryサブシステムをマウントしてみましょう。
/sys/fs/cgroup/memory
以下にmemoryサブシステムをマウントしました。cpuサブシステムとは作られているファイルが違いますね。ここで見たように複数の階層構造を持てるというのもcgroupの特徴のひとつです。
また、複数のサブシステムを同時に1つのマウントポイント以下にマウントすることも可能です。たとえば以下のようにcpuとmemoryを同時に1つのマウントポイント以下にマウントする事ができます。上でcpuとmemoryサブシステムをマウントしましたので、アンマウントしてから実行します。
ディレクトリ内を確認してみると、cpuサブシステム関連のファイルとmemoryサブシステム関連のファイルが同時に作られており、同じマウントポイントに2つのサブシステムがマウントされたことがわかります。
ただし、同じサブシステムを同時に別のマウントポイントにマウントすることはできません。上記のマウントを実行した後に再度memoryサブシステムをマウントしようとするとエラーとなります。
別階層へのプロセスの登録
サブシステムの解説とは少し外れますが、複数のcgroupfsをマウントする例をあげた所で、複数のcgroupfsへのプロセスの登録についても説明しておきましょう。
前回、cgroup間でのプロセスの移動とところで説明したとおり、あるcgroupにプロセスを登録すると、前に登録されていたcgroupからは自動的にプロセスが削除され、同時に2つのcgroupにプロセスが登録されることはありません。
それでは、cgroupfsが複数マウントされている場合はどうなるのでしょうか。先にcpuとmemoryのサブシステムを同時にマウントする例を試していますので、それをアンマウントしたあと、cpuとmemoryを別々にマウントして試してみましょう。
memoryサブシステムに作成したtest2
にプロセスを登録した後も、cpuサブシステムに作成したtest1
にはPID 1738は登録されたままになっています。つまりcgroupfsの別のマウントであれば、同じプロセスを登録できることがわかります。
実際のシステムでも、サブシステムごとにマウントしたcgroupfsごとにコンテナ用のcgroupを作り、サブシステムごとにPIDを登録してコンテナのリソース制限を行うといったことはよく行われています。
サブシステムの説明と複数のcgroupfsへのプロセスの登録の話が済んだところで、各サブシステムの機能を説明していきましょう。
なお各サブシステムの説明は、それぞれのサブシステムの機能を理解するのに必要な最低限の機能の説明に限りますので、詳しい機能についてはカーネル付属の文書などをご参照ください。
cpuサブシステム
cpuサブシステムでは、以下のような方法でcgroupに対してcpuを割り当てできます。
- 帯域制限
- 単位時間内にcgroup内のタスクがCPUを使用できる合計時間を制限
- 相対配分
- cgroup内のタスクが使えるcpu時間の割り当てを相対的に設定
最初に帯域制限を試してみましょう。まずは準備です。test1
とtest2
というcgroupを作成します。
test1
グループに現在のシェルを登録します。
ここで別のシェルを実行し、同様にtest2
グループに登録します。
帯域制御を行うにはcpu.cfs_quota_us
に実行できる時間をマイクロ秒単位で設定します。これでcpu.cfs_period_us
で指定されている単位時間内でどれだけCPUを使用できるかが設定できます。設定方法はtasks
ファイルへの書き込みと同様にcpu.cfs_quota_us
に設定したい値を書き込みます。
ここではtest1
グループには5ミリ秒、test2
グループには10ミリ秒を設定してみましょう。
さて、ここで2つ起動しているそれぞれのシェルで以下のような処理を実行します。通常であればCPU使用率がほぼ100%になりますね。
これをtop -p 2278,2953
としてCPU使用率を確認すると、測定タイミングによって値は変化しますが、それぞれのプロセスのCPU使用率はそれぞれおよそ10%と5%になっていることがわかります。
相対配分も簡単に説明しておきましょう。相対配分を行う場合は各cgroup以下のcpu.shares
ファイルを使います。このファイルに書かれた数値が1024であるtest1
グループと、512であるtest2
グループがある場合、test1
グループにはtest2
グループの倍のCPU時間が割り当てられます。
帯域制御についてはカーネル付属文書のsched-bwc.txtに、相対配分についてはsched-design-CFS.txtに少し説明があります。
cpuacctサブシステム
cpuacctはcgroup内のプロセスが使用したCPU時間のレポートが生成されます。このサブシステムには設定するような項目はありません。詳しくはカーネル付属文書のcpuacct.txtをご覧ください。
たとえば以下はcgroup内のプロセスが消費したCPU時間のレポートを表示しています(単位はナノ秒)。
cpusetサブシステム
cpusetサブシステムは、cgroupにCPUやメモリノードを割り当てます。CPUとメモリノードの設定はそれぞれcgroup内のcpuset.cpus
、cpuset.mems
ファイルに設定します。ルート直下のcgroup中のこの2つのファイルはシステム上の全てのCPUとメモリノードが設定されています。
ルート以下に新たにcgroupを作成した場合はこの2つのファイルは空になっており、この2つのファイルに値を設定しなければtasks
ファイルにプロセスを登録できませんので注意が必要です。ただしcgroupを作成した際、親のcgroupと同じ設定が書き込まれた状態でこの2つのファイルを作成して良いのであれば、cgroup.clone_children
ファイルに1
を書き込んでおけば、cgroupを作成した時に親のcgroupの設定がコピーされます。
この2つのファイルには"-"(ハイフン)や","(カンマ)を使って複数の値を設定できます。たとえばCPU 0から1と5を登録する場合、
といった風に設定できます。
詳しくはカーネル付属文書のcpuset.txtをご覧ください。
devicesサブシステム
devicesサブシステムはこれまでに説明したような数値で表されるリソースを制限するサブシステムではなく、cgroupに対するアクセス権を設定する機能です。
Linuxカーネルの持つコンテナ機能では、デバイスはコンテナごとに仮想化されることはありません。そのため、ホストOSから見えているデバイスへのコンテナに対するアクセス権を設定できるようにdevicesサブシステムが存在します。
アクセス権の設定はcgroup内のdevices.allow
(cgroupがアクセス可能なデバイスリスト)とdevices.deny
(cgroupがアクセスできないデバイスリスト)ファイルに設定します。この2つのファイルに3つのフィールドからなるエントリを設定し、アクセス権を設定します。
- タイプ
- ブロックデバイス(b)、キャラクタデバイス(c)、両方(a)
- デバイスノード番号
- メジャー番号とマイナー番号をコロンで連結
- アクセス権
- 読み取り(r)、書き込み(w)、デバイスファイルの作成(m)
以下はtest1
グループに対して、まず全てのアクセスを拒否した後、/dev/nullに対するアクセスを許可している例です。
devices.allow
,devices.deny
ファイルは設定専用のファイルですので、設定した結果はdevices.list
ファイルで確認します。
詳しくはカーネル付属文書のdevices.txtをご覧ください。
freezerサブシステム
freezerサブシステムはcgroupのサブシステムの中では少し変わった存在です。freezerはリソースを制御するのではなく、cgroupに属するタスクそのものの制御を行います。この機能を使うとcgroup内のプロセスを一括で一時停止させたり、一括で再開させたりできます。
実行中のコンテナの状態を保存して停止させ、その後再開させるcheckpoint/restartという機能があり、そこに使うことを考えて実装されたようです。しかし、現在checkpoint/restart機能のメジャーな実装であるCRIUではいまのところ使われていないようです。Docker Engine中のdocker pause/unpause
コマンドではこの機能を使っています。
ではfreezerサブシステムの使い方を簡単に見ておきましょう。今までの例と同様に、freezerサブシステムを/sys/fs/cgroup/freezer
にマウントし、そこにtest1
グループを作った上で試しています。freezerサブシステムには以下のようなファイルが存在します。
freezerサブシステムを使ってcgroup内のタスクの状態を変えたり、現在の状態を確認するにはfreezer.state
ファイルを使います。以下の3つの状態が存在し、freezer.state
ファイルに、そのcgroupの状態が書かれています。
- FROZEN
- タスクは一時停止中
- FREEZING
- タスクの一時停止処理を実行中
- THAWED
- タスクは実行中
状態を変更するにはfreezer.state
ファイルに変化させたい状態の文字列を書きます。一時停止させたいときはFROZEN
を、再開させたいときはTHAWED
を書きます。FREEZING
は状態の確認専用の値であり、書き込みはできません。
まずは他のサブシステムと同様にcgroupのtasks
にシェルのPIDを登録します。
ここで別のシェルを起動します。
プロセスの状態の変化を見るためにps
コマンドを実行しています。freezer.state
ファイルで状態を確認するとTHAWED
となっており、プロセスは実行状態であることがわかります。
この状態でfreezer.state
ファイルにFROZEN
を書き込み、一時停止状態にします。
書き込んだ後にfreezer.state
ファイルを確認するとFROZEN
となっており、一時停止状態であることがわかります。登録したPIDのシェルは反応がないはずです。ps
コマンドのSTAT
欄を見てもプロセスの状態が変化しているのがわかります。
再度実行状態にするにはfreezer.state
にTHAWED
を書き込みます。
状態がTHAWED
に変わっており、シェルも反応するはずです。
なお、ルート直下のcgroupに対しては一時停止ができません。そのためfreezer.state
ファイルは存在しません。
freezerサブシステムもカーネル付属文書にfreezer-subsystem.txtがありますのでご参照ください。
まとめ
今回は最初に現在カーネルに実装されているサブシステムの紹介を行いました。続くサブシステムのマウントやプロセスの登録について説明の部分で、いくつかcgroupの特徴を説明しましたので、ここでまとめておきましょう。
今回紹介した特徴をあげる前に、おさらいとして前回紹介した特徴を再度載せておきます。
- cgroupはcgroupfsという特別なファイルシステムで管理する
- cgroup はディレクトリで表される
- cgroupfsをマウントするとデフォルトでは全てのプロセスがトップディレクトリのグループにデフォルトで登録される子プロセスはデフォルトでは親のプロセスと同じcgroupに属する
- cgroupは階層構造を取り、グループ間の親子関係を構成できる
- システム上のプロセスは必ずcgroupfsのトップディレクトリ以下のcgroupのどれか1つだけに属する
ここに今回紹介した特徴が加わります。
- cgroupを使って制御を行いたい機能ごとに『サブシステム』が存在する
- cgroupfsには単独でサブシステムをマウントすることも、任意の複数のサブシステムを同時にマウントこともできる
- cgroupfsは複数の階層を持つことができる
- 同一のサブシステムを同時に別のマウントポイントにマウントすることはできない
最初に紹介した通り、サブシステムにはたくさんの種類がありますので、今回で全部を紹介することはできませんでした。次回は残ったサブシステムの紹介を行い、その後にcgroup開発の現状について少し触れたいと思います。