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

第38回Linuxカーネルのコンテナ機能 ― cgroupの改良版cgroup v2 [2]

前回の記事は、私が所属する会社のAdvent Calendarの5日目の記事でした。これまでは、この連載記事でAdvent Calendarに参加するのは1回だけでした。

今年はcgroup v2の話題を書こうと決めたときに、内容から考えて1回では済まない量になるだろうと思いました。そこで、続けて2回でcgroup v2の紹介をして、2つのAdvent Calendarに参加しようと決めました。1回目はちょうど社内で募集が始まっていた会社のAdvent Calendar、2回目は例年どおりLinux Advent Calendarに参加することにしました。

そういうわけで、今回の記事は昨年まで何度か参加していたLinux Advent Calendar 2017の19日目の記事です。


今回は、実際にcgroup v2を操作しながら、前回紹介したcgroup v2の特徴をおさらいしましょう。

今回の実行例は、cgroup v1、v2のマウントを自由に行うために、systemdが動いていない環境であるPlamo Linux 6.2の4.11.12カーネル上で実行しています。

前回書いたとおり、執筆時点で最新の4.14カーネルでは新しい機能が追加されています。新しい機能については後の回で紹介しますので、今回は少し前のバージョンである4.11カーネルを使用しています。4.13以降の新しいカーネルでは、今回の実行例とは実行結果が異なることがありますのでご注意ください。

cgroup v2のマウント

cgroup v2は、ファイルシステムのタイプとしてcgroup2を指定してマウントします。今回試している4.11カーネルの時点では、マウントオプションはありません。

# mount -t cgroup2 cgroup2 /sys/fs/cgroup/
# grep cgroup2 /proc/self/mountinfo (マウントされているのを確認)
25 19 0:22 / /sys/fs/cgroup rw,relatime - cgroup2 cgroup2 rw

cgroup v1では、サブシステム(コントローラ)名やその他いくつかオプションが存在しましたが、cgroup v2では上の実行例のように指定するオプションもなく、シンプルにファイルシステムをマウントするだけです。

上の実行例のようにマウントした際にcgroup rootとなる、/sys/fs/cgroupを確認してみましょう。

# ls /sys/fs/cgroup/
cgroup.controllers  cgroup.procs  cgroup.subtree_control

cgroup v2でコントロールを行うためのファイルが3つ現れており、cgroup v2がマウントされていることがわかります。

なお、4.13カーネルでcgroup namespace関連のマウントオプションがひとつだけ追加されました。この機能については後の回で紹介する予定です。

cgroup v2コアで使用するファイル

cgroup v2の各cgroup内に現れるファイルのうち、サブシステムと関係なく出現するファイルを紹介しておきましょう。それぞれのファイルの詳細については後で説明します。

表1 cgroup v2コアで使用するファイル
ファイル名 説明
cgroup.controllers そのcgroupで使えるサブシステム
cgroup.procs そのcgroupに属するプロセスの登録、確認
cgroup.subtree_control 子cgroupで有効にするサブシステム
cgroup.events そのcgroupと子孫のcgroupにプロセスが所属しているかを確認する。また、所属状況が変化した場合に通知を受け取れる。root以外に出現

cgroupの作成

cgroupを作成する方法は、前回説明した通りcgroup v1と同じです。

"test01"というcgroupを作ってみましょう。cgroup v1と同様にmkdirコマンドを使います。

# mkdir /sys/fs/cgroup/test01
# ls /sys/fs/cgroup/test01
cgroup.controllers  cgroup.events  cgroup.procs  cgroup.subtree_control

作成したcgroupの中のファイルを一覧してみると、rootよりはファイルがひとつ増えていますね。cgroup.eventsというファイルです。これは前回紹介したcgroup v2の特徴のうち、⁠cgroup状態通知」に関係するファイルです。このファイルについては次回説明します。

cgroupの削除

cgroupを作成する方法だけでなく、削除方法もcgroup v1と同様にrmdirコマンドで行います。上で作成した"test01"を削除してみましょう。

# rmdir /sys/fs/cgroup/test01/
# ls /sys/fs/cgroup/
cgroup.controllers  cgroup.procs  cgroup.subtree_control

cgroupにプロセスが所属しておらず、子cgroupを持っていないcgroupのみ削除できるのも、cgroup v1と同じです。

cgroupへのプロセスの追加、削除、確認

使用するファイル

cgroup v1でタスクをcgroupに所属させる場合は、スレッドIDやプロセスIDをファイルに書き込みました。また、cgroupに所属しているタスクは、ファイルの中身を見ることで確認できました。このとき使うファイルは2種類ありました。

表2 cgroupに所属するタスクに関連するファイル
ファイル名 表示されるID cgroupのバージョン
tasks スレッドID v1のみ
cgroup.procs (※) プロセスID v1, v2
※)
第3回での解説では紹介していませんでした。

プロセスの追加

cgroup v2ではプロセス単位で追加を行いますので、tasksファイルはありません。cgroup.procsファイルを使って登録や確認を行います。

プロセスを登録する操作はcgroup v1と同じです。プロセスをcgroupに登録してみましょう。さきほどの例で作成した"test01"グループに登録します。

# echo $$
3028
# echo $$ > /sys/fs/cgroup/test01/cgroup.procs 
# cat /sys/fs/cgroup/test01/cgroup.procs
3028
3035(これはcatコマンドのPID)

cgroup.procsにカレントシェルのPIDを書き込み、ファイルの中身を表示させると、登録したPIDが表示されました。

プロセスの削除

プロセスの削除は、PIDを別のcgroupに登録することで行います。これもcgroup v1と同じですね。

# grep 3028 /sys/fs/cgroup/test01/cgroup.procs (test01に3028が所属している)
3028
# echo $$ > /sys/fs/cgroup/cgroup.procs        (3028をrootに登録)
# grep 3028 /sys/fs/cgroup/cgroup.procs        (rootに登録されたことを確認)
3028
# grep 3028 /sys/fs/cgroup/test01/cgroup.procs (test01からは削除された)
# 

上の例のように、PIDが3028のプロセスをroot(または他のcgroup)に登録すると、"test01"からは削除されます。

プロセスが所属するcgroupの確認

プロセスが所属しているcgroupの情報は、/proc/[PID]/cgroupにあります("[PID]"にはプロセスのPIDが入ります⁠⁠。

cgroup v1では、このファイルの中身は次のようになっています。

# cat /proc/12314/cgroup 
15:debug:/
14:rdma:/
13:pids:/
12:hugetlb:/
11:net_prio:/
10:perf_event:/
9:net_cls:/
8:freezer:/
7:devices:/
6:memory:/
5:blkio:/
4:cpuacct:/
3:cpu:/test01
2:cpuset:/
1:name=systemd:/

階層ごとに1行エントリが作成されます。各行はコロン区切りで(階層を作成した順):(サブシステム名):(所属するcgroupパス)となります。ひとつの階層に複数のサブシステムが属している場合は、⁠サブシステム名⁠⁠」の部分はカンマ区切りでサブシステムが並びます。

上の例だと、PID:12314のプロセスは、cpuサブシステム用のツリーのみ/test01に属しており、その他のツリーではrootに属していることがわかります。

cgroup v2では、次のようになります。

# cat /proc/self/cgroup 
0::/test01

階層はひとつしかありませんので1行で、v1でサブシステム名が入っていた部分は何も入っていません。v2では番号は常に"0"で、サブシステム名の部分には何も入らず0::(所属するcgroupパス)となります。

ちなみにv1とv2を共存させた場合、cgroupファイルの中身は次のようになります。

# cat /proc/self/cgroup 
2:blkio,memory:/test01
1:cpu:/test01
0::/test01

cgroup v2は必ず"0"でエントリが始まり、サブシステム名が空ですので、v1のエントリと区別がつきます。v1のエントリは"1"から始まりますのでバッティングすることもありません。

サブシステム(コントローラ)の制御

cgroup.controllersファイル

各cgroupにはcgroup.controllersというファイルがあり、そのcgroupで使えるサブシステムがリストされます。

# ls /sys/fs/cgroup/
cgroup.controllers  cgroup.procs  cgroup.subtree_control
# cat /sys/fs/cgroup/cgroup.controllers 
io memory pids rdma

上の実行例ではroot cgroupのcgroup.controllersファイルを見ています。つまり、このシステム上のcgroup v2で使えるサブシステムが、io、memory、pids、rdmaの4つだということを表しています。

前回説明したように、cgroup v1で使われていないサブシステムのみがv2で使えますので、同じカーネルを使っていてもcgroup v1のマウント状況次第で、このファイルに現れるサブシステムは異なります。

図1 cgroup v2のサブシステム制御
図1 cgroup v2のサブシステム制御

図1は前回のサブシステム制御の解説部分で図2として載せた図を、今回の実行例に合わせて変えたものです。cgroup.controllersファイルは、この図で「有効なサブシステム」と書かれている部分に当たります。

cgroup.subtree_controlファイル

それでは、親cgroupから子cgroupのサブシステムを制御する方法を見ていきましょう。図1で「子で有効にするサブシステム」と表した部分に当たるファイルは、cgroup.subtree_controlというファイルです。

このファイルは、cgroup v2をマウントした直後や、cgroupを作成した直後は空です。

# mount -t cgroup2 cgroup2 /sys/fs/cgroup/
# cat /sys/fs/cgroup/cgroup.controllers 
io memory pids rdma
# cat /sys/fs/cgroup/cgroup.subtree_control (マウント直後のcgroup.subtree_controlファイルは空)
#

このファイルに、cgroup.controllersファイルに一覧されているサブシステムのうち、子cgroupで有効にしたいcgroupを登録します。また、子で有効にしているサブシステムの無効化もこのファイルで行います。

この有効にしたり、無効にしたりといった処理は、このファイルに書き込む事で行います。そして、このファイルの中身を見ることで、子cgroupで有効にしているサブシステムの確認を行えます。

子cgroupで使えるサブシステムの登録と削除は、次のような文字列をcgroup.subtree_controlファイルに書き込むことで行います。

  • サブシステムを子cgroupで使えるように有効化するには、サブシステム名の前に"+"をつける
  • 子cgroupで有効化されているサブシステムを削除するには、サブシステム名の前に"-"(マイナス)をつける

また、次のように複数のサブシステムを登録、削除できます。

  • 複数のサブシステムをスペース区切りで書けば、一度で有効化、無効化できる
  • 複数回、サブシステムを登録、削除する文字列を書き込めば、複数のサブシステムを有効化、無効化できる

それでは実際にサブシステムが子cgroupで使えるように、登録と削除を行ってみましょう。

サブシステムの登録

まずはrootで使えるサブシステムのすべてを子cgroupで使えるように登録してみます。

# echo "+io +memory +pids" > /sys/fs/cgroup/cgroup.subtree_control (io, memory, pidsを有効化)
# echo "+rdma" > /sys/fs/cgroup/cgroup.subtree_control (rdmaを有効化)
# cat /sys/fs/cgroup/cgroup.subtree_control (子cgroupで使えるサブシステムの確認)
io memory pids rdma

上の例では、まずio, memory, pidsサブシステムを有効にした後、rdmaサブシステムを有効にしています。同時に指定して登録したサブシステムも、その後に別に登録したサブシステムも有効になっていることがわかります。

サブシステムの削除

この後、登録したサブシステムを削除するには、サブシステム名の前に "-"(マイナス)をつけてcgroup.subtree_controlファイルに書き込みます。

# cat cgroup.subtree_control (4つサブシステムが登録されている)
io memory pids rdma
# echo "-rdma" > cgroup.subtree_control (rdmaを削除)
# cat cgroup.subtree_control (rdmaが削除されている)
io memory pids

子cgroupでのサブシステムの確認

では、ここで子cgroupを作成してみましょう。親cgroupのcgroup.subtree_controlファイルで、子cgroupで使えるサブシステムを登録したわけですから、子cgroupのcgroup.controllersファイルには登録したサブシステムが現れているはずです。

# cat /sys/fs/cgroup/cgroup.subtree_control (子cgroupで使えるサブシステムの確認)
io memory pids
# mkdir /sys/fs/cgroup/test01 (子cgroupとしてtest01 cgroupを作成)
# cat /sys/fs/cgroup/test01/cgroup.controllers (test01で使えるサブシステムを確認)
io memory pids
# echo "+rdma" > cgroup.subtree_control (親cgroupで子cgroupで使えるサブシステムを追加)
# cat /sys/fs/cgroup/test01/cgroup.controllers (追加したサブシステムが子cgroupで追加されている)
io memory pids rdma
# echo "-rdma" > cgroup.subtree_control (子cgroupで使えるサブシステムを削除)
# cat /sys/fs/cgroup/test01/cgroup.controllers (子cgroupから削除したサブシステムが削除されている)
io memory pids

上の例では、

  • 親cgroupのcgroup.subtree_controlで指定したサブシステムが、子cgroup("test01")内のcgroup.controllersファイルに出現している
  • 親cgroupのcgroup.subtree_controlで追加、削除したサブシステムが、子cgroup内cgroup.controllersに現れたり消えたりしている

ことが確認できます。

上の例では"test01" cgroupのcgroup.controllersファイルで有効になっているサブシステムを確認しました。もちろん、"test01"ディレクトリには各サブシステム用のファイルが出現しています。

# ls /sys/fs/cgroup/test01/ (cgroupコア用ファイルの他にio,memory,pid用のファイルが出現している)
cgroup.controllers      io.stat         memory.max           pids.events
cgroup.events           memory.current  memory.stat          pids.max
cgroup.procs            memory.events   memory.swap.current
cgroup.subtree_control  memory.high     memory.swap.max
io.max                  memory.low      pids.current

親cgroupでサブシステムを有効すると同時に、ディレクトリには有効化したサブシステムのファイルが出現し、逆に有効になっているサブシステムを親cgroupで無効化した場合は、サブシステム用のファイルもcgroupから消えます。

このように、あるcgroupのcgroup.controllersファイルに存在するサブシステムをcgroup.subtree_controlに登録して、子cgroupに分配していきます。

トップダウン制約

次に、上位のcgroupから許可されたサブシステム以外は、子孫のcgroupでは使えないことを確認してみましょう。

まずは、先に作成した"test01" cgroupに子cgroupを作成します。

# mkdir /sys/fs/cgroup/test01/test0{2,3} (test01の子cgroupとしてtest02,test03を作成)
# echo "+memory" > /sys/fs/cgroup/test01/cgroup.subtree_control
(test01の子cgroupではmemoryサブシステムのみ使用できるように設定)
# cat /sys/fs/cgroup/test01/cgroup.subtree_control (設定の確認)
memory
# cat /sys/fs/cgroup/test01/test02/cgroup.controllers (子cgroupではmemoryサブシステムのみ使用可能)
memory
# cat /sys/fs/cgroup/test01/test03/cgroup.controllers 
memory

上のように"test01" cgroupの子cgroupとして"test02"、"test03"を作成しました。そして、"test01"のcgroup.subtree_controlファイルに+memoryと書き込み、子cgroupではmemoryサブシステムのみ使用できるように設定しました。"test02"、"test03"では確かにmemoryサブシステムのみ使えるようになっています。

ここでroot cgroupで、子cgroupでは使えるように設定されていない"rdma"を"test01"と"test02"に登録してみましょう。

# echo "+rdma" > /sys/fs/cgroup/test01/test02/cgroup.subtree_control 
bash: echo: write error: No such file or directory
# echo "+rdma" > /sys/fs/cgroup/test01/cgroup.subtree_control 
bash: echo: write error: No such file or directory

このように、root cgroupでは配下のcgroupでrdmaサブシステムが使えるようには設定されていないので、root cgroup以外のcgroupでは、rdmaサブシステムをcgroup.subtree_controlファイルに登録しようとするとエラーになります。

上位のcgroupからサブシステムが使えるように設定されていない限りは、子孫のcgroupでサブシステムを使ったリソースの分配はできないことが確認できました。

まとめ

冒頭で書いたように、cgroup v2を2回で紹介するつもりで今回の記事を書きはじめました。しかし、いざ書いてみると意外に分量が多くなり、2回では収まらなくなりました (^_^;)。

次回以降で、今回紹介できなかったcgroup v2の操作や、サブシステムについて紹介していく予定です。

おすすめ記事

記事・ニュース一覧