前回 は、コンテナの仕組みとLinuxカーネルに実装されているコンテナ関連機能のひとつである名前空間について説明しました。今回は名前空間と並んでコンテナの実現に重要な役割を担っているcgroupについて説明していきます。
cgroupは2006年9月にGoogleのエンジニアによって最初のパッチが投稿され、2.6.24カーネルで最初のマージがなされた機能です。2010年にリリースされたRed Hat Enterprise Linux 6.0にこの機能が搭載され、専用のマニュアルが存在したことから、使ったことのある方や、名前をご存知の方も多いのではないでしょうか。
cgroupは"Control Group"の略です。プロセスをグループ化して、そのグループ内に存在するプロセスに対して共通の管理を行うために使います。たとえば、ホストOSが持つCPUやメモリなどのリソースに対して、グループごとに制限をかけることができます。
前回 説明したように、コンテナはプロセスを隔離空間に入れることによって作成しますので、コンテナ内に入っているプロセスの集合に対してまとめてリソース制限をかける必要のある場面は多く、このような場面にcgroupが使えます。cgroupを使って、あるコンテナがホストOSの持つ有限なリソースを使いつくして、ホストOS上のプロセスや他のコンテナに影響を与えないようにできます。
名前空間機能と同様に、cgroupもコンテナ専用の機能ではなく、ホストOS上で実行されているプロセスであれば、任意にグループ化できます。また、異なるコンテナ内に存在するプロセスであっても、それをグループ化して制限をかけることもできます。
cgroupがどのようなリソースに対して制限や管理を行えるのかという説明をする前に、まずはcgroupがどのようにプロセスをグループ化し、どのようにグループに対する操作を行うのかについて説明しましょう。
cgroupファイルシステム
cgroup機能を使ってプロセスをグループ化したものをcgroupと呼びます。cgroupはcgroupファイルシステム(以降cgroupfs)という仮想的なファイルシステムを使って操作します。カーネルのパラメータの値を見たり、パラメータを変更したりするときに/proc以下のファイルを使う場合があると思います。この/proc以下のファイルと同じような感覚で操作できます。
では、cgroupfsの操作をいくつか例をあげてみていきましょう。今までの例はUbuntu 14.04 LTS上で行っていましたが、ここではUbuntu 12.04 LTSを最小インストールした環境を使って説明を行います。これは、最近のバージョンだと最小インストールしてもcgroupfsが最初からマウントされていることが多いためです。
cgroupファイルシステムのマウント
cgroupfsは以下のようにマウントします。ここで"-o"で指定しているオプションは次回説明します。cgroupfsはどのディレクトリにでもマウント可能ですが、現在では/sys/fs/cgroup
以下にマウントするという風に決められています。
$ sudo mount -t tmpfs cgroup /sys/fs/cgroup
$ sudo mkdir /sys/fs/cgroup/cpu
$ sudo mount -t cgroup -o cpu cgroup /sys/fs/cgroup/cpu
この例では、まずtmpfsとして/sys/fs/cgroupをマウントします。これは必須の処理ではありませんが、このような処理を行ってからcgroupfsをマウントすることが多いので、ここでも合わせています。
その後、マウントポイントとして使用するディレクトリ/sys/fs/cgroup/cpu
を作成しています。その後、mount
コマンドの-t
オプションにcgroup
を指定してcgroupfsをマウントしています。
マウントしたcgroupfsの中は普通のディレクトリのように見ることができます。ls
コマンドで見てみましょう。
$ ls /sys/fs/cgroup/cpu
cgroup.clone_children cpu.cfs_period_us cpu.rt_runtime_us notify_on_release
cgroup.event_control cpu.cfs_quota_us cpu.shares release_agent
cgroup.procs cpu.rt_period_us cpu.stat tasks
マウントしただけでいろいろなファイルが自動的に作られていますね。この自動的に作られたファイルがcgroupの使用状況を見たり、cgroupをコントロールするのに使うファイルです。
cgroupの作成
それではマウントしたcgroupfsを使ってプロセスをグループ化するためのcgroupを作ってみましょう。cgroupはcgroupfs上のディレクトリで表されます 。作成は通常のファイルシステムと同じようにmkdir
コマンドで可能です。それではtest01
というcgroupを作ってみましょう。
$ cd /sys/fs/cgroup/cpu
$ sudo mkdir test01
$ ls -F
cgroup.clone_children cpu.cfs_quota_us cpu.stat test01/
cgroup.event_control cpu.rt_period_us notify_on_release
cgroup.procs cpu.rt_runtime_us release_agent
cpu.cfs_period_us cpu.shares tasks
test01
というディレクトリが作られています。
$ ls test01
cgroup.clone_children cpu.cfs_period_us cpu.rt_runtime_us notify_on_release
cgroup.event_control cpu.cfs_quota_us cpu.shares tasks
cgroup.procs cpu.rt_period_us cpu.stat
このディレクトリの中をのぞいてみると、先にマウントした際に/sys/fs/cgroup以下に出来たのと同じようなファイルが自動的に作られています。
プロセスの登録
さて、次は作成したcgroupにプロセスを登録してみましょう。この時使用するファイルがcgroupの各ディレクトリにあるtasks
ファイルです。
先ほど作成したtest01
cgroupを使って説明していきます。プロセスを登録する前に、test01
にあるtasks
ファイルの中身を見てみましょう。
$ cat /sys/fs/cgroup/cpu/test01/tasks
$
このとおり、ファイルの中身は空です。
プロセスを登録するには、このtasks
ファイルにPIDを登録するだけです。登録方法は簡単で通常のファイルに書き込むように登録できます。ここでは現在のシェルを登録してみましょう。
$ echo $$ | sudo tee -a /sys/fs/cgroup/cpu/test01/tasks
17955
プロセスがcgroupに登録されたかどうかを確認するのは、先ほど登録前に試したようにtasks
ファイルの中身を確認して、PIDが登録されているかどうかを見るだけです。では確認してみましょう。
$ cat /sys/fs/cgroup/cpu/test01/tasks
17955
18092
先ほど登録したシェルのPIDが登録されているのがわかります。シェルのPIDの他にもう1つPIDが登録されていますね。これは確認に使ったcatのPIDです。このようにcgroupに登録したプロセスの子プロセスやその子孫も自動的に同じcgroupに登録されます 。
プロセスの移動
次にcgroupに登録したプロセスを別のcgroupに移動させてみましょう。test02
というcgroupを作成します。そして、先ほど登録したシェルのPIDをtest02
に登録して、どのような動きになるか見てみます。
$ sudo mkdir /sys/fs/cgroup/cpu/test02
$ echo $$ | sudo tee -a /sys/fs/cgroup/cpu/test02/tasks
17955
$ cat /sys/fs/cgroup/cpu/test02/tasks
17955
18728
$ cat /sys/fs/cgroup/cpu/test01/tasks
$
このように新しく作成したtest02
グループに先ほどtest01
に登録したシェルのPIDを登録すると、test02
グループに登録されているのがわかります。その後test01
ディレクトリを確認してみると、tasks
ファイルの中身が空になっています。つまり、あるcgroupに登録されているプロセスを別のcgroupに移動させたいときは、単に移動させたいcgroupにPIDを登録するだけ で良いことがわかります。
cgroupの削除
この時点でtest01
に登録されたプロセスはなくなりました。tasks
ファイルの中が空になったcgroupは削除できます 。削除も通常のディレクトリ操作と同じようにrmdir
コマンドで削除できます。
$ sudo rmdir /sys/fs/cgroup/cpu/test01
$ ls /sys/fs/cgroup/cpu/test01
ls: cannot access /sys/fs/cgroup/cpu/test01: No such file or directory
これでcgroupが削除できました。
ここまでの例で、cgroupの操作は非常に簡単であることがおわかりいただけたのではないかと思います。
cgroupの階層構造
さて、ここまでの例では自分で作成したcgroupへプロセスを登録したり、移動させたりしました。それでは、自分の作成したcgroupからプロセスを削除したい場合はどうするのでしょうか? それを説明するには、cgroupの特徴をもうひとつ学ぶ必要がありますので説明していきましょう。
先にmount
コマンドを使って/sys/fs/cgroup/cpu
にcgroupfsをマウントしました。このcgroupfsのトップディレクトリにも作成したcgroupと同じようにいろいろなファイルができていました。この/sys/fs/cgroup/cpu
にあるtask
ファイルの中身を見てみましょう。
$ cat /sys/fs/cgroup/cpu/tasks
1
2
3
5
:(略)
PID 1を先頭に、ずらっとPIDが並んでいると思います。cgroupfsをマウントすると、まずはシステム上の全てのプロセスがここに登録されます。 つまりシステム全体のデフォルトのcgroupのような扱いですね。
新たにcgroupを作成してプロセスを登録すると、先に説明したようにプロセスがcgroup間を移動しますので、/sys/fs/cgroup/cpu/tasks
からはそのPIDが消えることになります。逆に、自分で作成したcgroupからプロセスを削除したい場合は、このトップディレクトリのtasks
にPIDを登録して移動させます。削除というよりは、デフォルトの状態に戻すことになりますね。つまり、cgroupfsがマウントされるとプロセスは必ずどこかのcgroupに属していることになります。
さて、ここまではトップディレクトリ直下にcgroupを作成していました。トップディレクトリだけでなく、通常のディレクトリと同様に自分で作ったcgroupの下にさらにcgroupを作ることもできます。
先ほど作成したtest02
グループの下にtest03
グループを作ってみましょう。
$ sudo mkdir /sys/fs/cgroup/cpu/test02/test03
$ ls /sys/fs/cgroup/cpu/test02/test03
cgroup.clone_children cpu.cfs_period_us cpu.rt_runtime_us notify_on_release
cgroup.event_control cpu.cfs_quota_us cpu.shares tasks
cgroup.procs cpu.rt_period_us cpu.stat
今までと全く同じようにディレクトリが作成され、中にいろいろなファイルができています。
ここまで見てきたように、通常のファイルシステムに階層構造でディレクトリと作るのと同じようにcgroupを作れます。cgroupfsのトップディレクトリをルートにした階層構造です。この階層構造のサポートがcgroupのもうひとつの特徴です 。
この階層構造を使ってプロセスをグループ化して管理できます。たとえば、図1 のようにcgroupを作成し、それぞれのグループにCPUを割り当てるといったことが可能です。
図1 cgroupの階層構造の例
この図では、「 デスクトップアプリ」と「デーモン」グループにそれぞれ30%ずつCPUを割り当てていますので、そのさらに下のcgroupは親の割り当てである30%からリソースが割り当てられます。「 ブラウザ」 、「 エディタ」グループに親である「デスクトップアプリ」グループに割り当てられたリソースの半分ずつを割り当てると、それぞれ15%が割り当てられます。
この例のようにcgroupの親子関係の間では、子のcgroupは親のcgroupの制限を受けます。絶対値で制限を設定するような場合、子のcgroupで親のcgroupの制限を超えるような設定を行っても、先に親のcgroupの制限に引っかかりますので意味がありません。
cgroupが管理するどのようなリソースでも階層構造を使用できます。しかし、リソースの種類によっては階層構造を使用する意味のないリソースもあります。
以上のようにcgroupを操作することにより、プロセスをグループ化して扱うことができます。cgroupの機能を使って、コンテナ内のプロセスを同じcgroupに入れて、コンテナのリソースに対する操作を一元的に行えるわけです。
まとめ
以上のようにcgroupを操作することにより、プロセスをグループ化して扱うことができます。cgroupの機能を使って、コンテナ内のプロセスを同じcgroupに入れて、コンテナのリソースに対する操作を一元的に行えるわけです。
さて、今回でcgroupの説明を終える予定でしたが、少し長くなりそうなので今回はここまでにしておきます。今回出てきたcgroupの特徴をまとめておきます。
cgroupはcgroupfsという特別なファイルシステムで管理する
cgroup はディレクトリで表される
cgroupfsをマウントするとデフォルトでは全てのプロセスがトップディレクトリのグループにデフォルトで登録される
子プロセスはデフォルトでは親のプロセスと同じcgroupに属する
cgroupは階層構造を取り、グループ間の親子関係を構成できる
システム上のプロセスは必ずcgroupfsのトップディレクトリ以下のcgroupのどれか1つだけに属する
ここまででプロセスをどうグループ化して管理するかの説明ができましたので、次回はcgroupを使ってどのようなリソースを管理できるのかを説明していきたいと思います。