おひさしぶりです。今年はこれまで結局一回もこの連載の記事を書きませんでした。年末が近づき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
コマンドを使う方法を教えていただきました。ありがとうございました。
次回も、