labunix's blog

labunixのラボUnix

debian stretch上のdockerでbridgeネットワークを使ってみる。

■debian stretch上のdockerでbridgeネットワークを使ってみる。
 ユーザ定義NWを作成したときの要件の確認方法にもなるはず。

 debian stretchにdockerを導入する。
 http://labunix.hateblo.jp/entry/20171230/1514570030

$ lsb_release -a
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 9.3 (stretch)
Release:	9.3
Codename:	stretch

$ uname -a
Linux vmx-rdebian 4.9.0-4-amd64 #1 SMP Debian 4.9.65-3+deb9u1 (2017-12-23) x86_64 GNU/Linux

$ sudo docker version
Client:
 Version:      17.05.0-ce
 API version:  1.29
 Go version:   go1.7.5
 Git commit:   89658be
 Built:        Thu May  4 22:09:06 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.05.0-ce
 API version:  1.29 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   89658be
 Built:        Thu May  4 22:09:06 2017
 OS/Arch:      linux/amd64
 Experimental: false

■ホストのネットワークを確認

$ sudo brctl show
bridge name	bridge id		STP enabled	interfaces
br0		8000.b888e30e05ab	no		eth0
							tap0
docker0		8000.02427f0e6490	no		veth6994522

$ ip a | awk '/^[0-9]/&&/docker|veth/{gsub(":|@.*","",$2);print "ip a show dev "$2}' | sh
5: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:7f:0e:64:90 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:7fff:fe0e:6490/64 scope link 
       valid_lft forever preferred_lft forever
19: veth6994522@if18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 0a:62:de:b4:cd:0c brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::862:deff:feb4:cd0c/64 scope link 
       valid_lft forever preferred_lft forever

■コンテナネットワークの設定を確認

 Docker コンテナ・ネットワークの理解
 http://docs.docker.jp/engine/userguide/networking/dockernetworks.html

$ sudo docker network ls
[sudo] labunix のパスワード:
NETWORK ID          NAME                DRIVER              SCOPE
f680c6aff70a        bridge              bridge              local
8adb60e19794        host                host                local
8c8319c9eba2        none                null                local

■bridge、host、noneの三種類のコンテナネットワークがある。

$ sudo docker network ls | awk '/[a-z]/{print "docker network inspect "$2}' | sudo sh
[
    {
        "Name": "bridge",
        "Id": "f680c6aff70a3f21e186de1b7acdd78fdd97412cea74f0ff5b29774cbcb371ec",
        "Created": "2017-12-30T02:02:27.2509216+09:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "Containers": {},
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]
[
    {
        "Name": "host",
        "Id": "8adb60e197949aa0bf906f49d89a28b553a001ccbcbc383445021e8219435636",
        "Created": "2017-12-30T01:22:14.977704593+09:00",
        "Scope": "local",
        "Driver": "host",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": []
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]
[
    {
        "Name": "none",
        "Id": "8c8319c9eba242568c52267309b721d01a858bcdc3105281ecd81dc179791bae",
        "Created": "2017-12-30T01:22:14.772088895+09:00",
        "Scope": "local",
        "Driver": "null",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": []
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

■上記を元にNW構成を描くと以下のようになる。
 「docker0 <--> br0」間はdockerネットワークで自動生成されるホストのIPマスカード/DNAT機能を使用する。

$ echo "[container1(eth0)],[container2(eth0)] -- [veth...1],[veth...2] -- [docker0] -- [br0]" | \
    graph-easy --dot | grep -v "1.*2\|2.*1" | graph-easy 
+------------------+     +----------+     +---------+     +-----+
| container1(eth0) | --> | veth...1 | --> | docker0 | --> | br0 |
+------------------+     +----------+     +---------+     +-----+
                                            ^
                                            |
                                            |
+------------------+     +----------+       |
| container2(eth0) | --> | veth...2 | ------+
+------------------+     +----------+

■以下のように直接「br0」に接続することも出来るようだが、
 ホストのNWに干渉しては困るので、この方式は見送るものとする。

 Dockerコンテナをブリッジ接続で使う
 https://www.agilegroup.co.jp/technote/docker-network-in-bridge.html

$ echo "[container1(eth0)],[container2(eth0)] -- [veth...1],[veth...2] -- [br0]" | \
    graph-easy --dot | grep -v "1.*2\|2.*1" | graph-easy
+------------------+     +----------+     +-----+
| container1(eth0) | --> | veth...1 | --> | br0 |
+------------------+     +----------+     +-----+
                                            ^
                                            |
                                            |
+------------------+     +----------+       |
| container2(eth0) | --> | veth...2 | ------+
+------------------+     +----------+

■デフォルトでコンテナはbridgeに接続、ホストはgateway側なので、
 以下のようにpingで疎通確認ができる。

$ sudo docker network inspect bridge | awk '/Gateway|Subnet/'
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"

$ sudo docker run -ti httpd /bin/bash
# ip a 
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    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
20: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 scope global eth0
       valid_lft forever preferred_lft forever
# ip route
default via 172.17.0.1 dev eth0 
172.17.0.0/16 dev eth0  proto kernel  scope link  src 172.17.0.2
# ping -c 3 172.17.0.1 
PING 172.17.0.1 (172.17.0.1): 56 data bytes
64 bytes from 172.17.0.1: icmp_seq=0 ttl=64 time=0.156 ms
64 bytes from 172.17.0.1: icmp_seq=1 ttl=64 time=0.118 ms
64 bytes from 172.17.0.1: icmp_seq=2 ttl=64 time=0.075 ms
--- 172.17.0.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.075/0.116/0.156/0.033 ms
# exit
exit

■コンテナをポート転送で起動、動作確認、停止、削除する。

$ docker run --help | awk '/-[dpv],/'
  -d, --detach                         Run container in background and print container ID
  -p, --publish list                   Publish a container's port(s) to the host
  -v, --volume list                    Bind mount a volume

$ test -d /tmp/mypage || sudo mkdir /tmp/mypage ;echo "Hello World" | sudo tee /tmp/mypage/index.html >/dev/null

$ sudo docker run -d -p 8000:80 -v "/tmp/mypage/:/usr/local/apache2/htdocs/" httpd
69c1b74c1e81ed4a73e7be0934ca297e8d4756d6c947461e9b6d61fed41a0f12

$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                  NAMES
69c1b74c1e81        httpd               "httpd-foreground"   22 seconds ago      Up 20 seconds       0.0.0.0:8000->80/tcp   admiring_lumiere

$ w3m -no-proxy -dump http://172.17.0.2/
Hello World

$ sudo docker ps | awk '/httpd/{print "docker stop "$1}' | sudo sh
69c1b74c1e81

$ sudo docker ps;sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS                      PORTS               NAMES
69c1b74c1e81        httpd               "httpd-foreground"   17 minutes ago      Exited (0) 21 seconds ago                       admiring_lumiere

$ sudo docker ps -a | awk '/httpd/{print "docker rm "$1}' | sudo sh
69c1b74c1e81

$ sudo docker ps;sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

■dockerのNATはホスト側のiptablesに自動追加されている。
 docker0以外のインターフェイス宛で送信元がbridgeのサブネット内ならIPマスカレードする。

$ cat /proc/sys/net/ipv4/conf/all/forwarding
1

$ sudo iptables -L -t nat -v -n
[sudo] labunix のパスワード:
Chain PREROUTING (policy ACCEPT 454 packets, 113K bytes)
 pkts bytes target     prot opt in     out     source               destination         
    2   144 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 454 packets, 113K bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 32329 packets, 1960K bytes)
 pkts bytes target     prot opt in     out     source               destination         
    2   120 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 32329 packets, 1960K bytes)
 pkts bytes target     prot opt in     out     source               destination         
    1    60 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0           

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    1    84 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0 

■「-p」オプションでコンテナを起動するとDNATルールが追加される。

$ sudo docker run -d -p 8000:80 -v "/tmp/mypage/:/usr/local/apache2/htdocs/" httpd
f1dfb6c3b7d2094ce21d16ea5dc63f078b18360eef8ea3259c18ec3ac706995e

$  sudo docker ps
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                  NAMES
f1dfb6c3b7d2        httpd               "httpd-foreground"   7 seconds ago       Up 4 seconds        0.0.0.0:8000->80/tcp   wizardly_stallman

$ w3m -no-proxy -dump http://172.17.0.2/
Hello World

$ w3m -no-proxy -dump http://vm-host:8000/
Hello World

$ sudo iptables -L -t nat -v -n
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    3   204 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 11 packets, 746 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    3   180 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 13 packets, 866 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    1    60 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0           
    0     0 MASQUERADE  tcp  --  *      *       172.17.0.2           172.17.0.2           tcp dpt:80

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    1    84 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0           
    2   120 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:8000 to:172.17.0.2:80

■ここでは「!docker0(br0) -- (DNAT) --> docker0」なので、
 自ホスト以外からのHTTPリクエストがあればカウントアップする。

$ sudo iptables -L -v -n | grep -v f2b
Chain INPUT (policy ACCEPT 3414 packets, 7527K bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
   10  1089 DOCKER-ISOLATION  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
    4   506 ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    1    60 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0           
    5   523 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0           

Chain OUTPUT (policy ACCEPT 3454 packets, 192K bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    1    60 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.2           tcp dpt:80

Chain DOCKER-ISOLATION (1 references)
 pkts bytes target     prot opt in     out     source               destination         
   10  1089 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0           

 pkts bytes target     prot opt in     out     source               destination         
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0