理解Docker跨多主機容器網絡
來自: http://tonybai.com/2016/02/15/understanding-docker-multi-host-networking/
1、 端口映射
將宿主機A的端口P映射到容器C的網絡空間監聽的端口P’上,僅提供四層及以上應用和服務使用。這樣其他主機上的容器通過訪問宿主機A的端口P實 現與容器C的通信。顯然這個方案的應用場景很有局限。
2、將物理網卡橋接到虛擬網橋,使得容器與宿主機配置在同一網段下
在各個宿主機上都建立一個新虛擬網橋設備br0,將各自物理網卡eth0橋接br0上,eth0的IP地址賦給br0;同時修改Docker daemon的DOCKER_OPTS,設置-b=br0(替代docker0),并限制Container IP地址的分配范圍為同物理段地址(–fixed-cidr)。重啟各個主機的Docker Daemon后,處于與宿主機在同一網段的Docker容器就可以實現跨主機訪問了。這個方案同樣存在局限和擴展性差的問題:比如需將物理網段的地址劃分 成小塊,分布到各個主機上,防止IP沖突;子網劃分依賴物理交換機設置;Docker容器的主機地址空間大小依賴物理網絡劃分等。
3、使用第三方的基于 SDN 的方案:比如 使用 Open vSwitch – OVS 或 CoreOS 的 Flannel 等。
關于這些第三方方案的細節大家可以參考O’Reilly的《 Docker Cookbook 》 一書。
Docker在1.9版本中給大家帶來了一種原生的跨多主機容器網絡的解決方案,該方案的實質是采用了基于 VXLAN 的覆蓋網技術。方案的使用有一些前提條件:
1、Linux Kernel版本 >= 3.16;
2、需要一個外部Key-value Store(官方例子中使用的是 consul );
3、各物理主機上的Docker Daemon需要一些特定的啟動參數;
4、物理主機允許某些特定TCP/UDP端口可用。
本文將帶著大家一起利用Docker 1.9.1創建一個跨多主機容器網絡,并分析基于該網絡的容器間通信原理。
一、實驗環境建立
1、升級Linux Kernel
由于實驗環境采用的是Ubuntu 14.04 server amd64,其kernel版本不能滿足建立跨多主機容器網絡要求,因此需要對內核版本進行升級。在 Ubuntu的內核站點 下載 3.16.7 utopic內核 的三個文件:
linux-headers-3.16.7-031607_3.16.7-031607.201410301735_all.deb linux-image-3.16.7-031607-generic_3.16.7-031607.201410301735_amd64.deb linux-headers-3.16.7-031607-generic_3.16.7-031607.201410301735_amd64.deb
在本地執行下面命令安裝:
sudo dpkg -i linux-headers-3.16.7-*.deb linux-image-3.16.7-*.deb
需要注意的是:kernel mainline上的3.16.7內核沒有帶linux-image-extra,也就沒有了 aufs 的驅動,因此Docker Daemon將不支持默認的存儲驅動:–storage-driver=aufs,我們需要將storage driver更換為 devicemapper 。
內核升級是一個有風險的操作,并且是否能升級成功還要看點“運氣”:我的兩臺刀片服務器,就是一臺升級成功一臺升級失敗(一直報網卡問題)。
2、升級Docker到1.9.1版本
從國內下載Docker官方的安裝包比較慢,這里利用 daocloud.io提供的方法 快速安裝Docker最新版本:
$ curl -sSL https://get.daocloud.io/docker | sh
3、拓撲
本次的跨多主機容器網絡基于兩臺在不同子網網段內的物理機承載,基于物理機搭建,目的是簡化后續網絡通信原理分析。
拓撲圖如下:
二、跨多主機容器網絡搭建
1、創建 consul 服務
考慮到kv store在本文并非關鍵,僅作跨多主機容器網絡創建啟動的前提條件之用,因此僅用包含一個server節點的”cluster”。
參照拓撲圖,我們在10.10.126.101上啟動一個consul,關于consul集群以及服務注冊、服務發現等細節可以參考我之前的一 篇文章:
$./consul -d agent -server -bootstrap-expect 1 -data-dir ./data -node=master -bind=10.10.126.101 -client=0.0.0.0 &
2、修改Docker Daemon DOCKER_OPTS參數
前面提到過,通過Docker 1.9創建跨多主機容器網絡需要重新配置每個主機節點上的Docker Daemon的啟動參數:
ubuntu系統這個配置在/etc/default/docker下:DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4 -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-advertise eth0:2375 --cluster-store consul://10.10.126.101:8500/network --storage-driver=devicemapper"</pre>
這里多說幾句:
-H(或–host)配置的是Docker client(包括本地和遠程的client)與Docker Daemon的通信媒介,也是Docker REST api的服務端口。默認是/var/run/docker.sock(僅用于本地),當然也可以通過tcp協議通信以方便遠程Client訪問,就像上面 配置的那樣。非加密網通信采用2375端口,而TLS加密連接則用2376端口。這兩個端口已經 申請在IANA注冊并獲批 ,變成了知名端口。-H可以配置多個,就像上面配置的那樣。 unix socket便于本地docker client訪問本地docker daemon;tcp端口則用于遠程client訪問。這樣一來:docker pull ubuntu,走docker.sock;而docker -H 10.10.126.101:2375 pull ubuntu則走tcp socket。
–cluster-advertise 配置的是本Docker Daemon實例在cluster中的地址;–cluster-store配置的是Cluster的分布式KV store的訪問地址;
如果你之前手工修改過iptables的規則,建議重啟Docker Daemon之前清理一下iptables規則:sudo iptables -t nat -F, sudo iptables -t filter -F等。
3、啟動各節點上的Docker Daemon
以10.10.126.101為例:
$ sudo service docker start$ ps -ef|grep docker root 2069 1 0 Feb02 ? 00:01:41 /usr/bin/docker -d --dns 8.8.8.8 --dns 8.8.4.4 --storage-driver=devicemapper -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-advertise eth0:2375 --cluster-store consul://10.10.126.101:8500/network</pre>
啟動后iptables的nat, filter規則與單機Docker網絡初始情況并無二致。
101節點上初始網絡driver類型: $docker network ls NETWORK ID NAME DRIVER 47e57d6fdfe8 bridge bridge 7c5715710e34 none null 19cc2d0d76f7 host host4、創建overlay網絡net1和net2
在101節點上,創建net1:
$ sudo docker network create -d overlay net1在71節點上,創建net2:
$ sudo docker network create -d overlay net2之后無論在71節點還是101節點,我們查看當前網絡以及驅動類型都是如下結果:
$ docker network ls NETWORK ID NAME DRIVER 283b96845cbe net2 overlay da3d1b5fcb8e net1 overlay 00733ecf5065 bridge bridge 71f3634bf562 none null 7ff8b1007c09 host host此時,iptables規則也并無變化。
5、啟動兩個overlay net下的containers
我們分別在net1和net2下面啟動兩個container,每個節點上各種net1和net2的container各一個:
101: sudo docker run -itd --name net1c1 --net net1 ubuntu:14.04 sudo docker run -itd --name net2c1 --net net2 ubuntu:14.0471: sudo docker run -itd --name net1c2 --net net1 ubuntu:14.04 sudo docker run -itd --name net2c2 --net net2 ubuntu:14.04</pre>
啟動后,我們就得到如下網絡信息(容器的ip地址可能與前面拓撲圖中的不一致,每次容器啟動ip地址都可能變化):
net1: net1c1 - 10.0.0.7 net1c2 - 10.0.0.5net2: net2c1 - 10.0.0.4 net2c2 - 10.0.0.6</pre>
6、容器連通性
在net1c1中,我們來看看其到net1和net2的連通性:
root@021f14bf3924:/# ping net1c2 PING 10.0.0.5 (10.0.0.5) 56(84) bytes of data. 64 bytes from 10.0.0.5: icmp_seq=1 ttl=64 time=0.670 ms 64 bytes from 10.0.0.5: icmp_seq=2 ttl=64 time=0.387 ms ^C --- 10.0.0.5 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 999ms rtt min/avg/max/mdev = 0.387/0.528/0.670/0.143 msroot@021f14bf3924:/# ping 10.0.0.4 PING 10.0.0.4 (10.0.0.4) 56(84) bytes of data. ^C --- 10.0.0.4 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 1008ms</pre>
可見,net1中的容器是互通的,但net1和net2這兩個overlay net之間是隔離的。
三、跨多主機容器網絡通信原理
在“單機容器網絡”一文中,我們說過容器間的通信以及容器到外部網絡的通信是通過docker0網橋并結合iptables實現的。那么在上面已經建立的跨多主機容器網絡里,容器的通信又是如何實現的呢?下面我們一起來理解一下。注意:有了單機容器網絡基礎后,這里很多網絡細節就不再贅述了。
我們先來看看,在net1下的容器的網絡配置,以101上的net1c1容器為例:
$ sudo docker attach net1c1root@021f14bf3924:/# ip route default via 172.19.0.1 dev eth1 10.0.0.0/24 dev eth0 proto kernel scope link src 10.0.0.4 172.19.0.0/16 dev eth1 proto kernel scope link src 172.19.0.2
root@021f14bf3924:/# ip a 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 8: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default link/ether 02:42:0a:00:00:04 brd ff:ff:ff:ff:ff:ff inet 10.0.0.4/24 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::42:aff:fe00:4/64 scope link valid_lft forever preferred_lft forever 10: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff inet 172.19.0.2/16 scope global eth1 valid_lft forever preferred_lft forever inet6 fe80::42:acff:fe13:2/64 scope link valid_lft forever preferred_lft forever</pre>
可以看出net1c1有兩個網口:eth0(10.0.0.4)和eth1(172.19.0.2);從路由表來看,目的地址在172.19.0.0/16范圍內的,走eth1;目的地址在10.0.0.0/8范圍內的,走eth0。
我們跳出容器,回到主機網絡范疇:
在101上: $ ip a ... ... 5: docker_gwbridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP link/ether 02:42:52:35:c9:fc brd ff:ff:ff:ff:ff:ff inet 172.19.0.1/16 scope global docker_gwbridge valid_lft forever preferred_lft forever inet6 fe80::42:52ff:fe35:c9fc/64 scope link valid_lft forever preferred_lft forever 6: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN link/ether 02:42:4b:70:68:9a brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 scope global docker0 valid_lft forever preferred_lft forever 11: veth26f6db4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP link/ether b2:32:d7:65:dc:b2 brd ff:ff:ff:ff:ff:ff inet6 fe80::b032:d7ff:fe65:dcb2/64 scope link valid_lft forever preferred_lft forever 16: veth54881a0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP link/ether 9e:45:fa:5f:a0:15 brd ff:ff:ff:ff:ff:ff inet6 fe80::9c45:faff:fe5f:a015/64 scope link valid_lft forever preferred_lft forever我們看到除了我們熟悉的docker0網橋外,還多出了一個docker_gwbridge網橋:
$ brctl show bridge name bridge id STP enabled interfaces docker0 8000.02424b70689a no docker_gwbridge 8000.02425235c9fc no veth26f6db4 veth54881a0并且從brctl的輸出結果來看,兩個veth都橋接在docker_gwbridge上,而不是docker0上;docker0在跨多主機容器網絡中并沒有被用到。docker_gwbridge替代了docker0,用來實現101上隸屬于net1網絡或net2網絡中容器間的通信以及容器到外部的通信,其職能就和單機容器網絡中docker0一樣。
但位于不同host且隸屬于net1的兩個容器net1c1和net1c2間的通信顯然并沒有通過docker_gwbridge完成,從net1c1路由表來看,當net1c1 ping net1c2時,消息是通過eth0,即10.0.0.4這個ip出去的。從host的視角,net1c1的eth0似乎沒有網絡設備與之連接,那網絡通信是如何完成的呢?
這一切是從創建network開始的。前面我們執行docker network create -d overlay net1來創建net1 overlay network,這個命令會創建一個新的network namespace。
我們知道每個容器都有自己的網絡namespace,從容器的視角看其網絡名字空間,我們能看到網絡設備諸如:lo、eth0。這個eth0與主機網絡名字空間中的vethx是一個虛擬網卡pair。overlay network也有自己的net ns,而overlay network的net ns與容器的net ns之間也有著一些網絡設備對應關系。
我們先來查看一下network namespace的id。為了能利用 iproute2 工具對network ns進行管理,我們需要做如下操作:
$cd /var/run $sudo ln -s /var/run/docker/netns netns這是因為iproute2只能操作/var/run/netns下的net ns,而docker默認的net ns卻放在/var/run/docker/netns下。上面的操作成功執行后,我們就可以通過ip命令查看和管理net ns了:
$ sudo ip netns 29170076ddf6 1-283b96845c 5ae976d9dc6a 1-da3d1b5fcb我們看到在101主機上,有4個已經建立的net ns。我們大膽猜測一下,這四個net ns分別是兩個container的net ns和兩個overlay network的net ns。從netns的ID格式以及結合下面命令輸出結果中的network id來看:
$ docker network ls NETWORK ID NAME DRIVER 283b96845cbe net2 overlay da3d1b5fcb8e net1 overlay dd84da8e80bf host host 3295c22b22b8 docker_gwbridge bridge b96e2d8d4068 bridge bridge 23749ee4292f none null我們大致可以猜測出來:
1-da3d1b5fcb 是 net1的net ns; 1-283b96845c是 net2的net ns; 29170076ddf6和5ae976d9dc6a則分屬于兩個container的net ns。由于我們以net1為例,因此下面我們就來分析net1的net ns – 1-da3d1b5fcb。通過ip命令我們可以得到如下結果:
$ sudo ip netns exec 1-da3d1b5fcb ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 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 2: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP link/ether 06:b0:c6:93:25:f3 brd ff:ff:ff:ff:ff:ff inet 10.0.0.1/24 scope global br0 valid_lft forever preferred_lft forever inet6 fe80::b80a:bfff:fecc:a1e0/64 scope link valid_lft forever preferred_lft forever 7: vxlan1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UNKNOWN link/ether ea:0c:e0:bc:19:c5 brd ff:ff:ff:ff:ff:ff inet6 fe80::e80c:e0ff:febc:19c5/64 scope link valid_lft forever preferred_lft forever 9: veth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UP link/ether 06:b0:c6:93:25:f3 brd ff:ff:ff:ff:ff:ff inet6 fe80::4b0:c6ff:fe93:25f3/64 scope link valid_lft forever preferred_lft forever$ sudo ip netns exec 1-da3d1b5fcb ip route 10.0.0.0/24 dev br0 proto kernel scope link src 10.0.0.1
$ sudo ip netns exec 1-da3d1b5fcb brctl show bridge name bridge id STP enabled interfaces br0 8000.06b0c69325f3 no veth2 vxlan1</pre>
看到br0、veth2,我們心里終于有了底兒了。我們猜測net1c1容器中的eth0與veth2是一個veth pair,并橋接在br0上,通過ethtool查找veth序號的對應關系可以證實這點:
$ sudo docker attach net1c1 root@021f14bf3924:/# ethtool -S eth0 NIC statistics: peer_ifindex: 9101主機: $ sudo ip netns exec 1-da3d1b5fcb ip -d link 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP link/ether 06:b0:c6:93:25:f3 brd ff:ff:ff:ff:ff:ff bridge 7: vxlan1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UNKNOWN link/ether ea:0c:e0:bc:19:c5 brd ff:ff:ff:ff:ff:ff vxlan 9: veth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UP link/ether 06:b0:c6:93:25:f3 brd ff:ff:ff:ff:ff:ff veth</pre>
可以看到net1c1的eth0的pair peer index為9,正好與net ns 1-da3d1b5fcb中的veth2的序號一致。
那么vxlan1呢?注意這個vxlan1并非是veth設備,在ip -d link輸出的信息中,它的設備類型為vxlan。前面說過Docker的跨多主機容器網絡是基于vxlan的,這里的vxlan1就是net1這個overlay network的一個 VTEP,即VXLAN Tunnel End Point – VXLAN隧道端點。它是VXLAN網絡的邊緣設備。VXLAN的相關處理都在VTEP上進行,例如識別以太網數據幀所屬的VXLAN、基于 VXLAN對數據幀進行二層轉發、封裝/解封裝報文等。
至此,我們可以大致畫出一幅跨多主機網絡的原理圖:
![]()
如果在net1c1中ping net1c2,數據包的行走路徑是怎樣的呢?
1、net1c1(10.0.0.4)中ping net1c2(10.0.0.5),根據net1c1的路由表,數據包可通過直連網絡到達net1c2。于是arp請求獲取net1c2的MAC地址(在vxlan上的arp這里不詳述了),得到mac地址后,封包,從eth0發出;2、eth0橋接在net ns 1-da3d1b5fcb中的br0上,這個br0是個網橋(交換機)虛擬設備,需要將來自eth0的包轉發出去,于是將包轉給了vxlan設備;這個可以通過arp -a看到一些端倪:
$ sudo ip netns exec 1-da3d1b5fcb arp -a ? (10.0.0.5) at 02:42:0a:00:00:05 [ether] PERM on vxlan13、vxlan是個特殊設備,收到包后,由vxlan設備創建時注冊的設備處理程序對包進行處理,即進行VXLAN封包(這期間會查詢consul中存儲的net1信息),將ICMP包整體作為UDP包的payload封裝起來,并將UDP包通過宿主機的eth0發送出去。
4、71宿主機收到UDP包后,發現是VXLAN包,根據VXLAN包中的相關信息(比如Vxlan Network Identifier,VNI=256)找到vxlan設備,并轉給該vxlan設備處理。vxlan設備的處理程序進行解包,并將UDP中的payload取出,整體通過br0轉給veth口,net1c2從eth0收到ICMP數據包,回復icmp reply。
我們可以通過 wireshark 抓取相關vxlan包,高版本wireshark內置VXLAN協議分析器,可以直接識別和展示VXLAN包,這里安裝的是2.0.1版本(注意:一些低版本wireshark不支持VXLAN分析器,比如1.6.7版本):
![]()
關于VXLAN協議的細節,過于復雜,在后續的文章中maybe會有進一步理解。
? 2016,bigwhite. 版權所有.
</div>