今年も年末を迎えて、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サブシステムと同じ場所です。
新しく作ったカーネルで起動すると、/proc/cgroups
にpids
という行が現れています。
マウントとグループの作成
pidsサブシステムを使う際にマウントを行う方法は、他のサブシステムと同様です。-o
オプションでpids
を指定します。
たとえば以下のように行います。
このマウントした/(ルート)を見てみると以下のようなファイルができています。
ここにtest01
というグループを作成してみましょう。
pids.max
とpids.current
が、pidsサブシステムが独自に持つファイルのようですね。
pidsサブシステム用ファイル
pidsサブシステム独自で使用するファイルは以上の 2 つです。ファイル名ですぐに役割が分かったのではないでしょうか。
ファイル名 |
ファイルの役割 |
pids.current |
グループ内の現在のプロセス数 |
pids.max |
グループ内で許可するプロセス数 |
それぞれのファイルが、グループを作成した直後にはどのような内容であるかを見てみましょう。さきほど作成したtest01
グループで見てみます。
グループを作成した直後ですから、グループにプロセスは登録されていませんのでpids.current
は当然0になりますね。制限なしとするにはpids.max
にmax
と書きます。初期値は制限なしでmax
です。
制限を追加する
制限値を設定する方法は通常のcgroupの使い方と同じです。制限値として2を設定してみましょう。
ファイルの内容は、設定した通り2になっていますね。
現在のシェルをグループに追加してみましょう。
pids.current
の内容は、追加したシェルと確認用に実行したcat
で2になっています。
ここでパイプでつないでコマンドを実行してみましょう。
シェルを登録してひとつ消費した状態で2つ実行しようとしたので、実行できずにエラーになっています。制限が効いていることがわかりますね。
階層構造
第3回でcgroupの特徴として階層構造が取れることを紹介しました。
それでは多層の場合に、pidsサブシステムでどのように制限がかかるのかを確認してみましょう。
先ほどのtest01
グループの下層にtest02
グループを作成します。test01
は2を設定しておき、test02
は作成したままで特に制限をかけないままにしておきます。
test02
にプロセスを追加して、test01
とtest02
のpids.current
を確認してみます。
test01
のtasks
は空ですので、test01
グループにはプロセスは登録されていない状態です。test02
のtasks
には追加したシェルのPIDとcat
のPIDが表示されています。つまりcat
を実行した瞬間は、test02
には2つプロセスが存在する状態ですね。
pids.current
を見てみると、test01
、test02
ともに2となっています。これは、階層構造の子孫のプロセスが上位でカウントされているということです。
ここで先ほどと同様にpids.max
を超えるプロセスを生成してみます。
プロセスは生成できませんね。つまり階層構造を持つグループの場合、先祖であるグループのどれかひとつの制限にかかると、プロセスが生成できないということです。
pidsサブシステムのカーネルの実装
今回は、Linux Advent Calendar 2015への参加を兼ねているので、普段の連載とは少し趣向を変えて、pidsサブシステムがカーネルでどのように実装されているかを軽く見てみましょう。
昨年のLinux Advent Calendar 2014の25日目の「システムコールの探しかた」を参考に、fork()
を探してみるとkernel/fork.c中に以下が見つかります(見やすくするために必要な行だけ抜き出しています)。
以上のfork()
の定義行の少し下にはvfork()
とclone()
の定義もあります。いずれも_do_fork
という関数を呼んでいますので、新しくプロセスを作る場合の処理は_do_fork
関数を見れば良いことがわかります。
_do_fork
関数も同じくkernel/fork.c中にあります。
_do_fork
関数は指定されたcloneフラグにしたがって各種資源のコピーを作成します。このコピーを行うのが、上記のcopy_process
関数です。この関数の中では、指定されたcloneフラグの組み合わせをチェックしたり、copy_
で始まる名前の関数が多数呼ばれて、いろいろコピーされてるっぽいことがなんとなくわかります。
このcopy_process
関数をきちっと追っていけば、pidsサブシステムで制限をかけている部分がわかるはずです。しかし、私の技術力では途中でつまづく可能性が高いので、手っ取り早くpidsサブシステムがカーネルにマージされた際のパッチを見てみました。
pidsサブシステムは比較的シンプルに実装されていて、以下の2つのマージされたパッチで実現されています。
1つ目のパッチを見てみると、ずばりcopy_process()
関数内の処理にパッチが当たっていることがわかります。パッチが当たっている部分を見てみると、以下のようなコメントとともにcgroup_can_fork()
関数が呼ばれています。
ここでエラーになると、エラーの後処理部分に処理が飛び、新しいプロセスは生成できなさそうなことがわかります。
2つ目のパッチはpidsサブシステム自体の処理を実装してあり、実際のプロセスの生成を制限するような処理はありません。
もう少し詳しく見ると正確に処理がわかるとは思います。しかしパッチをさらっと見るだけでも、fork()
、vfork()
、clone()
システムコールの実体である関数内にチェックが追加され、新しいプロセスの生成が制御されてるらしいということはわかります。他に同様のチェックが追加されている部分はなさそうです。これを理解した上で、もう少しpidsサブシステムの動きを見てみましょう。
制限以上のプロセスをグループに追加する
pids.max
を設定した上で、グループにプロセスを追加していってみましょう。
制限を2に設定したので、test01
グループにどんどんプロセスを追加してみましょう。
pids.max
を2に設定したのに、pids.current
がそれ以上になってもエラーにはなりません。
この動きは、先に紹介したカーネルの実装を考えると納得できます。実装では、新たにプロセスを生成しようとする処理で制限のチェックがされていました。すでに生成されているプロセスを新たにグループに追加しても、そのチェックには引っかからないので、このように制限値以上にtasks
にプロセスを追加していっても、既存のプロセスが影響を受けることはありません。
もちろん、上記の状態で新たにプロセスを生成させようとすると、pidsサブシステムにより新たにプロセスは生成できません。
プロセスが登録された状態でグループの制限値を変更する
さらに、pids.max < pids.current
となるようなケースを試してみましょう。
以上のようにすでに制限値に達しているグループがあります。この状態でpids.max
を減らしてみましょう。
プロセスが登録された状態で、登録されているプロセス数以下の制限値を設定しても、プロセスはそのままです。
この動きも、先ほどのプロセスをグループに追加していった場合と同様に、カーネルの実装から考えると納得できる動きですね。
まとめ
今回は少し予定を変更して、カーネル4.3で導入されたpidsサブシステムを紹介しました。
- pidsサブシステムを使うと、グループ内で起動するプロセス数が制限できる
- 制限はこれから起動しようとするプロセスに対する制限で、既存のプロセスには影響を与えない
サブシステムの動きだけを見ると、なぜこのような動きになるのだろうという疑問がわくかもしれません。実装を少し見ることにより、そのような動きになる理由が理解できたのではないでしょうか。
カーネルのコードはもう少し詳細に追っかけないと、サブシステムの動きが納得できないかもしれません。しかしこれ以上やるとそれだけで連載何回分にもなりそうなので、最小限の紹介にとどめました。興味のある方はこの記事をきっかけに詳細に動きを追ってみてください。
次回は、またLXC 1.1の新機能の紹介に戻る予定です。