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

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

昨年12月に前回の記事を書いて以来、世の中の状況がすっかり変わってしまいました。

筆者も4月以降はほとんど会社に出勤することなく、自宅で仕事をしています。自宅の物置のように使っていたスペースを片付け、少しずついろいろなものを買い揃え、自宅でもまずまず快適な環境で仕事ができるようになりました。同様に、これを機に自宅でも快適に仕事ができるようにいろいろと買い揃えた方も多いのではないでしょうか。残念ながら、自宅の環境が快適になったからと言ってこの記事の公開ペースが速くなるということはありませんが。:-p

さて、年末に書いた記事ではケーパビリティの話を3回に渡って説明しました。今回はそのケーパビリティシリーズの前に紹介していたcgroup v2の話題に戻って、その機能を紹介していきたいと思います。

cgroup v2とCPUコントローラ

この連載の第37回で書いたように、cgroup v1は自由度が高い反面、制約も多かったため、結局自由度の高さを活かすことができませんでした。

そこで、cgroup v1(以降v1)が持っていた問題点を解決するためにcgroup v2(以降v2)の開発が始まり、v2は4.5カーネル(2016年3月)で正式な機能となりました。

v2が4.5カーネルで正式機能になったとはいえ、リソースコントロールを行う対象としては重要な機能のひとつであるCPUコントローラが使えませんでした。4.5カーネルがリリースされた時点のカーネルに付属していたv2のドキュメントにはCPUコントローラに関する記載がありました。しかし、⁠CPUコントローラに対するインターフェースはまだマージされていない」という注意書きがあり、機能としてはマージされていませんでした。

これは、v1が持っていた問題点を解決すべく定められたv2の仕様が、スケジューラの開発者に受け入れられなかったためです(※1)

連載の第37回で説明したとおり、v2が持つ重要な特徴のひとつに、cgroupでコントロールする対象をプロセス単位にしたことが挙げられます。

この理由は、v1ではスレッド単位でのコントロールを行っていたにもかかわらず、ほとんどのコントローラではスレッド単位で制御を行う意味なかったためです。このため、v2では制御の単位をコントローラ全体で意味のあるプロセスとしました。

しかし、コントローラの中にはスレッド単位で制御を行う意味があるコントローラがありました。これがCPUコントローラです。スケジューラの観点から見ると、制御の単位はスレッドであり、プロセスという粗い単位で制御を行っても意味がないとさえ言えたわけです。

また、同じく第37回で説明した「プロセスが所属できるのは末端のcgroupのみ」という機能についてもCPUコントローラにとっては邪魔な制限でした。多くのコントローラではcgroupにプロセスと子cgroupの両方が所属した場合、cgroupに所属するタスクと子cgroupに所属するタスクの競合問題が発生しました。この問題に対するコントローラ側のサポートを簡単にするために、末端のcgroupにしかタスクが所属できなくなったのですが、これはCPUコントローラにとっては解決できる問題でした。

このようなv2が持つ特徴は、v2で一番重要な特徴である「単一階層構造」を取るために必要なことでした。単一の階層なのですから、あるcgroupに所属するタスクはいろいろなコントローラから同時に制御を受ける可能性があるためです。

スレッドモード

v2はデフォルトではプロセス単位でコントロールします。このプロセス単位でコントロールを行う動作は変えず、先に書いたような問題を解決する方法が導入されました。

これが今回紹介する、スレッド単位でコントロールしたいコントローラと、プロセス単位でコントロールを行うv2の仕様が両立できるように導入したスレッドモードです。スレッドモードは第41回で紹介したとおり、4.14カーネルで導入されました。

スレッドモードでは、スレッドモードに対応したコントローラはスレッド単位でコントロールできます。スレッド単位で制御を行いたいコントローラと、v2デフォルトのプロセス単位でのコントロールするcgroupとの両立をするために、cgroupツリー内の特定のcgroup配下のツリー(サブツリー)でスレッドが扱えるようになりました。このようにスレッド単位でコントロールができるサブツリーをスレッド化サブツリー(Threaded subtree)と呼びます。

スレッドコントローラ

スレッドモードは、スレッドでリソース制御が行えるコントローラのために作られたモードです。このため、スレッドモードに対応したコントローラだけがスレッド化サブツリー内で使えます。このようにスレッドモードに対応したコントローラをスレッドコントローラ(Threaded controllers)と呼びます。

執筆時点の5.7カーネル内に存在するスレッドコントローラは次のコントローラです。

  • cpu
  • cpuset
  • perf_event
  • pids

これ以外のコントローラはプロセス単位でのリソース制御のみに対応しておりドメインコントローラ(Domain controllers)と呼びます。

cgroupのタイプ

このスレッドモードが導入されたことにともない、cgroupツリー内にディレクトリとして作成するcgroupにはいくつかのタイプが定義されました。作成されたcgroupは表1のタイプのいずれかが設定されます。

表1 cgroupのタイプ
タイプ 説明
domain プロセス単位でのコントロールを行う従来のcgroup
threaded スレッド単位のコントロールを行うcgroup
domain threaded スレッド化サブツリーのroot。このcgroup配下のcgroupが"threaded"となる
domain invalid スレッド化サブツリー内でcgroupを作成した直後の"invalid"な状態

スレッド化サブツリー

スレッド化サブツリーがあるv2ツリーの様子を図で表すと図1のようになります。

図1 スレッド化サブツリー
図1 スレッド化サブツリー

通常のcgroup("cgroup A〜D")は"domain"となっており、これらのcgroupでは通常どおりプロセス単位でコントロールします。そして"cgroup T"をルートとした点線内がスレッド化サブツリーで、このサブツリー内の"cgroup T,T-a,T-b"はスレッド単位でコントロールできます。

このスレッド化サブツリー内ではスレッド単位でコントロールできるので、スレッドはサブツリー内に存在するどこのcgroupにも所属できますし、同一プロセス内のスレッドであっても異なるcgroupに所属できます。

また、v2が持つ特徴のひとつに、プロセスはツリーの末端cgroupにのみ所属できるという制約がありました。これは親子関係のあるcgroup間で内部タスクの競合が起こることを防ぐために設定された制約です。しかし、スレッド化サブツリー内ではこのような制約はありません。スレッド化サブツリー内であれば、末端であっても末端でなくても任意のcgroupでリソース制御ができますし、すでにスレッドが所属しているcgroupでcgroup.subtree_controlにスレッドコントローラを設定できます。

逆に言うとスレッドコントローラは、スレッド単位でコントロールすると同時に、親子関係にあるcgroup間に所属するタスクの競合をうまく扱えなければならないということです。

このように導入されたスレッド化サブツリーでは、v2で設定された制限が大幅に緩められているように見えますが、もちろんスレッド化サブツリーならではの制限があります。これを説明しましょう。

スレッド化サブツリー内はすべて"threaded"でなければならない

まず最初の制限は、スレッド化サブツリー内のcgroupは、ルートが"domain threaded"である以外は、すべて"threaded"なcgroupでなければなりません。

v2のデフォルトは"domain"ですので、新規にcgroupを作成した直後はスレッド化サブツリー内であっても初期状態としては"domain"として作成されます。スレッド化サブツリー内ではcgroupは"threaded"である必要がありますので、作成直後の"domain"状態では使えません。この状態が"domain invalid"の状態です。

たとえば、図1のように"cgroup T-b"に子cgroupとなる"cgroup T-c"を作ったとすると、作成直後は"domain"であり、スレッド化サブツリー内では使えない状態である"domain invalid"となります。この"domain invalid"のcgroupは"threaded"に変えることができます。

同じプロセス内のスレッドは同じスレッド化サブツリー所属でなければならない

スレッド化サブツリーはシステム上に複数存在できます。しかし、システム上にスレッド化サブツリーが複数ある場合でも、同じマルチスレッドプロセス内のスレッドは同じスレッド化サブツリーに所属している必要があります。

これは、スレッド単位でコントロールしたいプロセスを一旦スレッド化サブツリーのルートに登録して、そのプロセス内のスレッドをサブツリーに分配していくと考えるとわかりやすいでしょう。

また、この制限により、スレッドを扱わないドメインコントローラは、プロセスが"domain threaded"であるcgroupに属すると考えて処理できますので、ドメインコントローラとも共存できます。

スレッドモードに関係するファイル

cgroupは、これまでこの連載でも紹介した機能と同様に、cgroupツリー内に存在するファイルやディレクトリを操作することで設定などの制御を行います。

このうち、スレッドモードに関係するファイルを紹介しましょう。

表2 v2で使用するファイルのうちスレッドモードに関係するファイル
ファイル名 説明 出現するcgroup
cgroup.type cgroupのタイプ root以外
cgroup.threads cgroupに所属するスレッドのリスト rootを含むすべて

cgroup.type

cgroup.typeは、そのファイルが存在するcgroupが、表1で説明したcgroupタイプのうちどのタイプなのかを示します。また、cgroupのタイプを変更したい場合に、タイプを表す文字列を書き込むことでcgroupのタイプを変更します。root以外のすべてのcgroupに存在します。

# mount -t cgroup2 cgroup2 /sys/fs/cgroup/
# mkdir /sys/fs/cgroup/test01
# cat /sys/fs/cgroup/test01/cgroup.type 
domain

このように、作成したcgroupのcgroup.typeの中身を確認するとdomainであることがわかります。変更する方法は次回説明します。

cgroup.threads

cgroupに所属するスレッドIDが含まれるファイルです。rootを含むすべてのcgroupに存在します。

次のようにふたつのスレッドを持つプロセスがある場合、cgroup.procsには親となるプロセスのPIDのみが含まれており、cgroup.threadsにはcgroup.procsにも書かれていたPIDに加えて、プロセスに所属するスレッドのIDも含まれています。

# pstree -p 946
threadtest(946)─┬─{threadtest}(947)
                 └─{threadtest}(948)
# cat cgroup.procs 
946
# cat cgroup.threads 
946
947
948

上の実行例のように、v1のtasksファイルやv2のcgroup.procsと同様に所属させたいタスクのIDを書き込んだり、所属しているタスクを表示したりできます。cgroup.procsと異なるのはスレッドのIDであるTID単位で登録ができるということです。

まとめ

今回はスレッドモードの概要と、それにともない導入されたcgroupのタイプ、スレッドモードの制約、スレッドモードに関係するcgroup内のファイルについて説明しました。

今回は、スレッドモードについて次のようなことを説明しました。

  • cgroupツリーの一部にスレッドを扱えるスレッド化サブツリー(Threaded subtree)が作成できる
  • スレッドモードに対応したスレッドコントローラだけがスレッド化サブツリー内でスレッドを扱える
  • cgroupにはタイプが存在する
  • 同じプロセス内のスレッドは同一のスレッド化サブツリー内に所属している必要がある

次回は、実際にスレッド化サブツリーの操作を行っていく予定です。スレッド化サブツリーを作成し、その作成したスレッド化cgroupにプロセスやスレッドを追加したり、サブツリー内では同一プロセス内のスレッドでも別のcgroupに所属できることなど、これまで紹介してきたcgroup v2とは違う、スレッドモード独特の動きを紹介していきます。

おすすめ記事

記事・ニュース一覧