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

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

この記事を読むのに必要な時間:およそ 6 分

cgroup v2の場合

cgroup v2の場合,プロセスを移動するための条件が少し厳しくなっています。cgroup Bへプロセスを登録するには次のような権限が必要です。

表2 cgroup v2で委譲するために必要な権限

権限 実際の書き込み権
1 cgroup B以下にcgroupを作れる権限 ディレクトリBへの書き込み権
2 cgroup B以下でプロセスを自由に登録できる権限 登録先cgroup内のcgroup.procsファイルへの書き込み権
プロセスがもともと所属しているcgroupと登録先cgroupの共通の祖先となるcgroup内のcgroup.procsファイルへの書き込み権

実効UIDの制限がなくなった理由は,表2の2つめの条件が加えられたためです。完全に自身がコントロールしているツリーでなければプロセスを移動できませんので,任意のプロセスは移動できません※2)⁠

※2)
4.11カーネルまでは,v1で紹介した2つめの条件「移動しようとするユーザのプロセスの実効UIDが,ターゲットとなるプロセスの実UIDもしくは保存set-user-idと一致する」が必要です。4.11で,v2では不要であるということで削除されています

文字で書くと条件が少しわかりづらいですね。実際に,cgroup v2で非特権の場合にプロセスを移動させてみましょう。

図4 cgroup v2でのプロセスの移動(1)

図4 cgroup v2でのプロセスの移動(1)

図4のような構成のツリーがあるとします。まずはcgroup_Bを作成し,ディレクトリとcgroup.procsファイルの所有権を変更します。

$ sudo chown ubuntu /sys/fs/cgroup/cgroup_B (cgroup Bをubuntuユーザ所有に)
$ sudo chown ubuntu /sys/fs/cgroup/cgroup_B/cgroup.procs
(cgroup Bのcgroup.procsファイルをubuntuユーザ権限に)
$ ls -al /sys/fs/cgroup/cgroup_B
total 0
drwxr-xr-x 4 ubuntu root  0 Mar  7 20:27 ./
dr-xr-xr-x 4 root   root  0 Mar  7 20:14 ../
-r--r--r-- 1 root   root  0 Mar  7 20:38 cgroup.controllers
-r--r--r-- 1 root   root  0 Mar  7 20:38 cgroup.events
-rw-r--r-- 1 ubuntu root  0 Mar  7 20:25 cgroup.procs
-rw-r--r-- 1 root   root  0 Mar  7 20:38 cgroup.subtree_control
(cgroup Bディレクトリとcgroup.procsファイルのみubuntuの所有となっている)

root cgroupからcgroup Bへのプロセスの移動

図4の(1)の操作を試してみましょう。ubuntuユーザでcgroup_Bcgroup.procsに登録を試みます。cgroup v1の場合はcgroup B内のcgroup.procsに書き込み権があれば,プロセスが登録できました。

$ whoami 
ubuntu
$ echo $$ > /sys/fs/cgroup/cgroup_B/cgroup.procs
-bash: echo: write error: Permission denied
(ユーザ自身の権限で実行されているプロセスを登録しようとしたが失敗した)

しかしcgroup v2では,cgroup.procsファイルはubuntuユーザの所有であり,ユーザ権限で実行されているプロセスを登録しようしたにも関わらずエラーになります。

これは表2で示した条件の2番目に引っかかったためです。この場合,⁠プロセスがもともと所属しているcgroupとcgroup Bの共通の祖先」はroot cgroupになります。ubuntuユーザはroot cgroupのcgroup.procsファイルに対する権限を持っていませんので,プロセスを移動させようとするとエラーになるわけです。

これは,図2で示した権限委譲のパターンのように,コンテナに所属させるプロセスを決めるのはroot cgroupの管理者であるべきという考え方でしょう。通常,コンテナを起動するのは,コンテナ内に権限を持つユーザではなくホスト上で権限を持つユーザですから,実際のユースケースに合っています。

この考え方に従い,ここはroot権限でプロセスをcgroup_Bに登録しましょう。

$ echo $$ | sudo tee /sys/fs/cgroup/cgroup_B/cgroup.procs
3126 (root権限でubuntuユーザ権限で動いているプロセスをcgroup_Bに登録)
$ grep $$ /sys/fs/cgroup/cgroup_B/cgroup.procs
3126 (登録できた)

この操作はroot権限で行っていますので登録できます。

権限があるcgroup間でのプロセスの移動(共通のcgroup配下のcgroup間)

それではcgroup_B配下にcgroup_Dcgroup_Eを作成し,先に登録したプロセスを移動させてみましょう。

$ whoami 
ubuntu
$ mkdir /sys/fs/cgroup/cgroup_B/cgroup_{D,E}
(ubuntuユーザ権限でcgroup D,Eを作成)
$ ls -d /sys/fs/cgroup/cgroup_B/cgroup_*
/sys/fs/cgroup/cgroup_B/cgroup_D/  /sys/fs/cgroup/cgroup_B/cgroup_E/
(問題なく作成できた)

cgroup_Bubuntuユーザ所有ですので,cgroup_Dcgroup_Eは問題なく作成できました。

それでは,さきほどroot権限でroot cgroupからcgroup_Bへ移動させたプロセスをcgroup_Dに移動させましょう。図4の(2)のパターンです。

$ whoami 
ubuntu
$ echo $$ > /sys/fs/cgroup/cgroup_B/cgroup_D/cgroup.procs
(自身の所有であるcgroup_Bのcgroup.procsに登録されたプロセスを自身の所有であるcgroup Dに移動)
$ grep $$ /sys/fs/cgroup/cgroup_B/cgroup_D/cgroup.procs
3126

今度はエラーなく作成できました。これはcgroup_Bcgroup_Bcgroup_Dの共通の祖先」という扱いになるためです。つまり,

  • cgroup_Dcgroup.procsubuntuユーザが所有
  • cgroup_Dcgroup_Bの共通の祖先であるcgroup_Bcgroup.procsファイルはubuntuユーザが所有

となり,2つの条件を満たします。

次に,cgroup_Dからcgroup_Eへのプロセスの移動を見てみましょう。図4の(3)のパターンです。

  • cgroup_Ecgroup.procsubuntuユーザが所有
  • cgroup_Dcgroup_Eの共通の祖先であるcgroup_Bcgroup.procsファイルはubuntuユーザが所有

ですので,両方の条件を満たします。試してみましょう。

$ whoami
ubuntu
$ ls -l /sys/fs/cgroup/cgroup_B/cgroup.procs
-rw-r--r-- 1 ubuntu root 0 Mar  7 20:25 /sys/fs/cgroup/cgroup_B/cgroup.procs
$ ls -l /sys/fs/cgroup/cgroup_B/cgroup_E/cgroup.procs
-rw-r--r-- 1 ubuntu ubuntu 0 Mar  7 20:57 /sys/fs/cgroup/cgroup_B/cgroup_E/cgroup.procs
(共通の祖先と移動先のcgroup.procsファイルに権限があることの確認)
$ grep $$ /sys/fs/cgroup/cgroup_B/cgroup_D/cgroup.procs 
3126 (cgroup Dにプロセスが登録されていることの確認)
$ echo $$ > /sys/fs/cgroup/cgroup_B/cgroup_E/cgroup.procs
(プロセスをcgroup Eに移動)
$ grep $$ /sys/fs/cgroup/cgroup_B/cgroup_E/cgroup.procs 
3126 (移動できた)

移動できました。

権限があるcgroup間でのプロセスの移動(共通のcgroup配下でないcgroup間)

別の例を紹介しましょう。

図5 cgroup v2でのプロセスの移動(2)

図5 cgroup v2でのプロセスの移動(2)

図5のようにroot cgroup直下にcgroup_Acgroup_Bを作成し,それぞれに子cgroup cgroup_Ccgroup_Dを作成します。

$ ls -ld /sys/fs/cgroup/cgroup_? (cgroup_A,Bはubuntuユーザ所有)
drwxr-xr-x 3 ubuntu root 0  3月 14日  19:38 /sys/fs/cgroup/cgroup_A/
drwxr-xr-x 3 ubuntu root 0  3月 14日  19:38 /sys/fs/cgroup/cgroup_B/
$ ls -l /sys/fs/cgroup/cgroup_?/cgroup.procs
(ubuntuユーザはcgroup_A,Bのcgroup.procsに書き込み権あり)
-rw-r--r-- 1 ubuntu root 0  3月 14日  19:33 /sys/fs/cgroup/cgroup_A/cgroup.procs
-rw-r--r-- 1 ubuntu root 0  3月 14日  19:33 /sys/fs/cgroup/cgroup_B/cgroup.procs
$ ls -ld /sys/fs/cgroup/cgroup_?/cgroup_? (cgroup_C,Dはubuntuユーザ所有)
drwxr-xr-x 2 ubuntu users 0  3月 14日  19:38 /sys/fs/cgroup/cgroup_A/cgroup_C/
drwxr-xr-x 2 ubuntu users 0  3月 14日  19:38 /sys/fs/cgroup/cgroup_B/cgroup_D/
$ ls -l /sys/fs/cgroup/cgroup_?/cgroup_?/cgroup.procs
(ubuntuユーザはcgroup_C,Dのcgroup.procsに書き込み権あり)
-rw-r--r-- 1 ubuntu users 0  3月 14日  19:38 /sys/fs/cgroup/cgroup_A/cgroup_C/cgroup.procs
-rw-r--r-- 1 ubuntu users 0  3月 14日  19:38 /sys/fs/cgroup/cgroup_B/cgroup_D/cgroup.procs

以上のようにcgroup_A〜Dまですべてubuntuユーザ所有のグループで,cgroup内のcgroup.procsファイルの所有権もubuntuユーザになっており,図5のような状態のcgroupツリーを準備しました。

ここでcgroup_Cからcgroup_Dへプロセスが移動できるでしょうか? 試してみましょう。

まずはcgroup_Cにプロセスを登録します。root cgroupからの移動になりますのでroot権限で移動させます。

$ echo $$ | sudo tee /sys/fs/cgroup/cgroup_A/cgroup_C/cgroup.procs 
3058 (root権限でcgroup_Cにプロセスを登録)
$ grep $$ /sys/fs/cgroup/cgroup_A/cgroup_C/cgroup.procs 
3058 (登録されている)

これでcgroup_Cにプロセスが登録された状態になりました。つぎに,このプロセスをcgroup_Cからcgroup_Dに移動させてみましょう。

$ echo $$ > /sys/fs/cgroup/cgroup_B/cgroup_D/cgroup.procs
-bash: echo: write error: Permission denied

cgroup_Dcgroup.procsubuntuユーザ所有であり,1番目の条件を満たしています。しかし,cgroup_Ccgroup_Dの共通の祖先をたどるとroot cgroupになります。root cgroupのcgroup.procsファイルに対してubuntuユーザは書き込み権限を持っていませんので,2番目の条件を満たしませんので,プロセスは移動できません。

このような移動ができない理由を考えてみましょう。

図5の構造を見ると,cgroup_A以下とcgroup_B以下はそれぞれ独立したコンテナとみなせます。いくら同じユーザ権限で動いているコンテナとはいえ,通常はコンテナをまたいでプロセスを移動することは考えにくいので,実際のユースケースに合った条件になったと言えるでしょう。

このようにcgroup v2では,条件を複雑にすることなく,プロセスの移動をコンテナ内に制限できるようになりました。

まとめ

今回を含めて4回でcgroup v2コアの機能を紹介してきました。

今回は特権を持たないユーザに対して,cgroupツリーの一部を権限委譲する際の機能について紹介しました。cgroup v1に比べてcgroup v2は,よりコンテナで使う場合の実際的な条件や制限が設定されていることが,お分かりいただけたのではないでしょうか。

cgroup v2には,これまで紹介したプロセスをグループ分けする以外にも,v1と同様にコントローラによってリソース制限をする機能があります。また,コアの機能としても新たに追加された機能がありますので,この連載では引き続きcgroup v2の機能を紹介していきたいと思いますが,今回でいったん説明の区切りが付きましたので,次回以降は少し違う話題を扱う予定です。

次回以降はしばらく私ではなく,ご自身でmrubyによるコンテナ実装Haconiwaを開発していらっしゃるudzuraさんにお書きいただく予定です。テーマは,この連載の32回でも軽く扱ったCRIU(チェックポイント・リストア)の話題の予定です。

udzura さんは,Haconiwaの開発を通じてCRIUのいろいろな機能を調査されているようですので,私もCRIUの機能を詳細に説明していただけるのではないかと,今からワクワクしています。

お楽しみに。

著者プロフィール

加藤泰文(かとうやすふみ)

2009年頃にLinuxカーネルのcgroup機能に興味を持って以来,Linuxのコンテナ関連の最新情報を追っかけたり,コンテナの勉強会を開いたりして勉強しています。英語力のない自分用にLXCのmanページを日本語訳していたところ,あっさり本家にマージされてしまい,それ以来日本語訳のパッチを送り続けています。

Plamo Linuxメンテナ。ファーストサーバ株式会社所属。

Twitter:@ten_forward
技術系のブログ:http://tenforward.hatenablog.com/

コメント

コメントの記入