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

第49回 cgroup v2のリソース分配の方法とインターフェースファイルの操作

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

前回はcgroup v2で使えるコントローラのうち,v1から大きく変わったコントローラや,新たに実装されたコントローラについて説明しました。

今回はcgroup v2で行われるリソース制御のタイプ,cgroupで使うインターフェースファイルに関係する規則や,コントローラでリソース制限を設定する際に関係するファイルのフォーマットや書き込み方について説明したいと思います。

cgroupとcgroup内ファイルの命名規則

cgroup v1には,cgroupで使用するファイルやその内部の書式について,明確な規則はありませんでした。cgroup v2では,ファイルに関する規則がきちっと定められました。この決まりを説明していきましょう。

今回の実行例は,前回と同じくUbuntu 21.10環境で実行しています。

まず,cgroup自体の動きに影響するcgroupコアに関係するインターフェースファイルは,"cgroup."で始まります。各コントローラ用のインターフェースファイルは"(コントローラ名)."で始まります。

"(コントローラ名)."で始まる名前では,コントローラ内に複数の種類のコントローラを持っているような場合はドット.が複数出現することもあります。例えばこの後の例で使うioコントローラのうち,BFQというIOスケジューラで使うコントローラで制限値を設定するファイル名は"io.bfq."で始まります。

あるcgroupに子cgroupを作成する際には,そのcgroupのインターフェースファイルが出現するディレクトリ内にディレクトリを作ります。この際に,インターフェースファイルと同じ名前を持つディレクトリ(cgroup)を作ることができます。

このように,問題が発生するにもかかわらず,cgroup名としてインターフェースファイルと同じ名前をつけることはないと思います。しかし,cgroupコアは作成するcgroup名に対するエラーチェックは行いませんので,作成するユーザ側で注意する必要があります。

逆にcgroupのインターフェースファイルは,cgroup名として使われそうなワークロードに関係する一般的な単語は使用しないように実装されています。

コントローラ用のインターフェースファイル名で,"(コントローラ名)"の後のドット.に続く文字列はリソースを分配する方法によって決まっています。この決まりについてはこの後,リソース分配を行う方法を紹介するところで一緒に紹介していきます。

コントローラがリソース分配を行う方法

cgroup v1では,各種コントローラで色々なリソース分配が行われていました。しかし,そこでリソースを分配する方法としてどのようなモデルを使うか,設定する値としてどのような値を使うかについては,明確な基準はありませんでした。

cgroup v2では,リソースを分配するモデルやそこで設定する値について基準が示されましたので,それに従ってコントローラが実装されています。

ウェイト(Weights)

ウェイトは第4回で説明したCPUコントローラの相対配分として紹介した機能で使われているようなリソース配分です。

子cgroupで指定されている数値をすべて足して,合計に対してそれぞれに割り当てた値の割合で分配されます。

例えば,cgroup AとBがあり,それぞれに100と50が設定されているとすると,Aには2/3,Bには1/3のリソースが分配され,AにはBに割り当てる2倍のリソースが分配されます。その時点で使えるリソースをすべて割合に従い,それぞれに分配します。

ウェイトとして設定できる値は1〜10000の間で,デフォルト値は100と決まっています。

ウェイトでリソース分配を行う場合の,コントローラ用のインターフェースファイル名は"(コントローラ名).weight"です。このようなファイルの例としてcpu.weightがあります。

cpu.weightを使って,ウェイトタイプの制限値を設定する様子を見てみましょう。

まず,作成するcgroupでCPUコントローラが使えるようにします。Ubuntu 21.10環境では,デフォルトでメモリとpidsコントローラは使えるように設定されていますので,加えてCPUコントローラを使えるようにしておきます。ここの実行例での操作については第38回をご覧ください。

$ echo "+cpu" | sudo tee /sys/fs/cgroup/cgroup.subtree_control 
(子cgroupでCPUコントローラが使えるようにする)
+cpu
$ cat /sys/fs/cgroup/cgroup.subtree_control 
(cpuという文字列が書き込まれた)
cpu memory pids
$ sudo mkdir /sys/fs/cgroup/test
(root直下に"test" cgroupを作成)
$ cat /sys/fs/cgroup/test/cgroup.controllers
cpu memory pids ("test" cgroupでcpuコントローラが使えることを確認)

さて,test cgroupが作成できましたので,cpu.weightファイルを見てみましょう。

$ ls /sys/fs/cgroup/test/cpu.weight
/sys/fs/cgroup/test/cpu.weight (ファイルが存在する)
$ cat /sys/fs/cgroup/test/cpu.weight
100 (cgroup作成直後のデフォルト値は"100")

cgroup作成直後で,何も操作をしていない状態でcpu.weightファイルを確認すると,確かにデフォルト値である100という数値が書かれています。実際に操作をしてみましょう。

$ echo "10000" | sudo tee /sys/fs/cgroup/test/cpu.weight
10000
$ cat /sys/fs/cgroup/test/cpu.weight
10000
$ echo "1" | sudo tee /sys/fs/cgroup/test/cpu.weight
1
$ cat /sys/fs/cgroup/test/cpu.weight
1
$ echo "100000" | sudo tee /sys/fs/cgroup/test/cpu.weight
100000 (上限値以上の数値を書き込むとエラーになる)
tee: /sys/fs/cgroup/test/cpu.weight: Numerical result out of range

範囲内の110000を書き込むと正常に反映され,範囲外の100000を書くとエラーになることが確認できました。

制限(Limits)

制限(Limits)は文字通りリソース制限です。設定した上限を超えてリソースを使うことはできません。この制限値はオーバーコミットできます。つまり,子cgroupに割り当てる制限値の合計が,親が利用できるリソース量を超えるような設定ができます。

この制限は,絶対的な制限となるハードリミットとして実装されていることもありますし,ベストエフォートで動作するソフトリミットとして実装されていることもあります。両方とも実装されている場合もあります。

制限として,0以上の数値と"max"という文字列が指定できます。"max"は制限しないという意味になり,デフォルト値は"max"です。

制限でリソース制限を行う場合の,コントローラ用のインターフェースファイル名は,"(コントローラ名)."に続けて次のような文字列が付いた名前になります。

  • ハードリミットとして絶対的な割り当て制限を行う場合:max(例:memory.max
  • ソフトリミットとしてベストエフォートのリソース制限を行う場合:high(例:memory.high

ここで例としてあげたファイル名はmemory.maxmemory.highで,両方ともメモリコントローラです。つまりメモリコントローラはハードリミットとソフトリミットの両方が実装されているということです。

cgroupを作成して,memory.maxmemory.highファイルを見てみましょう。

$ sudo mkdir /sys/fs/cgroup/test2 (cgroupを作成)
$ cat /sys/fs/cgroup/test2/memory.max 
max (デフォルト値はmax)
$ cat /sys/fs/cgroup/test2/memory.high
max (デフォルト値はmax)

いずれも作成直後のデフォルト値はmaxとなっており,制限がかかっていない状態になっています。ファイルを書き換えてみましょう。

$ echo "128M" | sudo tee /sys/fs/cgroup/test2/memory.max
128M (128MBに制限)
$ cat /sys/fs/cgroup/test2/memory.max 
134217728 (制限値が書き込まれた)
$ echo "max" | sudo tee /sys/fs/cgroup/test2/memory.max
max (制限を外すためにmaxという文字列を書き込んだ)
$ cat /sys/fs/cgroup/test2/memory.max 
max (制限値がmaxに書き換わった)

まず"128M"という文字列を書き込み制限値を設定した後に,制限値を外すために"max"という文字列を書き込むと,memory.maxファイルの中身がmaxと書き換わりました。

保護(Protections)

保護(Protections)は,cgroupに所属するプロセスに対して,設定した量までのリソースを与えることを保証します。ただし,ツリー構造のすべての祖先のcgroupが保護レベル以下である場合だけです。

制限と同様に保護もオーバーコミットでき,オーバーコミットした場合は,親が使える量までしか子では保護されません。

また,強い保護とベストエフォートな保護のどちらの保護にすることもできるのも制限と同様です。両方とも実装されている場合もあります。

保護として,0以上の数値maxという文字列が設定できます。デフォルト値は0です。つまりデフォルトでは保護されるリソース値は設定されていません。

保護でリソース分配を行う場合の,コントローラ用のインターフェースファイル名は,"(コントローラ名)."に続けて次のような文字列が付いた名前になります。

  • リソースの絶対的な割り当て保証保護を行う場合:min(例:memory.min
  • リソースのベストエフォートのリソース割り当て保証保護を行う場合:low(例:memory.low

例としてあげたメモリコントローラは,制限と同様に絶対的な保護とベストエフォートの保護の両方が実装されています。

ここでもmemory.minmemory.lowを使って簡単に動きを見ておきましょう。cgroupを作成し,デフォルト値を確認します。

$ sudo mkdir /sys/fs/cgroup/test3 (cgroupを作成)
$ cat /sys/fs/cgroup/test3/memory.min 
0 (デフォルト値は0)
$ cat /sys/fs/cgroup/test3/memory.low
0 (デフォルト値は0)

いずれもデフォルト値は0になっていることが確認できました。数値以外の値として書き込める"max"を書き込み,その後数値を設定してみましょう。

$ echo max | sudo tee /sys/fs/cgroup/test3/memory.min 
(memory.minにmaxという文字列を書き込む)
max
$ cat /sys/fs/cgroup/test3/memory.min 
max (maxに書き換わっている)
$ echo 128M | sudo tee /sys/fs/cgroup/test3/memory.min
128M (制限値を128MBに設定する)
$ cat /sys/fs/cgroup/test3/memory.min
134217728 (制限値が書き込まれた)

このように"max"で最大値が設定できましたし,数値での制限値も設定できました。

割り当て(Allocations)

割り当て(Allocations)は,有限なリソースを独占的に割り当てます。つまりオーバーコミットできません。子に割り当てられるリソースの合計は親の割り当て量を超えられません。

割り当てとして,0以上の数値maxという文字列が設定できます。デフォルト値は0です。つまりデフォルトではリソースがない状態で,そこから有限なリソースを必要分だけ子に割り当てていくというモデルになります。

現時点ではこのモデルが実装されたコントローラはないようです。

このタイプでの制限では設定値を超えることはできませんので,ファイル名も"(コントローラ名).max"となるようです。

著者プロフィール

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

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

Plamo Linuxメンテナ

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