CLOVER🍀

That was when it all began.

使用しているLinux環境のcgroupが、v1なのかv2なのかを確認する

これは、なにをしたくて書いたもの?

使用しているLinux環境のcgroupがv1なのかv2なのかを確認するには?ということで。

Kubernetesのドキュメントに見分け方が書かれていたので、試してみることにしました。

cgroup v1とv2

Linuxでは、プロセスをグループに分けて管理する機能を使って、プロセスに割り当てるリソースを制限しています。
これがcgroupです。

cgroupにはv1とv2の2つがあります。

Control Groups — The Linux Kernel documentation

Control Group v2 — The Linux Kernel documentation

cgroupを使用することで、割り当てるリソースとしてCPU、メモリー、IOを制御できます。

それで、Linux環境ではどちらかのバージョンが使われていることになりますが、この見分け方はどうしたら?ということで。

答えはKubernetesのcgroup v2に関するドキュメントに書かれていました。

About cgroup v2 | Kubernetes

こちらですね。

About cgroup v2 / Identify the cgroup version on Linux Nodes

以下のコマンドで確認できるようです。

$ stat -fc %T /sys/fs/cgroup/

このコマンドの出力結果がcgroup2fsだとcgroup v2、tmpfsだとcgroup v1だそうです。

また、このページ内にcgroup v2の要求事項と主なLinuxディストリビューションのcgroup v2の対応が書かれていますね。

cgroup v2の要求事項は、以下です。

主要なLinuxディストリビューションのcgroup v2の対応状況は、以下の通りです。

この時点で使うLinuxディストリビューションとそのバージョンでだいたい答えは出るのですが、今回はそれを明示的に確認してみようと
思います。

対象には、Ubuntu Linux 22.04 LTSと20.04 LTSを使うことにします。

確認は、cgroup v2 → cgroup v1の順で行います。

環境

今回の環境は、こちら。

Ubuntu Linux 22.04 LTS。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.3 LTS
Release:        22.04
Codename:       jammy


$ uname -srvmpio
Linux 5.15.0-83-generic #92-Ubuntu SMP Mon Aug 14 09:30:42 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux


$ docker version
Client: Docker Engine - Community
 Version:           24.0.6
 API version:       1.43
 Go version:        go1.20.7
 Git commit:        ed223bc
 Built:             Mon Sep  4 12:31:44 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          24.0.6
  API version:      1.43 (minimum version 1.12)
  Go version:       go1.20.7
  Git commit:       1a79695
  Built:            Mon Sep  4 12:31:44 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.22
  GitCommit:        8165feabfdfe38c65b599c4993d227328c231fca
 runc:
  Version:          1.1.8
  GitCommit:        v1.1.8-0-g82f18fe
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Ubuntu Linux 20.04 TLS。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.6 LTS
Release:        20.04
Codename:       focal


$ uname -srvmpio
Linux 5.4.0-162-generic #179-Ubuntu SMP Mon Aug 14 08:51:31 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux


$ docker version
Client: Docker Engine - Community
 Version:           24.0.6
 API version:       1.43
 Go version:        go1.20.7
 Git commit:        ed223bc
 Built:             Mon Sep  4 12:32:12 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          24.0.6
  API version:      1.43 (minimum version 1.12)
  Go version:       go1.20.7
  Git commit:       1a79695
  Built:            Mon Sep  4 12:32:12 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.22
  GitCommit:        8165feabfdfe38c65b599c4993d227328c231fca
 runc:
  Version:          1.1.8
  GitCommit:        v1.1.8-0-g82f18fe
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

それぞれ、以下のVagrant Boxを使っています。

一応、Dockerまで入れておきました。

statコマンドでcgroupのバージョンを確認する

それでは、Kubernetesのドキュメントに習ってstatコマンドでcgroupのバージョンを確認してみましょう。

cgroup v2。

$ stat -fc %T /sys/fs/cgroup/
cgroup2fs

cgroup v1。

$ stat -fc %T /sys/fs/cgroup/
tmpfs

確かに、cgroup v2ではcgroup2fs、cgroup v1ではtmpfsとなりました。

ここで、statコマンドのオプションを確認してみます。

Ubuntu Manpage: stat - display file or file system status

statは、ファイルまたはファイルシステムの状態を表示するコマンドです。
-fがファイルシステムの状態を表示するようにするオプションで(通常はファイルの状態を表示)、-cは出力を指定の書式にする
オプションです。 %Tは対象がキャラクター/ブロックデバイスのスペシャルファイルの場合、マイナーデバイス番号を表示します。

cgroupは、sysfs内にコントローラーをマウントするようですね。

sysfs - _The_ filesystem for exporting kernel objects — The Linux Kernel documentation

cgroup v2は、cgroup2としてマウントします。

# mount -t cgroup2 none $MOUNT_POINT

Control Group v2 / Basic Operations / Mounting

cgroup v1は、tmpfsとしてマウントします。

1) mount -t tmpfs cgroup_root /sys/fs/cgroup
2) mkdir /sys/fs/cgroup/cpuset
3) mount -t cgroup -ocpuset cpuset /sys/fs/cgroup/cpuset
4) Create the new cgroup by doing mkdir's and write's (or echo's) in
   the /sys/fs/cgroup/cpuset virtual file system.
5) Start a task that will be the "founding father" of the new job.
6) Attach that task to the new cgroup by writing its PID to the
   /sys/fs/cgroup/cpuset tasks file for that cgroup.
7) fork, exec or clone the job tasks from this founding father task.

Control Groups / Control Groups / How do I use cgroups ?

cgroup v2、v1それぞれで/sys/fs/cgroup/内を見ると、こんな感じになっています。

cgroup v2。

$ tree -L 1 /sys/fs/cgroup/
/sys/fs/cgroup/
├── cgroup.controllers
├── cgroup.max.depth
├── cgroup.max.descendants
├── cgroup.procs
├── cgroup.stat
├── cgroup.subtree_control
├── cgroup.threads
├── cpu.pressure
├── cpu.stat
├── cpuset.cpus.effective
├── cpuset.mems.effective
├── dev-hugepages.mount
├── dev-mqueue.mount
├── init.scope
├── io.cost.model
├── io.cost.qos
├── io.pressure
├── io.prio.class
├── io.stat
├── memory.numa_stat
├── memory.pressure
├── memory.stat
├── misc.capacity
├── proc-sys-fs-binfmt_misc.mount
├── sys-fs-fuse-connections.mount
├── sys-kernel-config.mount
├── sys-kernel-debug.mount
├── sys-kernel-tracing.mount
├── system.slice
└── user.slice

10 directories, 20 files

cgroup v1。

$ tree -L 1 /sys/fs/cgroup/
/sys/fs/cgroup/
├── blkio
├── cpu -> cpu,cpuacct
├── cpu,cpuacct
├── cpuacct -> cpu,cpuacct
├── cpuset
├── devices
├── freezer
├── hugetlb
├── memory
├── net_cls -> net_cls,net_prio
├── net_cls,net_prio
├── net_prio -> net_cls,net_prio
├── perf_event
├── pids
├── rdma
├── systemd
└── unified

17 directories, 0 files

これらがcgroupのコントローラーですね。

以降、もう少し違う角度で情報を見ていってみます。

mountコマンドで見てみる

cgroupのコントローラーはsysfsファイルシステム上で動作しているようなので、mountコマンドでも確認できそうです。

cgroup v2。

$ mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)

cgroup v1。

$ mount | grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)

cgroup v2では、階層は単一になります。

Unlike v1, cgroup v2 has only single hierarchy.

Control Group v2 / Basic Operations / Mounting

別解。/proc/self/mountinfoから。

cgroup v2。

$ cat /proc/self/mountinfo | grep cgroup
35 25 0:30 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:9 - cgroup2 cgroup2 rw,nsdelegate,memory_recursiveprot

cgroup v1。

$ cat /proc/self/mountinfo | grep cgroup
36 26 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:9 - tmpfs tmpfs ro,mode=755
37 36 0:31 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:10 - cgroup2 cgroup2 rw,nsdelegate
38 36 0:32 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:11 - cgroup cgroup rw,xattr,name=systemd
41 36 0:35 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,hugetlb
42 36 0:36 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,net_cls,net_prio
43 36 0:37 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,memory
44 36 0:38 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:18 - cgroup cgroup rw,pids
45 36 0:39 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,cpu,cpuacct
46 36 0:40 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:20 - cgroup cgroup rw,perf_event
47 36 0:41 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:21 - cgroup cgroup rw,cpuset
48 36 0:42 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:22 - cgroup cgroup rw,rdma
49 36 0:43 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,blkio
50 36 0:44 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,devices
51 36 0:45 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,freezer

コンテナ内でも見てみましょう。

cgroup v2。

$ docker container run -it --rm ubuntu:22.04 cat /proc/self/mountinfo | grep cgroup
618 617 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw,nsdelegate,memory_recursiveprot

cgroup v1。

$ docker container run -it --rm ubuntu:20.04 cat /proc/self/mountinfo | grep cgroup
702 701 0:61 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755
703 702 0:32 /docker/e71d4f44ee79dd9e2704f4e7131c5db6b5268eb137db847153b29b09c79d04f5 /sys/fs/cgroup/systemd ro,nosuid,nodev,noexec,relatime master:11 - cgroup cgroup rw,xattr,name=systemd
704 702 0:35 /docker/e71d4f44ee79dd9e2704f4e7131c5db6b5268eb137db847153b29b09c79d04f5 /sys/fs/cgroup/hugetlb ro,nosuid,nodev,noexec,relatime master:15 - cgroup cgroup rw,hugetlb
705 702 0:36 /docker/e71d4f44ee79dd9e2704f4e7131c5db6b5268eb137db847153b29b09c79d04f5 /sys/fs/cgroup/net_cls,net_prio ro,nosuid,nodev,noexec,relatime master:16 - cgroup cgroup rw,net_cls,net_prio
706 702 0:37 /docker/e71d4f44ee79dd9e2704f4e7131c5db6b5268eb137db847153b29b09c79d04f5 /sys/fs/cgroup/memory ro,nosuid,nodev,noexec,relatime master:17 - cgroup cgroup rw,memory
707 702 0:38 /docker/e71d4f44ee79dd9e2704f4e7131c5db6b5268eb137db847153b29b09c79d04f5 /sys/fs/cgroup/pids ro,nosuid,nodev,noexec,relatime master:18 - cgroup cgroup rw,pids
708 702 0:39 /docker/e71d4f44ee79dd9e2704f4e7131c5db6b5268eb137db847153b29b09c79d04f5 /sys/fs/cgroup/cpu,cpuacct ro,nosuid,nodev,noexec,relatime master:19 - cgroup cgroup rw,cpu,cpuacct
709 702 0:40 /docker/e71d4f44ee79dd9e2704f4e7131c5db6b5268eb137db847153b29b09c79d04f5 /sys/fs/cgroup/perf_event ro,nosuid,nodev,noexec,relatime master:20 - cgroup cgroup rw,perf_event
710 702 0:41 /docker/e71d4f44ee79dd9e2704f4e7131c5db6b5268eb137db847153b29b09c79d04f5 /sys/fs/cgroup/cpuset ro,nosuid,nodev,noexec,relatime master:21 - cgroup cgroup rw,cpuset
711 702 0:42 /docker/e71d4f44ee79dd9e2704f4e7131c5db6b5268eb137db847153b29b09c79d04f5 /sys/fs/cgroup/rdma ro,nosuid,nodev,noexec,relatime master:22 - cgroup cgroup rw,rdma
712 702 0:43 /docker/e71d4f44ee79dd9e2704f4e7131c5db6b5268eb137db847153b29b09c79d04f5 /sys/fs/cgroup/blkio ro,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
713 702 0:44 /docker/e71d4f44ee79dd9e2704f4e7131c5db6b5268eb137db847153b29b09c79d04f5 /sys/fs/cgroup/devices ro,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,devices
714 702 0:45 /docker/e71d4f44ee79dd9e2704f4e7131c5db6b5268eb137db847153b29b09c79d04f5 /sys/fs/cgroup/freezer ro,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,freezer

/proc//cgroupで見てみる

最後に自分自身が所属するcgroupの情報を、/proc/self/cgroupで見てみましょう。

cgroup v2。

$ cat /proc/self/cgroup
0::/user.slice/user-1000.slice/session-4.scope

cgroup v1。

$ cat /proc/self/cgroup
12:freezer:/
11:devices:/user.slice
10:blkio:/user.slice
9:rdma:/
8:cpuset:/
7:perf_event:/
6:cpu,cpuacct:/user.slice
5:pids:/user.slice/user-1000.slice/session-6.scope
4:memory:/user.slice/user-1000.slice/session-6.scope
3:net_cls,net_prio:/
2:hugetlb:/
1:name=systemd:/user.slice/user-1000.slice/session-6.scope
0::/user.slice/user-1000.slice/session-6.scope

プロセスが所属するcgroupは、/proc/<pid>/cgroupで確認できます。自プロセスの場合は、/proc/self/cgroupとなります。

cgroupの名前空間は、/proc/<pid>/cgroupファイルとcgroupファイルシステム(v1の場合はtmpfsファイルシステム)で表現されることが
書かれています。

コンテナ内で確認してみましょう。

cgroup v2。

$ docker container run -it --rm ubuntu:22.04 cat /proc/self/cgroup
0::/

cgroup v1。

$ docker container run -it --rm ubuntu:20.04 cat /proc/self/cgroup
12:freezer:/docker/149c401b108d6be632678c8325ae96dbf9a7aba8c413747b2e1d764c61a128bc
11:devices:/docker/149c401b108d6be632678c8325ae96dbf9a7aba8c413747b2e1d764c61a128bc
10:blkio:/docker/149c401b108d6be632678c8325ae96dbf9a7aba8c413747b2e1d764c61a128bc
9:rdma:/docker/149c401b108d6be632678c8325ae96dbf9a7aba8c413747b2e1d764c61a128bc
8:cpuset:/docker/149c401b108d6be632678c8325ae96dbf9a7aba8c413747b2e1d764c61a128bc
7:perf_event:/docker/149c401b108d6be632678c8325ae96dbf9a7aba8c413747b2e1d764c61a128bc
6:cpu,cpuacct:/docker/149c401b108d6be632678c8325ae96dbf9a7aba8c413747b2e1d764c61a128bc
5:pids:/docker/149c401b108d6be632678c8325ae96dbf9a7aba8c413747b2e1d764c61a128bc
4:memory:/docker/149c401b108d6be632678c8325ae96dbf9a7aba8c413747b2e1d764c61a128bc
3:net_cls,net_prio:/docker/149c401b108d6be632678c8325ae96dbf9a7aba8c413747b2e1d764c61a128bc
2:hugetlb:/docker/149c401b108d6be632678c8325ae96dbf9a7aba8c413747b2e1d764c61a128bc
1:name=systemd:/docker/149c401b108d6be632678c8325ae96dbf9a7aba8c413747b2e1d764c61a128bc
0::/docker/149c401b108d6be632678c8325ae96dbf9a7aba8c413747b2e1d764c61a128bc

cgroup v1の/docker/の後ろに見えているのは、コンテナのidですね。

余談

cgroup v1の場合、自分自身のコンテナのidは/proc/self/cgroupや/proc/self/mountinfoを見ればわかることになります。

ではcgroup v2の場合はというと、/proc/self/mountinfoのcgroupとしてマウントしている箇所以外を見ればよさそうです。

$ docker container run -it --rm ubuntu:22.04 cat /proc/self/mountinfo | grep docker
588 425 0:46 / / rw,relatime master:223 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/XZFSJBHY5DCBKZT3WS3UVIIMNL:/var/lib/docker/overlay2/l/EJEZWN23ZAMZZ2VSUXSWEOV3OX,upperdir=/var/lib/docker/overlay2/a6049ac9fab5259cdddbeb67d0b36f2f6313f09a07a4b79a70097d00ca4c4ce5/diff,workdir=/var/lib/docker/overlay2/a6049ac9fab5259cdddbeb67d0b36f2f6313f09a07a4b79a70097d00ca4c4ce5/work
621 588 253:0 /var/lib/docker/containers/d2454afd9c7b4c47b726c8f556e6f83b100a464b4f6440fe9f0c92fcb39c61ea/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw
622 588 253:0 /var/lib/docker/containers/d2454afd9c7b4c47b726c8f556e6f83b100a464b4f6440fe9f0c92fcb39c61ea/hostname /etc/hostname rw,relatime - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw
623 588 253:0 /var/lib/docker/containers/d2454afd9c7b4c47b726c8f556e6f83b100a464b4f6440fe9f0c92fcb39c61ea/hosts /etc/hosts rw,relatime - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw

こんなところでしょうか。

おわりに

使用しているLinux環境のcgroupが、v1なのかv2なのかを確認する方法をちょっと調べてみました。ちょっと脱線もしていますけど。

あまり明示的に確認する機会はない気がしますが、コンテナに関する周辺知識を見ていける感じなのでたまにこういうのを追ってみると
面白いですね。

/sys/fs/cgroupや/proc/self/cgroup、/proc/self/mountinfoはランタイムやライブラリーのコンテナ対応でも出てくるので、
覚えておくとよいかもしれません。

過去に書いたエントリーは、このあたりです。

JavaがDockerコンテナ内でどのようにCPU数、メモリサイズを取得しているのかを調べてみる - CLOVER🍀

Dockerコンテナ内で動作するNode.jsが認識するCPU数、メモリサイズ(ヒープサイズ除く)は、ホスト側のものになるという話 - CLOVER🍀