Docker容器網絡-實現篇


通常,Linux容器的網絡是被隔離在它自己的Network Namespace中,其中就包括:網卡(Network Interface)、回環設備(Loopback Device)、路由表(Routing Table)和iptables規則。對於一個進程來說,這些要素,就構成了它發起和響應網絡請求的基本環境。

前文說到容器網絡對Linux虛擬化技術的依賴,這一篇章我們將一探究竟,看看Docker究竟是怎么做的。

管中窺豹

我們在執行 docker run -d --name xxx   之后,進入容器內部:

## docker ps 可查看所有docker
## 進入容器
docker exec -it 228ae947b20e /bin/bash

並執行 ifconfig :

$ ifconfig
eth0      Link encap:Ethernet  HWaddr 22:A4:C8:79:DD:1A
          inet addr:192.168.65.28  Bcast:0.0.0.0  Mask:255.255.255.255
          UP BROADCAST RUNNING MULTICAST  MTU:1440  Metric:1
          RX packets:2231528 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3340914 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:249385222 (237.8 MiB)  TX bytes:590701793 (563.3 MiB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

我們看到一張叫eth0的網卡,它正是一個Veth Pair設備在容器的這一端。
我們再通過 route 查看該容器的路由表:

$ route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         169.254.1.1     0.0.0.0         UG    0      0        0 eth0
169.254.1.1     *               255.255.255.255 UH    0      0        0 eth0

我們可以看到這個eth0是這個容器的默認路由設備。我們也可以通過第二條路由規則,看到所有對 169.254.1.1/16 網段的請求都會交由eth0來處理。
而Veth Pair 設備的另一端,則在宿主機上,我們同樣也可以通過查看宿主機的網絡設備來查看它:

$ ifconfig
......
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.16.241.192  netmask 255.255.240.0  broadcast 172.16.255.255
        ether 00:16:3e:0a:f3:75  txqueuelen 1000  (Ethernet)
        RX packets 3168620550  bytes 727592674740 (677.6 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2937180637  bytes 8661914052727 (7.8 TiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
......
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:16:58:92:43  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        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
......
vethd08be47: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        ether 16:37:8d:fe:36:eb  txqueuelen 0  (Ethernet)
        RX packets 193  bytes 22658 (22.1 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 134  bytes 23655 (23.1 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
......

在宿主機上,容器對應的Veth Pair設備是一張虛擬網卡,我們再用 brctl show 命令查看網橋:

$ brctl show
bridge name	bridge id		STP enabled	interfaces
docker0		8000.0242afb1a841	no		vethd08be47

可以清楚的看到Veth Pair的一端 vethd08be47 就插在 docker0 上。

我現在執行docker run 啟動兩個容器,就會發現docker0上插入兩個容器的 Veth Pair的一端。如果我們在一個容器內部互相ping另外一個容器的IP地址,是不是也能ping通?

$ brctl show
bridge name	bridge id		STP enabled	interfaces
docker0		8000.0242afb1a841	no		veth26cf2cc
																	veth8762ad2

容器1:

$ docker exec -it f8014a4d34d0 /bin/bash
$ ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:03
          inet addr:172.17.0.3  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:76 errors:0 dropped:0 overruns:0 frame:0
          TX packets:106 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:16481 (16.0 KiB)  TX bytes:14711 (14.3 KiB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:48 errors:0 dropped:0 overruns:0 frame:0
          TX packets:48 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:2400 (2.3 KiB)  TX bytes:2400 (2.3 KiB)
$ 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:

$ docker exec -it 9a6f38076c04 /bin/bash
$ ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:133 errors:0 dropped:0 overruns:0 frame:0
          TX packets:193 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:23423 (22.8 KiB)  TX bytes:22624 (22.0 KiB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:198 errors:0 dropped:0 overruns:0 frame:0
          TX packets:198 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:9900 (9.6 KiB)  TX bytes:9900 (9.6 KiB)
$ 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

從一個容器ping另外一個容器:

# -> 容器1內部 ping 容器2
$ ping 172.17.0.3
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.142 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.096 ms
64 bytes from 172.17.0.3: seq=2 ttl=64 time=0.089 ms

我們看到,在一個容器內部ping另外一個容器的ip,是可以ping通的。也就意味着,這兩個容器是可以互相通信的。

容器通信

我們不妨結合前文時所說的,理解下為什么一個容器能訪問另一個容器?先簡單看如一幅圖:docker0 bridge net.png

當在容器1里訪問容器2的地址,這個時候目的IP地址會匹配到容器1的第二條路由規則,這條路由規則的Gateway是0.0.0.0,意味着這是一條直連規則,也就是說凡是匹配到這個路由規則的請求,會直接通過eth0網卡,通過二層網絡發往目的主機。而要通過二層網絡到達容器2,就需要127.17.0.3對應的MAC地址。所以,容器1的網絡協議棧就需要通過eth0網卡來發送一個ARP廣播,通過IP找到MAC地址。所謂ARP(Address Resolution Protocol),就是通過三層IP地址找到二層的MAC地址的協議。這里說到的eth0,就是Veth Pair的一端,另一端則插在了宿主機的docker0網橋上。eth0這樣的虛擬網卡插在docker0上,也就意味着eth0變成docker0網橋的“從設備”。從設備會降級成docker0設備的端口,而調用網絡協議棧處理數據包的資格全部交給docker0網橋。
所以,在收到ARP請求之后,docker0就會扮演二層交換機的角色,把ARP廣播發給其它插在docker0網橋的虛擬網卡上,這樣,127.17.0.3就會收到這個廣播,並把其MAC地址返回給容器1。有了這個MAC地址,容器1的eth0的網卡就可以把數據包發送出去。
這個數據包會經過Veth Pair在宿主機的另一端veth26cf2cc,直接交給docker0。docker0轉發的過程,就是繼續扮演二層交換機,docker0根據數據包的目標MAC地址,在CAM表查到對應的端口為veth8762ad2,然后把數據包發往這個端口。而這個端口,就是容器2的Veth Pair在宿主機的另一端,這樣,數據包就進入了容器2的Network Namespace,最終容器2將響應(Pong)返回給容器1。在真實的數據傳遞中,Linux內核Netfilter/Iptables也會參與其中,這里不再贅述。

CAM就是交換機通過MAC地址學習維護端口和MAC地址的對應表

這里介紹的容器間的通信方式就是docker中最常見的bridge模式,當然此外還有host模式、container模式、none模式等,對其它模式有興趣的可以去閱讀相關資料。

跨主通信

好了,這里不禁問個問題,到目前為止只是單主機內部的容器間通信,那跨主機網絡呢?
在Docker默認配置下,一台宿主機的docker0網橋是無法和其它宿主機連通的,它們之間沒有任何關聯,所以這些網橋上的容器,自然就沒辦法多主機之間互相通信。但是無論怎么變化,道理都是一樣的,如果我們創建一個公共的網橋,是不是集群中所有容器都可以通過這個公共網橋去連接?
當然在正常的情況下,節點與節點的通信往往可以通過NAT的方式,但是,這個在互聯網發展的今天,在容器化環境下未必適用。例如在向注冊中心注冊實例的時候,肯定會攜帶IP,在正常物理機內的應用當然沒有問題,但是容器化環境卻未必,容器內的IP很可能就是上文所說的172.17.0.2,多個節點都會存在這個IP,大概率這個IP是沖突的。如果我們想避免這個問題,就會攜帶宿主機的IP和映射的端口去注冊。但是這又帶來一個問題,即容器內的應用去意識到這是一個容器,而非物理機,當在容器內,應用需要去拿容器所在的物理機的IP,當在容器外,應用需要去拿當前物理機的IP。顯然,這並不是一個很好的設計,這需要應用去配合配置。所以,基於此,我們肯定要尋找其他的容器網絡解決方案。
docker host net.png

在上圖這種容器網絡中,我們需要在我們已有的主機網絡上,通過軟件構建一個覆蓋在多個主機之上,且能把所有容器連通的虛擬網絡。這種就是Overlay Network(覆蓋網絡)。
關於這些具體的網絡解決方案,例如Flannel、Calico等,我會在后續篇幅繼續陳述。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM