docker容器中的網絡原理(單機模式下的容器網絡)

容器的本質是一個被隔離的進程,而這個進程又有其獨立的網絡棧,即網卡(Network Interface)、迴環設備(Loopback Device)、路由表(Routing Table)和 iptables 規則。單機時代的容器網絡實際上有三種通信需求,分別是:

  • 容器之間通信
  • 容器與宿主機之間通信
  • 容器與外部主機通信

在docker中實現單機時代容器的網絡,主要依靠以下這幾個工具。

名稱 作用
network namespace 網絡名稱空間,允許你在Linux創建相互隔離的網絡視圖,每個網絡名字空間都有獨立的網絡配置,比如:網絡設備、路由表、iptables規則等。新建的網絡名稱空間與主機默認網絡名字空間之間是隔離的。我們平時默認操作的是主機的默認網絡名稱空間。
Linux bridge 即Linux網橋設備,是Linux提供的一種虛擬網絡設備之一。其工作方式非常類似於物理的網絡交換機設備。Linux Bridge可以工作在二層,也可以工作在三層,默認工作在二層。工作在二層時,可以在同一網絡的不同主機間轉發以太網報文;一旦你給一個Linux Bridge分配了IP地址,也就開啓了該Bridge的三層工作模式。在Linux下,你可以用iproute2工具包或brctl命令對Linux bridge進行管理。
veth pair 是Linux提供的另外一種特殊的網絡設備,中文稱爲虛擬網卡接口。它總是成對出現,要創建就創建一個pair。一個Pair中的veth就像一個網絡線纜的兩個端點,數據從一個端點進入,必然從另外一個端點流出。每個veth都可以被賦予IP地址,並參與三層網絡路由過程。

概念總是抽象的,後面我將會做實驗使用這三種設備模擬主機上的容器通信流程,在這之前,讓我們先搞明白單機時代的容器通信過程,然後做實驗驗證這一過程。

容器通信原理

容器之間通信

不同容器之間的通信,類似於不同主機之間的通信。如果你想要實現兩臺主機之間的通信,最直接的辦法,就是把它們用一根網線連接起來;而如果你想要實現多臺主機之間的通信,那就需要用網線,把它們連接在一臺交換機上。在 Linux 中,能夠起到虛擬交換機作用的網絡設備,是網橋(Bridge)。Docker 項目會默認在宿主機上創建一個名叫 docker0 的網橋,凡是連接在 docker0 網橋上的容器,就可以通過它來進行通信。於是,問題的關鍵在如何將容器連接到docker0網橋上。使用一種名叫Veth Pair的虛擬設備。

Veth Pair 設備的特點是:它被創建出來後,總是以兩張虛擬網卡(Veth Peer)的形式成對出現的。並且,從其中一個“網卡”發出的數據包,可以直接出現在與它對應的另一張“網 卡”上,哪怕這兩個“網卡”在不同的 Network Namespace 裏。

下圖所示的container1中的eth0 網卡,是一個 Veth Pair,它的一端在這個容器的 Network Namespace 裏,而另一端則位於宿主機上(Host Namespace),並且被“插”在 了宿主機的 docker0 網橋上。一旦一張虛擬網卡被“插”在網橋上,它就會變成該網橋的“從設備”。從設備會被“剝奪”調用網絡協議棧處理數據包的資格,從而“降級”成爲網橋上的一個端口。而這個端口唯一的作用,就是接收流入的數據包,然後把這些數據包的“生殺大權”(比如轉發或者丟棄),全部交給對應的網橋。
在這裏插入圖片描述container1與container2的通信過程如下所示:

1.container 1查看自己的路由表,匹配到合適的路由。如下所示,container 1將會匹配到第二條路由,即直連路由(凡是匹配到這條規則的 IP 包,應該經過本機的eth0網卡,通過二層網絡直接發往目的主機),即無需gateway轉發便可以直接將數據包送達。

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.發起arp請求,查詢目標IP地址爲172.17.0.3的容器的mac地址。arp查詢後(要麼從arp cache中找到,要麼在docker0這個二層交換機中泛洪查詢)獲得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.得到對應mac地址後,將數據包轉發給該mac地址對應的網卡即可。類似局域網上兩臺主機之間的通信過程。此時兩個container之間連通,主要還是通過直連網絡,實質上是docker0在二層起到的作用。

容器與宿主機之間通信

當你在一臺宿主機上,訪問該宿主機上的容器的 IP 地址時,這個請求的數據包, 也是先根據路由規則到達 docker0 網橋,然後被轉發到對應的 Veth Pair設備,最後出現在容器裏。這個過程的示意圖,如下所示:
在這裏插入圖片描述
1.宿主機查看自己的路由信息,匹配到合適的路由,在本例中,匹配到了直連路由,無需gateway轉發便可以直接將數據包送達。

[root@node2 ~]# ip route
...
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
...

2.發起arp請求,查詢目標IP地址爲172.17.0.2的容器的mac地址。arp查詢後(要麼從arp cache中找到,要麼在docker0這個二層交換機中泛洪查詢)獲得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.得到對應mac地址後,將數據包轉發給該mac地址對應的網卡即可。

容器與宿主機外部通信

當一個容器試圖連接到另外一個宿主機時,比如:ping 10.168.0.3,它發出的請求數據包,首先經過 docker0 網橋出現在宿主機上。然後根據宿主機的路由表裏的直連路由規則 (10.168.0.0/24 via eth0)),對 10.168.0.3 的訪問請求就會交給宿主機的 eth0 處理。所以接下來,這個數據包就會經宿主機的 eth0 網卡轉發到宿主機網絡上,最終到達 10.168.0.3 對應的宿主機上。當然,這個過程的實現要求這兩臺宿主機本身是連通的。這個過程的示意圖如下所示。
在這裏插入圖片描述1.首先,根據上一個例子,可以知道,容器到宿主機之間的通信是通的,即數據包能夠發送到docker0網卡上,進入到宿主機內核空間。那麼問題的關鍵就在於,怎麼把這個數據包發送到宿主機外部。

2.已知172.17.0.0/16網段,是docker專門用來給容器通信使用的私有網段。想要讓數據包突破宿主機的關鍵就在於使用nat技術,將源ip地址爲容器ip,目的是要與容器外部通信的的數據包,修改爲MASQURADE模式,即修改爲宿主機網卡的ip地址。查看iptables規則,發現如下一條規則。

-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
#將數據包源地址屬於docker網段的,並且出口網卡不是docker0的(即),都做nat技術僞裝爲其出口網卡地址。

3.被nat技術修改過的報文將會被髮送到主機外部與主機進行通信。

[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單機模式下的通信原理,其核心在於理解docker0網橋在整個容器通信過程中承擔的作用。

下圖中我們給出了Docker0的雙重身份,並對比物理交換機,我們來理解一下Docker0這個軟網橋。
在這裏插入圖片描述
圖2 docker0的多重身份

1、從容器視角,網橋(交換機)身份
docker0對於通過veth pair“插在”網橋上的container1和container2來說,首先就是一個二層的交換機的角色:泛洪、維護cam表,在二層轉發數據包;同時由於docker0自身也具有mac地址(這個與純二層交換機不同),並且綁定了ip(這裏是172.17.0.1),因此在 container中還作爲container default路由的默認Gateway而存在。

2、從宿主機視角,網卡身份
物理交換機提供了由硬件實現的高效的背板通道,供連接在交換機上的主機高效實現二層通信;對於開啓了三層協議的物理交換機而言,其ip路由的處理 也是由物理交換機管理程序提供的。對於docker0而言,其負責處理二層交換機邏輯以及三層的處理程序其實就是宿主機上的Linux內核 tcp/ip協議棧程序。而從宿主機來看,所有docker0從veth(只是個二層的存在,沒有綁定ipv4地址)接收到的數據包都會被宿主機 看成從docker0這塊網卡(第二個身份,綁定172.17.0.1)接收進來的數據包,尤其是在進入三層時,宿主機上的iptables就會 對docker0進來的數據包按照rules進行相應處理(通過一些內核網絡設置也可以忽略docker0 brigde數據的處理)。

在Docker容器網絡通信流程分析中,docker0在這兩種身份間來回切換。

實驗:使用namespace模擬容器通信

爲了進一步瞭解network namespace、bridge和veth在docker容器網絡中的角色和作用,我們來做一個demo:用network namespace模擬Docker容器網絡。

實驗環境:

centos7

拓撲

已知,docker引擎中的容器網絡如圖1所示,因此,我們可以用network namespace來模擬出同樣的效果,實驗拓撲如圖3所示。
在這裏插入圖片描述圖3 實驗模擬拓撲結構

實驗步驟:

1.創建兩個獨立的ns

[root@node2 ~]# ip netns add ns1
[root@node2 ~]# ip netns add ns2

#查看創建的ns
[root@node2 ~]# ip netns list
ns2
ns1

#通過ip netns exec命令可以在特定ns的內部執行相關程序(虛擬網絡空間除了網絡是虛的以外,文件系統完全和當前系統共享,也就是說所有本地可以使用的命令都可以在虛擬網絡中使用),這個exec命令是至關重要的,後續還會發揮更大作用

[root@node2 ~]# ip netns exec ns1 ip a s  
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    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: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

#可以看到,新建的ns的網絡設備只有一個loopback口,並且路由表爲空。

2.創建網橋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網橋分配ip地址。
[root@node2 ~]# ip link set MyDocker0 up
#啓用網橋設備
[root@node2 ~]# ip route show        
...
172.16.0.0/16 dev MyDocker0 proto kernel scope link src 172.16.0.1 
...
#查看相應的路由信息

3.創建並連接Veth Pair設備

到目前爲止,default ns與ns1、ns2之間還沒有任何聯繫,接下來,將使用veth pair將二者聯繫起來。接下來創建ns1和default之間的veth pair,veth1和veth1p,並將二者分別連接到MyDocker0網橋上和ns1中。

#創建連接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: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether fa:06:c6:d6:53:bb brd ff:ff:ff:ff:ff:ff
21: veth1@veth1p: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 56:76:1d:19:69:77 brd ff:ff:ff:ff:ff:ff
 ...
 
#將網卡插到MyDocker0網橋上,並開啓此網卡
[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: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
20: veth1p@if21: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether fa:06:c6:d6:53:bb brd ff:ff:ff:ff:ff:ff link-netnsid 0
    
#這時,你在default ns中將看不到veth1p這個虛擬網絡設備了。按照圖3中的拓撲,位於ns1中的veth1p應該更名爲eth0

#修改網卡名字
[root@node2 ~]# ip netns exec ns1 ip link set veth1p name eth0 
[root@node2 ~]# ip netns exec ns1 ip a 
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
20: eth0@if21: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    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
#添加默認路由,使得ns與主機上的其他ns能夠連通。

對ns2進行相同的步驟

ip link add veth2 type veth peer name veth2p
#新建veth pair設備
brctl addif MyDocker0 veth2
#將veth pair一端插入網橋
ip link set veth2 up
#開啓網卡
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中的網卡名字,並重啓。
ip netns exec ns2 ip addr add 172.16.0.3/16 dev eth0
#給ns2中的網卡添加地址
ip netns exec ns2 ip route add default via 172.16.0.1
#添加默認路由,使得ns與主機上的其他ns能夠連通。

測試連通性:

  • ns1和MyDocker0是否互通,
  • ns1與ns2是否互通,
  • ns1與宿主機的其他網卡(比如docker中的docker0網橋是否相通)
  • ns1與宿主機外的其他主機是否互通
[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連通


#實現自制的ns與外網進行通信。
[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. 當ping執行後,根據ns1下的路由表,沒有匹配到直連網絡,只能通過default路由將數據包轉發給Gateway: 172.16.0.1。
  2. MyDocker0接收到數據,數據進入到宿主機的內核空間。雖然都是MyDocker0接收數據,但這次更類似於“數據被直接發到 Bridge 上,而不是Bridge從一個端口接收。二層的目的mac地址填寫的是gateway 172.16.0.1自己的mac地址(Bridge的mac地址),此時的MyDocker0更像是一塊普通網卡的角色,工作在三層(而不是之前的二層網橋的角色)。
  3. MyDocker0收到數據包後,發現並非是發給自己的ip包,通過主機路由表找到直連路由(凡是匹配到這條規則的 IP 包,通過二層網絡直接發往目的主機),通過arp查詢,查詢到IP地址爲172.17.0.2的mac地址。然後通過二層網絡將數據包轉發到docker container中。
[root@node2 ~]# ip neigh show dev docker0
172.17.0.2 lladdr 02:42:ac:11:00:02 STALE

MyDocker0將數據包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

參考文獻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章