コンテナ技術の基礎(2)リソースの分離(cgroup)
●
本連載では、エンタープライズシステムでコンテナ/Kubernetesを活用した業務システムを開発・運用するエンジニアに向けて、知っておくべきKubernetesセキュリティの基礎知識、Microsoftが提供するパブリッククラウド「Azure」を使ったクラウドでのKubernetesセキュリティ対策のポイント、気を付けておきたい注意点などの実践的なノウハウを紹介しています。
今回は前回に引き続き、コンテナセキュリティの基礎となるコンテナ技術について説明します。今回のテーマはリソースの分離に使われているcgroupです。
リソースを制限する仕組み「cgroup」
cgroupはコンテナから利用できるCPUやメモリを制限するために使われている仕組みです。cgroupには同じグループのプロセスを同時に一時停止・再開させたり(freezer)、ネットワークパケットのタグ付けをしたりする仕組み(net_cls)もあるのですが、本稿では、Kubernetesを利用していれば必ずお世話になっているはずのCPUとメモリの制限方法だけを確認します。
cgroupはcgroupfsという仮想ファイルシステムをマウントして利用します。v1とv1の複雑さを解消してシンプルにしたv2がありますが、ここではv1を試してみます。
$ mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
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/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
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/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/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
CPUを制限
まずは、プロセスが利用するCPUを制限してみます。以下の流れで作業していきます。
同じ制限を適用するグループを作る
制限を適用するプロセスのPIDを登録する
負荷をかける
CPUの制限をかける
CPUの使用率が抑えられていることを確認する
まず、同じ制限を適用するグループを作ります。グループを作るのは簡単で、フォルダを新しく作るとそれがグループになります。
# mkdir /sys/fs/cgroup/cpu/test
ls /sys/fs/cgroup/cpu/test
cgroup.clone_children cpu.shares cpuacct.stat cpuacct.usage_percpu_sys notify_on_release
cgroup.procs cpu.stat cpuacct.usage cpuacct.usage_percpu_user tasks
cpu.cfs_period_us cpu.uclamp.max cpuacct.usage_all cpuacct.usage_sys
cpu.cfs_quota_us cpu.uclamp.min cpuacct.usage_percpu cpuacct.usage_user
次に、制限を適用するプロセスのPIDを登録します。プロセスを登録するとその子のプロセスも自動的に登録されます。ここでは、子のプロセスとしてcatのプロセスが表示されています。
# echo $$ > /sys/fs/cgroup/cpu/test/tasks
root@i-17100000215267:~# cat /sys/fs/cgroup/cpu/test/tasks
101466
101525
続いて、stressコマンドで負荷をかけます。
# apt-get install stress
stress -c 1
CPU使用率がすぐに100%になりました。
top - 21:25:43 up 19 days, 13:25, 3 users, load average: 0.98, 0.54, 0.22
Tasks: 118 total, 2 running, 116 sleeping, 0 stopped, 0 zombie
%Cpu(s):100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 981.3 total, 156.4 free, 156.4 used, 668.5 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 661.3 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
101870 root 20 0 3856 100 0 R 99.7 0.0 3:44.57 stress
98654 root 20 0 0 0 0 I 0.3 0.0 0:28.87 kworker/0:0-events
1 root 20 0 169036 11524 6940 S 0.0 1.1 0:38.56 systemd
次に、cpu.cfs_periodusとcpu.cfs_quotausを使って制限をします。1つのコアを(cpu.cfs_periodus)マイクロ秒あたり(cpu.cfs_quotaus)マイクロ秒利用する設定を反映するという関係になっています。試している環境は1コアしかないので、以下の設定であれば、CPU使用率は50000(μs)/100000(μs)×1(コア数) = 50%程度に抑えられるはずです。
echo 100000 > /sys/fs/cgroup/cpu/test/cpu.cfs_period_us
echo 50000 > /sys/fs/cgroup/cpu/test/cpu.cfs__quota/_us
想定どおりCPU使用率を50%程度に抑えることができました。
top - 21:27:02 up 19 days, 13:27, 3 users, load average: 0.92, 0.64, 0.29
Tasks: 118 total, 2 running, 116 sleeping, 0 stopped, 0 zombie
%Cpu(s): 51.8 us, 0.3 sy, 0.0 ni, 47.6 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st
MiB Mem : 981.3 total, 156.4 free, 156.4 used, 668.5 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 661.3 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
101870 root 20 0 3856 100 0 R 49.7 0.0 4:58.42 stress
10 root 20 0 0 0 0 I 0.3 0.0 51:16.74 rcu_sched
98654 root 20 0 0 0 0 I 0.3 0.0 0:28.89 kworker/0:0-events
101879 root 20 0 11004 3844 3180 R 0.3 0.4 0:00.47 top
1 root 20 0 169036 11524 6940 S 0.0 1.1 0:38.56 systemd
●
メモリの制限
CPUの次は、メモリの制限にトライします。メモリ使用量の上限となるmemory.max_usage_in_bytesは、root groupには設定できません。下階層にコントロールグループを作成し、現在のシェルから実行されるタスクが登録されるようにします。
# mkdir /cgroup_memory
# mount -t cgroup -o memory none /cgroup_memory/
# mkdir /cgroup_memory/test
# ls /cgroup_memory/test/
cgroup.clone_children memory.kmem.tcp.failcnt memory.oom_control
cgroup.event_control memory.kmem.tcp.limit_in_bytes memory.pressure_level
cgroup.procs memory.kmem.tcp.max_usage_in_bytes memory.soft_limit_in_bytes
memory.failcnt memory.kmem.tcp.usage_in_bytes memory.stat
memory.force_empty memory.kmem.usage_in_bytes memory.swappiness
memory.kmem.failcnt memory.limit_in_bytes memory.usage_in_bytes
memory.kmem.limit_in_bytes memory.max_usage_in_bytes memory.use_hierarchy
memory.kmem.max_usage_in_bytes memory.move_charge_at_immigrate notify_on_release
memory.kmem.slabinfo memory.numa_stat tasks
頻繁に利用するのは、以下のファイルです。
memory.limit*inbytes メモリ消費量の制限値の設定と表示
memory.usage_in_bytes cgroup内のプロセスが消費しているメモリ
memory.max_usagein*bytes cgroup内のプロセスが消費したメモリの最大値
memory.oom*_control 有効化(0)/無効化(1)
oom*_controlを有効にした場合と無効にした場合の動きをそれぞれ確認しておきましょう。
以下のソースをコンパイルして試してみます。このコードは、メモリを1MBずつ5秒ごとに確保していきます。
#include
#include
#include
#include
#define MB 1024*1024
int main(int argc, char *argv[])
{
char *p;
int malloc_size = 1 * MB;
int i;
for(i = 1; i /cgroup_memory/test/tasks
# echo 1 > /cgroup_memory/test/memory.oom_control
# echo 5M > /cgroup_memory/test/memory.limit_in_bytes
# ./memtest
1MB
2MB
3MB
4MB
oom*_controlを有効にしてみます。今度はプロセスがKillされました。
# echo $$ > /cgroup_memory/test/tasks
# echo 0 > /cgroup_memory/test/memory.oom_control
# echo 5M > /cgroup_memory/test/memory.limit_in_bytes
# ./memtest
1MB
2MB
3MB
4MB
Killed
○野村総合研究所(NRI) 湯川勇太(ゆかわ ゆうた)
前職で大手家電量販店のECモールの立ち上げとECサイトの刷新プロジェクトを経験。NRI入社後は製造業の商品検索システムを担当した。現在は大手物流企業向けの基幹システムの方式設計や技術検証、トラブル対応を行っている。
本連載では、エンタープライズシステムでコンテナ/Kubernetesを活用した業務システムを開発・運用するエンジニアに向けて、知っておくべきKubernetesセキュリティの基礎知識、Microsoftが提供するパブリッククラウド「Azure」を使ったクラウドでのKubernetesセキュリティ対策のポイント、気を付けておきたい注意点などの実践的なノウハウを紹介しています。
リソースを制限する仕組み「cgroup」
cgroupはコンテナから利用できるCPUやメモリを制限するために使われている仕組みです。cgroupには同じグループのプロセスを同時に一時停止・再開させたり(freezer)、ネットワークパケットのタグ付けをしたりする仕組み(net_cls)もあるのですが、本稿では、Kubernetesを利用していれば必ずお世話になっているはずのCPUとメモリの制限方法だけを確認します。
cgroupはcgroupfsという仮想ファイルシステムをマウントして利用します。v1とv1の複雑さを解消してシンプルにしたv2がありますが、ここではv1を試してみます。
$ mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
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/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
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/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/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
CPUを制限
まずは、プロセスが利用するCPUを制限してみます。以下の流れで作業していきます。
同じ制限を適用するグループを作る
制限を適用するプロセスのPIDを登録する
負荷をかける
CPUの制限をかける
CPUの使用率が抑えられていることを確認する
まず、同じ制限を適用するグループを作ります。グループを作るのは簡単で、フォルダを新しく作るとそれがグループになります。
# mkdir /sys/fs/cgroup/cpu/test
ls /sys/fs/cgroup/cpu/test
cgroup.clone_children cpu.shares cpuacct.stat cpuacct.usage_percpu_sys notify_on_release
cgroup.procs cpu.stat cpuacct.usage cpuacct.usage_percpu_user tasks
cpu.cfs_period_us cpu.uclamp.max cpuacct.usage_all cpuacct.usage_sys
cpu.cfs_quota_us cpu.uclamp.min cpuacct.usage_percpu cpuacct.usage_user
次に、制限を適用するプロセスのPIDを登録します。プロセスを登録するとその子のプロセスも自動的に登録されます。ここでは、子のプロセスとしてcatのプロセスが表示されています。
# echo $$ > /sys/fs/cgroup/cpu/test/tasks
root@i-17100000215267:~# cat /sys/fs/cgroup/cpu/test/tasks
101466
101525
続いて、stressコマンドで負荷をかけます。
# apt-get install stress
stress -c 1
CPU使用率がすぐに100%になりました。
top - 21:25:43 up 19 days, 13:25, 3 users, load average: 0.98, 0.54, 0.22
Tasks: 118 total, 2 running, 116 sleeping, 0 stopped, 0 zombie
%Cpu(s):100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 981.3 total, 156.4 free, 156.4 used, 668.5 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 661.3 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
101870 root 20 0 3856 100 0 R 99.7 0.0 3:44.57 stress
98654 root 20 0 0 0 0 I 0.3 0.0 0:28.87 kworker/0:0-events
1 root 20 0 169036 11524 6940 S 0.0 1.1 0:38.56 systemd
次に、cpu.cfs_periodusとcpu.cfs_quotausを使って制限をします。1つのコアを(cpu.cfs_periodus)マイクロ秒あたり(cpu.cfs_quotaus)マイクロ秒利用する設定を反映するという関係になっています。試している環境は1コアしかないので、以下の設定であれば、CPU使用率は50000(μs)/100000(μs)×1(コア数) = 50%程度に抑えられるはずです。
echo 100000 > /sys/fs/cgroup/cpu/test/cpu.cfs_period_us
echo 50000 > /sys/fs/cgroup/cpu/test/cpu.cfs__quota/_us
想定どおりCPU使用率を50%程度に抑えることができました。
top - 21:27:02 up 19 days, 13:27, 3 users, load average: 0.92, 0.64, 0.29
Tasks: 118 total, 2 running, 116 sleeping, 0 stopped, 0 zombie
%Cpu(s): 51.8 us, 0.3 sy, 0.0 ni, 47.6 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st
MiB Mem : 981.3 total, 156.4 free, 156.4 used, 668.5 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 661.3 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
101870 root 20 0 3856 100 0 R 49.7 0.0 4:58.42 stress
10 root 20 0 0 0 0 I 0.3 0.0 51:16.74 rcu_sched
98654 root 20 0 0 0 0 I 0.3 0.0 0:28.89 kworker/0:0-events
101879 root 20 0 11004 3844 3180 R 0.3 0.4 0:00.47 top
1 root 20 0 169036 11524 6940 S 0.0 1.1 0:38.56 systemd
●
メモリの制限
CPUの次は、メモリの制限にトライします。メモリ使用量の上限となるmemory.max_usage_in_bytesは、root groupには設定できません。下階層にコントロールグループを作成し、現在のシェルから実行されるタスクが登録されるようにします。
# mkdir /cgroup_memory
# mount -t cgroup -o memory none /cgroup_memory/
# mkdir /cgroup_memory/test
# ls /cgroup_memory/test/
cgroup.clone_children memory.kmem.tcp.failcnt memory.oom_control
cgroup.event_control memory.kmem.tcp.limit_in_bytes memory.pressure_level
cgroup.procs memory.kmem.tcp.max_usage_in_bytes memory.soft_limit_in_bytes
memory.failcnt memory.kmem.tcp.usage_in_bytes memory.stat
memory.force_empty memory.kmem.usage_in_bytes memory.swappiness
memory.kmem.failcnt memory.limit_in_bytes memory.usage_in_bytes
memory.kmem.limit_in_bytes memory.max_usage_in_bytes memory.use_hierarchy
memory.kmem.max_usage_in_bytes memory.move_charge_at_immigrate notify_on_release
memory.kmem.slabinfo memory.numa_stat tasks
頻繁に利用するのは、以下のファイルです。
memory.limit*inbytes メモリ消費量の制限値の設定と表示
memory.usage_in_bytes cgroup内のプロセスが消費しているメモリ
memory.max_usagein*bytes cgroup内のプロセスが消費したメモリの最大値
memory.oom*_control 有効化(0)/無効化(1)
oom*_controlを有効にした場合と無効にした場合の動きをそれぞれ確認しておきましょう。
以下のソースをコンパイルして試してみます。このコードは、メモリを1MBずつ5秒ごとに確保していきます。
#include
#include
#include
#include
#define MB 1024*1024
int main(int argc, char *argv[])
{
char *p;
int malloc_size = 1 * MB;
int i;
for(i = 1; i /cgroup_memory/test/tasks
# echo 1 > /cgroup_memory/test/memory.oom_control
# echo 5M > /cgroup_memory/test/memory.limit_in_bytes
# ./memtest
1MB
2MB
3MB
4MB
oom*_controlを有効にしてみます。今度はプロセスがKillされました。
# echo $$ > /cgroup_memory/test/tasks
# echo 0 > /cgroup_memory/test/memory.oom_control
# echo 5M > /cgroup_memory/test/memory.limit_in_bytes
# ./memtest
1MB
2MB
3MB
4MB
Killed
○野村総合研究所(NRI) 湯川勇太(ゆかわ ゆうた)
前職で大手家電量販店のECモールの立ち上げとECサイトの刷新プロジェクトを経験。NRI入社後は製造業の商品検索システムを担当した。現在は大手物流企業向けの基幹システムの方式設計や技術検証、トラブル対応を行っている。