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

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

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

前回の最後で「次回以降はしばらく私ではなく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/にいるように見えました。

$ sudo mkdir /sys/fs/cgroup/test01 (test01 cgroupを作成)
$ echo $$ | sudo tee /sys/fs/cgroup/test01/cgroup.procs (カレントシェルをtest01に登録)
27668
$ sudo unshare --cgroup /bin/bash (cgroup名前空間を作成)
# echo $$ (自身のPIDを確認)
27700
# cat /proc/self/cgroup (自分が所属するcgroupを確認)
0::/

念のため,作成した名前空間の外から確認してみると次のように確かに"/test01"にいることが確認できます。

$ sudo cat /proc/27700/cgroup 
0::/test01
nsdelegateオプションを指定しない場合

上記の例はnsdelegateを指定せずにcgroup v2をマウントしていますので,そのままおさらいとして,第34回でも紹介したように,"test01"と同じ階層に作成した"test02"というcgroupにプロセスを移動して,所属するcgroupを確認してみましょう。

# mkdir /sys/fs/cgroup/test02 (root直下にtest02を作成)
# echo $$ > /sys/fs/cgroup/test02/cgroup.procs (カレントシェルをtest02に移動)
# cat /proc/self/cgroup (現在のcgroupを確認)
0::/../test02

元々,root直下の"/test01"にいたプロセスを,同じくroot直下の/test02に移動させました。すると,名前空間内から自身が所属するcgroupを確認すると"/../test02"に所属していると表示されました。rootの親cgroupの配下の"test02" cgroupに所属しているということで,少し変な所属に見えます。

名前空間を作るという操作は,コンテナを作成するということですから,実際のケースでは,コンテナをまたいでプロセスを移動させるという操作は考えづらいです。しかし,上の例のようにコンテナをまたいでプロセスを移動できました。このような不自然な動きを拒否するのに,新たに追加されたnsdelegateオプションが使えます。

早速試してみましょう。

nsdelegateオプションを指定した場合

すでにcgroup v2をマウントしている場合,再マウントを行い,nsdelegateオプションを指定してマウントできます。そして,改めて"test01" cgroupにプロセスを追加してみまし,cgroup名前空間を作成してみましょう。

$ sudo mount -t cgroup2 -o remount,nsdelegate /sys/fs/cgroup/
(nsdelegateオプションを指定して再マウント)
$ echo $$ | sudo tee /sys/fs/cgroup/test01/cgroup.procs 
27668
$ sudo unshare --cgroup /bin/bash (cgroup名前空間を作成)
# cat /proc/self/cgroup (自分が所属するcgroupを確認)
0::/

ここまでは先のnsdelegateを指定していないときの例と同じです。それではカレントシェルのPIDを"test02"に移動させてみましょう。

# echo $$ > /sys/fs/cgroup/test02/cgroup.procs 
bash: echo: write error: No such file or directory
# ls /sys/fs/cgroup/test02/cgroup.procs 
/sys/fs/cgroup/test02/cgroup.procs

上のように"No such file or directory"とエラーになりました。しかし"test02" cgroupは存在しています。

つまり"/test01"をrootとしてcgroup名前空間が作られているので,その名前空間の中からは,rootを超えて,つまり名前空間の境界を超えて,別階層のcgroupにプロセスを移動できなくなっているというわけです。これがnsdelegateオプションが持つ機能のひとつです。

ちなみに,cgroup名前空間の外からであれば,権限があれば上記の操作はエラーになることなく移動できます。

図1 nsdelegateオプションを付けたときのプロセスの移動

図1 nsdelegateオプション

cgroupの権限委譲

連載の第40回で説明したとおり,cgroup v2では一般ユーザがcgroupを管理する場合,次のような権限が必要でした。

  • cgroup(ディレクトリ)への書き込み権限
  • cgroup内のcgroup.procsファイルへの書き込み権限

そして,一般的には権限委譲するcgroup,つまり権限委譲対象のユーザにとってのcgroup root内に存在するコントローラ関連のファイルには書き込み権限を与えません。図2のように,コンテナにリソースを割り当てるのは,あくまでコンテナが稼働しているホストの管理者であるという考え方です。

図2 リソース分配の権限

図2 リソース分配の権限

この権限委譲を行うには,従来は第40回で説明したように,アクセス権を管理者自身が設定する必要がありました。nsdelegateオプションが導入されてからは,管理者がアクセス権を細かく設定する代わりに,nsdelegateオプション付きでcgroup v2をマウントするという方法が採れるようになりました。

nsdelegateオプションを指定しない場合

まずはnsdelegateを指定しない方法を改めて紹介します。ここではあえて対象となるcgroupで,一般ユーザに全権限を与えるようにしてみます。

準備として,マウントオプションなしでcgroup v2をマウントし,子cgroupでiopidsmemoryコントローラが使えるように設定します。

$ sudo mount -t cgroup2 cgroup2 /sys/fs/cgroup/ (マウント)
$ echo "+io +pids +memory" | sudo tee /sys/fs/cgroup/cgroup.subtree_control 
(子cgroupでio, pids, memoryコントローラが使えるように設定)
+io +pids +memory
$ cat /sys/fs/cgroup/cgroup.subtree_control
io memory pids (設定された)

このあたりの操作は第38回で紹介したとおりです。

ここで"test01" cgroupを作成します。そして一般ユーザである"gihyo"ユーザに対して,"test01"ディレクトリとディレクトリ内に存在するファイルすべてにアクセス権を与えます。

$ sudo mkdir /sys/fs/cgroup/test01 (cgroup作成)
$ sudo chown gihyo /sys/fs/cgroup/test01
(ディレクトリ所有権をgihyoに)
$ sudo chown gihyo /sys/fs/cgroup/test01/*
(ディレクトリ内のファイル所有権をgihyoに)

通常はディレクトリとcgroup.procsファイルにのみアクセス権を与えることが一般的です。ここでは,あとのnsdelegateオプションを指定した場合との比較のためにあえてこのようにしています。

カレントシェルのPIDを"test01" cgroupに登録します。

$ echo $$ | sudo tee /sys/fs/cgroup/test01/cgroup.procs 
(カレントシェルをtest01に登録)
4352

ここでユーザ名前空間とともにcgroup名前空間を作成します。現在のユーザである"gihyo"ユーザをユーザ名前空間内の"root"ユーザにマッピングするために--map-root-userオプションを指定しています。

$ unshare --user --cgroup --map-root-user /bin/bash
(cgroup名前空間を作成しbashを実行)
# cat /proc/self/cgroup (cgroup名前空間内でrootにいることを確認)
0::/

ここではpids.maxに制限を設定することにして,このファイルと"test01"ディレクトリに書き込み権があることと,cgroup名前空間内にいることを確認します。その後,pids.maxファイルに制限値を設定してみます。

# ls -l /sys/fs/cgroup/test01/pids.max
(pids.maxファイルに書き込み権があることを確認)
-rw-r--r-- 1 root nogroup 0 11月 30日  19:51 /sys/fs/cgroup/test01/pids.max
# echo 50 > /sys/fs/cgroup/test01/pids.max (制限値の書き込み)
# cat /sys/fs/cgroup/test01/pids.max 
50
(書き込めたことを確認)

無事にファイルに制限値を書き込めたことが確認できました。アクセス権から考えて予想できる動きではないかと思います。

nsdelegateオプションを指定した場合

それではnsdelegateオプションを指定してcgroup v2をマウントして試してみましょう。

マウント後,先の例と同じように,子cgroupでiopidsmemoryコントローラが使えるようにします。

$ sudo mount -t cgroup2 -o nsdelegate cgroup2 /sys/fs/cgroup/
(マウント)
$ echo "+io +pids +memory" | sudo tee /sys/fs/cgroup/cgroup.subtree_control
(子cgroupでio, pids, memoryコントローラが使えるように設定)
+io +pids +memory
$ cat /sys/fs/cgroup/cgroup.subtree_control
io memory pids (設定された)

そして,この後も先の例と同じように,子cgroupとして"test01" cgroupを作成し,ディレクトリ内のファイルの所有権を一般ユーザである"gihyo"ユーザにします。

$ sudo mkdir /sys/fs/cgroup/test01 (cgroup作成)
$ sudo chown gihyo /sys/fs/cgroup/test01
(ディレクトリ所有権をgihyoに)
$ sudo chown gihyo /sys/fs/cgroup/test01/*
(ディレクトリ内のファイル所有権をgihyoに)

ここでも,先のnsdelegateオプションを指定しないときの例と同様に,"test01" cgroup内のすべてのファイルにアクセス権を与えました。そしてカレントシェルを登録します。

$ echo $$ | sudo tee /sys/fs/cgroup/test01/cgroup.procs 
(カレントシェルをtest01に登録)
4399

次に,先の例と同じくユーザ名前空間とcgroup名前空間を作成します。

$ unshare --user --cgroup --map-root-user /bin/bash
(cgroup名前空間を作成しbashを実行)
# cat /proc/self/cgroup 
0::/ (cgroup名前空間内でrootにいることを確認)

所有権を設定していますので"test01"直下に存在するpid.maxファイルには書き込み権があります。

# ls -l /sys/fs/cgroup/test01/pids.max 
(pids.maxファイルに書き込み権があることを確認)
-rw-r--r-- 1 root nogroup 0 11月 30日  20:02 /sys/fs/cgroup/test01/pids.max
# echo 50 > /sys/fs/cgroup/test01/pids.max 
-bash: echo: write error: Operation not permitted
(書き込めない)

上の例のように,cgroup名前空間内のrootにあたるcgroup内に存在するコントローラ用のファイルに書き込もうとするとエラーになりますEPERMが返ります)⁠

著者プロフィール

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

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

Plamo Linuxメンテナ

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