おひさしぶりです。今年はこれまで結局一回もこの連載の記事を書きませんでした。年末が近づきAdvent Calendarの季節になり、
仕事が忙しかったりして、
さて、
今回から、
コントローラの使い方と言っても、
cgroup v2で使えるコントローラ
現時点
| コントローラ名 | 使えるようになったバージョン |
|---|---|
| cpu | 4. |
| cpuset | 5. |
| device | 4. |
| freezer | 5. |
| hugetlb | 5. |
| io | 4. |
| memory | 4. |
| misc | 5. |
| perf_ |
4. |
| pids | 4. |
| rdma | 4. |
5.cgroup.は次のようになっています。
$ uname -r 5.13.0-21-generic $ grep cgroup /proc/self/mountinfo 35 25 0:30 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:9 - cgroup2 cgroup2 rw,nsdelegate,memory_recursiveprot $ cat /sys/fs/cgroup/cgroup.controllers cpuset cpu io memory hugetlb pids rdma misc
Ubuntu 21.
第37回で説明したとおり、cgroup_を使ってcgroup v2で使いたいコントローラを指定します。allを指定するとcgroup v1側では全コントローラが無効になります。
ここでcgroupを1つ作成し、/sys/に出現しているコントローラを全部、
$ sudo mkdir /sys/fs/cgroup/test $ echo "+cpuset +cpu +io +memory +hugetlb +pids +rdma +misc" | sudo tee /sys/fs/cgroup/cgroup.subtree_control (使えるコントローラすべてを子cgroupで使えるようにする) +cpuset +cpu +io +memory +hugetlb +pids +rdma +misc $ cat /sys/fs/cgroup/cgroup.subtree_control cpuset cpu io memory hugetlb pids rdma misc
このように、cgroup.ファイルに子cgroupで使いたいコントローラを、+をつけて書き込むと登録できました。登録すると、cgroup.内に書かれているコントローラすべてを書いています。実際は、
次のように、cgroup.に登録すると大量のファイルが出現します。
$ ls /sys/fs/cgroup/test/ cgroup.controllers cpuset.cpus hugetlb.1GB.events.local io.stat memory.stat cgroup.events cpuset.cpus.effective hugetlb.1GB.max io.weight memory.swap.current cgroup.freeze cpuset.cpus.partition hugetlb.1GB.rsvd.current memory.current memory.swap.events cgroup.max.depth cpuset.mems hugetlb.1GB.rsvd.max memory.events memory.swap.high cgroup.max.descendants cpuset.mems.effective hugetlb.2MB.current memory.events.local memory.swap.max cgroup.procs cpu.stat hugetlb.2MB.events memory.high misc.current cgroup.stat cpu.uclamp.max hugetlb.2MB.events.local memory.low misc.max cgroup.subtree_control cpu.uclamp.min hugetlb.2MB.max memory.max pids.current cgroup.threads cpu.weight hugetlb.2MB.rsvd.current memory.min pids.events cgroup.type cpu.weight.nice hugetlb.2MB.rsvd.max memory.numa_stat pids.max cpu.max hugetlb.1GB.current io.max memory.oom.group rdma.current cpu.pressure hugetlb.1GB.events io.pressure memory.pressure rdma.max
ちなみにroot cgroup内を見てみると、
$ ls -F /sys/fs/cgroup/ cgroup.controllers cgroup.threads dev-mqueue.mount/ memory.numa_stat sys-kernel-config.mount/ cgroup.max.depth cpu.pressure init.scope/ memory.pressure sys-kernel-debug.mount/ cgroup.max.descendants cpuset.cpus.effective io.cost.model memory.stat sys-kernel-tracing.mount/ cgroup.procs cpuset.mems.effective io.cost.qos misc.capacity system.slice/ cgroup.stat cpu.stat io.pressure -.mount/ test/ cgroup.subtree_control dev-hugepages.mount/ io.stat sys-fs-fuse-connections.mount/ user.slice/
子cgroupよりはファイル数が大幅に少ないことがわかります。root cgroupはリソース制限が行えないので、
rootでもroot以外でも、cgroup.で始まり、
deviceコントローラ
ここで改めて先ほども見たcgroup.ファイルを見てみましょう。このファイルには使えるコントローラが一覧されています。
$ cat /sys/fs/cgroup/cgroup.controllers cpuset cpu io memory hugetlb pids rdma misc
先の表1とcgroup.ファイルを見比べると、
cgroup.内に記載がないからといって、
$ grep CGROUP_DEVICE /boot/config-5.13.0-21-generic CONFIG_CGROUP_DEVICE=y
cgroup v2からは、
ネットワーク系コントローラ
cgroup v2で使えるコントローラを眺めると、
cgroup v1には、
このcgroup v1のnet_
# mkdir /sys/fs/cgroup/net_cls/test # echo 1 > /sys/fs/cgroup/net_cls/test/net_cls.classid
このように、net_ファイルにIDを登録する必要がありました。
そして、
# iptables -A OUTPUT --protocol icmp --destination 192.168.122.1 -m cgroup --cgroup 1 -j DROP (cgroup v1のnet_clsを使って、指定したClass IDを指定してフィルタリングを設定) # ping 192.168.122.1 PING 192.168.122.1 (192.168.122.1) 56(84) bytes of data. ping: sendmsg: Operation not permitted ping: sendmsg: Operation not permitted ping: sendmsg: Operation not permitted ^C --- 192.168.122.1 ping statistics --- 3 packets transmitted, 0 received, 100% packet loss, time 2054ms
現在はiptablesでcgroup v2の対応が進み、
特にフィルタリングしない環境でpingが通っている環境があるとします。まずはcgroupを作成し、
$ sudo mkdir /sys/fs/cgroup/test $ echo $$ | sudo tee /sys/fs/cgroup/test/cgroup.procs 995
そして、--pathで指定してフィルタリングを設定します。
ここでは、192.に対するフィルタリングを、/test cgroupに対して--pathを使って設定します。
$ sudo iptables -A OUTPUT --protocol icmp --destination 192.168.122.1 -m cgroup --path /test -j DROP
$ sudo iptables -L -n -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 DROP icmp -- * * 0.0.0.0/0 192.168.122.1 cgroup /testここで指定するパスは、/proc/[PID]/cgroup内で確認できるcgroupとしてのパスです。今回の例だと/testということになります。
$ cat /proc/$$/cgroup 0::/test
フィルタリングが設定された状態で、pingを実行してみます。
$ ping 192.168.122.1 PING 192.168.122.1 (192.168.122.1) 56(84) bytes of data. ^C --- 192.168.122.1 ping statistics --- 19 packets transmitted, 0 received, 100% packet loss, time 18434ms
このようにフィルタリングされているのでエラーとなります。
もちろん、pingでの疎通ができます。
$ sudo iptables -D OUTPUT --protocol icmp --destination 192.168.122.1 -m cgroup --path /test -j DROP $ ping 192.168.122.1 PING 192.168.122.1 (192.168.122.1) 56(84) bytes of data. 64 bytes from 192.168.122.1: icmp_seq=1 ttl=64 time=0.183 ms 64 bytes from 192.168.122.1: icmp_seq=2 ttl=64 time=0.778 ms 64 bytes from 192.168.122.1: icmp_seq=3 ttl=64 time=0.764 ms ^C --- 192.168.122.1 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2006ms rtt min/avg/max/mdev = 0.183/0.575/0.778/0.277 ms
また、tcプログラムでeBPFプログラムをロードし、tcがロードしたeBPFプログラム内でcgroupのパスを取得して制御の対象かどうかを判定できるようになっているようです。
このあたりのサンプルプログラムがカーネルソースのサンプルプログラムに含まれていますので、
暗黙のうちに常に有効になるコントローラ
このほかに、
cgroupのコントローラを実装する際にはcgroup_という構造体を実装します。この構造体のメンバ変数にimplicit_というboolの変数が存在します。
struct cgroup_subsys {
:(略)
/*
* If %true, the controller, on the default hierarchy, doesn't show
* up in "cgroup.controllers" or "cgroup.subtree_control", is
* implicitly enabled on all cgroups on the default hierarchy, and
* bypasses the "no internal process" constraint. This is for
* utility type controllers which is transparent to userland.
*
* An implicit controller can be stolen from the default hierarchy
* anytime and thus must be okay with offline csses from previous
* hierarchies coexisting with csses for the current one.
*/
bool implicit_on_dfl:1;
:(略)
この変数がtrueに設定されたコントローラはcgroup v2では、
- cgroup v2ツリー上の全cgroupで暗黙のうちに有効となる
cgroup.とcontrollers cgroup.にはコントローラ名がリストされないsubtree_ control
ただし、
また第39回で、implicit_がtrueになっているコントローラは、
つまり、
執筆時点implicit_がtrueになっているコントローラはperf_コントローラのみです。
struct cgroup_subsys perf_event_cgrp_subsys = {
:(略)
/*
* Implicitly enable on dfl hierarchy so that perf events can
* always be filtered by cgroup2 path as long as perf_event
* controller is not mounted on a legacy hierarchy.
*/
.implicit_on_dfl = true,
:(略)
};
perfは主にLinuxでカーネルの性能に関する情報を収集し、
ここで、perf_コントローラの機能を使えることを簡単に試して見ておきましょう。
まず、testというcgroupを作成し、/test cgroupを指定してperf statコマンドを実行します。perf statコマンドでcgroupを指定するには--cgroupまたは-Gオプションを使います。
$ sudo mkdir /sys/fs/cgroup/test $ sudo perf stat -e task-clock --cgroup /test (/test を指定してperf statコマンドを実行)
ここで別のシェルを起動し、/test cgroupに登録します。登録されたのを確認してyesコマンドを実行します。
$ echo $$ | sudo tee /sys/fs/cgroup/test/cgroup.procs (シェルのPIDを/test cgroupに登録) 1116 $ cat /proc/self/cgroup (登録されたのを確認) 0::/test $ timeout 3 yes > /dev/null (3秒間yesを実行) $
ここでperf statを実行しているシェルに戻り、perf statを停止します。
$ sudo perf stat -e task-clock --cgroup /test
^C
Performance counter stats for 'system wide':
3,001.01 msec task-clock /test # 0.263 CPUs utilized
11.413407271 seconds time elapsed
このように/test cgroupを指定してイベントの取得ができています。
ここで、/test cgroupを指定してperf statを実行してから、/test cgroupから削除し、yesコマンドを実行してみましょう。
$ echo $$ | sudo tee /sys/fs/cgroup/cgroup.procs (シェルのPIDをroot cgroupに登録し、/test cgroupから削除) 1116 $ timeout 3 yes > /dev/null $
そして/test cgroupを指定して実行しているperf statコマンドを停止してみましょう。
$ sudo perf stat -e task-clock --cgroup /test
^C
Performance counter stats for 'system wide':
<not counted> msec task-clock /test
6.955355876 seconds time elapsed
/test cgroupの外でyesコマンドを実行したので、perf statではデータが収集できていません。
このように、perf_コントローラが常に全cgroupで有効になるため、perfコマンドでcgroupごとのデータを収集できます。
perf stat以外でも、perf record、perf reportコマンドでもcgroupごとのデータを収集して表示させたりできます。
miscコントローラ
表1やここまでの実行例で、
そうです、miscコントローラです。このコントローラはcgroup v1でも使えます。以下ではcgroup v2での実行例を示しますが、miscコントローラは5.cgroup.ファイルを見ると、miscという文字列が見えます。
$ cat /sys/fs/cgroup/cgroup.controllers cpuset cpu io memory hugetlb pids rdma misc
このコントローラは、
この機能を使いたい場合は、miscコントローラ用のインターフェースファイルに実装に従ってリソース制限を行うためのキーと値のペアが出現します。このようにシンプルなリソース制限を行うための汎用的な入れ物
Ubuntu 21.miscコントローラはroot cgroupにあるcgroup.には登録されていませんので、
$ echo "+misc" | sudo tee /sys/fs/cgroup/cgroup.subtree_control +misc (子cgroupでmiscコントローラを使えるようにする) $ cat /sys/fs/cgroup/cgroup.subtree_control cpuset cpu io memory pids misc (登録された)
この状態で、
$ sudo mkdir /sys/fs/cgroup/test (子cgroupを作成する) $ ls /sys/fs/cgroup/test/misc.* (misc用に2つファイルが出現する) /sys/fs/cgroup/test/misc.current /sys/fs/cgroup/test/misc.max
また、
$ ls /sys/fs/cgroup/misc.* /sys/fs/cgroup/misc.capacity (root cgroupに存在するmiscコントローラ用ファイル)
これらのインターフェースファイルは表2のような機能を持っています。
| ファイル名 | 機能 |
|---|---|
| misc. |
root cgroupのみに出現する読み取り専用ファイル。miscコントローラで制御するホスト上のリソース名と、 |
| misc. |
root cgroup以外に出現する読み取り専用ファイル。cgroupとその子孫のcgroupで使われている量が表示されます |
| misc. |
root cgroup以外に出現する読み書き可能なファイル。cgroupとその子孫で使えるリソースの制限値を表示・ |
| misc. |
root cgroup以外に出現する読み取り専用ファイル。cgroupとその子孫で制限値を超えようとした回数 |
miscコントローラは、
筆者の手元にはこの機能が使える環境がありませんので、
$ cat /sys/fs/cgroup/misc.capacity $ cat /sys/fs/cgroup/test/misc.*
このように具体例を示せませんので、res_、res_という2つのホスト上のリソースが制御できるようになっているとしましょう。
ホスト上ではres_が10個、res_が5個使用できるとします。このとき、misc.の中身は次のようになるでしょう。
$ cat /sys/fs/cgroup/misc.capacity res_a 10 res_b 5
test cgroupで、res_を3個、res_は使っていない場合、/sys/は次のようになります。
$ cat /sys/fs/cgroup/test/misc.current` res_a 3 res_b 0
test cgroupでは、res_は制限なし、res_は3個までという制限を設定したい場合は、
# echo res_a max > /sys/fs/cgroup/test/misc.max (res_aの制限を無制限に設定する) # echo res_a 3 > /sys/fs/cgroup/test/misc.max (res_bの制限を3に設定する) $ cat /sys/fs/cgroup/test/misc.max (制限値の確認) res_a max res_b 3
実際のmiscコントローラの定義と使用
ここまで仮想的な例で説明しましたので、
miscコントローラでリソースを制御したい場合、include/内のenum misc_でリソースを定義します。
/**
* Types of misc cgroup entries supported by the host.
*/
enum misc_res_type {
#ifdef CONFIG_KVM_AMD_SEV
/* AMD SEV ASIDs resource */
MISC_CG_RES_SEV,
/* AMD SEV-ES ASIDs resource */
MISC_CG_RES_SEV_ES,
#endif
MISC_CG_RES_TYPES
};
ここではMISC_とMISC_が定義されています。
同様にkernel/内のmisc_で、
/* Miscellaneous res name, keep it in sync with enum misc_res_type */
static const char *const misc_res_name[] = {
#ifdef CONFIG_KVM_AMD_SEV
/* AMD SEV ASIDs resource */
"sev",
/* AMD SEV-ES ASIDs resource */
"sev_es",
#endif
};
このように、sevとsev_というリソースが定義されています。リソースをコントロールしたいモジュールなどは、misc_関数を呼び出し、misc.に表示されます。
sev、sev_の場合は、arch/内のsev_関数内で、
sev_asid_count = max_sev_asid - min_sev_asid + 1;
if (misc_cg_set_capacity(MISC_CG_RES_SEV, sev_asid_count))
goto out;
リソースを使用する場合は、misc_関数でリソースを確保するようで、sev.内でもsev_関数でリソースを1つ確保するようになっています。
sev->misc_cg = get_current_misc_cg();
ret = misc_cg_try_charge(type, sev->misc_cg, 1);
まとめ
今回はcgroup v2で使えるコントローラのcgroup v1からの変化と、
cgroup v2では、
今回の記事を書くに当たり、perfコマンドを使う方法を教えていただきました。ありがとうございました。
次回も、
