概述
自從docker容器出現以來,容器的網路通訊就一直是大家關注的焦點,也是生產環境的迫切需求。而容器的網路通訊又可以分為兩大方面:單主機容器上的相互通訊和跨主機的容器相互通訊。而本文將分別針對這兩方面,對容器的通訊原理進行簡單的分析,幫助大家更好地使用docker。
docker單主機容器通訊
基於對net namespace的控制,docker可以為在容器建立隔離的網路環境,在隔離的網路環境下,容器具有完全獨立的網路棧,與宿主機隔離,也可以使容器共享主機或者其他容器的網路名稱空間,基本可以滿足開發者在各種場景下的需要。按docker官方的說法,docker容器的網路有五種模式:
- bridge:docker預設的網路模式,為容器建立獨立的網路名稱空間,容器具有獨立的網絡卡等所有單獨的網路棧,是最常用的使用方式。
- host:直接使用容器宿主機的網路名稱空間。
- none:為容器建立獨立網路名稱空間,但不為它做任何網路配置,容器中只有lo,使用者可以在此基礎上,對容器網路做任意定製。
- 其他容器:與host模式類似,只是容器將與指定的容器共享網路名稱空間。
- 使用者自定義:docker 1.9版本以後新增的特性,允許容器使用第三方的網路實現或者建立單獨的bridge網路,提供網路隔離能力。
這些網路模式在相互網路通訊方面的對比如下所示:
模式 |
是否支援多主機 | 南北向通訊機制 |
東西向通訊機制 |
bridge | 否 | 宿主機埠繫結 | 通過Linux bridge |
host | 是 | 按宿主機網路通訊 | 按宿主機網路通訊 |
none | 否 | 無法通訊 | 只能用link通訊 |
其他容器 | 否 | 宿主機埠繫結 | 通過link通訊 |
使用者自定義 | 按網路實現而定 | 按網路實現而定 | 按網路實現而定 |
南北向通訊指容器與宿主機外界的訪問機制,東西向流量指同一宿主機上與其他容器相互訪問的機制。
host模式
由於容器和宿主機共享同一個網路名稱空間,換言之,容器的IP地址即為宿主機的IP地址。所以容器可以和宿主機一樣,使用宿主機的任意網絡卡,實現和外界的通訊。其網路模型可以參照下圖:
採用host模式的容器,可以直接使用宿主機的IP地址與外界進行通訊,若宿主機具有公有IP,那麼容器也擁有這個公有IP。同時容器內服務的埠也可以使用宿主機的埠,無需額外進行NAT轉換,而且由於容器通訊時,不再需要通過linuxbridge等方式轉發或者資料包的拆封,效能上有很大優勢。當然,這種模式有優勢,也就有劣勢,主要包括以下幾個方面:
- 最明顯的就是容器不再擁有隔離、獨立的網路棧。容器會與宿主機競爭網路棧的使用,並且容器的崩潰就可能導致宿主機崩潰,在生產環境中,這種問題可能是不被允許的。
- 容器內部將不再擁有所有的埠資源,因為一些埠已經被宿主機服務、bridge模式的容器埠繫結等其他服務佔用掉了。
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,效能和效率上的損耗是可以預見的。
none模式
在這種模式下,容器有獨立的網路棧,但不包含任何網路配置,只具有lo這個loopback網絡卡用於程序通訊。也就是說,none模式為容器做了最少的網路設定,但是俗話說得好“少即是多”,在沒有網路配置的情況下,通過第三方工具或者手工的方式,開發這任意定製容器的網路,提供了最高的靈活性。
其他容器模式
其他網路模式是docker中一種較為特別的網路的模式。在這個模式下的容器,會使用其他容器的網路名稱空間,其網路隔離性會處於bridge橋接模式與host模式之間。當容器共享其他容器的網路名稱空間,則在這兩個容器之間不存在網路隔離,而她們又與宿主機以及除此之外其他的容器存在網路隔離。其網路模型可以參考下圖:
在這種模式下的容器可以通過localhost來同一網路名稱空間下的其他容器,傳輸效率較高。而且這種模式還節約了一定數量的網路資源,但它並沒有改變容器與外界通訊的方式。在一些特殊的場景中非常有用,例如,kubernetes的pod,kubernetes為pod建立一個基礎設施容器,同一pod下的其他容器都以其他容器模式共享這個基礎設施容器的網路名稱空間,相互之間以localhost訪問,構成一個統一的整體。
使用者定義網路模式
在使用者定義網路模式下,開發者可以使用任何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://<ETCD-IP>:4001 – -cluster-advertise=eth0:2376 - overlay網路依賴宿主機三層網路的組播實現,需要在所有宿主機的防火牆上開啟下列埠
協議
埠 說明
udp 4789 容器之間流量的vxlan埠 tcp/udp 7946 docker守護程序的控制埠 宿主機核心版本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中獲取需要生成的隔離規則,有一些效能上的隱患。