一、背景
? ? ? ? 之前分享關于NAT網絡地址轉換的相關文章,docker中的網絡正好使用到了NAT,順帶著把這個分享一下,分析docker容器的網絡數據包流出、數據流入原理分析。
? ? ? ? 知識回顧:
? ? ? ? docker運行一個容器之后,會給這個容器一個獨立的netns網絡命名空間和其他網絡命名空間進行隔離,形成自己獨立的、隔離的網絡棧。
? ? ? ? 并且,會生成1個veth paris虛擬網卡對(就像一根虛擬網線,有2個頭, 2個veth虛擬設備接口), 一頭插入容器的網絡命名空間,一頭插入docker0網橋(默認不創建自己的網橋,則會連接到docker0網橋)。 容器內部的路由表則將網關指向docker0的IP地址作為"下一跳", 非docker0內網網段的IP數據包會通過網關轉發,否則直接通過ARP廣播,拿到對方MAC地址,通過二層網絡直接通信。? ?
?
宿主機查看網絡設備:?
ip l
????????看到veth45c97? 位于15號網絡接口索引,連接if14號網絡接口。 并且后面看到關鍵詞 master docker0, 這個就代表這個veth橋接到了docker0網橋接口。
?容器內查看網絡設備:
ip l
????????看到容器自己的eth0位于14號網絡接口, 連接if15好網絡接口。兩者都是veth類型的虛擬網絡設備,是不是很像1根網線的2個插口,一邊插入docker0網橋, 一邊插入容器的網絡命名空間,從而實現通信。
二、運行nginx容器-分析網絡數據包流向
實驗信息:
? ? ? ? 1、宿主機內網IP地址:??192.168.2.116
? ? ? ? 2、我電腦內網IP地址:??192.168.2.104
1、運行nginx容器,并且8080:80端口映射
docker run -d -p 8080:80 nginx
查看容器ip地址為:? 172.17.0.4
?2、宿主機tcpdump監聽網卡數據包、docker0數據包
?我電腦使用curl訪問宿主機192.168.2.116:8080,抓包結果如下:
1、網卡enp0s3的抓包結果
請求數據包:源IP地址、端口: 192.168.2.104:52140 => 目的IP地址、端口: 192.168.2.116:8080再看響應數據包:源IP地址、端口: 192.168.2.116:8080 => 目的IP地址、端口: 192.168.2.104:52140
2、docker0網橋的抓包結果
請求數據包:源IP地址、端口: 192.168.2.104:52140 => 目的IP地址、端口: 172.17.0.4:80再看響應數據包:源IP地址、端口: 172.17.0.4:80 => 目的IP地址、端口: 192.168.2.104:52140
3、IP數據包差異分析
? ? ? ? 很明顯,我們發現, 目的IP數據包在enp0s3還是192.168.2.116:8080, 但是監聽docker0進來的數據,目的IP數據包變成了172.17.0.4:80。
? ? ? ? 數據包被修改了!? ?這個就是docker底層通過iptables做了DNAT的結果。? 就是將IP數據包為192.168.2.116:8080 做DNAT,修改為172.17.0.4:80, 再通過docker0轉發,docker0發現是自己的網橋橋接的內網IP,再將數據包轉發給nginx容器,完成數據傳輸。
4、DNAT驗證猜想,查看iptables規則表
iptables -t nat -L -n
?嘿嘿,果然不出所料,確實添加了一條DNAT規則, 將訪問宿主機8080端口數據包,改為轉發到172.17.0.4:80(nginx容器所在IP和端口)
5、SNAT原理也是如此
????????容器返回給客戶端的數據包,也經過了SNAT的過程,數據包經過SNAT,將源IP地址改為宿主機IP地址和宿主機端口, 最后客戶端才能得到?正確響應。
6、conntrack查看NAT映射關系記錄
yum install conntrack -yconntrack -Lconntrack -L | grep 172
? ? ? ? 這里就能清晰看到源客戶端IP是192.168.2.104、源客戶端端口是54640, 宿主機目的IP是192.168.2.116、宿主機目的端口8080?DNAT之后,目的IP為容器的172.17.0.2、容器端口變為80。? 因為要存在維持這么一條映射關系, 后面容器進行回包的時候,才能利用知道源地址用哪個宿主機IP、宿主機端口。??
? ? ? ? ?并且NAT記錄維持是有過期時間的,多久這個連接沒活躍狀態,則就會被回收。和我們使用家庭寬帶上網一樣,ISP做的NAT映射也有這么一條記錄,連接都很久沒活躍了,就會回收,要不然這個出口公網IP端口長期都占著茅坑不拉屎,那就造成了極大的資源浪費。
三、iptables后臺服務與docker是否能正常運行-誤區
1、誤區解釋
? ? ? ? 這里應該很多人有這個誤區。都說docker是依靠iptables規則做到NAT轉換,但是我查看宿主機進程,iptables沒有、firewalld也沒有,這到底是怎么實現的喲。 iptables服務都沒開啟,也能讓NAT生效?
? ? ? ? 之前我對此也是有嚴重誤解。我的宿主機也沒有iptables、firewalld服務運行,但是docker服務正常運行。
? ? ? ? 原理:??Docker默認通過iptables規則實現容器網絡通信和端口映射(如DNAT/SNAT),即使系統未顯式啟動iptables.service,只要內核加載了iptables模塊,Docker仍能自動管理規則。但若內核模塊未加載或iptables被完全禁用,Docker的NAT功能將失效。
? ? ? ? iptables的服務,只是用戶態的一個工具,用來操作內核netfilter規則的一個工具,所以只要內核級別的iptables模塊加載了,docker作為客戶端(與iptables用戶態工具級別一樣)也可以直接操作內核的iptables規則,從而實現NAT功能。 和用戶態iptables管理工具啟不啟用沒有任何關系。
2、docker依賴的內核模塊
lsmod | grep -E 'iptable|nf_|br_netfilter'
四、總結
? ? ? ? docker的容器之間相互訪問,可以通過veth和docker0網橋實現,源地址、目的地址都不需要做變化,就是在二層網絡進行傳輸,不需要網關的參與。
? ? ? ? 但是如果涉及到數據出宿主機則使用SNAT做源地址轉換,源數據包轉換為宿主機IP地址才能通過宿主機的網卡路由出去,反之,如果想訪問我容器的服務,則經過宿主機的時候要做DNAT,將數據包的目的IP和端口,改為容器的內網IP和端口,容器才能正常響應。
? ? ? ? 流程大致如下圖所示: