LoginSignup
12
11

More than 3 years have passed since last update.

Docker の構成要素である cgroup と namespace について確認した時のメモ

Last updated at Posted at 2019-11-19

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 のページに書いてあった。

第1章 コントロールグループの概要 (cgroup)

本書で 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 って何?

原理原則で理解するDocker

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 Bridge Network Driver

$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 で接続出来そうであり、やってみる。

Network: container

$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 も一つのみという状況のように見受けられる。

12
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
11