Docker容器間網路互聯原理,講不明白算我輸....

賜我白日夢發表於2021-11-05

@

一、今天我們要搞明白的實驗

如上紅字所描述:同一個宿主機上的不同容器之間的網路如何互通的???

下面就一步一步探索答案!

點選閱讀原文

點選檢視視訊講解
點選檢視視訊講解

二、前置網路知識

2.1、docker預設為我們建立的網路

我們安裝完docker之後,docker daemon會為我們自動建立3個網路,如下:

~]# docker network ls
NETWORK ID     NAME                DRIVER    SCOPE
e71575e3722a   bridge              bridge    local
ab8e3d45575c   host                host      local
0c9b7c1134ff   none                null      local

其實docker有4種網路通訊模型,分別是:bridge、host、none、container

預設的使用的網路模型是bridge,也是我們生產上會使用到的網路模型。

下文中跟大家分享docker容器互通原理到時候呢,用到的也是bridge網路模型,另外如果你之前不瞭解也沒關係,可以看下我下面的這篇文章補一下,不難,一看就懂!

1、白日夢的Docker網路入門筆記


2.2、怎麼理解docker0網橋

另外,當我們安裝完docker之後,docker會為我們建立一個叫docker0的網路裝置

通過ifconfig命令可以檢視到它,看起來它貌似和eth0網路地位相當,像是一張網路卡。然而並不是,docker0其實是一個Linux網橋

[root@vip ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:b4:97:ee brd ff:ff:ff:ff:ff:ff
    inet 10.4.7.99/24 brd 10.4.7.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:feb4:97ee/64 scope link
       valid_lft forever preferred_lft forever
       
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:db:fe:ff:db brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:dbff:fefe:ffdb/64 scope link
       valid_lft forever preferred_lft forever

何以見得?可以通過下面的命令檢視作業系統上的網橋資訊

 ~]# yum install bridge-utils
 ~]# brctl show
bridge name	bridge id		STP enabled	interfaces
docker0		8000.0242f0a8c0be	no		veth86e2ef2
																	vethf0a8bcb

那大家怎麼理解Linux網橋的概念呢?

其實大家可以把docker0理解成一臺虛擬的交換機!然後像下面這樣類比著理解,就會豁然開朗

1、它好比是大學在機房上課時,老師旁邊的那個大大的交換機裝置。

2、把機房裡的電腦都連線在交換機上,類比成docker 容器作為一臺裝置都連線著宿主機上的docker0。

3、把交換機和機房中的機器的ip在同一個網段,類比成docker0、和你啟動的docker容器的ip也同屬於172網段。

# docker0 ip是:
 ~]# ifconfig
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:db:fe:ff:db brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:dbff:fefe:ffdb/64 scope link
       valid_lft forever preferred_lft forever
       

# 進入容器中檢視ip是:
/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 13  bytes 1102 (1.0 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

類比成這樣:


2.3、什麼是veth-pair技術?

我們剛才做類比理解docker0的時候說:把機房裡的電腦都連線在交換機上,類比成docker 容器作為一臺裝置都連線著宿主機上的docker0。那具體的實現落地實現用的是啥技術呢?

答案是:veth pair

veth pair的全稱是:virtual ethernet,就是虛擬的乙太網卡。

說到乙太網卡大家都不陌生呀,不就是我們常見的那種叫eth0或者是ens的網路裝置嗎?

那這個veth pair是怎麼玩的呢?有啥用呢?大家可以看下面這張圖

veth-pair裝置總是會成對的出現,用於連線兩個不同network-namespace.

就上圖來說,從network-namespace1的veth0中傳送的資料會出現在 network-namespace2的veth1裝置中。

雖然這種特性很好,但是如果出現有多個容器,你就會發現組織架構會越來越複雜,越來越亂

不過好在我們已經循序漸進的瞭解Linux網橋(docker0),以及這裡的veth-pair裝置,於是我們可以把整體的架構圖重新繪製成下面這樣

因為不同容器有自己隔離後的network-namespace所以他們都有自己的網路協議棧

那我們能不能找到容器裡面的網路卡和物理機上的哪張卡是一對網路vethpair裝置呢?

如下:

# 進入容器
~]# docker exec -ti 545ed62d3abf /bin/bash

/# apt-get install ethtool
/# ethtool -S eth0
NIC statistics:
     peer_ifindex: 55

回到宿主機

~]# ip addr
	...
55: vethf0a8bcb@if54: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    link/ether ae:eb:5c:2f:7d:c3 brd ff:ff:ff:ff:ff:ff link-netnsid 10
    inet6 fe80::aceb:5cff:fe2f:7dc3/64 scope link
       valid_lft forever preferred_lft forever

意思是就是說,容器545ed62d3abfeth0網路卡和宿主機通過ip addr命令檢視的網路裝置標號55的裝置組成一對vethpair裝置,彼此流量互通!

三、同一個區域網中不同主機的互聯原理

先看個簡單的,同一個區域網中的不同主機A、B之間是如何互聯交換資料的。如下圖

那,既然是同一個區域網中,說明A、B的ip地址在同一個網段,如上圖就假設它們都在192.168.1.0網段。

還得再看下面這張OSI 7層網路模型圖。

主機A向主機B傳送資料,對主機A來說資料會從最上層的應用層一路往下層傳遞。比如應用層使用的http協議、傳輸層使用的TCP協議,那資料在往下層傳遞的過程中,會根據該層的協議新增上不同的協議頭等資訊。

根據OSI7層網路模型的設定,對於接受資料的主機B來說,它會接收到很多資料包!這些資料包會從最下層的物理層依次往上層傳遞,依次根據每一層的網路協議進行拆包。一直到應用層取出主機A傳送給他的資料。

那麼問題來了,主機B怎麼判斷它收到的資料包是否是傳送給自己的呢?萬一有人發錯了呢?

答案是:根據MAC地址,邏輯如下。

if 收到的資料包.MAC地址 == 自己的MAC地址{
  // 接收資料
  // 處理資料包
}else{
 // 丟棄
}

那對於主機A來說,它想傳送給主機B資料包,還不能讓主機B把這個資料包扔掉,它只能中規中矩的按乙太網網路協議要求封裝將要傳送出去的資料包,往下傳遞到資料鏈路層(這一層傳輸的資料要求,必須要有目標mac地址,因為資料鏈路層是基於mac地址做資料傳輸的)。

那資料包中都需要哪些欄位呢?如下:

src ip = 192.168.1.2  //源ip地址,交換機
dst ip = 192.168.1.3  //目標ip地址
//本機的mac地址(保證從主機B回來的包正常送達主機A,且主機A能正常處理它)
src mac = 主機A的mac地址
dst mac = 主機B的mac地址//目標mac地址

其中的dst ip好說,我們可以直接固定寫,或者通過DNS解析域名得到目標ip。

dst mac怎麼獲取呢?

這就不得不說ARP協議了! ARP其實是一種地址解析協議,它的作用就是:以目標ip為線索,找到目的ip所在機器的mac地址。也就是幫我們找到dst mac地址!大概的過程如下幾個step

推薦閱讀:白日夢的DNS筆記

簡述這個過程:主機A想給主機B發包,那需要知道主機B的mac地址。

  1. 主機A查詢本地的arp 快取記憶體中是否已經存在dst ipdst mac地址的對映關係了,如果已存在,那就直接用。
  2. 本地arp快取記憶體中不存在dst ipdst mac地址的對映關係的話那就只能廣播arp請求包,同一網段的所有機器都能收到arp請求包。
  3. 收到arp請求包的機器會對比arp包中的src ip是否是自己的ip,如果不是則直接丟棄該arp包。如果是的話就將自己的mac地址寫到arp響應包中。並且它會把請求包中src ipsrc mac的對映關係儲存在自己的本地。

補充:

交換機本身也有學習能力,他會記錄mac地址和交換機埠的對映關係。比如:mac=a,埠為1。

那當它接收到資料包,並發現mac=a時,它會直接將資料扔向埠1。

嗯,在arp協議的幫助下,主機A順利拿到了主機B的mac地址。於是資料包從網路層流轉到資料鏈路層時已經被封裝成了下面的樣子:

src ip = 192.168.1.2
src mac = 主機A的mac地址
dst ip = 192.168.1.3
dst mac = 主機B的mac地址

網路層基於ip地址做資料做轉發

資料鏈路基於mac地址做資料轉發

根據OIS7層網路模型,我們都知道資料包經過物理層傳送到機器B,機器B接收到資料包後,再將資料包向上流轉,拆包。流轉到主機B的資料鏈路層。

那主機B是如何判斷這個在資料鏈路層的包是否是發給自己的呢?

答案前面說了,根據目的mac地址判斷。

// 主機B
if 收到的資料包.MAC地址 == 自己的MAC地址{
  if dst ip == 本機ip{
    // 本地處理資料包
  }else{
    // 查詢路由表,根據路由表的規則,將資料包轉某個某卡、或者預設閘道器
  }
}else{
 // 直接丟棄
}

這個例子比較簡單,dst ip就是主機B的本機ip 所以它自己會處理這個資料包。

那資料包處理完之後是需要給主機A一個響應包,那問題又來了,響應包該封裝成什麼樣子呢?對主機B來說響應包也需要src ipsrc macdst ipdst mac

src ip = 192.168.1.3
src mac = 主機B的mac地址
dst ip = 192.168.1.2
src mac = 主機A的mac地址 (之前通過arp記錄在自己的arp快取記憶體中了,所以,這次直接用)

同樣的道理,響應包也會按照如下的邏輯被主機A接受,處理。

// 主機A
if 收到的資料包.MAC地址 == 自己的MAC地址{
  if dst ip == 本機ip{
    // 本地處理資料包
  }else{
    // 查詢路由表,根據路由表的規則,將資料包轉某個某卡、或者預設閘道器
  }
}else{
 // 直接丟棄
}

這一次,讓我在百度告訴你,當你請求www.baidu.com時都發生了什麼?

四、容器網路互通原理

有了上面那些知識儲備呢?再看我們今天要探究的問題,就不難了。

如下紅字部分:同一個宿主機上的不同容器是如何互通的?

那我們先分別登陸容器記錄下他們的ip

9001的ip是:172.17.0.2
9002的ip是:172.17.0.3

先看實驗效果:在9001上curl9002

/# curl 172.7.88.3
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
...

實驗結果是網路互通!

我們再完善一下上面的圖,把docker0、以及兩個容器的ip補充上去,如下圖:

那兩臺機器之前要通訊是要遵循OSI網路模型、和乙太網協議的。

我們管172.17.0.2叫做容器2

我們管172.17.0.3叫做容器3

比如我們現在是從:容器2上curl 容器3,那麼容器2也必須按照乙太網協議將資料包封裝好,如下

src ip = 172.17.0.2
src mac = 容器2的mac地址
dst ip = 172.17.0.3
dst mac = 容器3的mac地址 ???

那現在的問題是容器3的mac地址是多少?

刪掉所有容器,重新啟動,方便實驗抓包

容器2會先查自己的本地快取,如果之前沒有訪問過,那麼快取中也沒有任何記錄!

:/# arp -n

不過沒關係,還有arp機制兜底,於是容器2會傳送arp請求包,大概如下

1、這是一個arp請求包
2、我的ip地址是:172.17.0.2
3、我的mac地址是:容器2的mac地址
4、請問:ip地址為:172.17.0.3的機器,你的mac地址是多少?

容器2會查詢自己的路由表,將這個arp請求從自己的gateway傳送出去

/# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.7.88.1      0.0.0.0         UG    0      0        0 eth0
172.7.88.0      0.0.0.0         255.255.255.0   U     0      0        0 eth0

我們發現容器2的閘道器對應的網路裝置的ip就是docker0的ip地址,並且經由eth0傳送出去!

哎?eth0不就是我們之前說的veth-pair裝置嗎?

並且我們通過下面的命令可以知道它的另一端對應著宿主機上的哪個網路裝置:

/# ethtool -S eth0
NIC statistics:
     peer_ifindex: 53

而且我們可以下面的小實驗,驗證上面的觀點是否正確

# 在容器中ping百度
~]# ping 220.181.38.148

# 在宿主機上抓包
~]# yum install tcpdump -y
~]# tcpdump -i ${vethpair宿主機側的介面名} host 220.181.38.148

...

所以說從容器2的eth0出去的arp請求報文會同等的出現在宿主機的第53個網路裝置上。

通過下面的這張圖,你也知道第53個網路裝置其實就是下圖中的veth0-1

所以這個arp請求包會被髮送到docker0上,由docker0拿到這個arp包發現,目標ip是172.17.0.3並不是自己,所以docker0會進一步將這個arp請求報文廣播出去,所有在172.17.0.0網段的容器都能收到這個報文!其中就包含了容器3!

那容器3收到這個arp報文後,會判斷,哦!目標ip就是自己的ip,於是它將自己的mac地址填充到arp報文中返回給docker0!

同樣的我們可以通過抓包驗證,在宿主機上

# 在172.17.0.2容器上ping172.17.0.3
/# ping 172.17.0.3

~]# tcpdump -i vethdb0d222
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on vethdb0d222, link-type EN10MB (Ethernet), capture size 262144 bytes

17:25:30.218640 ARP, Request who-has 172.17.0.3 tell 172.17.0.2, length 28
17:25:30.218683 ARP, Reply 172.17.0.3 is-at 02:42:ac:11:00:03 (oui Unknown), length 28
17:25:30.218686 IP 172.17.0.2.54014 > 172.17.0.3.http: Flags [S], seq 3496600258, win 29200, options [mss 1460,sackOK,TS val 4503202 ecr 0,nop,wscale 7], length 0

於是容器2就拿到了容器3的mac地址,乙太網資料包需要的資訊也就齊全了!如下:

src ip = 172.17.0.2
src mac = 容器2的mac地址
dst ip = 172.17.0.3
dst mac = 容器3的mac地址

再之後容器2就可以和容器3正常互聯了!

容器3會收到很多資料包,那它怎麼知道哪些包是發給自己的,那些不是呢?可以參考如下的判斷邏輯

if 響應包.mac == 自己的mac{
 // 說明這是發給自己包,所以不能丟棄
  if 響應包.ip == 自己的ip{
    // 向上轉發到osi7層網路模型的上層
  }else{
    // 查自己的route表,找下一跳
  }
}else{
 // 直接丟棄
}

五、實驗環境

# 下載
 ~]# docker pull registry.cn-hangzhou.aliyuncs.com/changwu/nginx:1.7.9-nettools
 
# 先啟動1個容器
 ~]# docker run --name mynginx1 -i -t -d -p 9001:80 nginx-1.7.9-nettools:latest
eb569b938c07e95ccccbfc654c1fee6364eea55b20f5394382ff42b4ccf96312

~]# docker run --name mynginx2 -i -t -d -p 9002:80 nginx-1.7.9-nettools:latest
545ed62d3abfd63aa9c3ae196e9d7fe6f59bbd2e9ae4e6f2bd378f23587496b7

# 驗證
~]# curl 127.0.0.1:9001

六、推薦閱讀

1、白日夢的Docker網路入門筆記

2、這一次,讓我在百度告訴你,當你請求www.baidu.com時都發生了什麼?

3、白日夢的網路筆記:iptables、防火牆

4、白日夢的DNS筆記

七、原創不易!歡迎關注啊!

點選閱讀原文
點選閱讀原文
點選閱讀原文

點選檢視視訊講解
點選檢視視訊講解
點選檢視視訊講解

相關文章