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

第30回 Linuxカーネルのコンテナ機能[8]― cgroupのpidsサブシステム

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

今年も年末を迎えて,Advent Calendarの季節になり,いろいろなカレンダーの案内をみかけるようになりました。

昨年のこの時期に書いた第16回は,Linux Advent Calendar 2014の16日目の記事として書きました。連載記事を直接Advent Calendarに参加させるということが新鮮だったのか,うれしい反応をいただきました。

今年同じことをやっても新鮮味にはかけると思いますが,今年も今回の記事をLinux Advent Calendar 2015の15日目の記事としても書きました。

Linux Advent Calendarということで,前回の最後に「引き続きLXC 1.1の新機能や変更点を紹介」と書きましたが,今回は予定を変更してLinuxカーネルに新たに導入されたコンテナ関連の機能について書きたいと思います。

cgroup

この連載では,cgroupについて第3回から第5回にかけて紹介しています。今回はcgroupの基本的な機能については説明しませんので,必要に応じてそちらをご参照ください。

その際に説明したように,cgroupでは,制限するリソースごとに「サブシステム」または「コントローラ」と呼ばれるモジュールにわかれています。

カーネルのバージョン2.6の後半からバージョン3の前半にかけて,新しいサブシステムが続々と追加されました。その頃は,筆者は新しいカーネルがリリースされるたびに,cgroupに新しい機能が追加されていないかをチェックして,追加されるたびに試してブログに書いたりしていました。

その後,一通り機能が充実したあたりからは,すぐに試してわかりやすい結果が得られるような新機能の追加は少なくなりました。そういうこともあり,新しいcgroupの機能を試してブログに書くことは少なくなりました。それだけコンテナ関連の機能が成熟してきたということかもしれませんね。

新しい機能のリリースはなくても,コンテナが注目されるにつれ,コンテナ関連の機能をカーネルに取り入れたいという提案はいろいろなされていました。最近そのような中から,久々にカーネルにマージされる機能が出てきました。

それが4.3カーネルで導入された,pidsサブシステム(Process Number Controller)です。

cgroupによるプロセス数の制限

コンテナごとに起動できるプロセスの数を制限したいという要求は,特にマルチテナント環境では昔からあり,OpenVZでも実現されていた機能でした。

cgroupでも,以前"fork"という名前で似たような機能を持つサブシステムが提案されていたことがあります。しかし,この時はrejectされていました。

筆者が主催しているコンテナ型仮想化の情報交換会第2回では,mizzyさんに,提案されていた"fork"サブシステムに改造を加えてfork bomb対策を行ったという発表をしていただきました。筆者は発表を聞きながら,提案されているパッチにさらに改造を加えてカーネルに機能を追加し,サービスを作り上げる技術力の高さに感心したことを覚えています。いずれにせよ,実際にサービスを提供する際には,このような機能が必要な場合があるということを示していると思います。

私は,pidsサブシステムがマージされるまでの経緯を追っていないので,どういう議論がなされてサブシステムがマージされるに至ったのかはわかりませんが,待望されていた機能には違いないと思います。

pidsサブシステムを有効にしたカーネルの作成

それでは早速pidsサブシステムを使ってみましょう。今回の記事の実行例はPlamo 6.0上で作成した4.3.0カーネル上で実行しています。

pidsサブシステムを使うには,カーネルの設定でCONFIG_CGROUP_PIDSを有効にする必要があります。make menuconfigなどで設定する場所は他のcgroupサブシステムと同じ場所です。

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

新しく作ったカーネルで起動すると,/proc/cgroupspidsという行が現れています。

$ cat /proc/cgroups | grep pids
pids    12  5   1

マウントとグループの作成

pidsサブシステムを使う際にマウントを行う方法は,他のサブシステムと同様です。-oオプションでpidsを指定します。

たとえば以下のように行います。

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

このマウントした/(ルート)を見てみると以下のようなファイルができています。

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

ここにtest01というグループを作成してみましょう。

$ sudo 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.maxpids.currentが,pidsサブシステムが独自に持つファイルのようですね。

pidsサブシステム用ファイル

pidsサブシステム独自で使用するファイルは以上の 2 つです。ファイル名ですぐに役割が分かったのではないでしょうか。

ファイル名 ファイルの役割
pids.current グループ内の現在のプロセス数
pids.max グループ内で許可するプロセス数

それぞれのファイルが,グループを作成した直後にはどのような内容であるかを見てみましょう。さきほど作成したtest01グループで見てみます。

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

グループを作成した直後ですから,グループにプロセスは登録されていませんのでpids.currentは当然0になりますね。制限なしとするにはpids.maxmaxと書きます。初期値は制限なしでmaxです。

制限を追加する

制限値を設定する方法は通常のcgroupの使い方と同じです。制限値として2を設定してみましょう。

$ echo 2 | sudo tee /sys/fs/cgroup/pids/test01/pids.max
2
$ cat /sys/fs/cgroup/pids/test01/pids.max
2

ファイルの内容は,設定した通り2になっていますね。

現在のシェルをグループに追加してみましょう。

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

pids.currentの内容は,追加したシェルと確認用に実行したcatで2になっています。

ここでパイプでつないでコマンドを実行してみましょう。

$ ( /bin/echo "Gihyo" | 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

シェルを登録してひとつ消費した状態で2つ実行しようとしたので,実行できずにエラーになっています。制限が効いていることがわかりますね。

階層構造

第3回でcgroupの特徴として階層構造が取れることを紹介しました。

それでは多層の場合に,pidsサブシステムでどのように制限がかかるのかを確認してみましょう。

先ほどのtest01グループの下層にtest02グループを作成します。test01は2を設定しておき,test02は作成したままで特に制限をかけないままにしておきます。

$ sudo mkdir /sys/fs/cgroup/pids/test01/test02
$ cat /sys/fs/cgroup/pids/test01/pids.max 
2
$ cat /sys/fs/cgroup/pids/test01/test02/pids.max 
max

test02にプロセスを追加して,test01test02pids.currentを確認してみます。

$ echo $$ | sudo tee /sys/fs/cgroup/pids/test01/test02/tasks 
26500
$ cat /sys/fs/cgroup/pids/test01/tasks
$ cat /sys/fs/cgroup/pids/test01/test02/tasks
26500
26582
$ cat /sys/fs/cgroup/pids/test01/pids.current 
2
$ cat /sys/fs/cgroup/pids/test01/test02/pids.current 
2

test01tasksは空ですので,test01グループにはプロセスは登録されていない状態です。test02tasksには追加したシェルのPIDとcatのPIDが表示されています。つまりcatを実行した瞬間は,test02には2つプロセスが存在する状態ですね。

pids.currentを見てみると,test01test02ともに2となっています。これは,階層構造の子孫のプロセスが上位でカウントされているということです。

ここで先ほどと同様にpids.maxを超えるプロセスを生成してみます。

$ ( /bin/echo "Gihyo" | 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

プロセスは生成できませんね。つまり階層構造を持つグループの場合,先祖であるグループのどれかひとつの制限にかかると,プロセスが生成できないということです。

著者プロフィール

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

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

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

Twitter:@ten_forward
技術系のブログ:http://d.hatena.ne.jp/defiant/

コメント

コメントの記入