Docker の bridge と host ネットワークについて勉強する

Docker の bridge と host ネットワークについて勉強する

勉強した時のメモ。
もし違っている部分などあればご指摘頂けますと幸いです。
なお、bridge で docker0 ではなく、ユーザーが定義した bridge も使えるようですが、こちらについては調査していません

# 参考

+ [Docker Reference Architecture: Designing Scalable, Portable Docker Container Networks](http://success.docker.com/article/networking)
+ [Network overview](https://docs.docker.com/network/)
+ [Use bridge networks](https://docs.docker.com/network/bridge/)
+ [Dockerのネットワークの基礎](https://deeeet.com/writing/2014/05/11/docker-network/)

# まとめ

+ bridge・host いずれもインターネット経由でコンテナへのアクセスが可能
+ bridge はホストの任意のポートをコンテナのポートにマップすることが出来る
+ host はコンテナで expose されたポートをホストでも利用する。その為、一つのホストで同じポートを使うコンテナは利用できない

# 環境

ECS をよく使うので Amazon ECS-Optimized AMI(ami-f3f8098c) を使って確認。

```bash
$Containers: 1
 Running: 1
 Paused: 0
 Stopped: 0
Images: 2
Server Version: 17.12.1-ce
Storage Driver: devicemapper
 Pool Name: docker-docker--pool
 Pool Blocksize: 524.3kB
 Base Device Size: 10.74GB
 Backing Filesystem: ext4
 Udev Sync Supported: true
 Data Space Used: 366.5MB
 Data Space Total: 23.33GB
 Data Space Available: 22.96GB
 Metadata Space Used: 176.1kB
 Metadata Space Total: 25.17MB
 Metadata Space Available: 24.99MB
 Thin Pool Minimum Free Space: 2.333GB
 Deferred Removal Enabled: true
 Deferred Deletion Enabled: true
 Deferred Deleted Device Count: 0
 Library Version: 1.02.135-RHEL7 (2016-11-16)
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: 9b55aab90508bd389d7654c4baf173a981477d55
runc version: 9f9c96235cc97674e935002fc3d78361b696a69e
init version: 949e6fa
Security Options:
 seccomp
  Profile: default
Kernel Version: 4.14.33-51.37.amzn1.x86_64
Operating System: Amazon Linux AMI 2018.03
OSType: linux
Architecture: x86_64
CPUs: 1
Total Memory: 987.5MiB
Name: ip-172-31-20-151
ID: YBHO:HF57:Q7OR:UMAL:EZX7:GU7X:TMXR:C2A5:FZU7:B5MX:W6SR:OQ26
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
```

# bridge ネットワーク

## 概要

以下より図を拝借。

[Docker Reference Architecture: Designing Scalable, Portable Docker Container Networks](http://success.docker.com/article/networking)

![bridge-driver.png](https://qiita-image-store.s3.amazonaws.com/0/20193/f64cc08e-cb44-1f67-a3a5-775732f9eb47.png "bridge-driver.png")

+ `bridge` はデフォルトのネットワークドライバ。`docker run`で`--net`オプションを指定しない場合、bridge が利用される
+ bridge はデフォルトでは docker0という仮想ブリッジを通じて通信を行う
+ コンテナ内部と仮想ブリッジ(docker0)は Virtual Ethernet Tunnel(veth)によって接続されている
+ コンテナ内ではeth0を通じてネットワーク通信が出来る

以下より実際に操作しながら動きを見てみる。

## bridge ネットワークのコンテナを起動してネットワークを確認する

まずはホスト側の状況を確認。

```bash
# host 側のネットワークインターフェースとして docker 0 が確認できる。
$ifconfig docker0
docker0   Link encap:Ethernet  HWaddr 02:42:C3:DD:AF:A1
          inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)


# brctl コマンドをインストール
$sudo yum install bridge-utils

# docker0 という仮想ブリッジが確認できる	
$ brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242c3ddafa1       no
```

OK.
docker0 は 172.0.1/16 となっている。

この状態で bridge のコンテナを起動してコンテナの IPアドレスを確認してみる。
[docker run](https://docs.docker.com/engine/reference/run/#network-settings)のドキュメントにもあるように何も指定しない場合、デフォルトの bridge を利用する。

```bash
# busybox イメージのコンテナを起動。echo をずっと出力させてコンテナが終了しないようにする
$docker run -d --name c1 busybox /bin/sh -c "while true; do echo hello world; sleep 1; done"
910e1fd20f69d0b01169af02e47f54c1b751cd065db5c1a8ffa76cb9b8c25a0e

# コンテナ内で /bin/sh を実行
$docker exec -it c1 /bin/sh

#コンテナ内で ip アドレスを見る 
$ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
       valid_lft forever preferred_lft forever
12: eth0@if13: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

# ルートテーブルのデフォルトゲートウェイはdocker0 の 172.17.0.1
$ip route
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 scope link  src 172.17.0.2

#ping で外に届く
$ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=46 time=4.349 ms
64 bytes from 8.8.8.8: seq=1 ttl=46 time=4.298 ms
^C
--- 8.8.8.8 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 4.298/4.323/4.349 ms

# traceroute を確認。172.17.0.1 にまずは接続している。実際に接続も出来る
$traceroute www.google.co.jp
traceroute to www.google.co.jp (172.217.161.67), 30 hops max, 46 byte packets
 1  ip-172-17-0-1.ap-northeast-1.compute.internal (172.17.0.1)  0.005 ms  0.004 ms  0.002 ms
 2  ec2-175-41-192-146.ap-northeast-1.compute.amazonaws.com (175.41.192.146)  14.286 ms  21.584 ms  ec2-175-41-192-152.ap-northeast-1.compute.amazonaws.com (175.41.192.152)  20.200 ms
 
(省略)
17  108.170.242.193 (108.170.242.193)  5.247 ms  nrt20s09-in-f3.1e100.net (172.217.161.67)  5.338 ms  108.170.242.193 (108.170.242.193)  5.179 ms     

                        
# sh を終了してホストの操作をする
$exit       

# docker0 ブリッジに veth012d945 というインターフェースが追加されている。
$brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242c3ddafa1       no              veth012d945

# ifconfig で veth を確認
$ifconfig veth012d945
veth012d945 Link encap:Ethernet  HWaddr EA:66:DE:92:63:6C
          inet6 addr: fe80::e866:deff:fe92:636c/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:15 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 b)  TX bytes:1186 (1.1 KiB)



$ NetworkMode は default
$docker inspect c1 --format '{{ .HostConfig.NetworkMode }}'
default

# docker inspect でネットワークの設定が諸々見える
$docker inspect --format '{{json .NetworkSettings }}' c1 | jq .
{
  "Bridge": "",
  "SandboxID": "1f2e8d7244945d72f268c63f88172899807d0dce91aaa9cbbbb60f330079250d",
  "HairpinMode": false,
  "LinkLocalIPv6Address": "",
  "LinkLocalIPv6PrefixLen": 0,
  "Ports": {},
  "SandboxKey": "/var/run/docker/netns/1f2e8d724494",
  "SecondaryIPAddresses": null,
  "SecondaryIPv6Addresses": null,
  "EndpointID": "d49d1d764911932df51249ca9af7a6e51f6863ab4fb30bc05dd1536c470b6d7e",
  "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": "e35b6ce31179971ba74b870093a61596a8b031cffe02452c1b5bde6f45bf49d7",
      "EndpointID": "d49d1d764911932df51249ca9af7a6e51f6863ab4fb30bc05dd1536c470b6d7e",
      "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
    }
  }
}
```

分かったこと

+ bridge でコンテナを作成すると veth が作成される
+ コンテナで内では docker0 の IP(172.0.1/16)で IPアドレスが割り振られる
+ コンテナでは docker0 の IPアドレスがデフォルトの Gateway として設定され、こちらを通じてホスト経由でインターネットへの接続ができる

## bridge コンテナに外部からアクセスする

外部からコンテナにアクセスする場合、`-p`もしくは`--publish`オプションを利用してコンテナのポートをホストに publish する。

[Publish or expose port (-p, --expose)](https://docs.docker.com/engine/reference/commandline/run/#publish-or-expose-port--p---expose)

```bash
# nginx イメージを pull する
$docker pull nginx

# nginx イメージでは 80ポートを expose している
$docker image inspect nginx --format '{{ .Config.ExposedPorts }}'
map[80/tcp:{}]

# ホストの5000ポートをコンテナの80番ポートにマップする
$docker run -d --name C2 -p 5000:80 nginx

# PORTS 列でもポートのマッピングが分かる
$docker ps -f "name=C2"
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
28af5b8a506f        nginx               "nginx -g 'daemon of…"   2 minutes ago       Up 2 minutes        0.0.0.0:5000->80/tcp   C2

# ホストの5000ポートへのアクセスでコンテナの80番ポートにアクセスする
$curl http://localhost:5000
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```

上記についても図があると分かりやすいのでこちらも拝借。

[Docker Reference Architecture: Designing Scalable, Portable Docker Container Networks](http://success.docker.com/article/networking)

![nat.png](https://qiita-image-store.s3.amazonaws.com/0/20193/7f00e253-e0d9-dbeb-2625-5a126dab0295.png "nat.png")


次に同じホストからではなく、インターネット経由でコンテナにアクセスしみる。

```bash
# 停止
$docker stop C2

# 今度はホストの80番をコンテナの80番にマップする
$docker run -d --name C3 -p 80:80 nginx

# この状態で外部から対象の PublicIP にアクセスするとコンテナにアクセスできる。
$curl http://xxxx/
<!DOCTYPE html>
<html>
<head>

# 停止しておく
$docker stop C3
```

OK.

# Host ネットワーク

## 概要

こちらも概要図があると分かりやすい。
Host ではホスト側のネットワーク・インターフェースを利用する。

![host-driver.png](https://qiita-image-store.s3.amazonaws.com/0/20193/66c2093a-e350-a3e3-96e3-22a0d6eedb8b.png "host-driver.png")

## 試す

先程動作させた nginx コンテナを bridge ではなく、host で動かす。
`docker run` で `--net=host` とすることでコンテナは bridge ドライバではなく、ホストのネットワークを利用するように起動できる。

```bash
# host ネットワークでコンテナを起動
$docker run -d --name host --net host nginx

# PORTS 列が host となっている
$docker ps -f "name=host"
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
0d22b4718e80        nginx               "nginx -g 'daemon of…"   48 seconds ago      Up 47 seconds                           host

# アクセス可能
$curl http://localhost/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>

# NetworkMode が host になっている。
$docker inspect host --format '{{ .HostConfig.NetworkMode }}'
host

# 同じようにインターネット経由でもアクセス可能
$curl http://xxxxx/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
```

OK.
ただし、こちらは docker run からも分かるようにポートのマッピングはしていない。
上記より、例えば **80番ポートのコンテナを一つのホストで動かしたい** という事はできない。