LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術

第3回Linuxカーネルのコンテナ機能[2] ─cgroupとは(その1)

前回は、コンテナの仕組みと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が最初からマウントされていることが多いためです。

 CentOS 6、Debian 7の最小インストールでも例と同じような操作は可能です。

cgroupファイルシステムのマウント

cgroupfsは以下のようにマウントします。ここで"-o"で指定しているオプションは次回説明します。cgroupfsはどのディレクトリにでもマウント可能ですが、現在では/sys/fs/cgroup以下にマウントするという風に決められています。

 $ sudo mount -t tmpfs cgroup /sys/fs/cgroup  (/sys/fs/cgroupをtmpfsでマウント)
 $ sudo mkdir /sys/fs/cgroup/cpu              (cgroupマウント用のディレクトリ作成)
 $ sudo mount -t cgroup -o cpu cgroup /sys/fs/cgroup/cpu   (cgroupfsのマウント)

この例では、まず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   (test02グループの作成)
 $ echo $$ | sudo tee -a /sys/fs/cgroup/cpu/test02/tasks  (プロセスをtest02に登録)
 17955
 $ cat /sys/fs/cgroup/cpu/test02/tasks    (test02グループに属するプロセスの確認)
 17955                                    (確かに登録されている)
 18728
 $ cat /sys/fs/cgroup/cpu/test01/tasks    (test01グループに属するプロセスの確認)
 $                                        (登録されているプロセスはない!)

このように新しく作成した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の階層構造の例
図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を使ってどのようなリソースを管理できるのかを説明していきたいと思います。

おすすめ記事

記事・ニュース一覧