概要
Linuxのcgroup機能とは、プロセスをグループに分けて管理し、その単位ごとにCPU/メモリの割り当て等の設定を可能にするものです。
cgroupのサブシステムにはcpu, memoryなどの他にnet_clsというものがあり、これを使うと指定したグループからのパケットに対してタグ(classid)を付加することができ、これを条件にnetfilter(iptables/nftablesコマンド)でフィルタリングなどを行うことができます。
筆者自身深く理解してはいないので言葉遣いは間違っている可能性があります。日本語情報だと他にはLinux 3.14 で net_cls cgroup に追加された netfilter 対応 - TenForwardがあります。
net_clsサブシステムが使えるか確認する
まず、cat /proc/cgroupsを実行します。すると手元(Linux Mint 21.3, Ubuntu 22.04ベース)では以下のような出力が返ってきました。
#subsys_name hierarchy num_cgroups enabled
cpuset 0 170 1
cpu 0 170 1
cpuacct 0 170 1
blkio 0 170 1
memory 0 170 1
devices 0 170 1
freezer 0 170 1
net_cls 0 170 1
perf_event 0 170 1
net_prio 0 170 1
hugetlb 0 170 1
pids 0 170 1
rdma 0 170 1
misc 0 170 1
このようにnet_clsが含まれていればOKです。含まれていなかったらsudo modprobe cls_cgroupとかをすると出てくるかもしれないです(参考:第4回 Linuxカーネルのコンテナ機能[3] ─cgroupとは?(その2) | gihyo.jp)
マウントする
では、次に、ls /sys/fs/cgroupを実行します。手元では以下のようになりました。
cgroup.controllers cpuset.mems.effective memory.pressure
cgroup.max.depth dev-hugepages.mount memory.stat
cgroup.max.descendants dev-mqueue.mount misc.capacity
cgroup.procs init.scope proc-sys-fs-binfmt_misc.mount
cgroup.stat io.cost.model sys-fs-fuse-connections.mount
cgroup.subtree_control io.cost.qos sys-kernel-config.mount
cgroup.threads io.pressure sys-kernel-debug.mount
cpu.pressure io.prio.class sys-kernel-tracing.mount
cpu.stat io.stat system.slice
cpuset.cpus.effective memory.numa_stat user.slice
(フォルダとファイルが見分けられなくてすみません)
本当はここの中にnet_cls(というフォルダ)が含まれているべきなのですが、ありません。どうやら、マウントする必要があるようです。これは、2.3. 既存の階層へのサブシステムの接続と接続解除 Red Hat Enterprise Linux 6 | Red Hat Customer Portalみたいな感じでmountコマンドを打つと追加できます。しかし自分で打つのは面倒です。
実は、cgroupfs-mountというコマンドをsudoで実行するとこの部分を勝手にやってくれるということがわかりました。これはcgroupfs-mountというパッケージに入っています。実は、cgroups-mountという非常によく似た名前のコマンドもあり、こちらはcgroup-liteというパッケージに入っています。挙動もほぼ同じです。中身はシェルスクリプトで、見た感じ前者のほうが内容が多いので新しそうです。
というわけでcgroupfs-mountを実行するとls /sys/fs/cgroupは以下のようになります。
cgroup.controllers cpuacct io.prio.class proc-sys-fs-binfmt_misc.mount
cgroup.max.depth cpuset io.stat rdma
cgroup.max.descendants devices memory.numa_stat sys-fs-fuse-connections.mount
cgroup.procs freezer memory.pressure sys-kernel-config.mount
cgroup.stat hugetlb memory.stat sys-kernel-debug.mount
cgroup.subtree_control init.scope misc sys-kernel-tracing.mount
cgroup.threads io.cost.model net_cls system.slice
cpu.pressure io.cost.qos net_prio systemd
cpu.stat io.pressure perf_event user.slice
このようにnet_clsなどいろいろ追加されています。
cgroupの作成・設定
cgroupを作成するには、先ほど存在が確認できた/sys/fs/cgroup/net_clsに、testというフォルダを作成します。testの中に移動してみると、cgroup.procsとかnet_cls.classidといったファイルが勝手に作成されていることがわかります。これを編集することでcgroupに関する設定ができます。
ここではnet_cls.classidに1と書き込んでみます。(数字は何でもいいです)
# echo 1 > net_cls.classid
こうするとiptablesで -m cgroup --cgroup 1などと指定できるようになります。
次にプロセスをcgroupに追加します。適当なシェルを起動してecho $$と打ってPIDを取得し、これをcgroup.procsに書き込みます。(ここでは5008とします)
# echo 5008 > cgroup.procs
これで、該当のシェルの子プロセス(シェルで実行したコマンド)はclassidが1として扱われるようになります。あとはiptablesのルールで好き勝手にやるだけです。
cgroup指定は原則的にINPUTチェインでは機能しません(一応特殊な条件下では機能する?詳しくはhttps://ipset.netfilter.org/iptables-extensions.man.html)。
cgroup-tools
cgroupの作成や各種設定はcgcreateやcgsetといったコマンドで行うこともできます。これらはcgroup-toolsというパッケージに入っています。
今回作成したtestというcgroupで特定プロセスを実行するには以下のようにします。
sudo cgexec -g net_cls:test command args
勝手にcgroup.procsのところにcommandのPIDが追加されていることがわかります。
PIDでの指定について
Linux Netfilter iptables — Re: module owner does not work
これによると、Linux 2.6あたりの一時期には、--pid-owner、--sid-owner、および --cmd-ownerといったPIDなどを直接指定するiptablesルールがサポートされていたようですが、なにか問題があったようで現在は利用できません。--uid-ownerと--gid-ownerは引き続き利用可能です。これらはINPUTチェインでは使えません。