読者です 読者をやめる 読者になる 読者になる

TenForward

技術ブログ。はてなダイアリーから移転しました

Linux 4.3 の Process Number Controller (1)

Linux Container cgroup

連載書いたり、勉強会で発表したりしているとなかなかブログが更新できませんね。久々の更新です。

これを書いている時点では Linux カーネルの 4.3-rc1 がリリースされていますが、久々に cgroup に新しいコントローラが追加されそうですね。

説明が不要なほどシンプルで分かりやすいです。このブログエントリ読まなくても使えるはず。:-)

軽く使ってみました。大体は上記ドキュメントに沿って試してるだけです。

カーネル

カーネルの config では Cgroup 関連の設定以下に "PIDs cgroup subsystem" という項目が新設されているので、ここを有効にしてカーネルを作成するだけです。

General setup  --->
  Control Group support  --->
    [*] PIDs cgroup subsystem

起動すると /proc/cgroups に pids が現れます。

# cat /proc/cgroups 
#subsys_name	hierarchy	num_cgroups	enabled
cpuset	1	1	1
cpu	2	1	1
cpuacct	3	1	1
blkio	4	1	1
memory	5	1	1
devices	6	1	1
freezer	7	1	1
net_cls	8	1	1
perf_event	9	1	1
net_prio	10	1	1
hugetlb	11	1	1
pids	12	2	1
debug	13	1	1

マウント

まずはマウントしてみましょう。単一階層構造でなく、現在の仕様の cgroup でも使えるようです。

マウントは他のコントローラと同じです。例えば

# mkdir -p /sys/fs/cgroup/pids
# mount -t cgroup -o pids none /sys/fs/cgroup/pids

なかにはどんなファイルがあるかな? ルートは以下のような感じでした。

# ls /sys/fs/cgroup/pids/
cgroup.clone_children  cgroup.sane_behavior  pids.current   tasks
cgroup.procs           notify_on_release     release_agent

"pids.current" が PIDs サブシステム独自のファイルのようですね。

cgroup の作成

グループを作成してみます。ディレクトリを作ることに変わりはありません。

# mkdir /sys/fs/cgroup/pids/test01
# ls /sys/fs/cgroup/pids/test01
cgroup.clone_children  notify_on_release  pids.max
cgroup.procs           pids.current       tasks

"pids.current" と "pids.max" という 2 つのファイルがこのコントローラの独自のファイルのようですね。

制限の設定

ファイル名を見ただけで大体わかると思いますが、それぞれのファイルの役割は以下です。

pids.current
現在のプロセス(タスク)数
pids.max
許可するプロセス(タスク)数の最大値

先に作った "test01" の作成直後、つまり初期値は以下のようになっています。

# cat /sys/fs/cgroup/pids/test01/pids.current 
0
# cat /sys/fs/cgroup/pids/test01/pids.max     
max

"tasks" ファイルに何も追加していないので、当然現在値は 0 です。制限値なしにするには "pids.max" に "max" と書くようで、これが初期値です。

制限値を 2 にしてみましょう。

# echo 2 > /sys/fs/cgroup/pids/test01/pids.max
# cat /sys/fs/cgroup/pids/test01/pids.max 
2

きちんと設定されました。

現在のシェルをグループに追加します。

# echo $$ | tee /sys/fs/cgroup/pids/test01/tasks
5600
# cat /sys/fs/cgroup/pids/test01/pids.current 
2

作成後に現在値を見ると、追加したシェルと実行した cat コマンドで 2 となっていますね。では、ここで 2 つ同時にコマンドを実行してみましょう。

# ( /bin/echo "Here's some processes for you." | cat )
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: Resource temporarily unavailable
Terminated

シェルで 1 消費している状態で 2 つ実行しようとしたので、プロセスを生成できませんでした。

当たり前ですが、ちゃんと制限が効いていますね。

階層構造

上記の "test01" の子グループとして "test02" を作り、"test01" には制限をかけ、"test02" には制限をかけない状態にしてみます。

# mkdir /sys/fs/cgroup/pids/test01/test02
# echo 2 > /sys/fs/cgroup/pids/test01/pids.max 
# cat /sys/fs/cgroup/pids/test01/pids.max        (test01の制限値は2)
2
# cat /sys/fs/cgroup/pids/test01/test02/pids.max (test02は無制限)
max

以下のような設定です。

/sys/fs/cgroup/pids/
└── test01      --> (pids.max = 2)
    └── test02  --> (pids.max = max)

この状態で "test02" で "test01" の制限を超えるプロセスを起動してみます。

# echo $$ > /sys/fs/cgroup/pids/test01/test02/tasks (1 つ追加)
#  ( /bin/echo "Here's some processes for you." | cat )
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: Resource temporarily unavailable
Terminated

起動しませんね。つまり複数の階層がある場合は、上位の階層の制限にひとつでも引っかかればダメということです。

制限を設定した状態でグループのタスクを増やす

"pids.max" を設定した状態で、グループの "tasks" にたくさんプロセスを追加していったらどうなるでしょう?

# echo 2 > /sys/fs/cgroup/pids/test01/pids.max 
# cat /sys/fs/cgroup/pids/test01/pids.max 
2

"test01" に 2 の制限を設定しました。別途起動してあるプロセスを追加していってみましょう。

# echo 5954 > /sys/fs/cgroup/pids/test01/tasks 
# echo 5955 > /sys/fs/cgroup/pids/test01/tasks 
# echo 5956 > /sys/fs/cgroup/pids/test01/tasks
# 

おや、特にエラーにならずに、追加できてしまっています。

# cat /sys/fs/cgroup/pids/test01/tasks        (追加したタスクは全部存在する)
5954
5955
5956
# cat /sys/fs/cgroup/pids/test01/pids.max     (制限値は2に設定されている)
2
# cat /sys/fs/cgroup/pids/test01/pids.current (現在値は3で、制限値より多い)
3

つまり fork() や clone() で新しいプロセスを起動しようとする時にだけ制限が効くということですね。

存在するプロセス数以下の制限値を設定する

では、プロセスが既にグループに存在する状態で、その数以下に "pids.max" を設定してみます。

# echo 2 > /sys/fs/cgroup/pids/test01/pids.max (制限値は2)
# cat /sys/fs/cgroup/pids/test01/tasks 
5968
5970
# cat /sys/fs/cgroup/pids/test01/pids.current  (現在2つのタスクが存在)
2

制限値以下であるふたつのタスクが存在していますが、ここで制限値を 1 に減らしてみましょう。

# echo 1 > /sys/fs/cgroup/pids/test01/pids.max (制限値を1に下げる)
# cat /sys/fs/cgroup/pids/test01/tasks 
5968
5970
# cat /sys/fs/cgroup/pids/test01/pids.current  (タスクは2つのまま)
2

タスクは 2 つのままですね。

まとめ

Linux 4.3-rc1 環境で PIDs コントローラを試してみました。

  • 制限値を設定しておくと、グループ内で fork() や clone() で新たに起動するプロセスを制限できる
  • グループの "tasks" にプロセスを追加したり、制限値を変更したりしても、制限にひっかからない。つまり "pids.max" < "pids.current" となることもある
  • グループに属することのできるタスク数は、そのグループの上位(祖先)のグループの制限を受ける

つづくかも...