cgroupv2について理解する
前回はcgroupv1の仕組みついては書いたが、今回はLinuxカーネル4.5以降で道入されているcgroup v2について触れる。
cgroupはLinuxリソースを制限したり管理するための機能であり、cgroupfsというファイルシステムを経由して操作管理することができる。
cgroup v1の欠点、v2での改善
cgroup v1の問題点がありcgroup v2より全体の仕様が再設計された。そのうちいくつかを紹介する。
複数階層による管理
cgroup v1は複数の階層から構成される設計となっている。この設計はリソース管理の柔軟性を向上させるための仕組みとして意図された。
Red Hat Enterprise Linux 6 リソース管理ガイドより引用
しかし上記のルールのように複数の階層にサブシステムを所属させることはできないので、全く別の階層で管理したい場合などは柔軟に構成できないのである。
cgroup v1ではそれぞれのサブシステムに関連付けられたツリーのコントロールグループでそれぞれリソースの設定をする。
下の画像でtaks_1を制御しようとしたときのことを考える。cpuサブシステムを扱うgroup_1を作成し、更にgroup_4にmemoryサブシステムを割り当てた。 この状態ではtask_1自体はgroup_1, group_2と2つのコントロールグループに加わることになる。
この構造ではタスクは各階層中に存在し得るため、特定のタスクがどういった制御を実施しているかわかりにくい。またcpuとmemory両方を単一の階層に紐付けた場合は、他の階層には使用したサブシステムを使用できないため、他のタスクも両コントローラの制御下に入る。
cgroupv2ではcgroup v1と同じように階層構造を形成しているが、全体で単一のツリーとなるため管理が容易である。 CPUとメモリをそれぞれ制御しようとした場合、目的のリソースを制御するためのコントロールグループを作成してタスクを追加する。 このときタスクはいずれか1つのグループにしか所属できない。
cgroup v2ではプロセスに対するリソースの制約を参照、設定したければ唯一つのグループを特定すればよいためシンプルである。
管理粒度
制御の粒度に関しても問題があった。実は前回の手順ではプロセス単位でリソースを制限したが、cgroup v1の一部のサブシステムの対象(task)はプロセスではなくスレッド単位なのである
これによって同一プロセスにも関わらず、異なるリソース制御を割り当てることが可能となりリソースの競合が発生する。さらにcpuサブシステムはスレッド単位、memoryサブシステムはプロセス単位など、一部のという点が複雑さに拍車をかけている。
cgroup v2では管理単位はスレッドではなくプロセスがデフォルトとなった
cgroup v2の動作確認
すでにcgroupfsがマウントされているデフォルトの状態できどうした。v1のようにサブシステムごとにマウントされていないことが確認できる。
$ findmnt |grep cgroup │ ├─/sys/fs/cgroup cgroup2 cgroup2 rw,nosuid,nodev,noexec,relatime,seclabel,nsdelegate,memory_recursiveprot
ルートグループ配下はこのような構成になっている。細部システムによってはルートグループではコントロールできないパラメターもあるため、コントロールグループを作成するとより多くのファイルが存在する。
$ ls /sys/fs/cgroup cgroup.controllers cgroup.stat cpuset.cpus.effective dev-mqueue.mount io.pressure memory.numa_stat sys-fs-fuse-connections.mount system.slice cgroup.max.depth cgroup.subtree_control cpuset.mems.effective init.scope io.prio.class memory.pressure sys-kernel-config.mount user.slice cgroup.max.descendants cgroup.threads cpu.stat io.cost.model io.stat memory.stat sys-kernel-debug.mount cgroup.procs cpu.pressure dev-hugepages.mount io.cost.qos limit misc.capacity sys-kernel-tracing.mount
試しにlimit
というグループを作成して確認する。
$ sudo mkdir /sys/fs/cgroup/limit $ ls /sys/fs/cgroup/limit cgroup.controllers cgroup.procs cpu.max cpuset.mems io.latency memory.current memory.min memory.swap.events cgroup.events cgroup.stat cpu.max.burst cpuset.mems.effective io.max memory.events memory.numa_stat memory.swap.high cgroup.freeze cgroup.subtree_control cpu.pressure cpu.stat io.pressure memory.events.local memory.oom.group memory.swap.max cgroup.kill cgroup.threads cpuset.cpus cpu.weight io.prio.class memory.high memory.pressure pids.current cgroup.max.depth cgroup.type cpuset.cpus.effective cpu.weight.nice io.stat memory.low memory.stat pids.events cgroup.max.descendants cpu.idle cpuset.cpus.partition io.bfq.weight io.weight memory.max memory.swap.current pids.max
利用可能なサブシステム(コントローラ)はcgroup.controllers
を参照することで確認できる。
また該当グループで有効になっているサブシステムはcgroup.controllers
で確認できる。
$ cat /sys/fs/cgroup/limit/cgroup.controllers cpuset cpu io memory pids $ cat /sys/fs/cgroup/cgroup.subtree_control cpuset cpu io memory pids
先程作成したlimitグループを確認すると、初期状態では有効なサブシステムは登録されていないことがわかる。
$ cat /sys/fs/cgroup/limit/cgroup.controllers cpuset cpu io memory pids $ cat /sys/fs/cgroup/limit/group.subtree_control
そこでサブシステムを関連付けるためにgroup.subtree_control
に書き込みをする。
ドキュメントによるとこのような書式で登録、解除ができるように記載されている。(今回の場合親グループですでに有効になっているため本来は不要)
Space separated list of controllers prefixed with '+' or '-' can be written to enable or disable controllers. A controller name prefixed with '+' enables the controller and '-' disables. If a controller appears more than once on the list, the last one is effective. When multiple enable and disable operations are specified, either all succeed or all fail.
echo "+cpu" | sudo tee -a /sys/fs/cgroup/limit/group.subtree_control
$ echo "+cpu" | sudo tee -a /sys/fs/cgroup/limit/group.subtree_control +cpu
さてこれでlimit内でcpuサブシステムが有効になったのでCPU時間を制限してみる。 cgroup v1とはパラメータの指定方法が変更になっているがcpu時間の考え方については変わっていない。
1スレッドのマシンの場合だと1周期100000(μs)と設定した場合その半分である50000を指定すると指定したプロセスでCPU使用量50%となる。 (対象プロセスが100%の負荷をかけている状態で、かつcgroup内に存在するプロセスが1つもしくは、他のプロセスはCPU時間を消費しないものという前提)
今回使った私のマシンの場合は16スレッドのため50000*16/2=400000を指定する。
stressコマンドで全スレッドに対して負荷をかけると概ね50%程度になり意図した通りcpu時間が制限されている。
echo "800000 100000" |sudo tee /sys/fs/cgroup/limit/cpu.max 800000 100000 $ echo $$ | sudo tee -a /sys/fs/cgroup/limit/cgroup.procs $ stress -c 16 # 別窓で実行 $ sar 1 1 -u -P ALL Average: CPU %user %nice %system %iowait %steal %idle Average: all 50.59 0.00 0.55 0.00 0.00 48.86 Average: 0 50.50 0.00 0.99 0.00 0.00 48.51 Average: 1 52.94 0.00 0.98 0.00 0.00 46.08 Average: 2 49.02 0.00 1.96 0.00 0.00 49.02 Average: 3 49.50 0.00 0.99 0.00 0.00 49.50 Average: 4 54.90 0.00 0.00 0.00 0.00 45.10 Average: 5 49.02 0.00 0.98 0.00 0.00 50.00 Average: 6 50.50 0.00 0.00 0.00 0.00 49.50 Average: 7 49.02 0.00 0.98 0.00 0.00 50.00 Average: 8 50.98 0.00 0.00 0.00 0.00 49.02 Average: 9 49.50 0.00 0.00 0.00 0.00 50.50 Average: 10 51.49 0.00 0.00 0.00 0.00 48.51 Average: 11 50.00 0.00 0.98 0.00 0.00 49.02 Average: 12 49.00 0.00 0.00 0.00 0.00 51.00 Average: 13 51.96 0.00 0.00 0.00 0.00 48.04 Average: 14 52.48 0.00 0.00 0.00 0.00 47.52 Average: 15 48.51 0.00 0.99 0.00 0.00 50.50
同じようにlimitグループに対してメモリリソースに対する制限を追加する。
group.subtree_control
への追加は親グループでデフォルトで有効になっているため不要。
それではmemory.high
への書き込みによってメモリ使用量を制限する。
20GBのメモリを消費するようにstressを実行した。
$ echo "10g" |sudo tee /sys/fs/cgroup/limit/memory.high 10g [yota@ubuntu01 limit]$ cat /sys/fs/cgroup/limit/memoray.high 10737418240 # 別窓で実施 $ stress -m 1 --vm-hang 0 --vm-bytes 20G $ free -h total used free shared buff/cache available Mem: 15Gi 10Gi 4.0Gi 0.0Ki 738Mi 4.5Gi Swap: 8.0Gi 8.0Gi 0B
cgorupv1では上限メモリ設定値を超えた場合OMMでkillされていましたが、cgroup v2ではリソースを有効に使い切るという観点から改善されており、cpu.high
指定の容量はOOMせずに抑制しようとする。
cpu.max
の場合は最終上限として機能するため、指定すると指定容量から削減できない場合はOOMが実施される。
試しに5GBを設定した状態で先程と同じくstressを実行したところ、メモリ5GB使用後にスワップを使い切った段階でkillされた。
$ echo "5g" |sudo tee /sys/fs/cgroup/limit/memory.max 5g $ stress -m 1 --vm-hang 0 --vm-bytes 20G stress: info: [2502] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd stress: FAIL: [2502] (415) <-- worker 2503 got signal 9 stress: WARN: [2502] (417) now reaping child worker processes stress: FAIL: [2502] (451) failed run completed in 24s
なお他のcgroupに移動したい場合はそのままcgroup.procs
に書き込めば元のグループからは自動的に削除される。
$ sudo mkdir /sys/fs/cgroup/limit2 $ echo $$ | sudo tee -a /sys/fs/cgroup/limit2/cgroup.procs # 元のグループから消えた $ cat /sys/fs/cgroup/limit/cgroup.procs $
参考・関連
- kernel v5.6 Documentation
Understanding the new control groups API
cgroup-v2-internals
Red Hat Enterprise Linux 6 のシステムリソース管理
RED HAT ENTERPRISE LINUX 7 cgroups を使用した RHEL 7 のシステムリソースの管理
- https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html-single/resource_management_guide/index#chap-Using_libcgroup_Tools
cgroupについて理解する
cgroupはカーネルが提供するリソースコントロールのための仕組みである。プロセスをグループ化してCPUやメモリの使用量を制限や記録することができる。 この仕組はコンテナなどの仮想化技術のリソースマネジメントにも利用されていて大きな役割を担っている。
cgroupはカーネル4.5から導入されているv2とそれ以前に利用されていたv1に大きく分けられていて、内部のデータ構造などに大きな変更がある。この記事を書いている時点では、カーネルバージョン4.5未満を利用している環境は多く存在し、現役でv1が利用されている環境も多くある。
業務でv2を扱える環境が徐々に増えてきたこともありcgroupの基礎的な部分を中心に、バージョンによる差などをまとめる。
cgroupとは
cgroupはプロセスに割り当てるLinuxが提供するリソースを制限、監視するための仕組みである。例えば特定のアプリケーションが利用するメモリ上限を定めて、システム全体のクラッシュを防いだり、事前に取り決めたCPU時間を超えないように割り当てたりができる。さらには、特定のアプリケーションがどの程度リソースを消費したのか使用状況をカウントする事も可能である。
どのようなプロセスにどういった制限や監視をするかは、cgroupfsと呼ばれる疑似ファイルシステムを通して操作が行われる。インタフェースとし汎用的なファイルシステムを利用することによって様々なシステムやプログラムから操作が容易である。
登場する概念
タスク
プロセスのことを指す
コントロールグループ
ユーザが制御したい何かしらの基準によって分割されたプロセスのグループのこと。cgroupの世界ではリソースの制御はこのコントロールグループを単位として適用する。プロセスは何かしらのコントロールグループに所属する。コントロールグループは階層構造で構成し管理される。
サブシステム
Linuxリソース(CPUやメモリなど様々)に対する操作を実現するための手続きを抽象化したコントローラ。サブシステムをコントロールグループに接続することによって、コントロールグループがサブシステムによって制御される。各サブシステムの趣旨やパラメータの与え方については各カーネルバージョンのドキュメントを参照する。
- cpu
- cpuset
- memory
- blkio
- devices
- freezer
- hugetlb
- pids
上記の概念にはいくつかのルールが存在する。
1 単一階層には、単一または複数のサブシステムを接続することができる
Red Hat Enterprise Linux 6 リソース管理ガイドより引用
画像の例ではcpuサブシステムとmeoryサブシステムが関連付けられている。
2 すでに階層を割り当てているサブシステムは他の階層を割り当てることはできない
Red Hat Enterprise Linux 6 リソース管理ガイドより引用
すでに①でcpuサブシステムが、②でmemoryサブシステムの関連付けが行われているため、③の割当をすることはできない。
3 タスクは異なる階層の複数のcgoupのメンバになることが可能
Red Hat Enterprise Linux 6 リソース管理ガイドより引用
タスクは複数の階層のコントロールグループのメンバになることができる。しかし同一の階層の/cg1と/cg2のメンバになることは許されていない。 実際/cg1所属状態で/cg2に所属させる操作をした場合は、/cg1から/cg2への移動となる。 これは/cg1でCPU時間を全体の50%に制限していた場合、/cg2では異なる割合で制限をしており、設定値に矛盾が生じるためである。
4 プロセスがフォークした場合子プロセスは親のcgroupを継承する
Red Hat Enterprise Linux 6 リソース管理ガイドより引用
管理対象のプロセスが子プロセスを作成した場合、子プロセスは自動的にその親のコントロールグループのメンバーとなる。 この自動的に継承したコントロールグループから移動することも可能。
cgroup v1の動作確認
起動時にsystemdによって/sys/fs/cgroup
にcgroupfsがマウントされた状態で起動する。このときいくつかのサブシステムが/sys/fs/cgroup
配下にマウントされている事がわかる。
この状態は先ほほどの図でいうと、各サブシステムがそれぞれのhierarchyに接続されている状態である。
$ findmnt |grep cgroup │ ├─/sys/fs/cgroup tmpfs tmpfs ro,nosuid,nodev,noexec,seclabel,mode=755 │ │ ├─/sys/fs/cgroup/systemd cgroup cgroup rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd │ │ ├─/sys/fs/cgroup/cpu,cpuacct cgroup cgroup rw,nosuid,nodev,noexec,relatime,seclabel,cpuacct,cpu │ │ ├─/sys/fs/cgroup/cpuset cgroup cgroup rw,nosuid,nodev,noexec,relatime,seclabel,cpuset │ │ ├─/sys/fs/cgroup/perf_event cgroup cgroup rw,nosuid,nodev,noexec,relatime,seclabel,perf_event │ │ ├─/sys/fs/cgroup/memory cgroup cgroup rw,nosuid,nodev,noexec,relatime,seclabel,memory │ │ ├─/sys/fs/cgroup/freezer cgroup cgroup rw,nosuid,nodev,noexec,relatime,seclabel,freezer │ │ ├─/sys/fs/cgroup/devices cgroup cgroup rw,nosuid,nodev,noexec,relatime,seclabel,devices │ │ ├─/sys/fs/cgroup/pids cgroup cgroup rw,nosuid,nodev,noexec,relatime,seclabel,pids │ │ ├─/sys/fs/cgroup/net_cls,net_prio cgroup cgroup rw,nosuid,nodev,noexec,relatime,seclabel,net_prio,net_cls │ │ ├─/sys/fs/cgroup/hugetlb cgroup cgroup rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb │ │ └─/sys/fs/cgroup/blkio cgroup cgroup rw,nosuid,nodev,noexec,relatime,seclabel,blkio
利用できるサブシステムの一覧を表示したい場合は/proc/cgroups
を参照する。
$ cat /proc/cgroups #subsys_name hierarchy num_cgroups enabled cpuset 3 1 1 cpu 2 1 1 cpuacct 2 1 1 memory 5 1 1 devices 7 1 1 freezer 6 1 1 net_cls 9 1 1 blkio 11 1 1 perf_event 4 1 1 hugetlb 10 1 1 pids 8 1 1 net_prio 9 1 1
そのコントロールグループにも付けられているプロセスを参照したければtasks
ファイルを参照するといい。試しにcpuサブシステうが関連付けられている頂点のcgroup(root cgroup)のtasksファイルを見てみる。するとこのように多くのプロセスが表示されるはずだ。これはシステム上のプロセスは初期状態でデフォルトのcgroupのメンバとなること担っているためである。
$ cat /sys/fs/cgroup/cpu/tasks | head 1 2 3 4 5 6 7 8 9 10
それでは実際に特定のプロセスに対してCPU時間を制限してみる。
新たにcpu_limit
というcgroupを作成してそこにプロセスを所属させることにする。
ディレクトリを作成すると自動的にcpuサブシステムに必要なパラメータを与えるファイルとroot cgroupと同じようにtasksなどが自動的に作成されている。
もちろんroot cgroupと違い新しく作成したcgroupにはプロセスは何も登録されていない状態である。
$ sudo mkdir /sys/fs/cgroup/cpu/cpu_limit $ ls /sys/fs/cgroup/cpu/cpu_limit/ cgroup.clone_children cgroup.event_control cgroup.procs cpu.cfs_period_us cpu.cfs_quota_us cpu.rt_period_us cpu.rt_runtime_us cpu.shares cpu.stat cpuacct.stat cpuacct.usage cpuacct.usage_percpu notify_on_release tasks $ cat /sys/fs/cgroup/cpu/cpu_limit/tasks $
そして次にcgroup内に作成されたファイルに対して実際に値を設定する。
cpu.cfs_period_us
には指定したcgroup内のプロセスCPUリソースが割当される頻度をマイクロ秒単位で指定する。
cpu.cfs_quota_us
には1回割り当てられたタイミングで消費することのできるCPU時間の合計を指定する。
1スレッドのマシンの場合とcpu.cfs_period_us
が100000だとするとその半分である50000を指定すると指定したプロセスでCPU使用量50%となる。
(もちろん対象プロセスが100%の負荷をかけている状態で、かつcgroup内に存在するプロセスが1つもしくは、他のプロセスはCPU時間を消費しないものという前提)
回使った私のマシンの場合は16スレッドのため50000*16/2=400000を指定する。
そして対象となるプロセス(pid)はtasksに指定する。今回指定するプロセスには現在使用しているshellを指定した。 こうすることで負荷をかけるshell上で実行したプロセス(子プロセス)は親のcgroupを自動的に継承し制限の対象となるためである。
$ echo "100000" | sudo tee /sys/fs/cgroup/cpu/cpu_limit/cpu.cfs_period_us 100000 $ echo "400000" | sudo tee /sys/fs/cgroup/cpu/cpu_limit/cpu.cfs_quota_us 400000 $ echo $$ | sudo tee -a /sys/gs/cgroup/cpu/cpu_limit/tasks 1293
まずは先程作ったcgroupに所属させていないシェルからstressコマンドを使って負荷をかける。そしてsarコマンドでCPU使用率を確認するが、当然すべてのCPUスレッドで100%の使用率となっている。
$ stress -c 16 $ sar 1 1 -u -P ALL 平均値: CPU %user %nice %system %iowait %steal %idle 平均値: all 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 0 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 1 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 2 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 3 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 4 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 5 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 6 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 7 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 8 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 9 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 10 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 11 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 12 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 13 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 14 100.00 0.00 0.00 0.00 0.00 0.00 平均値: 15 100.00 0.00 0.00 0.00 0.00 0.00
次にcgroupに所属させたシェルから同じようにstressを実行する。こちらも期待通りすべてのスレッドで概ね50%となっている。
$ stress -c 16 平均値: CPU %user %nice %system %iowait %steal %idle 平均値: all 50.06 0.00 0.06 0.00 0.06 49.81 平均値: 0 50.00 0.00 0.00 0.00 0.00 50.00 平均値: 1 49.00 0.00 0.00 0.00 0.00 51.00 平均値: 2 50.00 0.00 0.00 0.00 0.00 50.00 平均値: 3 49.49 0.00 0.00 0.00 0.00 50.51 平均値: 4 50.51 0.00 0.00 0.00 0.00 49.49 平均値: 5 50.50 0.00 0.00 0.00 0.00 49.50 平均値: 6 48.48 0.00 0.00 0.00 0.00 51.52 平均値: 7 49.00 0.00 0.00 0.00 0.00 51.00 平均値: 8 51.00 0.00 0.00 0.00 0.00 49.00 平均値: 9 51.00 0.00 0.00 0.00 0.00 49.00 平均値: 10 51.00 0.00 0.00 0.00 0.00 49.00 平均値: 11 50.00 0.00 0.00 0.00 0.00 50.00 平均値: 12 50.00 0.00 0.00 0.00 0.00 50.00 平均値: 13 50.50 0.00 0.00 0.00 0.00 49.50 平均値: 14 50.00 0.00 0.00 0.00 0.00 50.00 平均値: 15 50.51 0.00 0.00 0.00 0.00 49.49
またstressを実行した状態でtsaksを見るとbashの子プロセスであるstressが自動的に記載されていることがわかる。 これによって子プロセスは自動的にcgroupを継承することが確認できた。
$ cat /sys/fs/cgroup/cpu/cpu_limit/tasks 1293 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 $ pstree -p -a 1293 bash,1293 └─stress,2037 -c 16 ├─stress,2038 -c 16 ├─stress,2039 -c 16 ├─stress,2040 -c 16 ├─stress,2041 -c 16 ├─stress,2042 -c 16 ├─stress,2043 -c 16 ├─stress,2044 -c 16 ├─stress,2045 -c 16 ├─stress,2046 -c 16 ├─stress,2047 -c 16 ├─stress,2048 -c 16 ├─stress,2049 -c 16 ├─stress,2050 -c 16 ├─stress,2051 -c 16 ├─stress,2052 -c 16 └─stress,2053 -c 16
CPU時間の制限に加えて、メモリ使用量の制限を加えたい場合はこのようにmemoryサブシステムを利用して、おなじpidを新たなコントロールグループに所属させる。
試しにメモリ使用量を10GBに制限してみる。
$ sudo mkdir /sys/fs/cgroup/memory/memory_limit $ echo "10g" | sudo tee /sys/fs/cgroup/memory/memory_limit/memory.limit_in_bytes $ cat /sys/fs/cgroup/memory/memory_limit/memory.limit_in_bytes 10737418240 $ echo $$ | sudo tee -a /sys/fs/cgroup/memory/memory_limit/memory.limit_in_bytes
この状態で再びstressコマンドを起動して今度はメモリに負荷をかけてみる。
5GBを消費するようstressを起動すると普通に動くが、設定した上限である10GBを超えるように指定するとstressがOOM(sig9)され、終了していることがわかる。
# 何もしていない状態 $ free -h total used free shared buff/cache available Mem: 15G 273M 15G 16M 74M 15G Swap: 0B 0B 0B $ stress -m 1 --vm-hang 0 --vm-bytes 5G $ free -h total used free shared buff/cache available Mem: 15G 5.3G 10G 16M 74M 10G Swap: 0B 0B 0B # 今度は15GB $ stress -m 1 --vm-hang 0 --vm-bytes 15G stress: info: [1653] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd stress: FAIL: [1653] (415) <-- worker 1654 got signal 9 stress: WARN: [1653] (417) now reaping child worker processes stress: FAIL: [1653] (451) failed run completed in 2s
もちろんこの状態では複数のサブシステムが関連付けられている状態のためCPUの制限も有効である。
まとめ
cgourpv1を中心に概念とその使用方法についておさらいした。 v2がリリースされしばらくたったとはいえ、cgroupv1で稼働している環境も多くあり現在は移行期といえる。
次回でcgroup v2の基本的な概念や操作方法、v1との相違点について着眼する。
参考・関連
- cgroups(7) — Linux manual page
Red Hat Enterprise Linux 6 のシステムリソース管理
RED HAT ENTERPRISE LINUX 7 cgroups を使用した RHEL 7 のシステムリソースの管理
- https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html-single/resource_management_guide/index#chap-Using_libcgroup_Tools
cgroup namespaceの役割と動き
Linuxに実装されているnamepsaceはいくつかあるがその中でも比較的新しく、近年の仮想化需要に対応したものがControl group(cgroup) Namespaceである。 cgroup namespaceはカーネル4.6から導入されている。
cgroup namepsaceを分割する意味
コンテナによる仮想化ではpid namespaceやmount namespaceを利用することによってホスト環境からプロセス実行環境の仮想化を実現している。
これよって例えばコンテナ内からホスト上で実行している他のプロセスの情報を隠蔽する。
そして実行しようとするコンテナに対してCPUやメモリなどのリソースを制限しようとしたときに、利用するのがcgroupである。
以前こちらの記事でcgroupが持つ機能について触れたが、cgroupを利用すると特定のプロセス(タスク)が利用するリソース料の監視や制限をすることができ、コンテナによる仮想化でも利用されている。
しかしこれには欠点があり、cgorup namespaceが分割されていない状態ではコンテナ内からホスト側での管理情報を覗ける状態となっていた。 これを解消することができるのがcgroup namepaceというわけだ。
Dcokerを使ってcgroup namespaceの効果を確認する
まずdockerによってコンテナを作成することによってcgroup namespaceがどのように機能するかを確認してみる。 cgroup namespacecが実装されていない古いカーネルと実装済みカーネルで比較をする。
Cgorup namespace未実装カーネルでの挙動
/proc/PID/ns/
を参照すると対象pidが所属するnamespaceを確認することができる。
カーネル3系のOSで試すと配下にはcgroup namespaceは存在しないことが確認できる。
$ uname -mr 3.10.0-1160.80.1.el7.x86_64 x86_64 $ ls -l /proc/self/ns/ 合計 0 lrwxrwxrwx. 1 user user 0 1月 18 00:12 ipc -> ipc:[4026531839] lrwxrwxrwx. 1 user user 0 1月 18 00:12 mnt -> mnt:[4026531840] lrwxrwxrwx. 1 user user 0 1月 18 00:12 net -> net:[4026531956] lrwxrwxrwx. 1 user user 0 1月 18 00:12 pid -> pid:[4026531836] lrwxrwxrwx. 1 user user 0 1月 18 00:12 user -> user:[4026531837] lrwxrwxrwx. 1 user user 0 1月 18 00:12 uts -> uts:[4026531838]
cgroup namespaceの存在しない3系でコンテナを立ち上げる。 そしてコンテナ内でプロセスが所属するcgroupの情報を見てみる。
その後下記のように/proc/PID/cgroup
を参照してbashの所属するcgroupを確認する。
すると何やら/system.sliceを起点としたグループに所属している事が確認できる。
$ sudo docker run -d --name test-container centos:latest sleep 100000 a237598aa9101713ef38c89affde05ccfd6fe1c9eb424f222a6a0b86beea591d $ sduo docker exec -it test-container bash $ cat /proc/1/cgroup 11:pids:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 10:memory:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 9:perf_event:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 8:freezer:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 7:cpuacct,cpu:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 6:cpuset:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 5:devices:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 4:hugetlb:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 3:net_prio,net_cls:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 2:blkio:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 1:name=systemd:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope
実はこれはホスト側で見えているグループと一致している。 確認のためコンテナホスト上でcgroupの確認をする。
# ホスト側でのpidの特定 $ sudo docker inspect 3df416c8da1b| jq .[0].State.Pid 1716 $ cat /proc/1716/cgroup 11:pids:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 10:memory:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 9:perf_event:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 8:freezer:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 7:cpuacct,cpu:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 6:cpuset:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 5:devices:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 4:hugetlb:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 3:net_prio,net_cls:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 2:blkio:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope 1:name=systemd:/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope
コンテナ内からcgourpfsのマウント情報を確認するとマウント情報を確認しても、ホスト上のcgroupfsの対照グループをマウントしているので、ホストに関する情報が見えている。
# findmnt -t cgroup TARGET SOURCE FSTYPE OPTIONS /sys/fs/cgroup/systemd cgroup[/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope] cgroup ro,nosuid,nodev,noexec,relati /sys/fs/cgroup/blkio cgroup[/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope] cgroup ro,nosuid,nodev,noexec,relati /sys/fs/cgroup/net_cls,net_prio cgroup[/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope] cgroup ro,nosuid,nodev,noexec,relati /sys/fs/cgroup/hugetlb cgroup[/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope] cgroup ro,nosuid,nodev,noexec,relati /sys/fs/cgroup/devices cgroup[/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope] cgroup ro,nosuid,nodev,noexec,relati /sys/fs/cgroup/cpuset cgroup[/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope] cgroup ro,nosuid,nodev,noexec,relati /sys/fs/cgroup/cpu,cpuacct cgroup[/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope] cgroup ro,nosuid,nodev,noexec,relati /sys/fs/cgroup/freezer cgroup[/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope] cgroup ro,nosuid,nodev,noexec,relati /sys/fs/cgroup/perf_event cgroup[/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope] cgroup ro,nosuid,nodev,noexec,relati /sys/fs/cgroup/memory cgroup[/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope] cgroup ro,nosuid,nodev,noexec,relati /sys/fs/cgroup/pids cgroup[/system.slice/docker-3df416c8da1be95d4aaeb4752a87e0bdf6469ac249e0d17a8dfaa9d1764d1914.scope] cgroup ro,nosuid,nodev,noexec,relati
Cgroup namespace実装カーネルの挙動
/proc/PID/ns/
配下にはcgroupの存在が確認できる。
$ $ ls -l /proc/self/ns/ total 0 lrwxrwxrwx. 1 user user 0 Jan 18 23:59 cgroup -> 'cgroup:[4026531835]' lrwxrwxrwx. 1 user user 0 Jan 18 23:59 ipc -> 'ipc:[4026531839]' lrwxrwxrwx. 1 user user 0 Jan 18 23:59 mnt -> 'mnt:[4026531841]' lrwxrwxrwx. 1 user user 0 Jan 18 23:59 net -> 'net:[4026531840]' lrwxrwxrwx. 1 user user 0 Jan 18 23:59 pid -> 'pid:[4026531836]' lrwxrwxrwx. 1 user user 0 Jan 18 23:59 pid_for_children -> 'pid:[4026531836]' lrwxrwxrwx. 1 user user 0 Jan 18 23:59 time -> 'time:[4026531834]' lrwxrwxrwx. 1 user user 0 Jan 18 23:59 time_for_children -> 'time:[4026531834]' lrwxrwxrwx. 1 user user 0 Jan 18 23:59 user -> 'user:[4026531837]' lrwxrwxrwx. 1 user user 0 Jan 18 23:59 uts -> 'uts:[4026531838]'
同じようにコンテナを作成して内部から、cgroupに関する情報を確認する。
コンテナ内ではプロセスのcgroup情報は/(ルート)となっており、ホストに関する情報は見えていない。 またマウント情報を確認しても同じようになっている。
$ sudo docker run -d --name test-container centos:latest sleep 100000 $ sudo docker exec -it test-container bash $ cat /proc/1/cgroup 0::/ $ findmnt -t cgroup2 TARGET SOURCE FSTYPE OPTIONS /sys/fs/cgroup cgroup cgroup2 ro,nosuid,nodev,noexec,relatime,seclabel,nsdelegate,memory_recursiveprot
ホスト上から該当プロセスのcgorup情報を確認するとコンテナ内とは違いホスト側での所属cgroupが表示されている。
$ sudo docker inspect test-container| jq .[0].State.Pid 1416 $ cat /proc/1416/cgroup 0::/system.slice/docker-3f141de544840800ac2aec654633947a3faf9b11c6b666f6f8a4e6b240de954f.scope $ findmnt -t cgroup2 TARGET SOURCE FSTYPE OPTIONS /sys/fs/cgroup cgroup cgroup2 ro,nosuid,nodev,noexec,relatime,seclabel,nsdelegate,memory_recursiveprot
Cgroupを手動で分割する
Dockerコンテナで確認した内容について手動でnamespaceを分割することによって確認する。
まずはログインした状態のbashプロセスが所属するcgroupを確認してみる。
$ cat /proc/self/cgroup 0::/user.slice/user-1000.slice/session-1.scope $ ls -l /proc/self/ns/cgroup lrwxrwxrwx. 1 user user 0 Feb 4 06:04 /proc/self/ns/cgroup -> 'cgroup:[4026531835]'
次にunshareコマンドを利用してcgroup namespaceを分割する。
cgroup namespace分割後はrootグループに所属し、unshare実行前のグループとは異なることが確認できる。
sudo unshare --cgroup $ cat /proc/self/cgroup 0::/ ls -l /proc/self/ns/cgroup lrwxrwxrwx. 1 root root 0 Feb 4 06:06 /proc/self/ns/cgroup -> 'cgroup:[4026532168]'
しかし親から見たグループは派生前の親と同一のグループに所属している。このことを確認するには、unshareしたbashプロセスのcgroup情報を別のbashプロセスから確認する。
するとunshare前のプロセスと同じグループに所属することがわかる。
# unshareでnamespaceを分割した実行したbashのpidを確認 $ echo $$ 1245 # 別の窓から確認したpidのcgroupを確認する $ cat /proc/1245/cgroup 0::/user.slice/user-1000.slice/session-1.scope $ ls -al /proc/1245/ns/cgroup lrwxrwxrwx. 1 root root 0 Feb 4 06:22 /proc/1245/ns/cgroup -> 'cgroup:[4026532168]'
cgroupfsのマウント情報
unshareを実行し分割したグループ内ではcgroupがrootとして表示されていることが確認できた。
しかしcgroupfsのマウント情報を確認するとルートからの相対パスで示されている。
$ findmnt -t cgroup2 TARGET SOURCE FSTYPE OPTIONS /sys/fs/cgroup cgroup2[/../../..] cgroup2 rw,nosuid,nodev,noexec,relatime,seclabel,nsdelegate,memory_recursiveprot
またmount情報はそのままのため、/sys/fs/cgroup/
を参照すると 該当cgroupに所属するプロセス以外の情報も確認できる状態である。
これを解決するためにはcgroup namespaceとともにmount namespaceを分割する必要がある。
$ sudo unshare --cgroup --mount # mount namspaceを分割しただけではまだそのまま $ findmnt -t cgroup2 TARGET SOURCE FSTYPE OPTIONS /sys/fs/cgroup cgroup2[/../../..] cgroup2 rw,nosuid,nodev,noexec,relatime,seclabel,nsdelegate,memory_recursiveprot # cgroupfsを一度アンマウントし再度マウントする $ umount /sys/fs/cgroup $ mount -t cgroup2 cgroup2 /sys/fs/cgroup # するとマウント情報でもルートとして見えるようになった $ findmnt -t cgroup2 TARGET SOURCE FSTYPE OPTIONS /sys/fs/cgroup cgroup2 cgroup2 rw,relatime,seclabel,nsdelegate,memory_recursiveprot
cgroupfsの再マウントを実行した後では/sys/fs/cgroup
配下を見ると他のグループに関連する情報は存在しない。
namespace内でこのように新しいグループを作成すると、親グループからも新たにグループが作成されていることが確認できる。
$ mkdir /sys/fs/cgroup/test $ ls /sys/fs/cgroup/test cgroup.controllers cgroup.freeze cgroup.max.depth cgroup.procs cgroup.subtree_control cgroup.type cpu.stat memory.pressure cgroup.events cgroup.kill cgroup.max.descendants cgroup.stat cgroup.threads cpu.pressure io.pressure # 別窓から親のcgroupfsを確認すると新しく作成したgroupが存在 $ ls /sys/fs/cgroup/user.slice/user-1000.slice/session-1.scope/test cgroup.controllers cgroup.freeze cgroup.max.depth cgroup.procs cgroup.subtree_control cgroup.type cpu.stat memory.pressure cgroup.events cgroup.kill cgroup.max.descendants cgroup.stat cgroup.threads cpu.pressure io.pressure
参考・関連
LWN.net: CGroup Namespaces
LKML: [GIT PULL] cgroup namespace support for v4.6-rc1
kernel doc: Control Group v2
runc: Add support for cgroup namespace