前回の最後で「次回以降はしばらく私ではなくudzuraさんにお書きいただく予定です」と書きました。しかし、年末に近づきAdvent Calendarの募集が始まるようになり、Advent Calendarの存在をすっかり忘れていたことに気づきました。
毎年、Advent Calendarに参加していましたので今年も何か参加しようと思って登録だけはしました。その後に書く内容を考えたのですが、思いついた内容がcgroup v2関連でしたので、これは連載の記事にしよう思いました。というわけで今回も前回までに続いて加藤による記事となります。この記事はLinux Advent Calendar 2018の14日目の記事です。
今回は4.13、4.14カーネルでcgroup v2に追加された機能について紹介します。
nsdelegate オプション
第38回でcgroup v2のマウント方法を紹介しました。第38回で使用していた4.11カーネルの時点では、cgroup v2をマウントする際に指定できるオプションはありませんでした。
その後、4.13カーネルでnsdelegate
というマウントオプションがひとつだけ追加されました。現時点でもcgroup v2のマウントで指定できるオプションは、このnsdelegate
のみです。
このオプションは、Linuxを起動したあとの初期の名前空間でcgroup v2をマウントするときのみ指定できます。すでにcgroup v2をマウントしている場合は、再マウントするにより有効にできます。それ以外の名前空間でマウントしても無視されます。
このオプションは、カーネル付属文書のcgroup v2文書では「cgroup名前空間を権限委譲の境界とみなします」と書かれています。このように、この連載の第34回で紹介したcgroup名前空間と連携して動作します。
それでは、この「権限委譲の境界とみなす」とはどういうことなのかを説明していきましょう。
cgroup間のプロセスの移動
cgroup名前空間は第34回で説明したとおり、新しく作成した名前空間内では、プロセスが所属しているcgroupがツリーのrootに見えるという機能でした。
詳しくは第34回を参照していただくとして、簡単に紹介しておきましょう。
次の例は4.14.44カーネル上のcgroup v2で試しています。シェルを"test01" cgroupに登録し、その子プロセスで新たにcgroup名前空間を作成します。そして、"/test01
"にいるはずのプロセスから所属しているcgroupを確認すると、root(/
)にいるように見えました。
念のため、作成した名前空間の外から確認してみると次のように確かに"/test01
"にいることが確認できます。
nsdelegateオプションを指定しない場合
上記の例はnsdelegate
を指定せずにcgroup v2をマウントしていますので、そのままおさらいとして、第34回でも紹介したように、"test01"と同じ階層に作成した"test02"というcgroupにプロセスを移動して、所属するcgroupを確認してみましょう。
元々、root直下の"/test01
"にいたプロセスを、同じくroot直下の/test02
に移動させました。すると、名前空間内から自身が所属するcgroupを確認すると"/../test02
"に所属していると表示されました。rootの親cgroupの配下の"test02" cgroupに所属しているということで、少し変な所属に見えます。
名前空間を作るという操作は、コンテナを作成するということですから、実際のケースでは、コンテナをまたいでプロセスを移動させるという操作は考えづらいです。しかし、上の例のようにコンテナをまたいでプロセスを移動できました。このような不自然な動きを拒否するのに、新たに追加されたnsdelegateオプションが使えます。
早速試してみましょう。
nsdelegateオプションを指定した場合
すでにcgroup v2をマウントしている場合、再マウントを行い、nsdelegate
オプションを指定してマウントできます。そして、改めて"test01" cgroupにプロセスを追加してから、cgroup名前空間を作成してみましょう。
ここまでは先のnsdelegateを指定していないときの例と同じです。それではカレントシェルのPIDを"test02"に移動させてみましょう。
上のように"No such file or directory"とエラーになりました。しかし"test02" cgroupは存在しています。
つまり"/test01
"をrootとしてcgroup名前空間が作られているので、その名前空間の中からは、rootを超えて、つまり名前空間の境界を超えて、別階層のcgroupにプロセスを移動できなくなっているというわけです。これがnsdelegateオプションが持つ機能のひとつです。
ちなみに、cgroup名前空間の外からであれば、権限があれば上記の操作はエラーになることなく移動できます。
cgroupの権限委譲
連載の第40回で説明したとおり、cgroup v2では一般ユーザがcgroupを管理する場合、次のような権限が必要でした。
- cgroup(ディレクトリ)への書き込み権限
- cgroup内の
cgroup.procs
ファイルへの書き込み権限
そして、一般的には権限委譲するcgroup、つまり権限委譲対象のユーザにとってのcgroup root内に存在するコントローラ関連のファイルには書き込み権限を与えません。図2のように、コンテナにリソースを割り当てるのは、あくまでコンテナが稼働しているホストの管理者であるという考え方です。
この権限委譲を行うには、従来は第40回で説明したように、アクセス権を管理者自身が設定する必要がありました。nsdelegate
オプションが導入されてからは、管理者がアクセス権を細かく設定する代わりに、nsdelegate
オプション付きでcgroup v2をマウントするという方法が採れるようになりました。
nsdelegateオプションを指定しない場合
まずはnsdelegate
を指定しない方法を改めて紹介します。ここではあえて対象となるcgroupで、一般ユーザに全権限を与えるようにしてみます。
準備として、マウントオプションなしでcgroup v2をマウントし、子cgroupでio
、pids
、memory
コントローラが使えるように設定します。
このあたりの操作は第38回で紹介したとおりです。
ここで"test01" cgroupを作成します。そして一般ユーザである"gihyo"ユーザに対して、"test01"ディレクトリとディレクトリ内に存在するファイルすべてにアクセス権を与えます。
通常はディレクトリとcgroup.procs
ファイルにのみアクセス権を与えることが一般的です。ここでは、あとのnsdelegate
オプションを指定した場合との比較のためにあえてこのようにしています。
カレントシェルのPIDを"test01" cgroupに登録します。
ここでユーザ名前空間とともにcgroup名前空間を作成します。現在のユーザである"gihyo"ユーザをユーザ名前空間内の"root"ユーザにマッピングするために--map-root-user
オプションを指定しています。
ここではpids.max
に制限を設定することにして、このファイルと"test01"ディレクトリに書き込み権があることと、cgroup名前空間内にいることを確認します。その後、pids.max
ファイルに制限値を設定してみます。
無事にファイルに制限値を書き込めたことが確認できました。アクセス権から考えて予想できる動きではないかと思います。
nsdelegateオプションを指定した場合
それではnsdelegate
オプションを指定してcgroup v2をマウントして試してみましょう。
マウント後、先の例と同じように、子cgroupでio
、pids
、memory
コントローラが使えるようにします。
そして、この後も先の例と同じように、子cgroupとして"test01" cgroupを作成し、ディレクトリ内のファイルの所有権を一般ユーザである"gihyo"ユーザにします。
ここでも、先のnsdelegate
オプションを指定しないときの例と同様に、"test01" cgroup内のすべてのファイルにアクセス権を与えました。そしてカレントシェルを登録します。
次に、先の例と同じくユーザ名前空間とcgroup名前空間を作成します。
所有権を設定していますので"test01"直下に存在するpid.max
ファイルには書き込み権があります。
上の例のように、cgroup名前空間内のrootにあたるcgroup内に存在するコントローラ用のファイルに書き込もうとするとエラーになります(EPERM
が返ります)。
cgroup v2コアで使用する4.14カーネルで追加されたファイル
第38回でcgroup v2コアで使用するファイルについて紹介しました。いずれもcgroup.
で始まるファイルでした。
その後、cgroup v2の開発が進むとともに、第38回で紹介したファイル以外に追加されたファイルがありますので、それらを今回紹介しましょう。
いずれも4.14カーネルで追加されました。
表1 cgroup v2コアで使用するファイル(追加分)
ファイル名 |
説明 |
可能な操作 |
デフォルト値 |
cgroup.type |
そのcgroupの現在のタイプ。root以外に存在 |
読み書き |
- |
cgroup.threads |
そのcgroupに属するスレッドの登録、確認 |
読み書き |
- |
cgroup.max.depth |
そのcgroupが持てる子孫の深さ |
読み書き |
max |
cgroup.max.descendants |
そのcgroupが持てる子孫の最大数 |
読み書き |
max |
cgroup.stat |
cgroupのステータス |
読み込み |
- |
表1が4.14カーネルで追加されたファイルです。実際にファイルが出現していることを確認しましょう。4.14.44カーネルで試しています。
それでは、これらの追加されたファイルそれぞれについて説明していきましょう。
スレッドモード
4.14カーネルでは、cgroup v2に大きな変更が加えられました。これは、スレッド単位でcgroupを制御する、「スレッドモード」というモードです。
表1で紹介した、4.14で追加されたファイルのうち、次の2つのファイルがこのスレッドモードに関係します。
- cgroup.type
- cgroup.threads
cgroup.type
ファイルは、cgroupがプロセスを扱うのか、スレッドを扱うのかなど、そのcgroupが持つ機能を表す文字列が書かれます。
cgroup.threads
ファイルは、cgroup.procs
ファイルのスレッド版で、スレッドをcgroupに追加・削除する際、cgroupに属するスレッドを確認する際に使うファイルです。cgroup v1に存在したtasks
ファイルと同様のファイルです。
スレッドモードを説明すると長くなりますので、スレッドモードの説明とこれらのファイルの使い方については、この連載の後の回で詳しく紹介したいと思います。
cgroupの子孫の制御
4.14カーネルでは、スレッドモードの他に、cgroupの子孫を制御する機能が追加されました。
これは、cgroupの階層が深くなったり子孫数が多くなると、パフォーマンスに影響を与える可能性があるためです。特に権限委譲を行うと、ホストの管理者が知らないところでシステム上に存在するcgroupの数が増えてしまう可能性がありますので、そのような場合に有効な機能でしょう。
cgroup.max.depth
cgroup.max.depth
ファイルは、cgroupの子孫の深さを制限します。デフォルト値はmax
であり、制限がないことを表します。実際に試しながら確認しましょう。
"test1" cgroupを作成し、cgroup.max.depth
ファイルに2
を書き込み、深さを2に制限します。つまり"test1"から見ると孫cgroupまでは許されます。しかし、ひ孫を作る所でエラーになるはずです。
制限を2に設定しました。設定が確認できましたので、子孫のcgroupを作成して、設定したとおりに制限されているかを確認しましょう。
"test1" cgroupの子として"test2"、孫として"test3"までは作成できました。しかし、さらにひ孫として"test4"を作成しようとしたところでエラーになりました。
cgroup.max.depth
の制限にひっかかった場合は、エラーとしてEAGAIN
が返ります。
ここで"test2"、"test3" cgroup内のcgroup.max.depth
ファイルはどうなっているのか確認してみましょう。
このように特に設定していないcgroupの制限は"max"となっています。この場合でも上位の"test1"で設定した制限が効いていますので、上位で設定した制限にひとつでもひっかかるとエラーになることがわかります。
cgroup.max.descendants
cgroup.max.descendants
ファイルは、cgroupが持てる子孫の数を制限します。デフォルト値はcgroup.max.depth
と同じく"max"であり、制限がないことを表します。試してみましょう。
"test1"のcgroup.max.descendants
ファイルに2
を書き込み、子孫数を2に制限します。
制限を2に設定しました。設定が確認できましたので、子cgroupを作成して、設定したとおりに制限されているか確認しましょう。
3つめのcgroupを作るところでエラーになりました。cgroup.max.depth
と同様にEAGAIN
が返ります。
cgroup.stat
cgroupの統計情報が含まれているファイルで、読み取り専用です。現時点ではcgroupの子孫情報のみ含まれています。
nr_descendants
- 利用できる子孫cgroupの数
nr_dying_descendants
- 消滅途中の子孫cgroupの数
「消滅途中」とは、ユーザがcgroupを削除しても、システム負荷に応じてcgroupが消滅するまでに時間がかかるので、子孫のうち、その削除途中の状態にあるcgroup数がカウントされます。
先のcgroup.max.descendants
の例で、2つ子cgroupを作成したあとのcgroup.stat
ファイルを確認してみましょう。
子cgroupをふたつ作成したので、nr_descendants
が2となっています。
まとめ
今回はcgroup v2に追加された新機能を紹介しました。
nsdelegateマウントオプションは、cgroup名前空間と連携して、自動的に権限委譲の境界を定義する機能です。この機能により、名前空間をまたいでプロセスは移動できません。そして、名前空間のrootではコントローラのリソース制限値を変更できません。
そして、これまでは無限に子孫を増やせたcgroupに、子孫の数を制限する機能が追加されました。システム上のcgroupの数や階層が増えてパフォーマンスに影響を与えるのを防げます。
次回からは、前回書いたとおりにudzuraさんにCRIUについて書いていただく予定です。先日開催されたJapanContainerDays v18.12で、udzuraさんのCRIUに関するセッションを聞きました。非常に面白い内容でしたので、今後の記事が楽しみです。