理解Docker跨多主機容器網絡

GlenMalone 8年前發布 | 171K 次閱讀 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 – OVSCoreOSFlannel 等。

關于這些第三方方案的細節大家可以參考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                host

4、創建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.04

71: 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.5

net2: 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 ms

root@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 net1c1

root@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: 9

101主機: $ 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 vxlan1

3、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>

 本文由用戶 GlenMalone 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!