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


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 で接続出来る

# 検証環境の メモ

```bash
 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)](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/resource_management_guide/chap-introduction_to_control_groups)


>本書で cgroup と略称される コントロールグループ は、システム上で実行されるプロセスの階層的に順序付けられたグループ間で、CPU 時間、システムメモリー、ネットワーク帯域幅またはこれらのリソースの組み合わせなどのリソースの割り当てを可能にする Linux カーネル機能です。cgroup を使用することにより、システム管理者は、システムリソースの割り当て、優先順位付け、拒否、管理および監視におけるより詳細なレベルの制御を行うことができます。ハードウェアリソースはアプリケーションおよびユーザー間でスマートに分割することができ、これにより全体の効率が向上します。 


Docker でもリソースの割り当て(CPU・メモリなど)ができ、上記は内部的には cgroup を使っているという理解。

## 検証してみる

`--cpu-shares` オプションの違いによる cgroups の違いを見てみる

まずは 100 を指定してみる。

```bash
$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 を指定してみる。

```bash
$docker run -d --cpu-shares 200 alpine sleep 100000

$cat /sys/fs/cgroup/cpu/docker/d1c4aa7a9c8d599d0c8df3d6d342823b98b2499933b3078b30e5cde104021dbc/cpu.shares
200
```

変わっている。

恐らく同じようにリソース制限をした場合も同様に変更されるだろう。

# namespace

## namespace って何？

[原理原則で理解するDocker](https://qiita.com/tajima_taso/items/28938415846dcc2e83ff)

namespace とはリソースを分離する為の機能であり、いくつかの分離が可能。

+ mount namespace
+ UTS namespace
+ IPC namespace
+ PID namespace
+ network namespace
+ user namespace

コンテナ内で `ps`　コマンドを実行するとホストで動いているプロセスの情報が見えないというのは「PID namespace が別だから」という理解。

## 検証してみる

まずは何も指定せず、実行。

```bash
$docker run -it --rm alpine ps
PID   USER     TIME  COMMAND
    1 root      0:00 ps
```

この場合、コンテナ内で `ps` コマンドを実行しても自身のプロセスしか見えない。

次に既に起動している nginx コンテナと PID namespace を共有するように起動してみる。

```bash
$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` 配下の情報も確認して見る。

```bash
# 最初に 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 の共有が影響している？

```bash
$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機能は何かをリンクしているわけではない](http://dqn.sakusakutto.jp/2017/03/docker-run-link.html)

上記をやりつつ、それぞれのコンテナなの net namespace を見たが、たしかに別。

```bash
$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](https://success.docker.com/article/networking#dockerbridgenetworkdriver)

```bash
$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
```

次に実験。

```bash
$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などを使うワンライナー](https://qiita.com/minamijoyo/items/9dd59109e9fe1a35f888)

Network namespace を共有すると例えば上記のように `tcpdump` を出来るということは有用に見える。
また、以下を見ると同じ Network namespace だと localhost で接続出来そうであり、やってみる。

[Network: container](https://docs.docker.com/engine/reference/run/#network-settings)

```bash
$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 も見てみる。

```bash
$ 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 アドレスなどはどうなっている？

```bash
# 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 アドレスが割り当たっていない。

```bash
docker exec -it 97a56695fb60 /bin/sh
# hostname -i
172.17.0.2
```

これを見る限りコンテナ側で作られる eth0 をこの2つのコンテナで共有しているように見える。
イメージとしてはコンテナを使わない場合でホスト上に nginx と tomcat を起動した場合、localhost:8080 で nginx から tomcat へ接続出来るが、それに近い気がする。（これは host の　network namspece を共有している）

一応、veth の動きも見てみる。

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