阿里开源富容器引擎 PouchContainer 的 network 连接机制
?
PouchContainer 是阿里巴巴集團(tuán)開(kāi)源的高效、輕量級(jí)企業(yè)級(jí)富容器引擎技術(shù),擁有隔離性強(qiáng)、可移植性高、資源占用少等特性。可以幫助企業(yè)快速實(shí)現(xiàn)存量業(yè)務(wù)容器化,同時(shí)提高超大規(guī)模下數(shù)據(jù)中心的物理資源利用率。
PouchContainer 源自阿里巴巴內(nèi)部場(chǎng)景,誕生初期,在如何為互聯(lián)網(wǎng)應(yīng)用保駕護(hù)航方面,傾盡了阿里巴巴工程師們的設(shè)計(jì)心血。PouchContainer 的強(qiáng)隔離、富容器等技術(shù)特性是最好的證明。在阿里巴巴的體量規(guī)模下,PouchContainer 對(duì)業(yè)務(wù)的支撐得到雙 11 史無(wú)前例的檢驗(yàn),開(kāi)源之后,阿里容器成為一項(xiàng)普惠技術(shù),定位于「助力企業(yè)快速實(shí)現(xiàn)存量業(yè)務(wù)容器化」。
本文將給大家介紹 PouchContainer 實(shí)現(xiàn) network 的機(jī)制以及將容器連接到 network 上的原理。為了充分闡述 network 的連接機(jī)制,本文將以Connect方法為例,敘述如何動(dòng)態(tài)地將一個(gè) container 連接到一個(gè)已存在的 network 上。
1. PouchContainer 實(shí)現(xiàn) network 的機(jī)制
在目前的容器網(wǎng)絡(luò)虛擬化技術(shù)中,Docker 推行的 CNM (Container Network Model)模型是一種通用的解決方案,CNM 構(gòu)建了一種成熟的容器虛擬化網(wǎng)絡(luò)模型,并定義了多種供開(kāi)發(fā)者調(diào)用的標(biāo)準(zhǔn)化接口。PouchContainer 沿用了 CNM 模型,基于 libnetwork 來(lái)實(shí)現(xiàn)容器間通信。下面先對(duì) Sandbox、Endpoint 和 Network 這三個(gè) CNM 中的核心組件進(jìn)行介紹。
Sandbox
Sandbox 一詞在不同的機(jī)制里,被分別賦予了不同的定義。例如,在 CRI(container runtime interface)里面 sandbox 就代表著 pod 的概念。而在 CNM 模型里,sandbox 代表著一個(gè)容器的網(wǎng)絡(luò)棧配置,包含管理容器的網(wǎng)卡,路由表以及 DNS 設(shè)置。Sandbox 的具體實(shí)現(xiàn)可以通過(guò) Linux 系統(tǒng)的 network namespace,一個(gè) FreeBSD Jail 或者其他類似的概念。一個(gè) sandbox 可以包含多個(gè) endpoints。
Endpoint
一個(gè) endpoint 將 sandbox 連接到 network 上。一個(gè) endpoint 的實(shí)現(xiàn)可以通過(guò) veth pair,Open vSwitch internal port 或者其他的方式。比較常見(jiàn)的方法是用 veth pair,顧名思義,veth pair一定是成對(duì)出現(xiàn)的,因此會(huì)存在 veth0 和 veth1 兩塊網(wǎng)卡。創(chuàng)建容器時(shí),其中一塊會(huì)被設(shè)置到容器內(nèi)部,充當(dāng)容器內(nèi)部的eth0,所有目的地址為容器 IP 的數(shù)據(jù)包都要經(jīng)過(guò) eth0 網(wǎng)卡;另一塊(以下稱為 veth 設(shè)備)則會(huì)被連接到宿主機(jī)的網(wǎng)橋上。從 veth 設(shè)備出去的數(shù)據(jù)包,會(huì)轉(zhuǎn)發(fā)到對(duì)應(yīng)的 eth0 設(shè)備上,當(dāng)數(shù)據(jù)包的目的地址為 eth0 設(shè)備的 IP 時(shí),就能被內(nèi)核協(xié)議棧處理。用 veth pair 來(lái)連接兩個(gè) network namespace,從而建立網(wǎng)絡(luò)連通關(guān)系。一個(gè) Endpoint 只能屬于一個(gè) Network,也只能屬于一個(gè) Sandbox。
Network
一個(gè) Network 是一組可以相互通信的 Endpoints 的集合。一個(gè) network 的實(shí)現(xiàn)可以通過(guò) Linux bridge,VLAN 或者其他方式。值得一提的是,一個(gè) network 中可以包含很多個(gè) endpoints。
可以看到,在如下圖所示的結(jié)構(gòu)下,Container A 和 Container B 同屬于 backend network,這兩個(gè) container通過(guò)各自紫色的 endpoint 構(gòu)成 network 連接;container B和 container C 同屬于 frontend network,通過(guò)藍(lán)色的 endpoint 構(gòu)成 network 連接。因此 container A 和 container B之間可以通信,container B和 container C之間也可以通信。
接下來(lái)重點(diǎn)看一下 container B 內(nèi)部的兩個(gè) endpoints,雖然 backend network 和 frontend network 在 container B 內(nèi)都有各自對(duì)應(yīng)的 endpoint,但紫色 endpoint 和藍(lán)色 endpoint 間不構(gòu)成通信。因此 backend network 和 frontend network 是兩個(gè)完全隔離的 network,并不因?yàn)檫B接同一個(gè) container 而產(chǎn)生連通。顯而易見(jiàn),container A 和 container C 間其實(shí)是無(wú)法通信的。
?
2.?PouchContainer 內(nèi)置的 network 模式
2.1 bridge 模式
bridge 模式是 PouchContainer 默認(rèn)的網(wǎng)絡(luò)模式,在創(chuàng)建容器不指定 network 模式,即不寫--net參數(shù),該容器就會(huì)以 bridge 模式創(chuàng)建。pouchd啟動(dòng)的時(shí)候,會(huì)自動(dòng)在主機(jī)上創(chuàng)建一個(gè)虛擬網(wǎng)橋 p0。后續(xù)以 bridge 模式創(chuàng)建容器時(shí),pouchd從 p0 網(wǎng)橋所在的 IP 網(wǎng)段中選取一個(gè)未使用的 IP 分配給容器的 eth0 網(wǎng)卡,p0 的 IP 是這些容器的默認(rèn)網(wǎng)關(guān)。
? ?
2.2 host 模式
在啟動(dòng)容器的時(shí)候,選擇 host 模式,那么容器將不會(huì)獲得獨(dú)立的 network namespace,而是和主機(jī)共享 network namespace。因此,這個(gè)容器也就沒(méi)有自己的網(wǎng)卡和 IP 配置,會(huì)使用主機(jī)的 IP 和端口,但 fs 和 pid 等與主機(jī)還是隔離的。
?
2.3 container 模式
以 container 模式創(chuàng)建的容器,會(huì)和已經(jīng)存在的容器共享一個(gè) network namespace,直接沿用其 veth 設(shè)備對(duì)。
?
2.4 none 模式
使用 none 模式創(chuàng)建的容器,擁有獨(dú)立的 network namespace,但是不會(huì)對(duì)容器進(jìn)行任何的網(wǎng)絡(luò)配置。因此,可以認(rèn)為 none 模式下的容器,是不和其它容器通信的。不過(guò),在容器創(chuàng)建后,可以再給它添加網(wǎng)卡、配置 IP,這樣就可以與同一個(gè) network 下的容器通信了。
?
2.5 CNM 與 network 模式的概念交叉
一個(gè) network 是一個(gè)唯一的、可識(shí)別的 endpoint 組,組內(nèi)的 endpoint 可以相互通訊。對(duì)比 CNM 來(lái)看,endpoint 可以簡(jiǎn)單理解成 veth 設(shè)備對(duì),容器的 sandbox 里可以有多個(gè) endpoints,每個(gè) endpoint 代表和一個(gè)特定 network 的連接關(guān)系。
3. network connect 的流程分析
// daemon/mgr/container.go
//?Connect?is?used?to?connect?a?container?to?a?network. func?(mgr?*ContainerManager)?Connect(ctx?context.Context,?name?string,?networkIDOrName?string,?epConfig?*types.EndpointSettings)?error?{……if?err?:=?mgr.updateNetworkConfig(c,?n.Name,?epConfig);?err?!=?nil?{return?err}?else?if?err?:=?mgr.connectToNetwork(ctx,?c,?networkIDOrName,?epConfig);?err?!=?nil?{return?err}return?c.Write(mgr.Store) }可以看到在Connect函數(shù)里,首先根據(jù)傳入的參數(shù)獲取到具體的 container 和 network。而epConfig參數(shù)里面,存放的是在 CLI 端通過(guò) flag 傳入的參數(shù),如 container 在特定 network 中的別名、指定的 IP 范圍等。
查看c.State.Status來(lái)判斷 container 此時(shí)的狀態(tài),dead 狀態(tài)的 container 是無(wú)法執(zhí)行 connect 操作的。對(duì)于非 running 但是還 live的container,只是簡(jiǎn)單地調(diào)用updateNetworkConfig()來(lái)更新 container 的網(wǎng)絡(luò)配置,將傳入的epConfig加入到容器的 network 配置中。在這種情況下,不會(huì)為 container 分配網(wǎng)卡,因此 container 并沒(méi)有成功連通到 network 中。對(duì)于 running 狀態(tài)的 container,調(diào)用connectToNetwork()來(lái)進(jìn)行后續(xù)的操作,connectToNetwork()會(huì)根據(jù)給定的 network 和 container 進(jìn)行網(wǎng)卡的配置,再在主機(jī)上分配一個(gè)網(wǎng)卡,最后將網(wǎng)卡加入到 container 的 sandbox 里面。這樣,container 就成功地連接到 network 上了!具體的流程會(huì)在后續(xù)進(jìn)行解析。
c.Write(mgr.Store)的作用,是將 container 連接到 network 上的一系列配置寫入 container 的 metadata 里面,這樣就保證了數(shù)據(jù)的持久化。否則,建立的 network 連接只是一次性的,所有的數(shù)據(jù)和相關(guān)配置在pouchd重啟后都會(huì)丟失。
// daemon/mgr/container.go
func?(mgr?*ContainerManager)?connectToNetwork(ctx?context.Context,?container?*Container,?networkIDOrName?string,?epConfig?*types.EndpointSettings)?(err?error)?{……endpoint?:=?mgr.buildContainerEndpoint(container)……if?_,?err?:=?mgr.NetworkMgr.EndpointCreate(ctx,?endpoint);?err?!=?nil?{……}return?mgr.updateNetworkConfig(container,?networkIDOrName,?endpoint.EndpointConfig) }endpoint 里面包含三部分的信息,一部分的信息來(lái)自于 container,一部分的信息來(lái)自 network,最后一部分信息是 connect 命令里 flag 中的配置。buildContainerEndpoint()的邏輯比較簡(jiǎn)單,就是獲取到 endpoint 需要的 container 相關(guān)信息。隨后調(diào)用了NetworkMgr的EndpointCreate()來(lái)進(jìn)行具體的構(gòu)建。
// daemon/mgr/network.go
//?EndpointCreate?is?used?to?create?network?endpoint. func?(nm?*NetworkManager)?EndpointCreate(ctx?context.Context,?endpoint?*types.Endpoint)?(string,?error)?{……//?create?endpointepOptions,?err?:=?endpointOptions(n,?endpoint)……endpointName?:=?containerID[:8]ep,?err?:=?n.CreateEndpoint(endpointName,?epOptions...)……//?create?sandboxsb?:=?nm.getNetworkSandbox(containerID)if?sb?==?nil?{sandboxOptions,?err?:=?buildSandboxOptions(nm.config,?endpoint)……sb,?err?=?nm.controller.NewSandbox(containerID,?sandboxOptions...)……}//?endpoint?joins?into?sandboxjoinOptions,?err?:=?joinOptions(endpoint)……if?err?:=?ep.Join(sb,?joinOptions...);?err?!=?nil?{return?"",?fmt.Errorf("failed?to?join?sandbox(%v)",?err)}//?update?endpoint?settingsepInfo?:=?ep.Info()if?epInfo.Gateway()?!=?nil?{endpointConfig.Gateway?=?epInfo.Gateway().String()}if?epInfo.GatewayIPv6().To16()?!=?nil?{endpointConfig.IPV6Gateway?=?epInfo.GatewayIPv6().String()}endpoint.ID?=?ep.ID()endpointConfig.EndpointID?=?ep.ID()endpointConfig.NetworkID?=?n.ID()iface?:=?epInfo.Iface()……return?endpointName,?nil}創(chuàng)建 endpoint 的整個(gè)過(guò)程,都是調(diào)用 libnetwork 來(lái)實(shí)現(xiàn)的。首先調(diào)用endpointOptions()來(lái)構(gòu)建接口要求的EndpointOption參數(shù),這個(gè) setter 函數(shù)類型的參數(shù)能將不同的 option 傳遞給 network 和 endpoint 的接口。隨后調(diào)用 libnetwork 的
CreateEndpoint()接口來(lái)進(jìn)行具體的構(gòu)建。CreateEndpoint()執(zhí)行的實(shí)際工作包括為這個(gè) endpoint 分配 IP 和接口(Iface),對(duì)應(yīng)的配置會(huì)被應(yīng)用到 Endpoint 中,其中包括 iptables 的配置規(guī)則和端口信息等。
Sandbox 所代表的就是 container 獨(dú)有的 network namespace,其創(chuàng)建也是基于 libnetwork。sandbox 里面包含 container 建立網(wǎng)絡(luò)通信的標(biāo)志性信息,如 IP 地址、Mac 地址、路由和 DNS 等配置。會(huì)對(duì)已存在的 sandbox 進(jìn)行遍歷,判斷是否存在相應(yīng)的 sandbox,存在的話就直接返回對(duì)應(yīng)的 sandbox。在 none 模式下,container 沿用主機(jī)的 namespace,返回的 sandbox 為空,這時(shí)候會(huì)創(chuàng)建一個(gè)新的 sandbox。sandbox 的創(chuàng)建過(guò)程,就是調(diào)用 namespace 和 cgroup 來(lái)創(chuàng)建一個(gè)獨(dú)立 sandbox 空間。
將 endpoint 加入到 sandbox 的操作,實(shí)際上就是將網(wǎng)卡分配給 container 的過(guò)程,將 endpoint 分配到的網(wǎng)絡(luò)資源注入到 sandbox 中。網(wǎng)卡是建立連接的核心,container 通過(guò)虛擬網(wǎng)卡連接到 network,從而與其它 container 進(jìn)行通信。
最后一步,將變化同步更新到 endpoint 的配置里面。
4. 總結(jié)
回顧建立 network 連接的整個(gè)流程,可以簡(jiǎn)單的分成幾步。container 在通信時(shí)需要唯一的 network namespace 來(lái)標(biāo)志自己,那么就要有 sandbox 的創(chuàng)建;通信的實(shí)現(xiàn)需要網(wǎng)卡作為基礎(chǔ),那么就要有 endpoint 的創(chuàng)建;最后將endpoint ?加入 sandbox,建立容器間通信的基礎(chǔ),連接的建立就成功完成了。
如果想更多了解 PouchContainer,請(qǐng)?jiān)L問(wèn) https://pouchcontainer.io
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的阿里开源富容器引擎 PouchContainer 的 network 连接机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 技术解析系列 | PouchContai
- 下一篇: Golang 在阿里集团调度集群管理系统