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

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

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

cgroup状態通知

cgroupで,コントローラを使ってリソース制限を行う以外に,プロセスを管理するためにcgroupを使う場合があります。最近のLinuxでは,systemdが広く使われていますので,どちらかというとプロセスの管理に使われることのほうが一般的ですね。

プロセスの管理を行う場合,cgroup内にタスクやプロセスがなくなったことを知ることができれば便利です。このような場合に使える通知を行う仕組みがcgroup v1にもv2にも存在します。

cgroup v1,v2それぞれで試してみましょう。

cgroup v1での状態通知

cgroup v1では,あるcgroupにタスクが存在しなくなった場合に,あらかじめ登録してあるプログラムを実行できます。

表1 cgroup v1の状態通知に関係するファイル

ファイル名 notify_on_release release_agent
説明 cgroupにタスクが存在しなくなった場合にrelease_agentに登録したプログラムを実行するかどうか cgroupにタスクが存在しなくなった場合に実行するプログラム
0 or 1 プログラムのフルパス
デフォルト値 親cgroupの値を引き継ぐ。rootでは0 なし
ファイルの場所 全cgroup root cgroup

cgroupにタスクが存在しなくなったときに,release_agentに登録したプログラムを実行する場合には,notify_on_releaseファイルに"1"を書き込みます。デフォルトは上位cgroupの値がコピーされます。

# cat /sys/fs/cgroup/pids/notify_on_release
0 (root cgroupでは0が設定されている)
# mkdir /sys/fs/cgroup/pids/test01 (test01を作成)
# cat /sys/fs/cgroup/pids/test01/notify_on_release
0 (cgroup作成直後は親cgroupの値を継承)

root cgroupでは"0"がデフォルト値です。このまま子cgroup "test01"を作成したところ,そのまま親であるroot cgroupの値を引き継ぎ,"test01"のnotify_on_releaseの値も"0"に設定されました。

root cgroupの値を"1"に変更してみましょう。

# echo 1 > /sys/fs/cgroup/pids/notify_on_release
(root cgroupのnotify_on_releaseを1に変更)
# mkdir /sys/fs/cgroup/pids/test02 (test02を作成)
# cat /sys/fs/cgroup/pids/test02/notify_on_release
1 (親の値を継承している)
# cat /sys/fs/cgroup/pids/test01/notify_on_release
0 (親の値が変わっても既存の子孫には反映されない)

"1"に変更したあとに,新たに子cgroup "test02"を作ると,"test02"のnotify_on_releaseは"1"になりました。ただし,このとき,先に作成した"test01"のnotify_on_releaseの値は変わっていません。あくまで作成時に親の値を参照して設定するだけです。

それではrelease_agentに登録するプログラムを準備しましょう。このプログラムが実行される場合は,引数としてroot cgroupからの相対パスが渡されます。次のようなシェルスクリプトを準備しました。

#!/bin/sh
rmdir /sys/fs/cgroup/pids/$1
logger "cgroup $1 is removed."

cgroupにタスクが存在しなくなった通知を受け取った場合には,そのcgroupを削除し,syslogにログを出力します。

それでは試してみましょう。

さきほどのシェルスクリプトを/usr/local/bin/rmcg.shに置き,実行権限をつけました。このスクリプトをrelease_agentに登録します。

# echo "/usr/local/bin/rmcg.sh" > /sys/fs/cgroup/pids/release_agent
(release_agentにプログラムを登録)
/usr/local/bin/rmcg.sh
# cat /sys/fs/cgroup/pids/release_agent 
(登録された)
/usr/local/bin/rmcg.sh

登録されたので,さきほど作成したnotify_on_releaseが有効な"test02"にプロセスを登録して,すぐに削除してみましょう。

# echo $$ > /sys/fs/cgroup/pids/test02/tasks 
(test02 cgroupにプロセスを登録)
# grep $$ /sys/fs/cgroup/pids/test02/tasks 
1729 (登録された)
# echo $$ > /sys/fs/cgroup/pids/tasks 
(test02 cgroupからプロセスを削除=root cgroupに移動)

では"test02" cgroupがどうなったか確認してみましょう。

# ls -ld /sys/fs/cgroup/pids/test02
ls: cannot access '/sys/fs/cgroup/pids/test02': No such file or directory
(test02 cgroupが存在しない)
# tail -n1 /var/log/syslog 
Mar 19 20:35:35 xenial root: cgroup /test02 is removed.
(loggerコマンドによりsyslogにログが書き込まれている)

"test02" cgroupにタスクが存在しなくなったので,release_agentが呼び出され,"test02" cgroupが削除され,syslog出力されています。

同じ操作を"test01"に対して行っても,"test01"は"notify_on_release`が"0"でしたので,何も起こりません。

# cat /sys/fs/cgroup/pids/test01/notify_on_release 
0 (test01のnotify_on_releaseは0)
# echo $$ > /sys/fs/cgroup/pids/test01/tasks
(test01にプロセスを登録)
# echo $$ > /sys/fs/cgroup/pids/tasks 
(test01からプロセスを削除)
# ls -ld /sys/fs/cgroup/pids/test01
drwxr-xr-x 2 root root 0 Mar 19 20:25 /sys/fs/cgroup/pids/test01
(test01は消去されていない)

実際には,cgroupごとにnotify_on_releaseの値を変えて,処理を変えることはないかもしれませんが,このようにcgroupごとにタスクがなくなったときの処理の有無を設定できます。実行するプログラムはroot cgroupのrelease_agentに登録するだけですので変えられません。

cgroup v1が持つ通知は以上のような仕組みです。v1では,cgroupにタスクがいなくなるたびにプロセスを起動しますのでコストが高くなります。

cgroup v2

それではcgroup v2の機能を見てみましょう。

cgroup v2ではrootではないcgroupには,cgroup.eventsというファイルが存在します。このファイルの中身を見れば,自身または子孫にプロセスが所属しているかどうかがわかります。

表2 cgroup v2の状態通知に関係するファイル

ファイル名 cgroup.events
説明 cgroup内のプロセスの有無を示すパラメータを含む
プロセスが存在するとき populated 0存在しないとき populated 1
デフォルト値 populated 0
ファイルの場所 root以外

このファイルにはpopulatedという項目の行だけが存在しており,この値はプロセスが存在していれば"1",存在していなければ"0"です。

# ls /sys/fs/cgroup/test01/ (子cgroupが存在しない)
cgroup.controllers  cgroup.events  cgroup.procs  cgroup.subtree_control
# cat /sys/fs/cgroup/test01/cgroup.procs  (プロセスが所属していない)
# cat /sys/fs/cgroup/test01/cgroup.events (populatedの値が0)
populated 0

この例では"test01"は子cgroupを持たず,プロセスも所属していないので,populatedの値は"0"です。この"test01"にプロセスを所属させて,cgroup.eventsの中身を見てみましょう。

# echo $$ > /sys/fs/cgroup/test01/cgroup.procs (プロセスを登録)
# grep $$ /sys/fs/cgroup/test01/cgroup.procs   (登録されたことを確認)
3081
# cat /sys/fs/cgroup/test01/cgroup.events (populatedの値が1になっている)
populated 1

populatedの値が"1"になりましたね。

ここで別のシェルを開いてinotifywatchコマンドでcgroup.eventsファイルを監視し,今度は"test01"からプロセスを削除してみましょう。

# inotifywait -m /sys/fs/cgroup/test01/cgroup.events 
Setting up watches.
Watches established.
(ここでtest01からプロセスをrootに移動)
/sys/fs/cgroup/test01/cgroup.events MODIFY cgroup.events

"test01"からプロセスが削除されると,cgroup.eventsファイルに"MODIFY"というinotifyイベントが発生しました。このイベントは"test01"の子cgroupに対する変化でも発生します。

# mkdir /sys/fs/cgroup/test01/test02 (test01配下にtest02を作成)

# echo $$ > /sys/fs/cgroup/test01/test02/cgroup.procs
(test02にプロセスを登録)

上の例のように"test01"の下に"test02"を作成し,"test02"にプロセスを登録すると,次のように"test01"のcgroup.eventsにinotifyイベントが発生します。

# inotifywait -m /sys/fs/cgroup/test01/cgroup.events 
Setting up watches.
Watches established.
/sys/fs/cgroup/test01/cgroup.events MODIFY cgroup.events
(test01自身ではなく子のtest02の変化もtest01で知れる)

もちろん,このときtest02内のcgroup.eventsファイルにも同じイベントが発生しています。

このように,cgroup.eventsファイルに発生するイベントを監視して何らかの処理を行うことができます。cgroupを監視するプログラムがcgroup.eventsを監視し,イベントを受信した処理を行えますので,v1に比べるとコストが低くなりました。

まとめ

今回は,親子cgroup間の内部タスクの競合を解決するためにcgroup v2で導入された制約を紹介しました。

また,cgroup内のタスク状態が変化した場合の通知機能について,cgroup v1,v2の両方を実際に試しながら紹介しました。

cgroup v1が持っていて,今でも使われている機能はそのままに,v1が持っていた問題点をうまく解決していることがおわかりいただけたのではないでしょうか。

その分cgroup v2は,いろいろな制約があり複雑になったと思われるかもしれません。この複雑さは,cgroupを管理するプログラムが理解して担当すれば良いという考え方なのだと思います。

実際,現在のほとんどのディストリビューションではsystemdがcgroupをすべて管理しています。cgroup v2もsystemdの存在を念頭に置いているのだろうと思います。cgroup v2の通知機能もinotifyを使っており,常時起動しているプログラムから監視をすることが前提となっています。

今回まででcgroup v2のコントローラに関わらない基本機能で,どのようなケースでも使う機能の紹介が済みました。次回は,基本機能ですが,特権を持たないプロセスがcgroupを扱う場合の機能を紹介する予定です。

著者プロフィール

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

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

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

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

コメント

コメントの記入