數論知識小結 [基礎篇]
前文說到容器網路對Linux虛擬化技術的依賴,這一篇章我們將一探究竟,看看Docker究竟是怎麼做的。
通常,Linux容器的網路是被隔離在它自己的Network Namespace中,其中就包括:網絡卡(Network Interface)、迴環裝置(Loopback Device)、路由表(Routing Table)和iptables規則。
對於一個程序來說,這些要素,就構成了它發起和響應網路請求的基本環境。
管中窺豹
我們在執行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通的。也就意味著,這兩個容器是可以互相通訊的。
容器通訊
我們不妨結合前文時所說的,理解下為什麼一個容器能訪問另一個容器?先簡單看如一幅圖:
當在容器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將響應(Ping)返回給容器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。顯然,這並不是一個很好的設計,這需要應用去配合配置。所以,基於此,我們肯定要尋找其他的容器網路解決方案。
在上圖這種容器網路中,我們需要在我們已有的主機網路上,通過軟體構建一個覆蓋在多個主機之上,且能把所有容器連通的虛擬網路。這種就是Overlay Network(覆蓋網路)。關於這些具體的網路解決方案,例如Flannel、Calico等,我會在後續篇幅繼續陳述。
福利:豆花同學為大家精心整理了一份關於linux和python的學習資料大合集!有需要的小夥伴們,關注豆花個人公眾號:python頭條!回覆關鍵詞“資料合集”即可免費領取!