Docker 1.12のoverlayネットワーク

以前Docker 1.9でのマルチホストネットワークの作成について記事を書きましたが,Docker 1.12になって手順がだいぶ変わったので改めてまとめようと思います.ついでに色々してみました.

結論から言うとDocker 1.12のoverlayネットワークはserviceが定義されたコンテナのみ接続されることが想定されていますが回避も可能です.

まず前の記事を参考にDocker swarmのクラスタを作成します.

  • node1(master) : ip-172-31-1-218

  • node2 : ip-172-31-1-219

  • node3 : ip-172-31-1-217

$ docker node ls
ID                           NAME             MEMBERSHIP  STATUS  AVAILABILITY  MANAGER STATUS
71g9k9xcb78u90r8w6zcer4z0 *  ip-172-31-1-218  Accepted    Ready   Active        Leader
7gg6fa9a7frr3h66widewkjc2    ip-172-31-1-219  Accepted    Ready   Active
em6kz4ijedaj48tfssi7k26l3    ip-172-31-1-217  Accepted    Ready   Active

node1でoverlayNWを作成してみます.

$ docker network create -d overlay backend

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
9lapeboarpux        backend             overlay             swarm
af3236af05f2        bridge              bridge              local
1592e8ed2c89        docker_gwbridge     bridge              local
950ee88d8b98        host                host                local
5jq39idymrkq        ingress             overlay             swarm
40051a135237        none                null                local

これでNWは作成されたはずですが,他のホストで確認して見るとこのNWはまだ見えません.

ubuntu@ip-172-31-1-217$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
1d3085970cb3        bridge              bridge              local
db8293d930ca        docker_gwbridge     bridge              local
d13db9be8a4c        host                host                local
5jq39idymrkq        ingress             overlay             swarm
a9f1cbd814fa        none                null                local

さらにネットワークを作成したnode1でこのNWに繋がるコンテナを作成しようとすると失敗します.

$ docker run -itd --net=backend busybox
docker: Error response from daemon: network backend not found.

この状態ではまだNWの実体がないんですね.inspectで確認するとサブネットなども設定されていないことがわかります.

$ docker network inspect backend
[
    {
        "Name": "backend",
        "Id": "10kza2wvjm6dz9nzt25jrdygl",
        "Scope": "swarm",
        "Driver": "overlay",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": []
        },
        "Internal": false,
        "Containers": null,
        "Options": {
            "com.docker.network.driver.overlay.vxlanid_list": "257"
        },
        "Labels": null
    }
]

なのでまずこのNWに紐づくserviceを作成します.これでようやくNWのサブネットやGWが割り当てられ,NWの実体の作成とコンテナのアタッチが行われます.

$ docker service create --name=backend --replicas=5 -p 8000:80/tcp --network=backend redis

$ docker network inspect backend
[
    {
        "Name": "backend",
        "Id": "10kza2wvjm6dz9nzt25jrdygl",
        "Scope": "swarm",
        "Driver": "overlay",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "10.0.0.0/24",
                    "Gateway": "10.0.0.1"
                }
            ]
        },
        "Internal": false,
        "Containers": {
            "53b2c41b5b957f5926b48b9517f67b7af833c2c33d1f91023fd80486f83bcd02": {
                "Name": "anotherbackend.1.caomy5tse6gaow0cv6u6vy6rb",
                "EndpointID": "f09a12d8c937b32e9b8707a7e042168ef806372708c4d03cf26152e55b4f0a83",
                "MacAddress": "02:42:0a:00:00:0b",
                "IPv4Address": "10.0.0.11/24",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.driver.overlay.vxlanid_list": "257"
        },
        "Labels": {}
    }
]

これで別のホスト上でもNWが作成されます.

ubuntu@ip-172-31-1-217:~$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
1ul9idog376w        backend             overlay             swarm
1d3085970cb3        bridge              bridge              local
db8293d930ca        docker_gwbridge     bridge              local
d13db9be8a4c        host                host                local
5jq39idymrkq        ingress             overlay             swarm
a9f1cbd814fa        none                null                local

しかしこの状態でもこのNWに繋がるコンテナをserviceの定義なしに作ることはできません.

$ docker run -itd --net=backend busybox
docker: Error response from daemon: swarm-scoped network (backend) is not compatible with `docker create` or `docker run`. This network can be only used docker service.
See 'docker run --help'.

あくまでoverlay NWに繋がるコンテナの作成はserviceの作成とセットになります.

$ docker service create --name=anotherbackend --replicas=5 --network=backend postgres

$ docker network inspect backend
[
    {
        "Name": "backend",
        "Id": "10kza2wvjm6dz9nzt25jrdygl",
        "Scope": "swarm",
        "Driver": "overlay",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "10.0.0.0/24",
                    "Gateway": "10.0.0.1"
                }
            ]
        },
        "Internal": false,
        "Containers": {
            "02d3b4a24d99720295539c4c56157e559ec2592c193e1f1ec82c79a265dfd655": {
                "Name": "backend.4.1wbj6bo0ruq87nvdkuswt9xy5",
                "EndpointID": "a248c65b18386464afb2baf4279c6541a9252432b153d7519cdc3fc6abc06654",
                "MacAddress": "02:42:0a:00:00:06",
                "IPv4Address": "10.0.0.6/24",
                "IPv6Address": ""
            },
            "06d638c77e2c3c12178ff6e93a453a3f67f9b7b43ce2d407753acb43e18b5d0e": {
                "Name": "backend.3.9387bpsqnvvyhnugj433ufx32",
                "EndpointID": "47c9969877337f965e742a08809a5065b339f543943cee1241af3f845cf94386",
                "MacAddress": "02:42:0a:00:00:05",
                "IPv4Address": "10.0.0.5/24",
                "IPv6Address": ""
            },
            "53b2c41b5b957f5926b48b9517f67b7af833c2c33d1f91023fd80486f83bcd02": {
                "Name": "anotherbackend.1.caomy5tse6gaow0cv6u6vy6rb",
                "EndpointID": "f09a12d8c937b32e9b8707a7e042168ef806372708c4d03cf26152e55b4f0a83",
                "MacAddress": "02:42:0a:00:00:0b",
                "IPv4Address": "10.0.0.11/24",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.driver.overlay.vxlanid_list": "257"
        },
        "Labels": {}
    }
]

もしもserviceに紐付かないコンテナをNWにどうしても接続したい場合は先にコンテナを作成してからNWにconnectしましょう.

$ docker run -itd busybox
2fda7c0bc0b7f70879c38461b9588f35880be76752ca594a6cd52a220a9ca077

$ docker network connect backend 2fda7c0bc0b7

何故作成時には許されなくて作成後は許されているのかはよく分かりませんが今のところはこれが可能です.
ちなみにdiscponnectも可能です.

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
2fda7c0bc0b7        busybox             "sh"                     4 minutes ago       Up 4 minutes                            angry_roentgen
53b2c41b5b95        postgres:latest     "/docker-entrypoint.s"   About an hour ago   Up About an hour    5432/tcp            anotherbackend.1.caomy5tse6gaow0cv6u6vy6rb
06d638c77e2c        redis:latest        "docker-entrypoint.sh"   About an hour ago   Up About an hour    6379/tcp            backend.3.9387bpsqnvvyhnugj433ufx32
02d3b4a24d99        redis:latest        "docker-entrypoint.sh"   About an hour ago   Up About an hour    6379/tcp            backend.4.1wbj6bo0ruq87nvdkuswt9xy5

$ docker network disconnect backend 06d638c77e2c

これをすると表面上サービスはscale=5で正しく動いているように見えて実はネットワーク不通のコンテナが発生することになります.
おいおいこれはまずいだろと思わなくもないですが,まあ新機能なのでこれから変わるのでしょう.

$ docker service tasks backend
          ID                         NAME       SERVICE  IMAGE  LAST STATE             DESIRED STATE  NODE
          euwtk7ck34ghknvggea6i1fg2  backend.1  backend  redis  Running About an hour  Running        ip-172-31-1-217
          24z71bl2u2fwebaa04dk2o186  backend.2  backend  redis  Running About an hour  Running        ip-172-31-1-219
実は不通-> 9387bpsqnvvyhnugj433ufx32  backend.3  backend  redis  Running About an hour  Running        ip-172-31-1-218 
          1wbj6bo0ruq87nvdkuswt9xy5  backend.4  backend  redis  Running About an hour  Running        ip-172-31-1-218
          1nt960d8v1vqkl69790yrborb  backend.5  backend  redis  Running About an hour  Running        ip-172-31-1-217

AWS上での悲しい出来事

これでできるはずなんですが,今回AWS上でテストしていることが原因(?)でDNSの名前解決したうえでの疎通がうまく行きませんでした.

$ docker exec -it 53b2c41b5b95 ping -c 3 backend
PING backend (10.0.0.2): 56 data bytes
92 bytes from 53b2c41b5b95 (10.0.0.11): Destination Host Unreachable
92 bytes from 53b2c41b5b95 (10.0.0.11): Destination Host Unreachable
92 bytes from 53b2c41b5b95 (10.0.0.11): Destination Host Unreachable
--- backend ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss

$ docker exec -it 53b2c41b5b95 ping -c 3 10.0.0.11
PING 10.0.0.11 (10.0.0.11): 56 data bytes
64 bytes from 10.0.0.11: icmp_seq=0 ttl=64 time=0.060 ms
64 bytes from 10.0.0.11: icmp_seq=1 ttl=64 time=0.076 ms
64 bytes from 10.0.0.11: icmp_seq=2 ttl=64 time=0.057 ms
--- 10.0.0.11 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.057/0.064/0.076/0.000 ms

あれ!?っと思ったけどそういやなんかissue見たことあるなーと思ったら案の定こちらで報告されてました.
まあそのうち治るでしょう.以前もAWS上でNWの問題があったのでなにか相性が悪いことがあるようです.

Docker ロードバランサ内部実装

先日のDockerCon16でDocker 1.12RCが発表されまして,主な機能追加として

  • SwarmのDocker engineへの統合とそれに伴うクラスタ構築の簡略化

  • Service機能の追加

  • Load Balancer機能の追加

が発表されました.
今回はSwarmクラスタの構築~Serviceの定義まで行って,ロードバランサの内部実装を詳しく追ってみます.

Docker 1.12RCのインストール

これは既にget dockerで問題なく可能です.
今回はAWS上にクラスタを構築します.

  • node1(master) : ip-172-31-1-218

  • node2 : ip-172-31-1-219

  • node3 : ip-172-31-1-217

これら3台のホスト上でそれぞれ以下のコマンドでDocker 1.12をインストールします.

$ wget -qO- https://experimental.docker.com/ | sh

Swarm クラスタの構築

node1上で以下のコマンドを実行し,Swarmクラスタのmasterとして定義します.

$ docker swarm init
Swarm initialized: current node (71g9k9xcb78u90r8w6zcer4z0) is now a manager.

次にnode2, node3それぞれで以下のコマンドを実行し,Swarmノードへ追加します.

$ docker swarm join 172.31.1.218:2377
This node joined a Swarm as a worker.

これで3台のSwarmクラスタが構築されました.
node1で以下のコマンドを実行し,クラスタが構築されていることを確認します.

$ docker node ls
ID NAME MEMBERSHIP STATUS AVAILABILITY MANAGER STATUS
71g9k9xcb78u90r8w6zcer4z0 * ip-172-31-1-218 Accepted Ready Active Leader
7gg6fa9a7frr3h66widewkjc2 ip-172-31-1-219 Accepted Ready Active
em6kz4ijedaj48tfssi7k26l3 ip-172-31-1-217 Accepted Ready Active

これまでのポートを開放したりSwarmコンテナを構築したりといった手順がなくなり手軽に構築できました.

Dockerサービス定義

次は1.12から追加されたサービス機能です.
composeではサービスの定義はコンテナのラベルで管理されていましたが,正式にserviceというリソースが定義されdockerコマンドで管理できるようになりました.
と言っても難しいことは特にないです.docker runの延長の様な感じです.

node1上で以下のコマンドを実行します.

$ docker service create --name vote -p 8080:80 instavote/vote
4g17854r60v68gcti1gjbvjqx

これでvoteサービスが定義され,instavote/voteイメージのコンテナが1台配備されます.

現在定義されているサービスを確認

$ docker service ls
ID NAME REPLICAS IMAGE COMMAND
4g17854r60v6 vote 1/1 instavote/vote

更にそのサービス上で動作しているコンテナを確認

$ docker service tasks vote
ID NAME SERVICE IMAGE LAST STATE DESIRED STATE NODE
c003owls6tcdfnfwpn7o89g32 vote.1 vote instavote/vote Running 57 seconds Running ip-172-31-1-218

node1上でinstavote/voteイメージのコンテナが1台動作していることがわかります.
composeと同様にscaleコマンドも存在します.

$ docker service scale vote=6
vote scaled to 6

$ docker service tasks vote
ID NAME SERVICE IMAGE LAST STATE DESIRED STATE NODE
c003owls6tcdfnfwpn7o89g32 vote.1 vote instavote/vote Running About a minute Running ip-172-31-1-218
aff60et0v925cylsiw62docss vote.2 vote instavote/vote Preparing 1 seconds Running ip-172-31-1-217
chpnwvc3cv3fuq9yorzfz6wj3 vote.3 vote instavote/vote Preparing 1 seconds Running ip-172-31-1-219
4jsnmbn9fne9pouh8ffxuo6no vote.4 vote instavote/vote Preparing 1 seconds Running ip-172-31-1-219
7safag59ght9u4pobgw47ae04 vote.5 vote instavote/vote Preparing 1 seconds Running ip-172-31-1-218
4yyo054kzasvupckynhi9dx7b vote.6 vote instavote/vote Preparing 1 seconds Running ip-172-31-1-217

それぞれのホストに6台のコンテナが分散されて配備されたのがわかります.
これで実はサービスは8080番のポートで外部に公開されており,どのホストに8080番でアクセスしてもこれら6台のコンテナにロードバランスされます.
このinstavote/voteはアクセスするとコンテナIDを表示しているのでリロードする度着弾するコンテナが変わるのがわかりやすいです.

LBwebUI

ロードバランサの内部実装

これで使う分にはOKですが,内部がどうなっているか一応理解しておかないと気持ち悪いのでもう少し深追いしてみます.
まず,8080番のポートはDockerらしくiptablesでコンテナ内の80番にNATされています.

$ sudo iptables-save
...
-A DOCKER-INGRESS -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.18.0.2:8080
...

さて,このIP 172.18.0.2はどのコンテナにも該当しません.
例えばnode1上のコンテナでは

$ docker exec -it vote.1.c003owls6tcdfnfwpn7o89g32 ip address|grep -A 1 -B 1 link/eth
18: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP
link/ether 02:42:0a:ff:00:07 brd ff:ff:ff:ff:ff:ff
inet 10.255.0.7/16 scope global eth0
--
20: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:12:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.3/16 scope global eth1

docker exec -it vote.5.7safag59ght9u4pobgw47ae04 ip address|grep -A 1 -B 1 link/eth
22: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP
link/ether 02:42:0a:ff:00:0b brd ff:ff:ff:ff:ff:ff
inet 10.255.0.11/16 scope global eth0
--
24: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:12:00:04 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.4/16 scope global eth1

実は,コンテナに紐付かないingress用のnetwork namespaceが作られています.
今回その中を覗いてみます.

まずnode1上のコンテナを改めて確認.

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a060a2cdfa46 instavote/vote:latest "gunicorn app:app -b " About a minute ago Up About a minute 80/tcp vote.5.7safag59ght9u4pobgw47ae04
c423eeebc7f4 instavote/vote:latest "gunicorn app:app -b " 2 minutes ago Up 2 minutes 80/tcp vote.1.c003owls6tcdfnfwpn7o89g32

これらのコンテナのnetwork namespaceのIDを確認.

$ docker inspect a060a2cdfa46|grep Sandbox
"SandboxID": "567239b6a8b4d9116210e16eb6419a140d510296b090667bf2e45a6a9340d4fa",
"SandboxKey": "/var/run/docker/netns/567239b6a8b4",

$ docker inspect c423eeebc7f4|grep Sandbox
"SandboxID": "df78773b11ec2d07feffdc3b557d1971f4955d7bf5fca66cc9eb882e9d6bbd76",
"SandboxKey": "/var/run/docker/netns/df78773b11ec",

ここでのSandboxというのがdockerのCNMの用語でLinuxでのnetwork namespaceに相当します.
それらの実体はSandboxKeyに記述されているファイルです.
実際に/var/run/docker/netnsを見てみると

$ sudo ls /var/run/docker/netns
1-5jq39idymr 567239b6a8b4 c47f7eacb1bc df78773b11ec

コンテナは2台しかないはずなのにSandbox (network namespace)は4つ作られています.
のうち,567239b6a8b4df78773b11ecはコンテナのSandboxでc47f7eacb1bcはIngress用のSandbox,1-5jq39idymrはコンテナ間をつなぐoverlay networkのvtepが入っているSandboxになります.
これらのSandboxは/var/run/netns以下にシンボリックリンクを貼れば中を除くことが可能です.

$ sudo ln -s /var/run/docker/netns/1-5jq39idymr /var/run/netns/vtep

$ sudo ln -s /var/run/docker/netns/c47f7eacb1bc /var/run/netns/lbingress

まずIngress のnetwork namespaceの中は

$ sudo ip netns exec lbingress ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
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
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
14: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 02:42:0a:ff:00:03 brd ff:ff:ff:ff:ff:ff
inet 10.255.0.3/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::42:aff:feff:3/64 scope link
valid_lft forever preferred_lft forever
16: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.2/16 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::42:acff:fe12:2/64 scope link
valid_lft forever preferred_lft forever

ここに,iptablesのNAT先の172.18.0.2があることがわかります.
そしてこのnetwork namespace内のipvsの設定を確認します.

$ sudo ip netns exec lbingress ipvsadm -L
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
FWM 256 rr
-> ip-10-255-0-7.ap-northeast-1 Masq 1 0 0
-> ip-10-255-0-8.ap-northeast-1 Masq 1 0 0
-> ip-10-255-0-9.ap-northeast-1 Masq 1 0 0
-> ip-10-255-0-10.ap-northeast- Masq 1 0 1
-> ip-10-255-0-11.ap-northeast- Masq 1 0 0
-> ip-10-255-0-12.ap-northeast- Masq 1 0 0

これで外からのパケットが10.255.0.0/16のネットワークにラウンドロビンで転送されることがわかります.
次にvtepの存在するnetwork namespaceを確認します.

$ sudo ip netns exec vtep ip -d link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 06:4b:04:c2:0e:15 brd ff:ff:ff:ff:ff:ff promiscuity 0
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:e7:e2:42:9c brd ff:ff:ff:ff:ff:ff promiscuity 0
bridge
9: docker_gwbridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:bd:90:69:18 brd ff:ff:ff:ff:ff:ff promiscuity 0
bridge
12: ov-000100-5jq39: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP mode DEFAULT group default
link/ether ae:17:c3:4a:f7:a7 brd ff:ff:ff:ff:ff:ff promiscuity 0
bridge
13: vx-000100-5jq39: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master ov-000100-5jq39 state UNKNOWN mode DEFAULT group default
link/ether ce:8d:fa:16:ca:ab brd ff:ff:ff:ff:ff:ff promiscuity 1
vxlan id 256 port 32768 61000 proxy l2miss l3miss ageing 300
15: vethddbc8d5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master ov-000100-5jq39 state UP mode DEFAULT group default
link/ether b2:a9:a5:49:f0:b4 brd ff:ff:ff:ff:ff:ff promiscuity 1
veth
17: veth75b6707: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP mode DEFAULT group default
link/ether 2e:59:0d:5c:68:27 brd ff:ff:ff:ff:ff:ff promiscuity 1
veth
19: veth8b7c018: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master ov-000100-5jq39 state UP mode DEFAULT group default
link/ether d2:61:21:33:e4:b7 brd ff:ff:ff:ff:ff:ff promiscuity 1
veth
21: veth18584b0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP mode DEFAULT group default
link/ether 72:4d:09:77:76:41 brd ff:ff:ff:ff:ff:ff promiscuity 1
veth
23: vetha2a1ee3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master ov-000100-5jq39 state UP mode DEFAULT group default
link/ether ae:17:c3:4a:f7:a7 brd ff:ff:ff:ff:ff:ff promiscuity 1
veth
25: veth3589589: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP mode DEFAULT group default
link/ether a6:a9:a6:c5:ca:da brd ff:ff:ff:ff:ff:ff promiscuity 1
veth
27: veth2d99f87: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether 66:3e:a3:35:55:f1 brd ff:ff:ff:ff:ff:ff promiscuity 1
veth
29: vethd1e34b5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether 06:07:8a:d0:d5:df brd ff:ff:ff:ff:ff:ff promiscuity 1
veth

たくさんインターフェースがありますが,これは同一ovelay networkに繋がるホスト上のコンテナ全てがこのnetwork namespace上でブリッジングされているためです.
そしてvx-000100-5jq39がvtepでov-000100-5jq39に接続されてます.
中のfdbとルーティングテーブルはこんな感じ

$ sudo ip netns exec vtep bridge fdb show dev vx-000100-5jq39
ce:8d:fa:16:ca:ab vlan 0 permanent
02:42:0a:ff:00:04 vlan 0
02:42:0a:ff:00:04 dst 172.31.1.219 self permanent
02:42:0a:ff:00:05 dst 172.31.1.217 self permanent
02:42:0a:ff:00:08 dst 172.31.1.217 self permanent
02:42:0a:ff:00:09 dst 172.31.1.219 self permanent
02:42:0a:ff:00:0a dst 172.31.1.219 self permanent
02:42:0a:ff:00:0c dst 172.31.1.217 self permanent

$ sudo ip netns exec vtep ip route
default via 172.31.0.1 dev eth0
10.255.0.0/16 dev ov-000100-5jq39 proto kernel scope link src 10.255.0.1
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.18.0.0/16 dev docker_gwbridge proto kernel scope link src 172.18.0.1
172.31.0.0/20 dev eth0 proto kernel scope link src 172.31.1.218

ingressからこのnetwork namespaceに飛ばされて,vtepでカプセリングされてそれぞれのコンテナに飛ばされることがわかりました.
ingress

ちなみに,これは外からのアクセスの場合.
内部でのコンテナ間通信でも実はDNSのラウンドロビンでロードバランサは実装されています.
Dockerコンテナ間はDockerのembededDNSサーバが名前解決することでサービス名で疎通することが可能で,その際にラウンドロビンでロードバランスされます.
inner

Docker 1.12のswarmはサービスのオートヒーリングの機能も実装されて,ようやくDockerクラスタの機能が揃った感じがあります.
kubernetesと比較して簡単にクラスタやロードバランサの設定ができるのは魅力ですが,スケーラビリティとかそれぞれの機能比較は今後したい感じです.