Docker の構成要素である cgroup と namespace について確認した時のメモ。
まとめ
- cgroup はリソースの割り当て(CPU・メモリ)などを行う。例えば
--cpu-shares
オプションを指定すると cgroup の cpu.shares ファイルによって制限している - namespace はリソースの分離を行う。例えばデフォルトではコンテナは自身のプロセス情報かしか見えない。しかし
--pid
オプションを指定して他のコンテナと PID namespace を共有することで別のコンテナのプロセス情報が見える。 - プロセス(今回の場合、コンテナ)の namespace の情報は
/proc
配下より確認出来る - Docker という意味では Bridge の場合、同じ Bridge Network 内であれば network namespace は別でも問題なく通信できることは確認出来た
- Netwrok namespace を共有するとコンテナで側から見える eth0 を共有しているように見える。Network namespace を共有している場合に各コンテナは localhost で接続出来る
検証環境の メモ
docker info
Containers: 16
Running: 11
Paused: 0
Stopped: 5
Images: 8
Server Version: 18.06.1-ce
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge host macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 468a545b9edcd5932818eb9de8e72413e616e86e
runc version: 69663f0bd4b60df09991c08812a60108003fa340
init version: fec3683
Security Options:
seccomp
Profile: default
Kernel Version: 4.14.77-80.57.amzn2.x86_64
Operating System: Amazon Linux 2
OSType: linux
Architecture: x86_64
CPUs: 1
Total Memory: 985.7MiB
Name: ip-172-31-31-21.ap-northeast-1.compute.internal
ID: CPNA:MY2O:PCPB:AVWO:3EUM:B5A6:CAEY:XZUE:Y63E:XLWV:SNQJ:IMNU
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
127.0.0.0/8
Live Restore Enabled: false
cgroup
cgroup って何?
そもそも cgroup って何というのは以下の Redhat のページに書いてあった。
本書で cgroup と略称される コントロールグループ は、システム上で実行されるプロセスの階層的に順序付けられたグループ間で、CPU 時間、システムメモリー、ネットワーク帯域幅またはこれらのリソースの組み合わせなどのリソースの割り当てを可能にする Linux カーネル機能です。cgroup を使用することにより、システム管理者は、システムリソースの割り当て、優先順位付け、拒否、管理および監視におけるより詳細なレベルの制御を行うことができます。ハードウェアリソースはアプリケーションおよびユーザー間でスマートに分割することができ、これにより全体の効率が向上します。
Docker でもリソースの割り当て(CPU・メモリなど)ができ、上記は内部的には cgroup を使っているという理解。
検証してみる
--cpu-shares
オプションの違いによる cgroups の違いを見てみる
まずは 100 を指定してみる。
$docker run -d --cpu-shares 100 alpine sleep 100000
98301369d6cd3b79ce6f2968c95a5e3f9db46e5ab5eb4ed2b970d8d62b4700ce
$cat /sys/fs/cgroup/cpu/docker/98301369d6cd3b79ce6f2968c95a5e3f9db46e5ab5eb4ed2b970d8d62b4700ce/cpu.shares
100
該当 cgroup の cpu.shares が 100 になっている。
次に 200 を指定してみる。
$docker run -d --cpu-shares 200 alpine sleep 100000
$cat /sys/fs/cgroup/cpu/docker/d1c4aa7a9c8d599d0c8df3d6d342823b98b2499933b3078b30e5cde104021dbc/cpu.shares
200
変わっている。
恐らく同じようにリソース制限をした場合も同様に変更されるだろう。
namespace
namespace って何?
namespace とはリソースを分離する為の機能であり、いくつかの分離が可能。
- mount namespace
- UTS namespace
- IPC namespace
- PID namespace
- network namespace
- user namespace
コンテナ内で ps
コマンドを実行するとホストで動いているプロセスの情報が見えないというのは「PID namespace が別だから」という理解。
検証してみる
まずは何も指定せず、実行。
$docker run -it --rm alpine ps
PID USER TIME COMMAND
1 root 0:00 ps
この場合、コンテナ内で ps
コマンドを実行しても自身のプロセスしか見えない。
次に既に起動している nginx コンテナと PID namespace を共有するように起動してみる。
$docker run -it --rm --pid container:5651ac977c40 alpine ps
PID USER TIME COMMAND
1 root 0:00 nginx: master process nginx -g daemon off;
6 101 0:00 nginx: worker process
27 root 0:00 /bin/sh
35 root 0:00 ps
この場合、PID namespace を共有しているので nginx のコンテナで動いているプロセス情報も確認出来る。
/proc
配下の情報も確認して見る。
# 最初に nginx コンテナの namespace を確認
$docker inspect d21f51a92cc10d9d12b8917d11fb636e11494aaa34aac2a941b3ef0242e51f86 --format '{{.State.Pid}}'
10970
$sudo ls -l /proc/10970/ns/
total 0
lrwxrwxrwx 1 root root 0 Nov 19 01:43 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Nov 19 01:43 ipc -> ipc:[4026532691]
lrwxrwxrwx 1 root root 0 Nov 19 01:43 mnt -> mnt:[4026532689]
lrwxrwxrwx 1 root root 0 Nov 19 01:42 net -> net:[4026532693]
lrwxrwxrwx 1 root root 0 Nov 19 01:43 pid -> pid:[4026532800]
lrwxrwxrwx 1 root root 0 Nov 19 01:43 pid_for_children -> pid:[4026532800]
lrwxrwxrwx 1 root root 0 Nov 19 01:43 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Nov 19 01:43 uts -> uts:[4026532690]
# 次に alphine コンテナで確認
$docker run -d --pid container:5651ac977c40 alpine sleep 1000
d21f51a92cc10d9d12b8917d11fb636e11494aaa34aac2a941b3ef0242e51f86
$docker inspect d21f51a92cc10d9d12b8917d11fb636e11494aaa34aac2a941b3ef0242e51f86 --format '{{.State.Pid}}'
10970
# pid が 4026532800 で nginx コンテナと同じ
$sudo ls -l /proc/10970/ns
total 0
lrwxrwxrwx 1 root root 0 Nov 19 01:43 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Nov 19 01:43 ipc -> ipc:[4026532691]
lrwxrwxrwx 1 root root 0 Nov 19 01:43 mnt -> mnt:[4026532689]
lrwxrwxrwx 1 root root 0 Nov 19 01:42 net -> net:[4026532693]
lrwxrwxrwx 1 root root 0 Nov 19 01:43 pid -> pid:[4026532800]
lrwxrwxrwx 1 root root 0 Nov 19 01:43 pid_for_children -> pid:[4026532800]
lrwxrwxrwx 1 root root 0 Nov 19 01:43 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Nov 19 01:43 uts -> uts:[4026532690]
上記に書いたとおり、pid は 4026532800 で同じである。
network namespae について
上記結果を見ると net についても共有していることになっている。
これは PID namespace の共有が影響している?
$docker run -d alpine sleep 1000
39fdc52fef8f7b73e85eca417dfe572dd688f4783e52a51ffb86ba5d720e1959
$docker inspect 39fdc52fef8f7b73e85eca417dfe572dd688f4783e52a51ffb86ba5d720e1959 --format '{{.State.Pid}}'
11091
$sudo ls -l /proc/11091/ns
total 0
lrwxrwxrwx 1 root root 0 Nov 19 01:50 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Nov 19 01:50 ipc -> ipc:[4026532987]
lrwxrwxrwx 1 root root 0 Nov 19 01:50 mnt -> mnt:[4026532985]
lrwxrwxrwx 1 root root 0 Nov 19 01:49 net -> net:[4026532990]
lrwxrwxrwx 1 root root 0 Nov 19 01:50 pid -> pid:[4026532988]
lrwxrwxrwx 1 root root 0 Nov 19 01:50 pid_for_children -> pid:[4026532988]
lrwxrwxrwx 1 root root 0 Nov 19 01:50 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Nov 19 01:50 uts -> uts:[4026532986]
net の ID が別になった。
ただ、だからといって通信できないということではない認識。
以下の対応をやってみたが通信は可能。
dockerのlink機能は何かをリンクしているわけではない
上記をやりつつ、それぞれのコンテナなの net namespace を見たが、たしかに別。
$docker inspect 92d9e26e17c7 --format '{{.State.Pid}}'
11733
$sudo ls -l /proc/11733/ns
total 0
lrwxrwxrwx 1 root root 0 Nov 19 02:00 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Nov 19 02:00 ipc -> ipc:[4026533113]
lrwxrwxrwx 1 root root 0 Nov 19 02:00 mnt -> mnt:[4026533111]
lrwxrwxrwx 1 root root 0 Nov 19 01:58 net -> net:[4026533116]
lrwxrwxrwx 1 root root 0 Nov 19 02:00 pid -> pid:[4026533114]
lrwxrwxrwx 1 root root 0 Nov 19 02:00 pid_for_children -> pid:[4026533114]
lrwxrwxrwx 1 root root 0 Nov 19 02:00 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Nov 19 02:00 uts -> uts:[4026533112]
$docker inspect 6ff7746fffee --format '{{.State.Pid}}'
11580
$sudo ls -l /proc/11580/ns
total 0
lrwxrwxrwx 1 root root 0 Nov 19 02:01 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Nov 19 02:01 ipc -> ipc:[4026533050]
lrwxrwxrwx 1 root root 0 Nov 19 02:01 mnt -> mnt:[4026533048]
lrwxrwxrwx 1 root root 0 Nov 19 01:57 net -> net:[4026533053]
lrwxrwxrwx 1 root root 0 Nov 19 02:01 pid -> pid:[4026533051]
lrwxrwxrwx 1 root root 0 Nov 19 02:01 pid_for_children -> pid:[4026533051]
lrwxrwxrwx 1 root root 0 Nov 19 02:01 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Nov 19 02:01 uts -> uts:[4026533049]
[ec2-user@ip-172-31-31-21 ~]$
namespace とは別観点でいうといずれもネットワークは何も指定していないのでデフォルトの docker0 を使っているはず。
brctl show
より確認出来た。
試しに作ってやってみる
$docker network create -d bridge --subnet 10.0.0.0/24 my_bridge
$docker run --rm -itd --name c2 --net my_bridge busybox sh
$docker run --rm -itd --name c3 --net my_bridge --ip 10.0.0.254 busybox sh
次に実験。
$docker ps |grep busybox
860dfb70e2df busybox "sh" About a minute ago Up About a minute c3
d67bbf152ecd busybox "sh" 2 minutes ago Up 2 minutes c2
$docker exec -it 860dfb70e2df sh
$hostname -i
10.0.0.254
# 先程 bridge0 からの通信
$docker exec -it 860dfb70e2df sh
$hostname -i
10.0.0.254
$ping 172.17.0.15
PING 172.17.0.15 (172.17.0.15): 56 data bytes
$ip route
default via 10.0.0.1 dev eth0
10.0.0.0/24 dev eth0 scope link src 10.0.0.254
到達できない。
そもそも、IP アドレス帯も異なる。
こねくり回せば出来るのかもしれないが、基本的には同じホストのコンテナ同士で通信する場合、同じ Bridge ネットワークを使うのが良さそう。
じゃあ結局「network namespace を分ける」とは何なのかまだ理解出来ず。。。時間ある時に再度確認してみたい。
(追記)Network namespace の共有について
Dockerでデバッグ対象のコンテナにツールを入れずにtcpdump/straceなどを使うワンライナー
Network namespace を共有すると例えば上記のように tcpdump
を出来るということは有用に見える。
また、以下を見ると同じ Network namespace だと localhost で接続出来そうであり、やってみる。
$docker run -d --name redis redis --bind 127.0.0.1
97a56695fb60c63b6cf2034f79e54793849cab87bd4fc1824a6e464a226ab52d
$docker run --rm -it --network container:redis --rm redis redis-cli -h 127.0.0.1
127.0.0.1:6379> SET hoge bar
OK
localhost
で接続できた。
Network namespace も見てみる。
$ docker ps |grep redis
cc3d31d37474 redis "docker-entrypoint.s…" About a minute ago Up About a minute priceless_brahmagupta
97a56695fb60 redis "docker-entrypoint.s…" 5 minutes ago Up 5 minutes 6379/tcp redis
# redis を動かしているコンテナ
$docker inspect 97a56695fb60 --format '{{.State.Pid}}'
17112
$sudo ls -l /proc/17112/ns
total 0
lrwxrwxrwx 1 chrony input 0 Nov 19 07:51 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 chrony input 0 Nov 19 07:51 ipc -> ipc:[4026532213]
lrwxrwxrwx 1 chrony input 0 Nov 19 07:51 mnt -> mnt:[4026532211]
lrwxrwxrwx 1 chrony input 0 Nov 19 07:43 net -> net:[4026532216]
lrwxrwxrwx 1 chrony input 0 Nov 19 07:51 pid -> pid:[4026532214]
lrwxrwxrwx 1 chrony input 0 Nov 19 07:51 pid_for_children -> pid:[4026532214]
lrwxrwxrwx 1 chrony input 0 Nov 19 07:51 user -> user:[4026531837]
lrwxrwxrwx 1 chrony input 0 Nov 19 07:51 uts -> uts:[4026532212]
# redis-cli を動かしているコンテナ
$docker inspect cc3d31d37474 --format '{{.State.Pid}}'
17854
$sudo ls -l /proc/17854/ns
total 0
lrwxrwxrwx 1 root root 0 Nov 19 07:49 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Nov 19 07:49 ipc -> ipc:[4026532342]
lrwxrwxrwx 1 root root 0 Nov 19 07:49 mnt -> mnt:[4026532340]
lrwxrwxrwx 1 root root 0 Nov 19 07:49 net -> net:[4026532216]
lrwxrwxrwx 1 root root 0 Nov 19 07:49 pid -> pid:[4026532343]
lrwxrwxrwx 1 root root 0 Nov 19 07:49 pid_for_children -> pid:[4026532343]
lrwxrwxrwx 1 root root 0 Nov 19 07:49 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Nov 19 07:49 uts -> uts:[4026532341]
Network namspace が同じ。
IP アドレスなどはどうなっている?
# redfis を起動しているコンテナから抜粋
$docker inspect 97a56695fb60 |less
"NetworkMode": "default",
・・・
"NetworkSettings": {
"Bridge": "",
"SandboxID": "95fe416e58b4ea533438de791abe4f6834b3341173a49196b0e69f3c8869d8ae",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {
"6379/tcp": null
},
"SandboxKey": "/var/run/docker/netns/95fe416e58b4",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "4153c79fa3f943ca823fb0d7d34b725c871f0db000182b76a9ae6944cbdcb494",
"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:02",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "3af74f27193cf16c7025dddd9d8bd48ed53b2accaa09fd26acb11e977dade732",
"EndpointID": "4153c79fa3f943ca823fb0d7d34b725c871f0db000182b76a9ae6944cbdcb494",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
}
}
}
}
# redis-cli を実行しているコンテナから抜粋
$docker inspect cc3d31d3747 |less
"NetworkMode": "container:97a56695fb60c63b6cf2034f79e54793849cab87bd
4fc1824a6e464a226ab52d",
・・・
"NetworkSettings": {
"Bridge": "",
"SandboxID": "",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {},
"SandboxKey": "",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "",
"Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"MacAddress": "",
"Networks": {}
}
後者はそもそも IP アドレスが割り当たっていない。
docker exec -it 97a56695fb60 /bin/sh
# hostname -i
172.17.0.2
これを見る限りコンテナ側で作られる eth0 をこの2つのコンテナで共有しているように見える。
イメージとしてはコンテナを使わない場合でホスト上に nginx と tomcat を起動した場合、localhost:8080 で nginx から tomcat へ接続出来るが、それに近い気がする。(これは host の network namspece を共有している)
一応、veth の動きも見てみる。
# 最初の状態
$brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242b34c9248 no
ecs-bridge 8000.000000000000 no
# bridge のコンテナを起動
$docker run -d --name redis redis --bind 127.0.0.1
# docker0 に紐づく veth が一つ追加される
$brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242b34c9248 no vethbcd35df
ecs-bridge 8000.000000000000 no
# redis-cli を起動
$docker run --rm -it --network container:redis --rm redis redis-cli -h 127.0.0.1
# 別ターミナルで確認。veth は増えてない
$brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242b34c9248 no vethbcd35df
ecs-bridge 8000.000000000000 no
上記から同じ Network namspace を共有するとコンテナから見える eth0 が共通で利用され、eth0 と紐づく veth も一つのみという状況のように見受けられる。