此文已由做者黃揚受權網易雲社區發佈。html
歡迎訪問網易雲社區,瞭解更多網易技術產品運營經驗。node
在早先的k8s版本中,kubelet代碼裏提供了networkPlugin,networkPlugin是一組接口,實現了pod的網絡配置、解除、獲取,當時kubelet的代碼中有個一個docker_manager,負責容器的建立和銷燬,亦會負責容器網絡的操做。而現在咱們能夠看到基本上kubelet的啓動參數中,networkPlugin的值都會設置爲cni。linux
使用CNI插件時,須要作三個配置:git
kubelet啓動參數中networkPlugin設置爲cnigithub
在/etc/cni/net.d中增長cni的配置文件,配置文件中能夠指定須要使用的cni組件及參數docker
將須要用到的cni組件(二進制可執行文件)放到/opt/cni/bin目錄下數據庫
全部的cni組件都支持兩個命令:add和del。即配置網絡和解除網絡配置。json
cni插件的配置文件是一個json文件,不一樣版本的接口、以及不一樣的cni組件,有着不一樣的配置內容結構,目前比較通用的接口版本是0.3.1的版本。後端
在配置文件中咱們能夠填入多個cni組件,當這些cni組件的配置以數組形式記錄時,kubelet會對全部的組件進行按序鏈式調用,全部組件調用成功後,視爲網絡配置完成,過程當中任何一步出現error,都會進行回滾的del操做。以保證操做流上的原子性。api
cni插件按照代碼中的存放目錄能夠分爲三種:ipam、main、meta。
ipam cni用於管理ip和相關網絡數據,配置網卡、ip、路由等。
main cni用於進行網絡配置,好比建立網橋,vethpair、macvlan等。
meta cni有的是用於和第三方CNI插件進行適配,如flannel,也有的用於配置內核參數,如tuning
因爲官方提供的cni組件就有不少,這裏咱們詳細介紹一些使用率較高的組件。
ipam類型的cni插件,在執行add命令時會分配一個IP給調用者。執行del命令時會將調用者指定的ip放回ip池。社區開源的ipam有host-local、dhcp。
咱們能夠經過host-local的配置文件的數據結構來搞懂這個組件是如何管理ip的。
type IPAMConfig struct { *Range Name string Type string `json:"type"` Routes []*types.Route `json:"routes"`//交付的ip對應的路由 DataDir string `json:"dataDir"`//本地ip池的數據庫目錄 ResolvConf string `json:"resolvConf"`//交付的ip對應的dns Ranges []RangeSet `json:"ranges"`//交付的ip所屬的網段,網關信息 IPArgs []net.IP `json:"-"` // Requested IPs from CNI_ARGS and args } #配置文件範例: { "cniVersion": "0.3.1", "name": "mynet", "type": "ipvlan", "master": "foo0", "ipam": { "type": "host-local", "resolvConf": "/home/here.resolv", "dataDir": "/home/cni/network", "ranges": [ [ { "subnet": "10.1.2.0/24", "rangeStart": "10.1.2.9", "rangeEnd": "10.1.2.20", "gateway": "10.1.2.30" }, { "subnet": "10.1.4.0/24" } ], [{ "subnet": "11.1.2.0/24", "rangeStart": "11.1.2.9", "rangeEnd": "11.1.2.20", "gateway": "11.1.2.30" }] ] } }
從上面的配置咱們能夠清楚:
host-local組件經過在配置文件中指定的subnet進行網絡劃分
host-local在本地經過指定目錄(默認爲/var/lib/cni/networks)記錄當前的ip pool數據
host-local將IP分配並告知調用者時,還能夠告知dns、路由等配置信息。這些信息經過配置文件和對應的resolv文件記錄。
host-local的應用範圍比較廣,kubenet、bridge、ptp、ipvlan等cni network插件都被用來和host-local配合進行ip管理。
社區的cni組件中就包含了dhcp這個ipam,但並無提供一個能夠參考的案例,翻看了相關的源碼,大體邏輯是:
向dhcp申請ip時,dhcp會使用rpc訪問本地的socket(/run/cni/dhcp.sock)申請一個ip的租約。而後將IP告知調用者。
向dhcp刪除IP時,dhcp一樣經過rpc請求,解除該IP的租約。
main類型的cni組件作的都是一些核心功能,好比配置網橋、配置各類虛擬化的網絡接口(veth、macvlan、ipvlan等)。這裏咱們着重講使用率較高的bridge和ptp。
brige模式,即網橋模式。在node上建立一個linux bridge,並經過vethpair的方式在容器中設置網卡和IP。只要爲容器配置一個二層可達的網關:好比給網橋配置IP,並設置爲容器ip的網關。容器的網絡就能創建起來。
以下是bridge的配置項數據結構:
type NetConf struct { types.NetConf BrName string `json:"bridge"` //網橋名 IsGW bool `json:"isGateway"` //是否將網橋配置爲網關 IsDefaultGW bool `json:"isDefaultGateway"` // ForceAddress bool `json:"forceAddress"`//若是網橋已存在且已配置了其餘IP,經過此參數決定是否將其餘ip除去 IPMasq bool `json:"ipMasq"`//若是true,配置私有網段到外部網段的masquerade規則 MTU int `json:"mtu"` HairpinMode bool `json:"hairpinMode"` PromiscMode bool `json:"promiscMode"` }
咱們關注其中的一部分字段,結合代碼能夠大體整理出bridge組件的工做內容。首先是ADD命令:
執行ADD命令時,brdige組件建立一個指定名字的網橋,若是網橋已經存在,就使用已有的網橋;
建立vethpair,將node端的veth設備鏈接到網橋上;
從ipam獲取一個給容器使用的ip數據,並根據返回的數據計算出容器對應的網關;
進入容器網絡名字空間,修改容器中網卡名和網卡ip,以及配置路由,並進行arp廣播(注意咱們只爲vethpair的容器端配置ip,node端是沒有ip的);
若是IsGW=true,將網橋配置爲網關,具體方法是:將第三步計算獲得的網關IP配置到網橋上,同時根據須要將網橋上其餘ip刪除。最後開啓網橋的ip_forward內核參數;
若是IPMasq=true,使用iptables增長容器私有網網段到外部網段的masquerade規則,這樣容器內部訪問外部網絡時會進行snat,在不少狀況下配置了這條路由後容器內部才能訪問外網。(這裏代碼中會作exist檢查,防止生成重複的iptables規則);
配置結束,整理當前網橋的信息,並返回給調用者。
其次是DEL命令:
根據命令執行的參數,確認要刪除的容器ip,調用ipam的del命令,將IP還回IP pool;
進入容器的網絡名字空間,根據容器IP將對應的網卡刪除;
若是IPMasq=true,在node上刪除建立網絡時配置的幾條iptables規則。
ptp實際上是bridge的簡化版。可是它作的網絡配置其實看上去卻是更復雜了點。而且有一些配置在自測過程當中發現並無太大用處。它只建立vethpair,可是會同時給容器端和node端都配置一個ip。容器端配置的是容器IP,node端配置的是容器IP的網關(/32),同時,容器裏作了一些特殊配置的路由,以知足讓容器發出的arp請求能被vethpair的node端響應。實現內外的二層連通。
ptp的網絡配置步驟以下:
從ipam獲取IP,根據ip類型(ipv4或ipv6)配置響應的內核ip_forward參數;
建立一對vethpair;一端放到容器中;
進入容器的網絡namespace,配置容器端的網卡,修改網卡名,配置IP,並配置一些路由。假如容器ip是10.18.192.37/20,所屬網段是10.18.192.0/20,網關是10.18.192.1,咱們這裏將進行這樣的配置:
配置IP後,內核會自動生成一條路由,形如:10.18.192.0/20 dev eth0 scope link
,咱們將它刪掉:ip r d ****
配置一條私有網到網關的真實路由:ip r a 10.18.192.0/20 via 10.18.192.1 dev eth0
配置一條到網關的路由:10.18.192.1/32 dev eth0 scope link
退出到容器外,將vethpair的node端配置一個IP(ip爲容器ip的網關,mask=32);
配置外部的路由:訪問容器ip的請求都路由到vethpair的node端設備去。
若是IPMasq=true,配置iptables
獲取完整的網卡信息(vethpair的兩端),返回給調用者。
與bridge不一樣主要的不一樣是:ptp不使用網橋,而是直接使用vethpair+路由配置,這個地方其實有不少其餘的路由配置能夠選擇,同樣能夠實現網絡的連通性,ptp配置的方式只是其中之一。萬變不離其宗的是:
只要容器內網卡發出的arp請求,能被node回覆或被node轉發並由更上層的設備回覆,造成一個二層網絡,容器裏的數據報文就能被髮往node上;而後經過node上的路由,進行三層轉發,將數據報文發到正確的地方,就能夠實現網絡的互聯。
bridge和ptp實際上是用了不一樣方式實現了這個原則中的「二層網絡」:
bridge組件給網橋配置了網關的IP,並給容器配置了到網關的路由。實現二層網絡
ptp組件給vethpair的對端配置了網關的IP,並給容器配置了單獨到網關IP的路由,實現二層網絡
ptp模式的路由還存在一個問題:沒有配置default路由,所以容器不能訪問外部網絡,要實現也很簡單,以上面的例子,在容器裏增長一條路由:default via 10.18.192.1 dev eth0
相比前面兩種cni main組件,host-device顯得十分簡單由於他就只會作兩件事情:
收到ADD命令時,host-device根據命令參數,將網卡移入到指定的網絡namespace(即容器中)。
收到DEL命令時,host-device根據命令參數,將網卡從指定的網絡namespace移出到root namespace。
細心的你確定會注意到,在bridge和ptp組件中,就已經有「將vethpair的一端移入到容器的網絡namespace」的操做。那這個host-device不是畫蛇添足嗎?
並非。host-device組件有其特定的使用場景。假設集羣中的每一個node上有多個網卡,其中一個網卡配置了node的IP。而其餘網卡都是屬於一個網絡的,能夠用來作容器的網絡,咱們只須要使用host-device,將其餘網卡中的某一個丟到容器裏面就行。
host-device模式的使用場景並很少。它的好處是:bridge、ptp等方案中,node上全部容器的網絡報文都是經過node上的一塊網卡出入的,host-device方案中每一個容器獨佔一個網卡,網絡流量不會通過node的網絡協議棧,隔離性更強。缺點是:在node上配置數十個網卡,可能並很差管理;另外因爲不通過node上的協議棧,因此kube-proxy直接廢掉。k8s集羣內的負載均衡只能另尋他法了。
有關macvlan的實踐能夠參考這篇文章。這裏作一個簡單的介紹:macvlan是linux kernal的特性,用於給一個物理網絡接口(parent)配置虛擬化接口,虛擬化接口與parent網絡接口擁有不一樣的mac地址,但parent接口上收到發給其對應的虛擬化接口的mac的包時,會分發給對應的虛擬化接口,有點像是將虛擬化接口和parent接口進行了'橋接'。給虛擬化網絡接口配置了IP和路由後就能互相訪問。
macvlan省去了linux bridge,可是配置macvlan後,容器不能訪問parent接口的IP。
ipvlan與macvlan有點相似,但對於內核要求更高(3.19),ipvlan也會從一個網絡接口建立出多個虛擬網絡接口,但他們的mac地址是同樣的, 只是IP不同。經過路由能夠實現不一樣虛擬網絡接口之間的互聯。
使用ipvlan也不須要linux bridge,但容器同樣不能訪問parent接口的IP。 關於ipvlan的內容能夠參考這篇文章
關於macvlan和ipvlan,還能夠參考這篇文章
meta組件一般進行一些額外的網絡配置(tuning),或者二次調用(flannel)。
用於進行內核網絡參數的配置。並將調用者的數據和配置後的內核參數返回給調用者。
有時候咱們須要配置一些虛擬網絡接口的內核參數,好比:網易雲在早期經典網絡方案中曾修改vethpair的proxy_arp參數(後面會介紹)。能夠經過這個組件進行配置。 另一些可能會改動的網絡參數好比:
accept_redirects
send_redirects
proxy_delay
accept_local
arp_filter
能夠在這裏查看可配置的網絡參數和釋義。
用於在node上配置iptables規則,進行SNAT,DNAT和端口轉發。
portmap組件一般在main組件執行完畢後執行,由於它的執行參數仰賴以前的組件提供
cni plugins中的flannel是開源網絡方案flannel的「調用器」。這也是flannel網絡方案適配CNI架構的一個產物。爲了便於區分,如下咱們稱cni plugins中的flannel 爲flanenl cni
。
咱們知道flannel是一個容器的網絡方案,一般使用flannel時,node上會運行一個daemon進程:flanneld,這個進程會返回該node上的flannel網絡、subnet,MTU等信息。並保存到本地文件中。
若是對flannel網絡方案有必定的瞭解,會知道他在作網絡接口配置時,其實幹的事情和bridge組件差很少。只不過flannel網絡下的bridge會跟flannel0網卡互聯,而flannel0網卡上的數據會被封包(udp、vxlan下)或直接轉發(host-gw)。
而flannel cni
作的事情就是:
執行ADD命令時,flannel cni
會從本地文件中讀取到flanneld的配置。而後根據命令的參數和文件的配置,生成一個新的cni配置文件(保存在本地,文件名包含容器id以做區分)。新的cni配置文件中會使用其餘cni組件,並注入相關的配置信息。以後,flannel cni
根據這個新的cni配置文件執行ADD命令。
執行DEL命令時,flannel cni
從本地根據容器id找到以前建立的cni配置文件,根據該配置文件執行DEL命令。
也就是說flannel cni
此處是一個flannel網絡模型的委託者,falnnel網絡模型委託它去調用其餘cni組件,進行網絡配置。一般調用的是bridge和host-local。
上述全部的cni組件,能完成的事情就是創建容器到虛擬機上的網絡。而要實現跨虛擬機的容器之間的網絡,有幾種可能的辦法:
容器的IP就是二層網絡裏分配的IP,這樣容器至關於二層網絡裏的節點,那麼就能夠自然互訪;
容器的IP與node的IP不屬於同一個網段,node上配置個到各個網段的路由(指向對應容器網段所部屬的node IP),經過路由實現互訪[flannel host-gw, calico bgp均是經過此方案實現];
容器的IP與node的IP不屬於同一個網段,node上有服務對容器發出的包進行封裝,對發給容器的包進行解封。封裝後的包經過node所在的網絡進行傳輸。解封后的包經過網橋或路由直接發給容器,即overlay網絡。[flannel udp/vxlan,calico ipip,openshift-sdn均經過此方案實現]
瞭解經常使用的網絡方案前,咱們先了解一下kubenet,kubenet實際上是k8s代碼中內置的一個cni組件。若是咱們要使用kubenet,就得在kubelet的啓動參數中指定networkPlugin
值爲kubenet
而不是cni
。
若是你閱讀了kubernetes的源碼,你就能夠在一個名爲kubenet_linux.go的文件中看到kubenet作了什麼事情:
身爲一種networkPlugin,kubenet天然要實現networkPlugin的一些接口。好比SetUpPod,TearDownPod,GetPodNetworkStatus等等,kubelet經過這些接口進行容器網絡的建立、解除、查詢。
身爲一個代碼中內置的cni,kubenet要主動生成一個cni配置文件(字節流數據),本身按照cni的規矩去讀取配置文件,作相似ADD/DEL指令的工做。實現網絡的建立、解除。
設計上其實挺蠢萌的。其實是爲了省事。咱們能夠看下自生成的配置文件:
{ "cniVersion": "0.1.0", "name": "kubenet", "type": "bridge", "bridge": "%s", //一般這裏默認是「cbr0」 "mtu": %d, //kubelet的啓動參數中能夠配置,默認使用機器上的最小mtu "addIf": "%s", //配置到容器中的網卡名字 "isGateway": true, "ipMasq": false, "hairpinMode": %t, "ipam": { "type": "host-local", "subnet": "%s", //node上容器ip所屬子網,一般是kubelet的pod-cidr參數指定 "gateway": "%s", //經過subnet能夠肯定gateway "routes": [ { "dst": "0.0.0.0/0" } ] } }
配置文件中明確了要使用的其餘cni組件:bridge、host-local(這裏代碼中還會調用lo組件,一般lo組件會被k8s代碼直接調用,因此不須要寫到cni配置文件中)。以後的事情就是執行二進制而已。
爲何咱們要學習kubenet?由於kubenet可讓用戶以最簡單的成本(配置networkPlugin和pod-cidr兩個啓動kubelet啓動參數),配置出一個簡單的、虛擬機本地的容器網絡。結合上面提到的幾種「跨虛擬機的容器之間的網絡方案」,就是一個完整的k8s集羣網絡方案了。
一般kubenet不適合用於overlay網絡方案,由於overlay網絡方案定製化要求會比較高。
許多企業使用vpc網絡時,使用自定義路由實現不一樣pod-cidr之間的路由,他們的網絡方案裏就會用到kubenet,好比azure AKS(基礎網絡)。
關於flannel,上面的文章也提到了一下。網上flannel的文章也是一搜一大把。這裏簡單介紹下flannel對k8s的支持,以及通用的幾個flannel backend(後端網絡配置方案)。
flannel在對kubernets進行支持時,flanneld啓動參數中會增長--kube-subnet-mgr
參數,flanneld會初始化一個kubernetes client,獲取本地node的pod-cidr,這個pod-cidr將會做爲flannel爲node本地容器規劃的ip網段。記錄到/run/flannel/subnet.env。(flannel_cni組件會讀取這個文件並寫入到net-conf.json中,供cni使用)。
flannel的overlay方案。每一個node節點上都有一個flanneld進程,和flannel0網橋,容器網絡會與flannel0網橋互聯,並經由flannel0發出,因此flanneld能夠捕獲到容器發出的報文,進行封裝。udp方案下會給報文包裝一個udp的頭部,vxlan下會給報文包裝一個vxlan協議的頭部(配置了相同VNI的node,就能進行互聯)。 目前flannel社區還提供了更多實驗性的封裝協議選擇,好比ipip,但仍舊將vxlan做爲默認的backend。
flannel的三層路由方案。每一個node節點上都會記錄其餘節點容器ip段的路由,經過路由,node A上的容器發給node B上的容器的數據,就能在node A上進行轉發。
相似kubenet,只分配子網,不作其餘任何事情。
flannel支持了aliVPC、gce、aws等雲廠商的vpc網絡。原理都是同樣的,就是當flanneld在某雲廠商的機器上運行時,根據機器自身的vpc網絡IP,和flanneld分配在該機器上的subnet,調用雲廠商的api建立對應的自定義路由。
calico是基於BGP路由實現的容器集羣網絡方案,對於使用者來講,基礎的calico使用體驗可能和flannel host-gw是基本同樣的:node節點上作好對容器arp的響應。而後經過node上的路由將容器發出的包轉發到對端容器所在node的IP。對端節點上再將包轉發給對端容器。
ipip模式則如同flannel ipip模式。對報文封裝一個ipip頭部,頭部中使用node ip。發送到對端容器所在node的IP,對端的網絡組件再解包,並轉發給容器。
不一樣之處在於flannel方案下路由都是經過代碼邏輯進行配置。而calico則在每一個節點創建bgp peer,bgp peer彼此之間會進行路由的共享和學習,因此自動生成並維護了路由。
更多網易技術、產品、運營經驗分享請點擊。
相關文章:
【推薦】 網易考拉Android客戶端網絡模塊設計
【推薦】 質量評估面面觀--聊一聊軟件上線前的質量評估