容器網絡接口,就是在網絡解決方案由網絡插件提供,這些插件配置容器網絡則經過CNI定義的接口來完成,也就是CNI定義的是容器運行環境與網絡插件之間的接口規範。這個接口只關心容器的網絡鏈接,在建立容器是分配網絡,刪除容器是移除網絡。插件就是對CNI的規範的具體實現。git
這裏咱們簡要回顧一下,容器具備本身的網絡協議棧並且被隔離在它本身的網絡名稱空間內,在這個隔離的網絡空間裏會爲容器提供網卡、迴環設備、IP地址、路由表、防火牆規則等這些基本的網絡環境。github
每個POD中都有一個特殊的容器且永遠是POD中建立的第一個容器,只是咱們查看的時候看不到,由於它完成做用就暫停了。這個容器就是Infra容器,使用匯編語言編寫。當建立POD的時kubernetes先建立名稱空間,而後把其中的網絡名稱空間和這個Infra關聯,後面屬於你的建立的容器都是經過Join的方式與這個Infra容器關聯在一塊兒,這樣這些容器和Infra都屬於一個網絡名稱空間,這也就是爲何POD中多個容器可使用本地通訊的緣由,因此嚴格意義來講網絡名稱空間屬於Infra。咱們看下面的演示docker
咱們啓動PODjson
登錄Srv03api
查看這個進程所屬的名稱空間,以下圖:bash
上圖說容器名稱空間,實際上是那個進程的名稱空間,名稱空間名稱-->該名稱空間的文件描述符。服務器
就拿net來講,它爲進程提供了一個徹底獨立的網絡協議棧視圖,包括網絡接口、IPV4/6協議棧、IP路由表、防火牆規則等,也就是爲進程提供一個獨立的網絡環境。網絡
進入POD中的容器,查看這個名稱空間app
能夠看到連接的文件的指向的都是同樣的,我這裏的POD只有一個容器,若是設置2個你就能夠分別進入查看,指向都是同樣的。工具
上面的結果其實爲進程建立的名稱空間,Infra容器抓住網絡名稱空間,而後POD中的容器JOIN到Infra中,這樣POD中的容器就能使用這個網絡名稱空間。因此Kubernetes的網絡插件考慮的不是POD中容器的網絡配置,而是配置這個POD的Network Namespace。
Infra是爲了給容器共享網絡名稱空間,固然也能夠共享volume。
理解了爲何POD裏面的容器能夠進行本地通訊的緣由,咱們就要向下看一層,是誰給這個Infra使用的網絡名稱空間提供的各類網絡棧配置呢?
咱們這裏以網橋模式爲例來講明。容器就是進程,容器擁有隔離的網絡名稱空間,就意味着進程所在的網絡名稱空間也是隔離的且和外界不能聯繫,那麼如何讓實現和其餘容器交互呢?你就理解爲2臺獨立的主機要想通訊就把它們鏈接到一個交換機上,經過一箇中間設備來鏈接兩個獨立的網絡。這個就是能起到交換機做用的網橋,Linux中就有這樣一個虛擬設備。有了這樣一個設備那麼就要解決誰來建立、誰來鏈接兩個容器的網絡名稱空間的問題。
在Dcoker項目中,這個網橋是由docker來建立的且安裝完以後就會默認建立一個這樣的設備,默認叫作docker0,這種網橋並非docker的專利,是屬於Linux內核就支持的功能,docker只是經過某些接口調用建立這樣一個網橋而且命名爲docker0。
網橋是一個二層設備,傳統網橋在處理報文時只有2個操做,一個是轉發;一個是丟棄。固然網橋也會作MAC地址學習就像交換機同樣。可是Linux中的虛擬網橋除了具備傳統網橋的功能外還有特殊的地方,由於運行虛擬網橋的是一個運行着Linux內核的物理主機,有可能網橋收到的報文的目的地址就是這個物理主機自己,因此這時候處理轉發和丟棄以外,還須要一種處理方式就是交給網絡協議棧的上層也就是網絡層,從而被主機自己消化,因此Linux的虛擬網橋能夠說是一個二層設備也能夠說是一個三層設備。另外還有一個不一樣的地方是虛擬網橋能夠有IP地址。
充當鏈接容器到網橋的虛擬介質叫作Veth Pair。這個東西一頭鏈接在容器的eth0上,一頭鏈接在網橋上,並且鏈接在網橋上的這一端沒有網絡協議棧功能能夠理解爲就是一個普通交換機的端口,只有一個MAC地址;而鏈接在容器eth0上的一端則具備網站的網絡協議棧功能。
完成上述功能就是經過Libnetwork來實現的:
Libnetwork是CNM原生實現的,它爲Docker daemon和網絡驅動之間提供了接口,網絡控制器負責將驅動和一個網絡對接,每一個驅動程序負責管理它擁有的網絡以及爲網絡提供各類負責,好比IPAM,這個IPAM就是IP地址管理。
Docker daemon啓動也就是dockerd這個進程,它會建立docker0,而docker0默認用的驅動就是bridge,也就是建立一個虛擬網橋設備,且具備IP地址,因此dockerd啓動後你經過ifconfig 或者 ip addr
能夠看到docker0的IP地址。
接下來就能夠建立容器,咱們使用docker
這個客戶端工具來運行容器,容器的網絡棧會在容器啓動前建立完成,這一系列的工做都由Libnetwork的某些接口(底層仍是Linux系統調用)來實現的,好比建立虛擬網卡對(Veth pair),一端鏈接容器、一端鏈接網橋、建立網絡名稱空間、關聯容器中的進程到其本身的網絡名稱空間中、設置IP地址、路由表、iptables等工做。
在dockerd這個程序中有一個參數叫作--bridige和--bip,它就是設置使用哪一個網橋以及網橋IP,以下:
dockerd --bridge=docker0 --bip=X.X.X.X/XX
經過上面的描述以及這樣的設置,啓動的容器就會鏈接到docker0網橋,不寫--bridge默認也是使用docker0,--bip則是網橋的IP地址,而容器就會得到和網橋IP同網段的IP地址。
上述關於網絡的東西也沒有什麼神祕的,其實經過Linux命令均可以實現,以下就是在主機上建立網橋和名稱空間,而後進入名稱空間配置該空間的網卡對、IP等,最後退出空間,將虛擬網卡對的一端鏈接在網橋上。
# 建立網橋 brctl addbr # 建立網絡名稱空間 ip netns add # 進入名稱空間 ip netns exec bash # 啓動lo ip link set lo up # 在建立的名稱空間中創建veth pair ip link add eth0 type veth peer name veth00001 # 爲eth0設置IP地址 ip addr add x.x.x.x/xx dev eth0 # 啓動eth0 ip link set eth0 up # 把網絡名稱空間的veth pair的一端放到主機名稱空間中 ip link set veth00001 netns # 退出名稱空間 exit # 把主機名稱空間的虛擬網卡鏈接到網橋上 brctl addif veth0001 # 在主機上ping網絡名稱空間的IP地址 ping x.x.x.x
可是對於Kubernetes項目稍微有點不一樣,它本身不提供網絡功能(早期版本有,後來取消了)。因此就致使了有些人的迷惑尤爲是初學者,本身安裝的Kubernetes集羣使用的cni0這個網橋,有些人則使用docker0這個網橋。其實這就是由於Kubernetes項目自己不負責網絡管理也不爲容器提供具體的網絡設置。而網絡插件的目的就是爲了給容器設置網絡環境。
這就意味着在Kubernetes中我可使用docker做爲容器引擎,而後也可使用docker的網絡驅動來爲容器提供網絡設置。因此也就是說當你的POD啓動時,因爲infra是第一個POD中的容器,那麼該容器的網絡棧設置是由Docker daemon調用Libnetwork接口來作的。那麼我也可使用其餘的可以完成爲infra設置網絡的其餘程序來執行這個操做。因此CNI Plugin它的目的就是一個可執行程序,在容器須要爲容器建立或者刪除網絡是進行調用來完成具體的操做。
因此這就是爲何以前要說一下POD中多容器是如何進行本地通訊的緣由,你設置POD的網絡其實就是設置Infra這個容器的Network Namespace的網絡棧。
在Kubernetes中,kubelet主要負責和容器打交道,而kubelet並非直接去操做容器,它和容器引擎交互使用的是CRI(Container Runtime Interface,它規定了運行一個容器所必須的參數和標準)接口,因此只要容器引擎提供了符合CRI標準的接口那麼能夠被Kubernetes使用。那麼容器引擎會把接口傳遞過來的數據進行翻譯,翻譯成對Linux的系統調用操做,好比建立各類名稱空間、Cgroups等。而kubelet默認使用的容器運行時是經過kubelet命令中的
--container-runtime=
來設置的,默認就是docker。
若是咱們不使用docker爲容器設置網絡棧的話還能夠怎麼作呢?這就是CNI,kubelet使用CNI規範來配置容器網絡,那麼同理使用CNI這種規範也是經過設置Infra容器的網絡棧實現POD內容器共享網絡的方式。那麼具體怎麼作呢?在kubelet啓動的命令中有以下參數:
--cni-bin-dir=STRING
這個是用於搜索CNI插件目錄,默認/opt/cni/bin
--cni-conf-dir=STRING
這個是用於搜索CNI插件配置文件路徑,默認是/opt/cni/net.d
--network-plugin=STRING
這個是要使用的CNI插件名,它就是去--cni-bin-dir
目錄去搜索
咱們先看看CNI插件有哪些,官網分了三大類,以下圖:
Main插件:這就是具體建立網絡設備的二進制程序文件,好比birdge就是建立Linux網橋的程序、ptp就是建立Veth Pair設備的程序、loopback就是建立lo設備的程序,等等。
IPAM插件:負責分配IP地址的程序文件,好比dhcp就是會向DHCP服務器發起地址申請、host-local就是會使用預先設置的IP地址段來進行分配,就像在dockerd中設置--bip同樣。
Meta其餘插件:這個是由CNI社區維護的,好比flannel就是爲Flannel項目提供的CNI插件,不過這種插件不能獨立使用,必須調用Main插件使用。
因此要用這些東西就要解決2個問題,咱們以flannel爲例,一個是網絡方案自己如何解決跨主機通訊;另一個就是如何配置Infra網絡棧並鏈接到CNI網橋上。
下面咱們以flannel爲例的網橋模式爲例說一下流程
結合使用docker容器引擎以及flannel插件來講就是,kubelet經過CRI接口建立POD,它會第一個建立Infra容器,這一步是調用Dokcer對CRI的實現(dockershim),dockershim調用docker api來完成,而後就會設置網絡,首選須要準備CNI插件參數、其次就是傳遞參數並調用這個CNI插件去設置Infra網絡。對於flannel插件所須要的參數包含2個部分:
kubelet經過CRI調用dockershim時候傳遞的一組信息,也就是具體動做好比ADD或者DEL以及這些動做所須要的參數,若是是ADD的話則包括容器網卡名稱、POD的Network namespace路徑、容器的ID等。
dockershim從CNI配置文件中加載到的(在CNI中叫作Network Configuration),也就是從--cni-conf-dir
目錄中加載的默認配置信息。
有了上面2部分信息以後,dockershim就會把這個信息給flannel插件,插件會對第二部分信息作補充,而後這個插件來作ADD操做,而這個操做是由CNI bridge這個插件來完成的。這個bridge使用所有的參數信息來執行把容器加入到CNI網絡的操做了。它會作以下內容:
檢查是否有CNI網橋,若是沒有就建立並UP這個網橋,至關於在宿主機上執行ip link add cni0 type bridge
、ip link set cni0 up
命令
接下來bridge會經過傳遞過來的Infra容器的網絡名稱空間文件進入這個網絡名稱空間中,而後建立Veth Pair設備,而後UP容器的這一端eth0,而後將另一端放到宿主機名稱空間裏並UP這個設備。
bridge把宿主機的一端鏈接到網橋cni0上
bridge會調用IPAM插件爲容器的eht0分配IP地址,並設置默認路由。
bridge會爲CNI網橋添加IP地址,若是是第一次的話。
最後CNI插件會把容器的IP地址等信息返回給dockershim,而後被kubelet添加到POD的status字段中。
如今咱們再來回顧一下以前那個問題,個人環境是kubernetes、flannel、docker都是二進制程序安裝經過系統服務形式啓動。我沒有/opt/cni/bin目錄,也就是沒有任何CNI插件,因此也就沒有設置--network-plugin。那經過kubernetes部署的POD是如何被設置網絡的呢?
經過以前的介紹咱們知道了kubelet經過CRI與容器引擎打交道來操做容器,那麼kubelet默認是用容器運行時就是docker,由於kubelet啓動參數--container-runtime就是docker,而docker又提供了符合CRI規範的接口那麼kubelet就天然可讓容器運行在docker引擎上。接下來講網絡,--network-plugin沒有設置,那kubelet到底用的什麼網絡插件呢?而CNI插件裏面也沒有docker插件這個東西。咱們看一下這個參數說明以下:
The name of the network plugin to be invoked for various events in kubelet/pod lifecycle. This docker-specific flag only works when container-runtime is set to docker.
前半句意思是說這個參數定義POD使用的網絡插件名稱,可是後面半句的意思是若是使用的容器運行時是docker,那麼默認網絡插件就是docker。
個人環境全部組件都是二進制安裝,以系統服務形式運行。最初是使用docker0網橋。這裏說一下過程:
你須要向etcd寫入子網信息{"Network":"'${CLUSTER_CIDR}'", "SubnetLen": 24, "Backend": {"Type": "vxlan"}}
。
flanneld服務啓動後會去etcd中申請本機使用的子網網段,而後就會寫入到/run/flannel/subnet.env
文件中
而後flanneld的service文件中有一條ExecStartPost=
指令,就是在flanneld啓動後執行的,做用就是調用一個腳本去修改dockerd的--bip參數把獲取的子網信息寫入
啓動docker服務,而後dockerd會根據--bip的信息設置docker0網橋
在上面這個過程當中部署的POD容器的網絡協議棧都是docker來設置的,固然是kubelet經過調用CRI接口來去和docker交互的,跨主機通訊是由flannel來完成的。
如今咱們要在某一臺主機上切換成CNI,須要作以下修改:
創建目錄mkdir -p /opt/cni/{bin,net.d}
下載CNI插件到上面的bin目錄中,而後解壓。
在上面的net.d目錄中創建10-flannel.conflist文件,內容後面再說。
中止該主機的docker、kubelet服務,這樣該主機的容器就會被master調度到其餘可用主機,不過DaemonSet類型的則不行。
修改docker服務文件的dockerd的啓動參數,去掉那個環境變量,增長--bip=10.0.0.1/16,這個IP隨便,主要是爲了避免佔用etcd中要使用的地址段,由於最初它用就是etcd中分配的。
修改kubelet服務文件,增長3個參數--cni-bin-dir=/opt/cni/bin --cni-conf-dir=/opt/cni/net.d --network-plugin=cni
,最後這個必定要寫cni,不要寫flannel,由於這裏咱們要用的就是cni,而至於cni會使用什麼插件,這個在配置文件中寫。由於在kubernetes中只有2類插件,一個是cni一個是kubenet,這裏設置的是用哪一類插件。
而後啓動以前中止的服務
10-flannel.conflist文件內容
{ "name": "cni0", "cniVersion": "0.4.0", "plugins": [ { "type": "flannel", "delegate": { "hairpinMode": true, "isDefaultGateway": true } }, { "type": "portmap", "capabilities": { "portMappings": true } } ] }
還能夠簡化爲這樣
{ "name": "cni0", "type": "flannel", "subnetFile": "/run/flannel/subnet.env", "delegate": { "hairpinMode": true, "isDefaultGateway": true } }
name: 網絡名稱而不是網橋的名字
cniVersion: 插件版本,能夠不寫。
subnetFile: 本機flanneld從etcd中申請的本機網段,這個信息存放在哪一個文件中,默認就是/run/flannel/subnet.env,我沒有改位置,因此能夠省略。
plugins:表示使用什麼插件
type: 插件類型,這裏是flannel
delegate:這個CNI插件並非本身完成,而是須要調用某中內置的CNI插件完成,對於flannel來講,就是調用bridge。flannel只是實現了網絡方案,而網絡方案對應的CNI插件是bridge,咱們下載的插件裏面就包括了bridge,全部flannel對應的CNI插件就已經被內置了,由於flannel方案自己就是使用網橋來作的。
執行過程以下:
kubelet調用CRI接口也就是dockershim,它調用CNI插件,也就是/opt/cni/bin中的flannel程序,這個程序須要2部分參數,一部分是dockershim的CNI環境變量也就是具體動做ADD或者DEL,若是是ADD則是把容器添加到CNI網絡裏,ADD會有一些參數;另一部分就是dockershim加載的JSON文件,而後/opt/cni/bin/flannel程序會對2部分參數作整合以及填充其餘信息好比ipam信息也就是具體的子網信息,ipMasp信息,mtu信息,這些都是從/run/flannel/subnet.env文件中讀取的,而後flannel會調用CNI的birdge插件,也就是執行/opt/cni/bin/bridge這個命令,由它來完成將容器加入CNI網絡的具體操做。