docker容器中的網(wǎng)絡(luò)原理(單機(jī)模式下的容器網(wǎng)絡(luò))(docker網(wǎng)絡(luò)模式詳解)
Docker容器中的網(wǎng)絡(luò)原理(單機(jī)模式下的容器網(wǎng)絡(luò))
單機(jī)模式下的容器網(wǎng)絡(luò)
容器通信原理
容器之間通信
容器與宿主機(jī)之間通信
容器與宿主機(jī)外部通信
docker0的多重身份
實(shí)驗(yàn):使用namespace模擬容器通信
自制ns與Docker容器之間的通信流程
參考文獻(xiàn)
容器的本質(zhì)是一個被隔離的進(jìn)程,而這個進(jìn)程又有其獨(dú)立的網(wǎng)絡(luò)棧,即網(wǎng)卡(Network Interface)、回環(huán)設(shè)備(Loopback Device)、路由表(Routing Table)和 iptables 規(guī)則。單機(jī)時代的容器網(wǎng)絡(luò)實(shí)際上有三種通信需求,分別是:
容器之間通信
容器與宿主機(jī)之間通信
容器與外部主機(jī)通信
在docker中實(shí)現(xiàn)單機(jī)時代容器的網(wǎng)絡(luò),主要依靠以下這幾個工具。
概念總是抽象的,后面我將會做實(shí)驗(yàn)使用這三種設(shè)備模擬主機(jī)上的容器通信流程,在這之前,讓我們先搞明白單機(jī)時代的容器通信過程,然后做實(shí)驗(yàn)驗(yàn)證這一過程。
容器通信原理
容器之間通信
不同容器之間的通信,類似于不同主機(jī)之間的通信。如果你想要實(shí)現(xiàn)兩臺主機(jī)之間的通信,最直接的辦法,就是把它們用一根網(wǎng)線連接起來;而如果你想要實(shí)現(xiàn)多臺主機(jī)之間的通信,那就需要用網(wǎng)線,把它們連接在一臺交換機(jī)上。在 Linux 中,能夠起到虛擬交換機(jī)作用的網(wǎng)絡(luò)設(shè)備,是網(wǎng)橋(Bridge)。Docker 項(xiàng)目會默認(rèn)在宿主機(jī)上創(chuàng)建一個名叫 docker0 的網(wǎng)橋,凡是連接在 docker0 網(wǎng)橋上的容器,就可以通過它來進(jìn)行通信。于是,問題的關(guān)鍵在如何將容器連接到docker0網(wǎng)橋上。使用一種名叫Veth Pair的虛擬設(shè)備。
Veth Pair 設(shè)備的特點(diǎn)是:它被創(chuàng)建出來后,總是以兩張?zhí)摂M網(wǎng)卡(Veth Peer)的形式成對出現(xiàn)的。并且,從其中一個“網(wǎng)卡”發(fā)出的數(shù)據(jù)包,可以直接出現(xiàn)在與它對應(yīng)的另一張“網(wǎng) 卡”上,哪怕這兩個“網(wǎng)卡”在不同的 Network Namespace 里。
下圖所示的container1中的eth0 網(wǎng)卡,是一個 Veth Pair,它的一端在這個容器的 Network Namespace 里,而另一端則位于宿主機(jī)上(Host Namespace),并且被“插”在 了宿主機(jī)的 docker0 網(wǎng)橋上。一旦一張?zhí)摂M網(wǎng)卡被“插”在網(wǎng)橋上,它就會變成該網(wǎng)橋的“從設(shè)備”。從設(shè)備會被“剝奪”調(diào)用網(wǎng)絡(luò)協(xié)議棧處理數(shù)據(jù)包的資格,從而“降級”成為網(wǎng)橋上的一個端口。而這個端口唯一的作用,就是接收流入的數(shù)據(jù)包,然后把這些數(shù)據(jù)包的“生殺大權(quán)”(比如轉(zhuǎn)發(fā)或者丟棄),全部交給對應(yīng)的網(wǎng)橋。
container1與container2的通信過程如下所示:
1.container 1查看自己的路由表,匹配到合適的路由。如下所示,container 1將會匹配到第二條路由,即直連路由(凡是匹配到這條規(guī)則的 IP 包,應(yīng)該經(jīng)過本機(jī)的eth0網(wǎng)卡,通過二層網(wǎng)絡(luò)直接發(fā)往目的主機(jī)),即無需gateway轉(zhuǎn)發(fā)便可以直接將數(shù)據(jù)包送達(dá)。
route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 172.17.0.1 0.0.0.0 UG 0 0 0 eth0
172.17.0.0 * 255.255.0.0 U 0 0 0 eth0
2.發(fā)起arp請求,查詢目標(biāo)IP地址為172.17.0.3的容器的mac地址。arp查詢后(要么從arp cache中找到,要么在docker0這個二層交換機(jī)中泛洪查詢)獲得172.17.0.3的mac地址。
arp -n
? (172.17.0.1) at 02:42:3e:c5:c8:5c [ether] on eth0
? (172.17.0.3) at 02:42:ac:11:00:03 [ether] on eth0
3.得到對應(yīng)mac地址后,將數(shù)據(jù)包轉(zhuǎn)發(fā)給該mac地址對應(yīng)的網(wǎng)卡即可。類似局域網(wǎng)上兩臺主機(jī)之間的通信過程。此時兩個container之間連通,主要還是通過直連網(wǎng)絡(luò),實(shí)質(zhì)上是docker0在二層起到的作用。
容器與宿主機(jī)之間通信
當(dāng)你在一臺宿主機(jī)上,訪問該宿主機(jī)上的容器的 IP 地址時,這個請求的數(shù)據(jù)包, 也是先根據(jù)路由規(guī)則到達(dá) docker0 網(wǎng)橋,然后被轉(zhuǎn)發(fā)到對應(yīng)的 Veth Pair設(shè)備,最后出現(xiàn)在容器里。這個過程的示意圖,如下所示:
1.宿主機(jī)查看自己的路由信息,匹配到合適的路由,在本例中,匹配到了直連路由,無需gateway轉(zhuǎn)發(fā)便可以直接將數(shù)據(jù)包送達(dá)。
[root@node2 ~]# ip route
...
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
...
2.發(fā)起arp請求,查詢目標(biāo)IP地址為172.17.0.2的容器的mac地址。arp查詢后(要么從arp cache中找到,要么在docker0這個二層交換機(jī)中泛洪查詢)獲得172.17.0.2的mac地址。
[root@node2 ~]# ip neigh show dev docker0
172.17.0.2 lladdr 02:42:ac:11:00:02 STALE
172.17.0.3 lladdr 02:42:ac:11:00:03 STALE
3.得到對應(yīng)mac地址后,將數(shù)據(jù)包轉(zhuǎn)發(fā)給該mac地址對應(yīng)的網(wǎng)卡即可。
容器與宿主機(jī)外部通信
當(dāng)一個容器試圖連接到另外一個宿主機(jī)時,比如:ping 10.168.0.3,它發(fā)出的請求數(shù)據(jù)包,首先經(jīng)過 docker0 網(wǎng)橋出現(xiàn)在宿主機(jī)上。然后根據(jù)宿主機(jī)的路由表里的直連路由規(guī)則 (10.168.0.0/24 via eth0)),對 10.168.0.3 的訪問請求就會交給宿主機(jī)的 eth0 處理。所以接下來,這個數(shù)據(jù)包就會經(jīng)宿主機(jī)的 eth0 網(wǎng)卡轉(zhuǎn)發(fā)到宿主機(jī)網(wǎng)絡(luò)上,最終到達(dá) 10.168.0.3 對應(yīng)的宿主機(jī)上。當(dāng)然,這個過程的實(shí)現(xiàn)要求這兩臺宿主機(jī)本身是連通的。這個過程的示意圖如下所示。
1.首先,根據(jù)上一個例子,可以知道,容器到宿主機(jī)之間的通信是通的,即數(shù)據(jù)包能夠發(fā)送到docker0網(wǎng)卡上,進(jìn)入到宿主機(jī)內(nèi)核空間。那么問題的關(guān)鍵就在于,怎么把這個數(shù)據(jù)包發(fā)送到宿主機(jī)外部。
2.已知172.17.0.0/16網(wǎng)段,是docker專門用來給容器通信使用的私有網(wǎng)段。想要讓數(shù)據(jù)包突破宿主機(jī)的關(guān)鍵就在于使用nat技術(shù),將源ip地址為容器ip,目的是要與容器外部通信的的數(shù)據(jù)包,修改為MASQURADE模式,即修改為宿主機(jī)網(wǎng)卡的ip地址。查看iptables規(guī)則,發(fā)現(xiàn)如下一條規(guī)則。
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
#將數(shù)據(jù)包源地址屬于docker網(wǎng)段的,并且出口網(wǎng)卡不是docker0的(即),都做nat技術(shù)偽裝為其出口網(wǎng)卡地址。
3.被nat技術(shù)修改過的報文將會被發(fā)送到主機(jī)外部與主機(jī)進(jìn)行通信。
[root@node2 ~]# docker exec -it busybox3 sh
/ # ping www.baidu.com
PING www.baidu.com (220.181.112.244): 56 data bytes
64 bytes from 220.181.112.244: seq=0 ttl=50 time=36.401 ms
64 bytes from 220.181.112.244: seq=1 ttl=50 time=38.166 ms
docker0的多重身份
理解docker單機(jī)模式下的通信原理,其核心在于理解docker0網(wǎng)橋在整個容器通信過程中承擔(dān)的作用。
下圖中我們給出了Docker0的雙重身份,并對比物理交換機(jī),我們來理解一下Docker0這個軟網(wǎng)橋。
圖2 docker0的多重身份
1、從容器視角,網(wǎng)橋(交換機(jī))身份
docker0對于通過veth pair“插在”網(wǎng)橋上的container1和container2來說,首先就是一個二層的交換機(jī)的角色:泛洪、維護(hù)cam表,在二層轉(zhuǎn)發(fā)數(shù)據(jù)包;同時由于docker0自身也具有mac地址(這個與純二層交換機(jī)不同),并且綁定了ip(這里是172.17.0.1),因此在 container中還作為container default路由的默認(rèn)Gateway而存在。
2、從宿主機(jī)視角,網(wǎng)卡身份
物理交換機(jī)提供了由硬件實(shí)現(xiàn)的高效的背板通道,供連接在交換機(jī)上的主機(jī)高效實(shí)現(xiàn)二層通信;對于開啟了三層協(xié)議的物理交換機(jī)而言,其ip路由的處理 也是由物理交換機(jī)管理程序提供的。對于docker0而言,其負(fù)責(zé)處理二層交換機(jī)邏輯以及三層的處理程序其實(shí)就是宿主機(jī)上的Linux內(nèi)核 tcp/ip協(xié)議棧程序。而從宿主機(jī)來看,所有docker0從veth(只是個二層的存在,沒有綁定ipv4地址)接收到的數(shù)據(jù)包都會被宿主機(jī) 看成從docker0這塊網(wǎng)卡(第二個身份,綁定172.17.0.1)接收進(jìn)來的數(shù)據(jù)包,尤其是在進(jìn)入三層時,宿主機(jī)上的iptables就會 對docker0進(jìn)來的數(shù)據(jù)包按照rules進(jìn)行相應(yīng)處理(通過一些內(nèi)核網(wǎng)絡(luò)設(shè)置也可以忽略docker0 brigde數(shù)據(jù)的處理)。
在Docker容器網(wǎng)絡(luò)通信流程分析中,docker0在這兩種身份間來回切換。
實(shí)驗(yàn):使用namespace模擬容器通信
為了進(jìn)一步了解network namespace、bridge和veth在docker容器網(wǎng)絡(luò)中的角色和作用,我們來做一個demo:用network namespace模擬Docker容器網(wǎng)絡(luò)。
實(shí)驗(yàn)環(huán)境:
centos7
拓?fù)?/p>
已知,docker引擎中的容器網(wǎng)絡(luò)如圖1所示,因此,我們可以用network namespace來模擬出同樣的效果,實(shí)驗(yàn)拓?fù)淙鐖D3所示。
實(shí)驗(yàn)步驟:
1.創(chuàng)建兩個獨(dú)立的ns
[root@node2 ~]# ip netns add ns1
[root@node2 ~]# ip netns add ns2
#查看創(chuàng)建的ns
[root@node2 ~]# ip netns list
ns2
ns1
#通過ip netns exec命令可以在特定ns的內(nèi)部執(zhí)行相關(guān)程序(虛擬網(wǎng)絡(luò)空間除了網(wǎng)絡(luò)是虛的以外,文件系統(tǒng)完全和當(dāng)前系統(tǒng)共享,也就是說所有本地可以使用的命令都可以在虛擬網(wǎng)絡(luò)中使用),這個exec命令是至關(guān)重要的,后續(xù)還會發(fā)揮更大作用
[root@node2 ~]# ip netns exec ns1 ip a s
1: lo:
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
[root@node2 ~]# ip netns exec ns1 route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
[root@node2 ~]# ip netns exec ns2 ip a s
1: lo:
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
#可以看到,新建的ns的網(wǎng)絡(luò)設(shè)備只有一個loopback口,并且路由表為空。
2.創(chuàng)建網(wǎng)橋MyDocker0
[root@node2 ~]# brctl addbr MyDocker0
[root@node2 ~]# brctl show
bridge name bridge id STP enabled interfaces
MyDocker0 8000.000000000000 no
...
[root@node2 ~]# ip addr add 172.16.0.1/16 dev MyDocker0
#為MyDocker0網(wǎng)橋分配ip地址。
[root@node2 ~]# ip link set MyDocker0 up
#啟用網(wǎng)橋設(shè)備
[root@node2 ~]# ip route show
...
172.16.0.0/16 dev MyDocker0 proto kernel scope link src 172.16.0.1
...
#查看相應(yīng)的路由信息
3.創(chuàng)建并連接Veth Pair設(shè)備
到目前為止,default ns與ns1、ns2之間還沒有任何聯(lián)系,接下來,將使用veth pair將二者聯(lián)系起來。接下來創(chuàng)建ns1和default之間的veth pair,veth1和veth1p,并將二者分別連接到MyDocker0網(wǎng)橋上和ns1中。
#創(chuàng)建連接default ns與ns1之間的veth pair。 veth1和veth1p
[root@node2 ~]# ip link add veth1 type veth peer name veth1p
[root@node2 ~]# ip link show
...
20: veth1p@veth1:
link/ether fa:06:c6:d6:53:bb brd ff:ff:ff:ff:ff:ff
21: veth1@veth1p:
link/ether 56:76:1d:19:69:77 brd ff:ff:ff:ff:ff:ff
...
#將網(wǎng)卡插到MyDocker0網(wǎng)橋上,并開啟此網(wǎng)卡
[root@node2 ~]# brctl addif MyDocker0 veth1
[root@node2 ~]# ip link set veth1 up
[root@node2 ~]# brctl show
bridge name bridge id STP enabled interfaces
MyDocker0 8000.56761d196977 no veth1
...
#將veth1p放入ns1中:
[root@node2 ~]# ip link set veth1p netns ns1
[root@node2 ~]# ip netns exec ns1 ip a s
1: lo:
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
20: veth1p@if21:
link/ether fa:06:c6:d6:53:bb brd ff:ff:ff:ff:ff:ff link-netnsid 0
#這時,你在default ns中將看不到veth1p這個虛擬網(wǎng)絡(luò)設(shè)備了。按照圖3中的拓?fù)洌挥趎s1中的veth1p應(yīng)該更名為eth0
#修改網(wǎng)卡名字
[root@node2 ~]# ip netns exec ns1 ip link set veth1p name eth0
[root@node2 ~]# ip netns exec ns1 ip a
1: lo:
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
20: eth0@if21:
link/ether fa:06:c6:d6:53:bb brd ff:ff:ff:ff:ff:ff link-netnsid 0
#將ns1中的eth0生效并配置IP地址:
[root@node2 ~]# ip netns exec ns1 ip link set eth0 up
[root@node2 ~]# ip netns exec ns1 ip addr add 172.16.0.2/16 dev eth0
[root@node2 ~]# ip netns exec ns1 ip route add default via 172.16.0.1
#添加默認(rèn)路由,使得ns與主機(jī)上的其他ns能夠連通。
對ns2進(jìn)行相同的步驟
ip link add veth2 type veth peer name veth2p
#新建veth pair設(shè)備
brctl addif MyDocker0 veth2
#將veth pair一端插入網(wǎng)橋
ip link set veth2 up
#開啟網(wǎng)卡
ip link set veth2p netns ns2
#將vethpair一端放到指定ns中
ip netns exec ns2 ip link set veth2p name eth0
ip netns exec ns2 ip link set eth0 up
#修改ns2中的網(wǎng)卡名字,并重啟。
ip netns exec ns2 ip addr add 172.16.0.3/16 dev eth0
#給ns2中的網(wǎng)卡添加地址
ip netns exec ns2 ip route add default via 172.16.0.1
#添加默認(rèn)路由,使得ns與主機(jī)上的其他ns能夠連通。
測試連通性:
ns1和MyDocker0是否互通,
ns1與ns2是否互通,
ns1與宿主機(jī)的其他網(wǎng)卡(比如docker中的docker0網(wǎng)橋是否相通)
ns1與宿主機(jī)外的其他主機(jī)是否互通
[root@node2 ~]# ip netns exec ns1 ping -c 3 172.16.0.1
PING 172.16.0.1 (172.16.0.1) 56(84) bytes of data.
64 bytes from 172.16.0.1: icmp_seq=1 ttl=64 time=0.236 ms
64 bytes from 172.16.0.1: icmp_seq=2 ttl=64 time=0.152 ms
64 bytes from 172.16.0.1: icmp_seq=3 ttl=64 time=0.238 ms
--- 172.16.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, t
#ns1和MyDocker0連通
[root@node2 ~]# ip netns exec ns1 ping -c 3 172.17.0.1
PING 172.17.0.1 (172.17.0.1) 56(84) bytes of data.
64 bytes from 172.17.0.1: icmp_seq=1 ttl=64 time=0.104 ms
64 bytes from 172.17.0.1: icmp_seq=2 ttl=64 time=0.117 ms
#ns1與docker0連通
[root@node2 ~]# ip netns exec ns1 ping -c 3 172.16.0.3
PING 172.16.0.3 (172.16.0.3) 56(84) bytes of data.
64 bytes from 172.16.0.3: icmp_seq=1 ttl=64 time=0.153 ms
64 bytes from 172.16.0.3: icmp_seq=2 ttl=64 time=0.146 ms
#ns1與ns2連通
#實(shí)現(xiàn)自制的ns與外網(wǎng)進(jìn)行通信。
[root@node2 ~]# iptables -t nat -A POSTROUTING -s 172.16.0.0/16 ! -o mydocker0 -j MASQUERADE
#增加一條
[root@node2 ~]# ip netns exec ns1 ping www.baidu.com
PING www.wshifen.com (103.235.46.39) 56(84) bytes of data.
64 bytes from 103.235.46.39 (103.235.46.39): icmp_seq=1 ttl=42 time=247 ms
64 bytes from 103.235.46.39 (103.235.46.39): icmp_seq=2 ttl=42 time=235 ms
自制ns與docker容器之間的通信流程
如果是在ns1中ping某個docker container的地址,比如172.17.0.2,那么其流程如下所示。
1.當(dāng)ping執(zhí)行后,根據(jù)ns1下的路由表,沒有匹配到直連網(wǎng)絡(luò),只能通過default路由將數(shù)據(jù)包轉(zhuǎn)發(fā)給Gateway: 172.16.0.1。
2.MyDocker0接收到數(shù)據(jù),數(shù)據(jù)進(jìn)入到宿主機(jī)的內(nèi)核空間。雖然都是MyDocker0接收數(shù)據(jù),但這次更類似于“數(shù)據(jù)被直接發(fā)到 Bridge 上,而不是Bridge從一個端口接收。二層的目的mac地址填寫的是gateway 172.16.0.1自己的mac地址(Bridge的mac地址),此時的MyDocker0更像是一塊普通網(wǎng)卡的角色,工作在三層(而不是之前的二層網(wǎng)橋的角色)。
3.MyDocker0收到數(shù)據(jù)包后,發(fā)現(xiàn)并非是發(fā)給自己的ip包,通過主機(jī)路由表找到直連路由(凡是匹配到這條規(guī)則的 IP 包,通過二層網(wǎng)絡(luò)直接發(fā)往目的主機(jī)),通過arp查詢,查詢到IP地址為172.17.0.2的mac地址。然后通過二層網(wǎng)絡(luò)將數(shù)據(jù)包轉(zhuǎn)發(fā)到docker container中。
[root@node2 ~]# ip neigh show dev docker0
172.17.0.2 lladdr 02:42:ac:11:00:02 STALE
MyDocker0將數(shù)據(jù)包Forward到通過traceroute可以印證這一過程:
[root@node2 ~]# ip netns exec ns1 traceroute -n 172.17.0.2
traceroute to 172.17.0.2 (172.17.0.2), 30 hops max, 60 byte packets
1 172.16.0.1 0.100 ms 0.052 ms 0.050 ms
2 172.17.0.2 0.138 ms 0.117 ms 0.119 ms
參考文獻(xiàn)
理解Docker單機(jī)容器網(wǎng)絡(luò)
理解Docker容器網(wǎng)絡(luò)之Linux Network Namespace
network namespace 簡介
張磊,極客時間專欄
Docker 容器 網(wǎng)絡(luò)
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。