コンテナ技術の基礎(3)namespaceとcgroupで下から見るKubernetes
●
本連載では、エンタープライズシステムでコンテナやKubernetesを活用した業務システムを開発・運用するエンジニアに向けて、知っておくべきKubernetesセキュリティの基礎知識、Microsoftが提供するパブリッククラウド「Azure」を使ったクラウドでのKubernetesセキュリティ対策のポイント、気を付けておきたい注意点などの実践的なノウハウを紹介します。
今回も引き続き、コンテナセキュリティの基礎となるコンテナ技術について説明します。前回と前前回は、コンテナの基礎になっているLinuxカーネルの機能「namespace」「cgroup」「Capability」について説明しました。
コンテナ技術の基礎の最終回である第3回では、これらの技術がKubernetesとどのような関係があるのかを説明していきます。
Kubeneresとnamespace
まずは、Kubeneresのノードの中にどのようなnamespaceがあるのかを見てみます。ノードの中を探索するための特権を持ったDaemonSetをデプロイします。DaemonSetのイメージの素になるDockerfileは以下にしました。
FROM ubuntu:20.04
ENTRYPOINT ["tail","-f", "/etc/profile"]
DaemonSetを定義するyamlは以下です。securityContextで特権を与えており、hostPIDとhostNetworkをtrueにし、hostPathのルートをマウントする極めて危険なものです。この中にkubectl exec -it root-daemon-xxxxx -- bashで入り、ノードの中を探索します。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: root-daemon
spec:
selector:
matchLabels:
app: root-daemon
template:
metadata:
labels:
app: root-daemon
spec:
hostPID: true
hostNetwork: true
containers:
- name: network-collect
image: xxxxxxxxxxxxxxxxxx.azurecr.io/root-daemon:v0.0.1
securityContext:
privileged: true
resources:
limits:
cpu: 50m
memory: 50Mi
requests:
cpu: 50m
memory: 50Mi
volumeMounts:
- mountPath: /hostroot
name: hostroot
volumes:
- name: hostroot
hostPath:
path: /
ベーシックなnginxのDeploymentも適用してみます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-seccomp
spec:
selector:
matchLabels:
app: nginx-seccomp
replicas: 2
template:
metadata:
labels:
app: nginx-seccomp
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
resources:
limits:
cpu: 100m
memory: 125Mi
requests:
cpu: 90m
memory: 115Mi
kubectl exec -it root-daemon-xxxxx -- bashでノードの中を探索する前に、同じノードのnginxのContainer IDを確認しておきます。今回は、f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437dがContainer IDです。
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-seccomp-55449744c7-g4zp9 1/1 Running 0 124m 10.240.0.104 aks-agentpool-23069546-vmss000000
nginx-deployment-seccomp-55449744c7-z2zd5 1/1 Running 0 124m 10.240.0.200 aks-agentpool-23069546-vmss000002
root-daemon-gzcrq 1/1 Running 0 149m 10.240.0.115 aks-agentpool-23069546-vmss000002
root-daemon-jbzcd 1/1 Running 0 149m 10.240.0.4 aks-agentpool-23069546-vmss000000
$ kubectl describe pod nginx-deployment-seccomp-55449744c7-z2zd5 | grep "Container ID"
Container ID: containerd://f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d
次に、nginxのnamespaceがどうなっているのかを確認してみます。
# ls -l /proc/14611/ns
lrwxrwxrwx 1 root root 0 Nov 30 01:57 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 ipc -> 'ipc:[4026532474]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 mnt -> 'mnt:[4026532476]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 net -> 'net:[4026532402]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 pid -> 'pid:[4026532477]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 pid_for_children -> 'pid:[4026532477]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 uts -> 'uts:[4026532473]'
●
nginxのコンテナに入ってみる
続いて、出てきたnamespaceのIDとlsnsの結果をひもづけていきましょう。userのnamespaceは分離されておらず、networkとuts、ipcなどの通信関連も、pauseというプロセスのnamespaceのものを使っていることが分かります。
# lsns | grep 4026531835
4026531835 cgroup 209 1 root /sbin/init
# lsns | grep 4026532474
4026532474 ipc 3 14509 65535 /pause
# lsns | grep 4026532476
4026532476 mnt 2 14611 root nginx: master process nginx -g daemon off;
# lsns | grep 4026532402
4026532402 net 3 14509 65535 /pause
# lsns | grep 4026532477
4026532477 pid 2 14611 root nginx: master process nginx -g daemon off;
# lsns | grep 4026531837
4026531837 user 209 1 root /sbin/init
# lsns | grep 4026532473
4026532473 uts 3 14509 65535 /pause
ps auxfでプロセスを確認してみます。containerdからcontainerd-shimが立ち上がり、そこからpauseとnginxのプロセスが起動していることが分かります。pauseは、複数のコンテナを同じPodにまとめて、同じOS上で動いているように見せるために存在しているコンテナです。
root 2413 2.1 2.3 2215532 94044 ? Ssl Nov25 152:52 /usr/bin/containerd
root 14484 0.0 0.2 709076 10304 ? Sl 01:32 0:00 _ containerd-shim -namespace k8s.io
-workdir /var/lib/containerd/io.containerd.runtime.v1.linux/k8s.io/652192845922a51436085d74c89c7d21f64feb62568432681f845633a174eb31
-address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd
65535 14509 0.0 0.0 964 4 ? Ss 01:32 0:00 | _ /pause
root 14590 0.0 0.1 708820 7356 ? Sl 01:32 0:00 _ containerd-shim -namespace k8s.io
-workdir /var/lib/containerd/io.containerd.runtime.v1.linux/k8s.io/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d
-address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd
root 14611 0.0 0.1 32628 5188 ? Ss 01:32 0:00 _ nginx: master process nginx -g daemon off;
101 14656 0.0 0.0 33076 2344 ? S 01:32 0:00 _ nginx: worker process
nginxのコンテナに入ってみましょう。nginxのコンテナには最低限のものしか含まれていないので、psコマンドを使うこともできません。
# nsenter --target 14611 --all bash
# ps
-sh: 2: ps: not found
# cat /etc/debian_version
9.8
今度はnsenterでホスト側のnamespaceに入り、そこからnginxコンテナのrootfsの中をのぞいてみます。mountの結果を事前に確認したnginxコンテナのIDでgrepすると、overlay on 〜が表示されます。
ここがnginxコンテナのrootfsです。cdで移動して、cat ./etc/debian_versionを叩いてみると、さきほどと同じ9.8が表示されました。
# nsenter --target 1 --all bash
# mount | grep f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d
overlay on /run/containerd/io.containerd.runtime.v1.linux/k8s.io/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d/rootfs type overlay (rw,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/522/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/521/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/518/fs,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/552/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/552/work,xino=off)
# cd /run/containerd/io.containerd.runtime.v1.linux/k8s.io/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d/rootfs
# cat ./etc/debian_version
9.8
●
ネットワークを確認する
ネットワークについても確認してみます。5つのnamespaceとnamespaceにひもづくvethが作られていることが分かります。
# nsenter --target 1 --all bash
# ip netns list
cni-a78e763e-11b1-03c0-889b-ccf6e2290d92 (id: 2)
cni-2ff38e97-62c6-90c9-5f23-2525ff3c9292 (id: 5)
cni-3e8372c7-7b9c-1325-f3bc-60d4443e2811 (id: 1)
cni-27dd6491-b667-2f36-a5ab-8cb404276547 (id: 7)
cni-6f11b87d-1a2b-e95b-0c4d-8b57d4aaf822 (id: 0)
# ip link show
1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:22:48:68:f4:bd brd ff:ff:ff:ff:ff:ff
4: azveb78922e0be@if3: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 2a:c6:90:c3:66:b3 brd ff:ff:ff:ff:ff:ff link-netnsid 0
18: azv233e3487410@if17: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 66:d8:33:ab:3a:6f brd ff:ff:ff:ff:ff:ff link-netnsid 7
20: azvdeb7c0733b9@if19: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 6e:18:fd:7a:63:6d brd ff:ff:ff:ff:ff:ff link-netnsid 1
26: azv19d57a972e9@if25: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 92:de:0e:c5:07:41 brd ff:ff:ff:ff:ff:ff link-netnsid 5
30: azv6bf1c5f71ac@if29: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 5a:37:35:af:83:e6 brd ff:ff:ff:ff:ff:ff link-netnsid 2
# ip -d link show azv6bf1c5f71ac
30: azv6bf1c5f71ac@if29: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 5a:37:35:af:83:e6 brd ff:ff:ff:ff:ff:ff link-netnsid 2 promiscuity 0
veth addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
# ip route
default via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.115 metric 100
10.240.0.0/16 dev eth0 proto kernel scope link src 10.240.0.115
10.240.0.120 dev azveb78922e0be proto static
10.240.0.122 dev azv19d57a972e9 proto static
10.240.0.185 dev azvdeb7c0733b9 proto static
10.240.0.192 dev azv233e3487410 proto static
10.240.0.200 dev azv6bf1c5f71ac proto static
168.63.129.16 via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.115 metric 100
169.254.169.254 via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.115 metric 100
nginxのPIDに割り当てられているnamespaceを確認してみます。
ip netns identify 14611
cni-a78e763e-11b1-03c0-889b-ccf6e2290d92
今度は、cni-a78e763e-11b1-03c0-889b-ccf6e2290d92にひもづくPIDを確認してみます。
ip netns pids cni-a78e763e-11b1-03c0-889b-ccf6e2290d92
14509 ★pauseコンテナ
14611 ★nginxのMaster
14656 ★nginxのWorker
cni-a78e763e-11b1-03c0-889b-ccf6e2290d92の中でip addressを実行すると、10.240.0.200/16のeth0@if30がでてきます。これはnginxのPodに割り当てられているIPアドレスと同じものになっているのが分かります。
ip netns exec cni-a78e763e-11b1-03c0-889b-ccf6e2290d92 ip address
1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
validlft forever preferredlft forever
inet6 ::1/128 scope host
validlft forever preferredlft forever
29: eth0@if30: mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 86:0d:b8:e5:96:1e brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.240.0.200/16 scope global eth0
validlft forever preferredlft forever
inet6 fe80::840d:b8ff:fee5:961e/64 scope link
validlft forever preferredlft forever
●
Kubeneresとcgroups
Kubernetesでコンテナのリソースの上限を指定するのに使われるlimitはcgroupを使って実現されています。
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
resources:
limits:
cpu: 100m
memory: 125Mi
requests:
cpu: 90m
memory: 115Mi
nginxのmasterプロセスのPIDは14611でした。このプロセスのcgroupがどこに定義されているかは、/proc/プロセスID/cgroupを見れば分かります。
cat /proc/14611/cgroup
7:memory:/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d
3:cpu,cpuacct:/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d
CPU
まずは、CPUから確認していきましょう。cgroupのtasksをみると、nginxのmasterプロセスの14611とworkerプロセスの14656が対象になっています。
cat /hostroot/sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d/tasks
14611
14656
limitのcpu: 100mは以下の2つの値で表現されています。
cpu.cfs_quota_us 10000
cpu.cfs_period_us 100000
cat /hostroot/sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d/cpu.cfs_quota_us
10000
cat /hostroot/sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597
ca7003c66bbb437d/cpu.cfs_period_us
100000
メモリ
次に、メモリを確認します。memory.limit_in_bytesを見てみると131072000と出てきますね。計算すると分かりますが、これは125MBのことです。想定どおり、limitの値が反映されていることが分かります。
cat /hostroot/sys/fs/cgroup/memory/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca700
3c66bbb437d/memory.limit_in_bytes
131072000
KubeneresとCapability
確認したように、User namespaceは分離されていません。したがって、ホストのrootはコンテナのrootと違うものではありません。その状態でコンテナの権限を制限しているのはCapabilityです。nginxのプロセスのCapabilityを確認してみましょう。getcapsコマンドを使います。
nginxのプロセスには、cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcapだけを許可されていることが分かります。
$ nsenter --target 1 --all
$ getpcaps 1
Capabilities for 1': = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read+ep
$ getpcaps 14611
Capabilities for14611': = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip
許可されているものの一覧は、以下になります。説明はMan page of CAPABILITIESからの引用です。
許可されているものの一覧は、以下になります。説明はMan page of CAPABILITIESからの引用です。
今回は、コンテナセキュリティを学ぶ上での基礎となる技術について説明しました。次回は、Kubernetes クラスタの保護について説明します。
○野村総合研究所(NRI) 湯川勇太(ゆかわ ゆうた)
前職で大手家電量販店のECモールの立ち上げとECサイトの刷新プロジェクトを経験。NRI入社後は製造業の商品検索システムを担当した。現在は大手物流企業向けの基幹システムの方式設計や技術検証、トラブル対応を行っている。
本連載では、エンタープライズシステムでコンテナやKubernetesを活用した業務システムを開発・運用するエンジニアに向けて、知っておくべきKubernetesセキュリティの基礎知識、Microsoftが提供するパブリッククラウド「Azure」を使ったクラウドでのKubernetesセキュリティ対策のポイント、気を付けておきたい注意点などの実践的なノウハウを紹介します。
コンテナ技術の基礎の最終回である第3回では、これらの技術がKubernetesとどのような関係があるのかを説明していきます。
Kubeneresとnamespace
まずは、Kubeneresのノードの中にどのようなnamespaceがあるのかを見てみます。ノードの中を探索するための特権を持ったDaemonSetをデプロイします。DaemonSetのイメージの素になるDockerfileは以下にしました。
FROM ubuntu:20.04
ENTRYPOINT ["tail","-f", "/etc/profile"]
DaemonSetを定義するyamlは以下です。securityContextで特権を与えており、hostPIDとhostNetworkをtrueにし、hostPathのルートをマウントする極めて危険なものです。この中にkubectl exec -it root-daemon-xxxxx -- bashで入り、ノードの中を探索します。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: root-daemon
spec:
selector:
matchLabels:
app: root-daemon
template:
metadata:
labels:
app: root-daemon
spec:
hostPID: true
hostNetwork: true
containers:
- name: network-collect
image: xxxxxxxxxxxxxxxxxx.azurecr.io/root-daemon:v0.0.1
securityContext:
privileged: true
resources:
limits:
cpu: 50m
memory: 50Mi
requests:
cpu: 50m
memory: 50Mi
volumeMounts:
- mountPath: /hostroot
name: hostroot
volumes:
- name: hostroot
hostPath:
path: /
ベーシックなnginxのDeploymentも適用してみます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-seccomp
spec:
selector:
matchLabels:
app: nginx-seccomp
replicas: 2
template:
metadata:
labels:
app: nginx-seccomp
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
resources:
limits:
cpu: 100m
memory: 125Mi
requests:
cpu: 90m
memory: 115Mi
kubectl exec -it root-daemon-xxxxx -- bashでノードの中を探索する前に、同じノードのnginxのContainer IDを確認しておきます。今回は、f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437dがContainer IDです。
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-seccomp-55449744c7-g4zp9 1/1 Running 0 124m 10.240.0.104 aks-agentpool-23069546-vmss000000
nginx-deployment-seccomp-55449744c7-z2zd5 1/1 Running 0 124m 10.240.0.200 aks-agentpool-23069546-vmss000002
root-daemon-gzcrq 1/1 Running 0 149m 10.240.0.115 aks-agentpool-23069546-vmss000002
root-daemon-jbzcd 1/1 Running 0 149m 10.240.0.4 aks-agentpool-23069546-vmss000000
$ kubectl describe pod nginx-deployment-seccomp-55449744c7-z2zd5 | grep "Container ID"
Container ID: containerd://f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d
次に、nginxのnamespaceがどうなっているのかを確認してみます。
# ls -l /proc/14611/ns
lrwxrwxrwx 1 root root 0 Nov 30 01:57 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 ipc -> 'ipc:[4026532474]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 mnt -> 'mnt:[4026532476]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 net -> 'net:[4026532402]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 pid -> 'pid:[4026532477]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 pid_for_children -> 'pid:[4026532477]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Nov 30 01:57 uts -> 'uts:[4026532473]'
●
nginxのコンテナに入ってみる
続いて、出てきたnamespaceのIDとlsnsの結果をひもづけていきましょう。userのnamespaceは分離されておらず、networkとuts、ipcなどの通信関連も、pauseというプロセスのnamespaceのものを使っていることが分かります。
# lsns | grep 4026531835
4026531835 cgroup 209 1 root /sbin/init
# lsns | grep 4026532474
4026532474 ipc 3 14509 65535 /pause
# lsns | grep 4026532476
4026532476 mnt 2 14611 root nginx: master process nginx -g daemon off;
# lsns | grep 4026532402
4026532402 net 3 14509 65535 /pause
# lsns | grep 4026532477
4026532477 pid 2 14611 root nginx: master process nginx -g daemon off;
# lsns | grep 4026531837
4026531837 user 209 1 root /sbin/init
# lsns | grep 4026532473
4026532473 uts 3 14509 65535 /pause
ps auxfでプロセスを確認してみます。containerdからcontainerd-shimが立ち上がり、そこからpauseとnginxのプロセスが起動していることが分かります。pauseは、複数のコンテナを同じPodにまとめて、同じOS上で動いているように見せるために存在しているコンテナです。
root 2413 2.1 2.3 2215532 94044 ? Ssl Nov25 152:52 /usr/bin/containerd
root 14484 0.0 0.2 709076 10304 ? Sl 01:32 0:00 _ containerd-shim -namespace k8s.io
-workdir /var/lib/containerd/io.containerd.runtime.v1.linux/k8s.io/652192845922a51436085d74c89c7d21f64feb62568432681f845633a174eb31
-address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd
65535 14509 0.0 0.0 964 4 ? Ss 01:32 0:00 | _ /pause
root 14590 0.0 0.1 708820 7356 ? Sl 01:32 0:00 _ containerd-shim -namespace k8s.io
-workdir /var/lib/containerd/io.containerd.runtime.v1.linux/k8s.io/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d
-address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd
root 14611 0.0 0.1 32628 5188 ? Ss 01:32 0:00 _ nginx: master process nginx -g daemon off;
101 14656 0.0 0.0 33076 2344 ? S 01:32 0:00 _ nginx: worker process
nginxのコンテナに入ってみましょう。nginxのコンテナには最低限のものしか含まれていないので、psコマンドを使うこともできません。
# nsenter --target 14611 --all bash
# ps
-sh: 2: ps: not found
# cat /etc/debian_version
9.8
今度はnsenterでホスト側のnamespaceに入り、そこからnginxコンテナのrootfsの中をのぞいてみます。mountの結果を事前に確認したnginxコンテナのIDでgrepすると、overlay on 〜が表示されます。
ここがnginxコンテナのrootfsです。cdで移動して、cat ./etc/debian_versionを叩いてみると、さきほどと同じ9.8が表示されました。
# nsenter --target 1 --all bash
# mount | grep f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d
overlay on /run/containerd/io.containerd.runtime.v1.linux/k8s.io/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d/rootfs type overlay (rw,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/522/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/521/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/518/fs,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/552/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/552/work,xino=off)
# cd /run/containerd/io.containerd.runtime.v1.linux/k8s.io/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d/rootfs
# cat ./etc/debian_version
9.8
●
ネットワークを確認する
ネットワークについても確認してみます。5つのnamespaceとnamespaceにひもづくvethが作られていることが分かります。
# nsenter --target 1 --all bash
# ip netns list
cni-a78e763e-11b1-03c0-889b-ccf6e2290d92 (id: 2)
cni-2ff38e97-62c6-90c9-5f23-2525ff3c9292 (id: 5)
cni-3e8372c7-7b9c-1325-f3bc-60d4443e2811 (id: 1)
cni-27dd6491-b667-2f36-a5ab-8cb404276547 (id: 7)
cni-6f11b87d-1a2b-e95b-0c4d-8b57d4aaf822 (id: 0)
# ip link show
1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:22:48:68:f4:bd brd ff:ff:ff:ff:ff:ff
4: azveb78922e0be@if3: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 2a:c6:90:c3:66:b3 brd ff:ff:ff:ff:ff:ff link-netnsid 0
18: azv233e3487410@if17: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 66:d8:33:ab:3a:6f brd ff:ff:ff:ff:ff:ff link-netnsid 7
20: azvdeb7c0733b9@if19: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 6e:18:fd:7a:63:6d brd ff:ff:ff:ff:ff:ff link-netnsid 1
26: azv19d57a972e9@if25: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 92:de:0e:c5:07:41 brd ff:ff:ff:ff:ff:ff link-netnsid 5
30: azv6bf1c5f71ac@if29: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 5a:37:35:af:83:e6 brd ff:ff:ff:ff:ff:ff link-netnsid 2
# ip -d link show azv6bf1c5f71ac
30: azv6bf1c5f71ac@if29: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 5a:37:35:af:83:e6 brd ff:ff:ff:ff:ff:ff link-netnsid 2 promiscuity 0
veth addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
# ip route
default via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.115 metric 100
10.240.0.0/16 dev eth0 proto kernel scope link src 10.240.0.115
10.240.0.120 dev azveb78922e0be proto static
10.240.0.122 dev azv19d57a972e9 proto static
10.240.0.185 dev azvdeb7c0733b9 proto static
10.240.0.192 dev azv233e3487410 proto static
10.240.0.200 dev azv6bf1c5f71ac proto static
168.63.129.16 via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.115 metric 100
169.254.169.254 via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.115 metric 100
nginxのPIDに割り当てられているnamespaceを確認してみます。
ip netns identify 14611
cni-a78e763e-11b1-03c0-889b-ccf6e2290d92
今度は、cni-a78e763e-11b1-03c0-889b-ccf6e2290d92にひもづくPIDを確認してみます。
ip netns pids cni-a78e763e-11b1-03c0-889b-ccf6e2290d92
14509 ★pauseコンテナ
14611 ★nginxのMaster
14656 ★nginxのWorker
cni-a78e763e-11b1-03c0-889b-ccf6e2290d92の中でip addressを実行すると、10.240.0.200/16のeth0@if30がでてきます。これはnginxのPodに割り当てられているIPアドレスと同じものになっているのが分かります。
ip netns exec cni-a78e763e-11b1-03c0-889b-ccf6e2290d92 ip address
1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
validlft forever preferredlft forever
inet6 ::1/128 scope host
validlft forever preferredlft forever
29: eth0@if30: mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 86:0d:b8:e5:96:1e brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.240.0.200/16 scope global eth0
validlft forever preferredlft forever
inet6 fe80::840d:b8ff:fee5:961e/64 scope link
validlft forever preferredlft forever
●
Kubeneresとcgroups
Kubernetesでコンテナのリソースの上限を指定するのに使われるlimitはcgroupを使って実現されています。
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
resources:
limits:
cpu: 100m
memory: 125Mi
requests:
cpu: 90m
memory: 115Mi
nginxのmasterプロセスのPIDは14611でした。このプロセスのcgroupがどこに定義されているかは、/proc/プロセスID/cgroupを見れば分かります。
cat /proc/14611/cgroup
7:memory:/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d
3:cpu,cpuacct:/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d
CPU
まずは、CPUから確認していきましょう。cgroupのtasksをみると、nginxのmasterプロセスの14611とworkerプロセスの14656が対象になっています。
cat /hostroot/sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d/tasks
14611
14656
limitのcpu: 100mは以下の2つの値で表現されています。
cpu.cfs_quota_us 10000
cpu.cfs_period_us 100000
cat /hostroot/sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d/cpu.cfs_quota_us
10000
cat /hostroot/sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597
ca7003c66bbb437d/cpu.cfs_period_us
100000
メモリ
次に、メモリを確認します。memory.limit_in_bytesを見てみると131072000と出てきますね。計算すると分かりますが、これは125MBのことです。想定どおり、limitの値が反映されていることが分かります。
cat /hostroot/sys/fs/cgroup/memory/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca700
3c66bbb437d/memory.limit_in_bytes
131072000
KubeneresとCapability
確認したように、User namespaceは分離されていません。したがって、ホストのrootはコンテナのrootと違うものではありません。その状態でコンテナの権限を制限しているのはCapabilityです。nginxのプロセスのCapabilityを確認してみましょう。getcapsコマンドを使います。
nginxのプロセスには、cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcapだけを許可されていることが分かります。
$ nsenter --target 1 --all
$ getpcaps 1
Capabilities for 1': = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read+ep
$ getpcaps 14611
Capabilities for14611': = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip
許可されているものの一覧は、以下になります。説明はMan page of CAPABILITIESからの引用です。
許可されているものの一覧は、以下になります。説明はMan page of CAPABILITIESからの引用です。
今回は、コンテナセキュリティを学ぶ上での基礎となる技術について説明しました。次回は、Kubernetes クラスタの保護について説明します。
○野村総合研究所(NRI) 湯川勇太(ゆかわ ゆうた)
前職で大手家電量販店のECモールの立ち上げとECサイトの刷新プロジェクトを経験。NRI入社後は製造業の商品検索システムを担当した。現在は大手物流企業向けの基幹システムの方式設計や技術検証、トラブル対応を行っている。