
<a target="_blank"></a>
我們在使用docker run建立docker容器時,可以用--net選項指定容器的網絡模式,docker有以下4種網絡模式:
host模式,使用--net=host指定。
container模式,使用--net=container:name_or_id指定。
none模式,使用--net=none指定。
bridge模式,使用--net=bridge指定,預設設定。
下面分别介紹一下docker的各個網絡模式。
衆所周知,docker使用了linux的namespaces技術來進行資源隔離,如pid namespace隔離程序,mount namespace隔離檔案系統,network namespace隔離網絡等。一個network namespace提供了一份獨立的網絡環境,包括網卡、路由、iptable規則等都與其他的network namespace隔離。一個docker容器一般會配置設定一個獨立的network namespace。但如果啟動容器的時候使用host模式,那麼這個容器将不會獲得一個獨立的network namespace,而是和主控端共用一個network namespace。容器将不會虛拟出自己的網卡,配置自己的ip等,而是使用主控端的ip和端口。
例如,我們在10.10.101.105/24的機器上用host模式啟動一個含有web應用的docker容器,監聽tcp80端口。當我們在容器中執行任何類似ifconfig指令檢視網絡環境時,看到的都是主控端上的資訊。而外界通路容器中的應用,則直接使用10.10.101.105:80即可,不用任何nat轉換,就如直接跑在主控端中一樣。但是,容器的其他方面,如檔案系統、程序清單等還是和主控端隔離的。
在了解了host模式後,這個模式也就好了解了。這個模式指定新建立的容器和已經存在的一個容器共享一個network namespace,而不是和主控端共享。新建立的容器不會建立自己的網卡,配置自己的ip,而是和一個指定的容器共享ip、端口範圍等。同樣,兩個容器除了網絡方面,其他的如檔案系統、程序清單等還是隔離的。兩個容器的程序可以通過lo網卡裝置通信。
這個模式和前兩個不同。在這種模式下,docker容器擁有自己的network namespace,但是,并不為docker容器進行任何網絡配置。也就是說,這個docker容器沒有網卡、ip、路由等資訊。需要我們自己為docker容器添加網卡、配置ip等。
bridge模式是docker預設的網絡設定,此模式會為每一個容器配置設定network namespace、設定ip等,并将一個主機上的docker容器連接配接到一個虛拟網橋上。下面着重介紹一下此模式。
docker完成以上網絡配置的過程大緻是這樣的:
在主機上建立一對虛拟網卡veth pair裝置。veth裝置總是成對出現的,它們組成了一個資料的通道,資料從一個裝置進入,就會從另一個裝置出來。是以,veth裝置常用來連接配接兩個網絡裝置。
docker将veth pair裝置的一端放在新建立的容器中,并命名為eth0。另一端放在主機中,以veth65f9這樣類似的名字命名,并将這個網絡裝置加入到docker0網橋中,可以通過brctl show指令檢視。
從docker0子網中配置設定一個ip給容器使用,并設定docker0的ip位址為容器的預設網關。
網絡拓撲介紹完後,接着介紹一下bridge模式下容器是如何通信的。
在bridge模式下,連在同一網橋上的容器可以互相通信(若出于安全考慮,也可以禁止它們之間通信,方法是在docker_opts變量中設定--icc=false,這樣隻有使用--link才能使兩個容器通信)。
容器也可以與外部通信,我們看一下主機上的iptable規則,可以看到這麼一條
-a postrouting -s 172.17.0.0/16 ! -o docker0 -j masquerade
這條規則會将源位址為172.17.0.0/16的包(也就是從docker容器産生的包),并且不是從docker0網卡發出的,進行源位址轉換,轉換成主機網卡的位址。這麼說可能不太好了解,舉一個例子說明一下。假設主機有一塊網卡為eth0,ip位址為10.10.101.105/24,網關為10.10.101.254。從主機上一個ip為172.17.0.1/16的容器中ping百度(180.76.3.151)。ip包首先從容器發往自己的預設網關docker0,包到達docker0後,也就到達了主機上。然後會查詢主機的路由表,發現包應該從主機的eth0發往主機的網關10.10.105.254/24。接着包會轉發給eth0,并從eth0發出去(主機的ip_forward轉發應該已經打開)。這時候,上面的iptable規則就會起作用,對包做snat轉換,将源位址換為eth0的位址。這樣,在外界看來,這個包就是從10.10.101.105上發出來的,docker容器對外是不可見的。
那麼,外面的機器是如何通路docker容器的服務呢?我們首先用下面指令建立一個含有web應用的容器,将容器的80端口映射到主機的80端口。
docker run -d --name web -p 80:80 fmzhen/simpleweb
然後檢視iptable規則的變化,發現多了這樣一條規則:
-a docker ! -i docker0 -p tcp -m tcp --dport 80 -j dnat --to-destination 172.17.0.5:80
此條規則就是對主機eth0收到的目的端口為80的tcp流量進行dnat轉換,将流量發往172.17.0.5:80,也就是我們上面建立的docker容器。是以,外界隻需通路10.10.101.105:80就可以通路到容器中得服務。
除此之外,我們還可以自定義docker使用的ip位址、dns等資訊,甚至使用自己定義的網橋,但是其工作方式還是一樣的。
pipework是由docker的工程師jérôme petazzoni開發的一個docker網絡配置工具,由200多行shell實作,友善易用。下面用三個場景來示範pipework的使用和工作原理。
為了使本地網絡中的機器和docker容器更友善的通信,我們經常會有将docker容器配置到和主機同一網段的需求。這個需求其實很容易實作,我們隻要将docker容器和主機的網卡橋接起來,再給docker容器配上ip就可以了。
下面我們來操作一下,我主機a位址為10.10.101.105/24,網關為10.10.101.254,需要給docker容器的位址配置為10.10.101.150/24。在主機a上做如下操作:
#安裝pipework
git clone https://github.com/jpetazzo/pipework
cp ~/pipework/pipework /usr/local/bin/
#啟動docker容器。
docker run -itd --name test1 ubuntu /bin/bash
#配置容器網絡,并連到網橋br0上。網關在ip位址後面加@指定。
#若主機環境中存在dhcp伺服器,也可以通過dhcp的方式擷取ip
#pipework br0 test1 dhcp
pipework br0 test1 10.10.101.150/[email protected]
#将主機eth0橋接到br0上,并把eth0的ip配置在br0上。這裡由于是遠端操作,中間網絡會斷掉,是以放在一條指令中執行。
ip addr add 10.10.101.105/24 dev br0; \
ip addr del 10.10.101.105/24 dev eth0; \
brctl addif br0 eth0; \
ip route del default; \
ip route add default gw 10.10.101.254 dev br0
完成上述步驟後,我們發現docker容器已經可以使用新的ip和主機網絡裡的機器互相通信了。
那麼容器到底發生了哪些變化呢?我們docker attach到test1上,發現容器中多了一塊eth1的網卡,并且配置了10.10.101.150/24的ip,而且預設路由也改為了10.10.101.254。這些都是pipework幫我們配置的。通過檢視源代碼,可以發現pipework br0 test1 10.10.101.150/[email protected]是由以下指令完成的(這裡隻列出了具體執行操作的代碼)。
#建立br0網橋
#若ovs開頭,則建立ovs網橋 ovs-vsctl add-br ovs*
brctl addbr $ifname
#建立veth pair,用于連接配接容器和br0
ip link add name $local_ifname mtu $mtu type veth peer name $guest_ifname mtu $mtu
#找到docker容器test1在主機上的pid,建立容器網絡命名空間的軟連接配接
dockerpid=$(docker inspect --format='{{ .state.pid }}' $guestname)
ln -s /proc/$nspid/ns/net /var/run/netns/$nspid
#将veth pair一端放入docker容器中,并設定正确的名字eth1
ip link set $guest_ifname netns $nspid
ip netns exec $nspid ip link set $guest_ifname name $container_ifname
#将veth pair另一端加入網橋
#若為ovs網橋則為 ovs-vsctl add-port $ifname $local_ifname ${vlan:+"tag=$vlan"}
brctl addif $ifname $local_ifname
#為新增加的容器配置ip和路由
ip netns exec $nspid ip addr add $ipaddr dev $container_ifname
ip netns exec $nspid ip link set $container_ifname up
ip netns exec $nspid ip route delete default
ip netns exec $nspid ip route add $gateway/32 dev $container_ifname
首先pipework檢查是否存在br0網橋,若不存在,就自己建立。若以"ovs"開頭,就會建立openvswitch網橋,以"br"開頭,建立linux bridge。
建立veth pair裝置,用于為容器提供網卡并連接配接到br0網橋。
使用docker inspect找到容器在主機中的pid,然後通過pid将容器的網絡命名空間連結到/var/run/netns/目錄下。這麼做的目的是,友善在主機上使用ip netns指令配置容器的網絡。因為,在docker容器中,我們沒有權限配置網絡環境。
将之前建立的veth pair裝置分别加入容器和網橋中。在容器中的名稱預設為eth1,可以通過pipework的-i參數修改該名稱。
然後就是配置新網卡的ip。若在ip位址的後面加上網關位址,那麼pipework會重新配置預設路由。這樣容器通往外網的流量會經由新配置的eth1出去,而不是通過eth0和docker0。(若想完全抛棄自帶的網絡設定,在啟動容器的時候可以指定--net=none)
以上就是pipework配置docker網絡的過程,這和docker的bridge模式有着相似的步驟。事實上,docker在實作上也采用了相同的底層機制。
通過源代碼,可以看出,pipework通過封裝linux上的ip、brctl等指令,簡化了在複雜場景下對容器連接配接的操作指令,為我們配置複雜的網絡拓撲提供了一個強有力的工具。當然,如果想了解底層的操作,我們也可以直接使用這些linux指令來完成工作,甚至可以根據自己的需求,添加額外的功能。
pipework不僅可以使用linux bridge連接配接docker容器,還可以與openvswitch結合,實作docker容器的vlan劃分。下面,就來簡單示範一下,在單機環境下,如何實作docker容器間的二層隔離。
為了示範隔離效果,我們将4個容器放在了同一個ip網段中。但實際他們是二層隔離的兩個網絡,有不同的廣播域。
#在主機a上建立4個docker容器,test1、test2、test3、test4
docker run -itd --name test2 ubuntu /bin/bash
docker run -itd --name test3 ubuntu /bin/bash
docker run -itd --name test4 ubuntu /bin/bash
#将test1,test2劃分到一個vlan中,vlan在mac位址後加@指定,此處mac位址省略。
pipework ovs0 test1 192.168.0.1/24 @100
pipework ovs0 test2 192.168.0.2/24 @100
#将test3,test4劃分到另一個vlan中
pipework ovs0 test3 192.168.0.3/24 @200
pipework ovs0 test4 192.168.0.4/24 @200
完成上述操作後,使用docker attach連到容器中,然後用ping指令測試連通性,發現test1和test2可以互相通信,但與test3和test4隔離。這樣,一個簡單的vlan隔離容器網絡就已經完成。
由于openvswitch本身支援vlan功能,是以這裡pipework所做的工作和之前介紹的基本一樣,隻不過将linux bridge替換成了openvswitch,在将veth pair的一端加入ovs0網橋時,指定了tag。底層操作如下:
ovs-vsctl add-port ovs0 veth* tag=100
上面介紹完了單主機上vlan的隔離,下面我們将情況延伸到多主機的情況。有了前面兩個例子做鋪墊,這個也就不難了。為了實作這個目的,我們把主控端上的網卡橋接到各自的ovs網橋上,然後再為容器配置ip和vlan就可以了。我們實驗環境如下,主機a和b各有一塊網卡eth0,ip位址分别為10.10.101.105/24、10.10.101.106/24。在主機a上建立兩個容器test1、test2,分别在vlan 100和vlan 200上。在主機b上建立test3、test4,分别在vlan 100和vlan 200 上。最終,test1可以和test3通信,test2可以和test4通信。
#在主機a上
#建立docker容器
#劃分vlan
pipework ovs0 test2 192.168.0.2/24 @200
#将eth0橋接到ovs0上
ip addr add 10.10.101.105/24 dev ovs0; \
ovs-vsctl add-port ovs0 eth0; \
ip route add default gw 10.10.101.254 dev ovs0
#在主機b上
pipework ovs0 test1 192.168.0.3/24 @100
pipework ovs0 test2 192.168.0.4/24 @200
ip addr add 10.10.101.106/24 dev ovs0; \
ip addr del 10.10.101.106/24 dev eth0; \
完成上面的步驟後,主機a上的test1和主機b上的test3容器就劃分到了一個vlan中,并且與主機a上的test2和主機b上的test4隔離(主機eth0網卡需要設定為混雜模式,連接配接主機的交換機端口應設定為trunk模式,即允許vlan 100和vlan 200的包通過)。拓撲圖如下所示(省去了docker預設的eth0網卡和主機上的docker0網橋):
除此之外,pipework還支援使用macvlan裝置、設定網卡mac位址等功能。不過,pipework有一個缺陷,就是配置的容器在關掉重新開機後,之前的設定會丢失。
通過上面的介紹,我相信大家對docker的網絡已經有了一定的了解。對于一個基本應用而言,docker的網絡模型已經很不錯了。然而,随着雲計算和微服務的興起,我們不能永遠停留在使用基本應用的級别上,我們需要性能更好且更靈活的網絡功能。pipework正好滿足了我們這樣的需求,從上面的樣例中,我們可以看到pipework的友善之處。但是,同時也應注意到,pipework并不是一套解決方案,它隻是一個網絡配置工具,我們可以利用它提供的強大功能,幫助我們建構自己的解決方案。
原文釋出時間:2015-01-20
本文來自雲栖合作夥伴“linux中國”