Docker容器網絡通信原理分析
概述
自從docker容器出現以來,容器的網絡通信就一直是大家關注的焦點,也是生產環境的迫切需求。而容器的網絡通信又可以分為兩大方面:單主機容器上的相互通信和跨主機的容器相互通信。而本文將分別針對這兩方面,對容器的通信原理進行簡單的分析,幫助大家更好地使用docker。
一、docker單主機容器通信
基于對net namespace的控制,docker可以為在容器創建隔離的網絡環境,在隔離的網絡環境下,容器具有完全獨立的網絡棧,與宿主機隔離,也可以使容器共享主機或者其他容器的網絡命名空間,基本可以滿足開發者在各種場景下的需要。按docker官方的說法,docker容器的網絡有五種模式:
bridge:docker默認的網絡模式,為容器創建獨立的網絡命名空間,容器具有獨立的網卡等所有單獨的網絡棧,是最常用的使用方式。
host:直接使用容器宿主機的網絡命名空間。
none:為容器創建獨立網絡命名空間,但不為它做任何網絡配置,容器中只有lo,用戶可以在此基礎上,對容器網絡做任意定制。
其他容器:與host模式類似,只是容器將與指定的容器共享網絡命名空間。
用戶自定義:docker 1.9版本以后新增的特性,允許容器使用第三方的網絡實現或者創建單獨的bridge網絡,提供網絡隔離能力。
概述
自從docker容器出現以來,容器的網絡通信就一直是大家關注的焦點,也是生產環境的迫切需求。而容器的網絡通信又可以分為兩大方面:單主機容器上的相互通信和跨主機的容器相互通信。而本文將分別針對這兩方面,對容器的通信原理進行簡單的分析,幫助大家更好地使用docker。
一、docker單主機容器通信
基于對net namespace的控制,docker可以為在容器創建隔離的網絡環境,在隔離的網絡環境下,容器具有完全獨立的網絡棧,與宿主機隔離,也可以使容器共享主機或者其他容器的網絡命名空間,基本可以滿足開發者在各種場景下的需要。按docker官方的說法,docker容器的網絡有五種模式:
bridge:docker默認的網絡模式,為容器創建獨立的網絡命名空間,容器具有獨立的網卡等所有單獨的網絡棧,是最常用的使用方式。
host:直接使用容器宿主機的網絡命名空間。
none:為容器創建獨立網絡命名空間,但不為它做任何網絡配置,容器中只有lo,用戶可以在此基礎上,對容器網絡做任意定制。
其他容器:與host模式類似,只是容器將與指定的容器共享網絡命名空間。
用戶自定義:docker 1.9版本以后新增的特性,允許容器使用第三方的網絡實現或者創建單獨的bridge網絡,提供網絡隔離能力。
這些網絡模式在相互網絡通信方面的對比如下所示:
南北向通信指容器與宿主機外界的訪問機制,東西向流量指同一宿主機上與其他容器相互訪問的機制。
1.1 host模式
由于容器和宿主機共享同一個網絡命名空間,換言之,容器的IP地址即為宿主機的IP地址。所以容器可以和宿主機一樣,使用宿主機的任意網卡,實現和外界的通信。其網絡模型可以參照下圖:
采用host模式的容器,可以直接使用宿主機的IP地址與外界進行通信,若宿主機具有公有IP,那么容器也擁有這個公有IP。同時容器內服務的端口也可以使用宿主機的端口,無需額外進行NAT轉換,而且由于容器通信時,不再需要通過linuxbridge等方式轉發或者數據包的拆封,性能上有很大優勢。當然,這種模式有優勢,也就有劣勢,主要包括以下幾個方面:
最明顯的就是容器不再擁有隔離、獨立的網絡棧。容器會與宿主機競爭網絡棧的使用,并且容器的崩潰就可能導致宿主機崩潰,在生產環境中,這種問題可能是不被允許的。
容器內部將不再擁有所有的端口資源,因為一些端口已經被宿主機服務、bridge模式的容器端口綁定等其他服務占用掉了。
1.2 bridge模式
bridge模式是docker默認的,也是開發者最常使用的網絡模式。在這種模式下,docker為容器創建獨立的網絡棧,保證容器內的進程使用獨立的網絡環境,實現容器之間、容器與宿主機之間的網絡棧隔離。同時,通過宿主機上的docker0網橋,容器可以與宿主機乃至外界進行網絡通信。其網絡模型可以參考下圖:
從該網絡模型可以看出,容器從原理上是可以與宿主機乃至外界的其他機器通信的。同一宿主機上,容器之間都是連接到docker0這個網橋上的,它可以作為虛擬交換機使容器可以相互通信。然而,由于宿主機的IP地址與容器veth pair的 IP地址均不在同一個網段,故僅僅依靠veth pair和namespace的技術,還不足以使宿主機以外的網絡主動發現容器的存在。為了使外界可以訪問容器中的進程,docker采用了端口綁定的方式,也就是通過iptables的NAT,將宿主機上的端口端口流量轉發到容器內的端口上。
舉一個簡單的例子,使用下面的命令創建容器,并將宿主機的3306端口綁定到容器的3306端口:
docker run -tid –name db -p 3306:3306 MySQL
在宿主機上,可以通過iptables -t nat -L -n,查到一條DNAT規則:
DNAT tcp — 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 to:172.17.0.5:3306
上面的172.17.0.5即為bridge模式下,創建的容器IP。
很明顯,bridge模式的容器與外界通信時,必定會占用宿主機上的端口,從而與宿主機競爭端口資源,對宿主機端口的管理會是一個比較大的問題。同時,由于容器與外界通信是基于三層上iptables NAT,性能和效率上的損耗是可以預見的。
1.3 none模式
在這種模式下,容器有獨立的網絡棧,但不包含任何網絡配置,只具有lo這個loopback網卡用于進程通信。也就是說,none模式為容器做了最少的網絡設置,但是俗話說得好“少即是多”,在沒有網絡配置的情況下,通過第三方工具或者手工的方式,開發任意定制容器的網絡,提供了最高的靈活性。
1.4 其他容器模式
其他網絡模式是docker中一種較為特別的網絡的模式。在這個模式下的容器,會使用其他容器的網絡命名空間,其網絡隔離性會處于bridge橋接模式與host模式之間。當容器共享其他容器的網絡命名空間,則在這兩個容器之間不存在網絡隔離,而它們又與宿主機以及除此之外其他的容器存在網絡隔離。其網絡模型可以參考下圖:
在這種模式下的容器可以通過localhost來統一網絡命名空間下的其他容器,傳輸效率較高。而且這種模式還節約了一定數量的網絡資源,但它并沒有改變容器與外界通信的方式。在一些特殊的場景中非常有用,例如,kubernetes的pod,kubernetes為pod創建一個基礎設施容器,同一pod下的其他容器都以其他容器模式共享這個基礎設施容器的網絡命名空間,相互之間以localhost訪問,構成一個統一的整體。
1.5 用戶定義網絡模式
在用戶定義網絡模式下,開發者可以使用任何docker支持的第三方網絡driver來定制容器的網絡。并且,docker 1.9以上的版本默認自帶了bridge和overlay兩種類型的自定義網絡driver。可以用于集成calico、weave、openvswitch等第三方廠商的網絡實現。
除了docker自帶的bridge driver,其他的幾種driver都可以實現容器的跨主機通信。而基于bdrige driver的網絡,docker會自動為其創建iptables規則,保證與其他網絡之間、與docker0之間的網絡隔離。例如,使用下面的命令創建一個基于bridge driver的自定義網絡:
docker network create bri1
則docker會自動生成如下的iptables規則,保證不同網絡上的容器無法互相通信。
-A DOCKER-ISOLATION -i br-8dba6df70456 -o docker0 -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-8dba6df70456 -j DROP
除此之外,bridge driver的所有行為都和默認的bridge模式完全一致。而overlay及其他driver,則可以實現容器的跨主機通信。
二、docker跨主機容器通信
早期大家的跨主機通信方案主要有以下幾種:
容器使用host模式:容器直接使用宿主機的網絡,這樣天生就可以支持跨主機通信。雖然可以解決跨主機通信問題,但這種方式應用場景很有限,容易出現端口沖突,也無法做到隔離網絡環境,一個容器崩潰很可能引起整個宿主機的崩潰。
端口綁定:通過綁定容器端口到宿主機端口,跨主機通信時,使用主機IP+端口的方式訪問容器中的服務。顯而易見,這種方式僅能支持網絡棧的四層及以上的應用,并且容器與宿主機緊耦合,很難靈活的處理,可擴展性不佳。
docker外定制容器網絡:在容器通過docker創建完成后,然后再通過修改容器的網絡命名空間來定義容器網絡。典型的就是很久以前的pipework,容器以none模式創建,pipework通過進入容器的網絡命名空間為容器重新配置網絡,這樣容器網絡可以是靜態IP、vxlan網絡等各種方式,非常靈活,容器啟動的一段時間內會沒有IP,明顯無法在大規模場景下使用,只能在實驗室中測試使用。
第三方SDN定義容器網絡:使用Open vSwitch或Flannel等第三方SDN工具,為容器構建可以跨主機通信的網絡環境。這些方案一般要求各個主機上的docker0網橋的cidr不同,以避免出現IP沖突的問題,限制了容器在宿主機上的可獲取IP范圍。并且在容器需要對集群外提供服務時,需要比較復雜的配置,對部署實施人員的網絡技能要求比較高。
上面這些方案有各種各樣的缺陷,同時也因為跨主機通信的迫切需求,docker 1.9版本時,官方提出了基于vxlan的overlay網絡實現,原生支持容器的跨主機通信。同時,還支持通過libnetwork的plugin機制擴展各種第三方實現,從而以不同的方式實現跨主機通信。就目前社區比較流行的方案來說,跨主機通信的基本實現方案有以下幾種:
基于隧道的overlay網絡:按隧道類型來說,不同的公司或者組織有不同的實現方案。docker原生的overlay網絡就是基于vxlan隧道實現的。ovn則需要通過geneve或者stt隧道來實現的。flannel最新版本也開始默認基于vxlan實現overlay網絡。
基于包封裝的overlay網絡:基于UDP封裝等數據包包裝方式,在docker集群上實現跨主機網絡。典型實現方案有weave、flannel的早期版本。
基于三層實現SDN網絡:基于三層協議和路由,直接在三層上實現跨主機網絡,并且通過iptables實現網絡的安全隔離。典型的方案為Project Calico。同時對不支持三層路由的環境,Project Calico還提供了基于IPIP封裝的跨主機網絡實現。
下面,本從網絡通信模型的角度,對這些方案的通信原理做一個簡單的比較,從中可以窺見各種方案在性能上的本質差別。
docker容器的CNM模型
首先,科普下docker容器的CNM網絡模型,calico、weave等第三方實現都是基于CNM模型與docker集成的。CNM網絡模型的結構如下圖所示:
在上面的圖中:
Sandbox代表容器的網絡命名空間,包含了容器的完整網絡棧,不同的容器之間可以完全隔離。在宿主機上,就表現為獨立的網絡命名空間。
Endpoint代表容器接入網絡的端點,可以形象地認為一個Endpoint對容器來說,就是一張物理網卡。
Network代表一組可以直接相互通信的Endpoint集合,可以基于LinuxBridge或者VLAN實現。在宿主機上,每個網絡都是一個獨立的網絡命名空間,宿主機上同一網絡的的容器,都通過veth pair鏈接到這個網絡命名空間上。
docker原生overlay的網絡通信模型
docker官方文檔的示例中,overlay網絡是在swarm集群中配置的,但實際上,overlay網絡可以獨立于swarm集群實現,只需要滿足以下前提條件即可。
有consul或者etcd,zookeeper的集群key-value存儲服務;
組成集群的所有主機的主機名不允許重復,因為docker守護進程與consul通信時,以主機名相互區分;
所有主機都可以訪問集群key-value的服務端口,按具體類型需要打開進行配置。例如docker daemon啟動時增加參數?–cluster-store=etcd://
overlay網絡依賴宿主機三層網絡的組播實現,需要在所有宿主機的防火墻上打開下列端口
宿主機內核版本10以上(1.9版本時,要求3.16以上)
滿足以上條件后,就可以通過docker network命令來創建跨主機的overlay網絡了,例如:docker network create -d overlay overlaynet?在集群的不同主機上,使用overlaynet這個網絡創建容器,形成如下圖所示的網絡拓撲:
由于容器和overlay的網絡的網絡命名空間文件不再操作系統默認的/var/run/netns下,只能手動通過軟連接的方式查看。
ln -s /var/run/docker/netns /var/run/netns
這樣就可以通過ip netns查看到容器和網絡的網絡命名空間了。
容器的網絡命名空間名稱可以通過docker inspect -f ‘{{.NetworkSettings.SandboxKey}}’ <容器ID>方式查看到。網絡的網絡命名空間則是通過docker network ls查看到的網絡短ID。
有時候網絡的網絡命名空間名稱前面會帶上1-、2-等序號,有時候不帶。但不影響網絡的通信和操作。
從這個通信過程中來看,跨主機通信過程中的步驟如下:
容器的網絡命名空間與overlay網絡的網絡命名空間通過一對veth pair連接起來,當容器對外通信時,veth pair起到網線的作用,將流量發送到overlay網絡的網絡命名空間中。
容器的veth pair對端eth2與vxlan設備通過br0這個Linux bridge橋接在一起,br0在同一宿主機上起到虛擬機交換機的作用,如果目標地址在同一宿主機上,則直接通信,如果不再則通過設置在vxlan1這個vxlan設備進行跨主機通信。
vxlan1設備上會在創建時,由docker daemon為其分配vxlan隧道ID,起到網絡隔離的作用。
docker主機集群通過key/value存儲共享數據,在7946端口上,相互之間通過gossip協議學習各個宿主機上運行了哪些容器。守護進程根據這些數據來在vxlan1設備上生成靜態MAC轉發表。
根據靜態MAC轉發表的設置,通過UDP端口4789,將流量轉發到對端宿主機的網卡上。
根據流量包中的vxlan隧道ID,將流量轉發到對端宿主機的overlay網絡的網絡命名空間中。
對端宿主機的overlay網絡的網絡命名空間中br0網橋,起到虛擬交換機的作用,將流量根據MAC地址轉發到對應容器內部。
雖然上面的網絡通信模型可以實現容器的跨主機通信,但還是有一些缺陷,造成實際使用上的不便,例如:
由于vxlan網絡與宿主機網絡默認不再同一網絡環境下,為了解決宿主機與容器的通信問題,docker為overlay網絡中的容器額外增加了網卡eth1作為宿主機與容器通信的通道。這樣在使用容器服務時,就必須根據訪問性質的不同,選擇不同的網卡地址,造成使用上的不便。
容器對外暴露服務仍然只能使用端口綁定的方式,外界無法簡單地直接使用容器IP訪問容器服務。
從上面的通信過程中來看,原生的overlay網絡通信必須依賴docker守護進程及key/value存儲來實現網絡通信,約束較多,容器在啟動后的一段時間內可能無法跨主機通信,這對一些比較敏感的應用來說是不可靠的。
weave網絡通信模型
weave通過在docker集群的每個主機上啟動虛擬的路由器,將主機作為路由器,形成互聯互通的網絡拓撲,在此基礎上,實現容器的跨主機通信。其主機網絡拓撲參見下圖:
如上圖所示,在每一個部署Docker的主機(可能是物理機也可能是虛擬機)上都部署有一個W(即weave router,它本身也可以以一個容器的形式部署)。weave網絡是由這些weave routers組成的對等端點(peer)構成,并且可以通過weave命令行定制網絡拓撲。
每個部署了weave router的主機之間都會建立TCP和UDP兩個連接,保證weave router之間控制面流量和數據面流量的通過。控制面由weave routers之間建立的TCP連接構成,通過它進行握手和拓撲關系信息的交換通信。控制面的通信可以被配置為加密通信。而數據面由weave routers之間建立的UDP連接構成,這些連接大部分都會加密。這些連接都是全雙工的,并且可以穿越防火墻。
當容器通過weave進行跨主機通信時,其網絡通信模型可以參考下圖:
從上面的網絡模型圖中可以看出,對每一個weave網絡中的容器,weave都會創建一個網橋,并且在網橋和每個容器之間創建一個veth pair,一端作為容器網卡加入到容器的網絡命名空間中,并為容器網卡配置ip和相應的掩碼,一端連接在網橋上,最終通過宿主機上weave router將流量轉發到對端主機上。其基本過程如下:
容器流量通過veth pair到達宿主機上weave router網橋上。
weave router在混雜模式下使用pcap在網橋上截獲網絡數據包,并排除由內核直接通過網橋轉發的數據流量,例如本子網內部、本地容器之間的數據以及宿主機和本地容器之間的流量。捕獲的包通過UDP轉發到所其他主機的weave router端。
在接收端,weave router通過pcap將包注入到網橋上的接口,通過網橋的上的veth pair,將流量分發到容器的網卡上。
weave默認基于UDP承載容器之間的數據包,并且可以完全自定義整個集群的網絡拓撲,但從性能和使用角度來看,還是有比較大的缺陷的:
weave自定義容器數據包的封包解包方式,不夠通用,傳輸效率比較低,性能上的損失也比較大。
集群配置比較負載,需要通過weave命令行來手工構建網絡拓撲,在大規模集群的情況下,加重了管理員的負擔。
calico網絡通信模型
calico是純三層的SDN 實現,它基于BPG 協議和Linux自身的路由轉發機制,不依賴特殊硬件,容器通信也不依賴iptables NAT或Tunnel 等技術。能夠方便的部署在物理服務器、虛擬機(如 OpenStack)或者容器環境下。同時calico自帶的基于iptables的ACL管理組件非常靈活,能夠滿足比較復雜的安全隔離需求。
在主機網絡拓撲的組織上,calico的理念與weave類似,都是在主機上啟動虛擬機路由器,將每個主機作為路由器使用,組成互聯互通的網絡拓撲。當安裝了calico的主機組成集群后,其拓撲如下圖所示:
每個主機上都部署了calico/node作為虛擬路由器,并且可以通過calico將宿主機組織成任意的拓撲集群。當集群中的容器需要與外界通信時,就可以通過BGP協議將網關物理路由器加入到集群中,使外界可以直接訪問容器IP,而不需要做任何NAT之類的復雜操作。
當容器通過calico進行跨主機通信時,其網絡通信模型如下圖所示:
從上圖可以看出,當容器創建時,calico為容器生成veth pair,一端作為容器網卡加入到容器的網絡命名空間,并設置IP和掩碼,一端直接暴露在宿主機上,并通過設置路由規則,將容器IP暴露到宿主機的通信路由上。于此同時,calico為每個主機分配了一段子網作為容器可分配的IP范圍,這樣就可以根據子網的CIDR為每個主機生成比較固定的路由規則。
當容器需要跨主機通信時,主要經過下面的簡單步驟:
容器流量通過veth pair到達宿主機的網絡命名空間上。
根據容器要訪問的IP所在的子網CIDR和主機上的路由規則,找到下一跳要到達的宿主機IP。
流量到達下一跳的宿主機后,根據當前宿主機上的路由規則,直接到達對端容器的veth pair插在宿主機的一端,最終進入容器。
從上面的通信過程來看,跨主機通信時,整個通信路徑完全沒有使用NAT或者UDP封裝,性能上的損耗確實比較低。但正是由于calico的通信機制是完全基于三層的,這種機制也帶來了一些缺陷,例如:
calico目前只支持TCP、UDP、ICMP、ICMPv6協議,如果使用其他四層協議(例如NetBIOS協議),建議使用weave、原生overlay等其他overlay網絡實現。
基于三層實現通信,在二層上沒有任何加密包裝,因此只能在私有的可靠網絡上使用。
流量隔離基于iptables實現,并且從etcd中獲取需要生成的隔離規則,有一些性能上的隱患。
Docker 容器 網絡
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。