本連載では、エンタープライズシステムでコンテナ/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入社後は製造業の商品検索システムを担当した。現在は大手物流企業向けの基幹システムの方式設計や技術検証、トラブル対応を行っている。