轉(zhuǎn)載:Kubernetes 網(wǎng)絡(luò)模型解析
原創(chuàng):加多(某大型互聯(lián)網(wǎng)公司資深 Java 開發(fā)工程師)
編輯:小君君(才云)
來源:技術(shù)原始積累
計(jì)算、網(wǎng)絡(luò)、存儲(chǔ)、安全一直是 Kubernetes 繞不開的話題。今天,我們就詳細(xì)了解一下,Kubernetes 網(wǎng)絡(luò)模型的那些事。
*注:萬字長文,建議收藏后閱讀!
Kubernetes 對 Pod 之間如何進(jìn)行組網(wǎng)通信提出了要求,Kubernetes 對集群網(wǎng)絡(luò)有以下要求:
所有的 Pod 之間可以在不使用 NAT 網(wǎng)絡(luò)地址轉(zhuǎn)換的情況下相互通信;
所有的 Node 之間可以在不使用 NAT 網(wǎng)絡(luò)地址轉(zhuǎn)換的情況下相互通信;
每個(gè) Pod 看到的自己的 IP 和其他 Pod 看到的一致。
Kubernetes 網(wǎng)絡(luò)模型設(shè)計(jì)基礎(chǔ)原則:每個(gè) Pod 都擁有一個(gè)獨(dú)立的 IP 地址,而且假定所有 Pod 都在一個(gè)可以直接連通的、扁平的網(wǎng)絡(luò)空間中。所以不管它們是否運(yùn)行在同一個(gè) Node (宿主機(jī)) 中,都要求它們可以直接通過對方的 IP 進(jìn)行訪問。設(shè)計(jì)這個(gè)原則的原因是,用戶不需要額外考慮如何建立 Pod 之間的連接,也不需要考慮將容器端口映射到主機(jī)端口等問題。
由于 Kubernetes 的網(wǎng)絡(luò)模型是假設(shè) Pod 之間訪問時(shí)使用的是對方 Pod 的實(shí)際地址,所以一個(gè) Pod 內(nèi)部的應(yīng)用程序看到自己的 IP 地址和端口與集群內(nèi)其他 Pod 看到的一樣。它們都是 Pod 實(shí)際分配的 IP 地址 (從 docker0 上分配的)。這個(gè) IP 地址和端口在 Pod 內(nèi)部和外部都保持一致,我們可以不使用 NAT 來進(jìn)行轉(zhuǎn)換。
鑒于上面這些要求,我們需要解決 4 個(gè)不同的網(wǎng)絡(luò)問題:
容器和容器之間的網(wǎng)絡(luò);
Pod 與 Pod 之間的網(wǎng)絡(luò);
Pod 與 Service 之間的網(wǎng)絡(luò);
Internet 與 Service 之間的網(wǎng)絡(luò)。
下面我們對每種網(wǎng)絡(luò)問題以及解決方法進(jìn)行一一討論。
容器和容器之間的網(wǎng)絡(luò)
在 Kubernetes 中每個(gè) Pod 中管理著一組 Docker 容器,這些 Docker 容器共享同一個(gè)網(wǎng)絡(luò)命名空間;
Pod 中的每個(gè) Docker 容器擁有與 Pod 相同的 IP 和 port 地址空間,并且由于它們在同一個(gè)網(wǎng)絡(luò)命名空間,它們之間可以通過 localhost 相互訪問。什么機(jī)制讓同一個(gè) Pod 內(nèi)的多個(gè) Docker 容器相互通信呢?那是因?yàn)槭褂昧?Docker 的一種網(wǎng)絡(luò)模型:–net=container。
container 模式指定新創(chuàng)建的 Docker 容器和已經(jīng)存在的一個(gè)容器共享一個(gè)網(wǎng)絡(luò)命名空間,而不是和宿主機(jī)共享。新創(chuàng)建的 Docker 容器不會(huì)創(chuàng)建自己的網(wǎng)卡,配置自己的 IP,而是和一個(gè)指定的容器共享 IP、端口范圍等。
每個(gè) Pod 容器都有一個(gè) Pause 容器,它有獨(dú)立的網(wǎng)絡(luò)命名空間,在 Pod 內(nèi)啟動(dòng) Docker 容器的時(shí)候,我們使用 –net=container 就可以讓當(dāng)前的 Docker 容器加入到 Pod 容器擁有的網(wǎng)絡(luò)命名空間中(Pause 容器)。
Pod?與 Pod?之間的網(wǎng)絡(luò)
在 Kubernetes 中,每個(gè) Pod 擁有一個(gè) IP 地址,不同的 Pod 之間可以直接更改 IP 與彼此進(jìn)行通訊;
在同一個(gè) Node 上,從 Pod 視角來看,它存在于自己的網(wǎng)絡(luò)命名空間中,并且需要與該 Node 上的其他網(wǎng)絡(luò)命名空間上的 Pod 進(jìn)行通信。
那么是如何做到的?這多虧了我們可以使用 Linux 虛擬以太網(wǎng)設(shè)備,或者說是由兩個(gè)虛擬接口組成的 veth 對使不同的網(wǎng)絡(luò)命名空間鏈接起來,這些虛擬接口分布在多個(gè)網(wǎng)絡(luò)命名空間上(這里是指多個(gè) Pod 上)。
為了讓多個(gè) Pod 的網(wǎng)絡(luò)命名空間鏈接起來,我們可以讓 veth 對的一端鏈接到 root 網(wǎng)絡(luò)命名空間(宿主機(jī)的),另一端鏈接到 Pod 的網(wǎng)絡(luò)命名空間。
每對 veth 就像一根接插電纜,連接兩側(cè)并允許流量在它們之間流動(dòng);這種 veth 對可以推廣到同一個(gè) Node 上任意多的 Pod 上。上圖展示了使用 veth 對鏈接每個(gè) Pod 到虛擬機(jī)的 root 網(wǎng)絡(luò)命名空間。
接下來,我們應(yīng)該如何使用網(wǎng)橋設(shè)備來讓通過 veth 對鏈接到 root 命名空間的多個(gè) Pod 進(jìn)行通信呢?
Linux 以太網(wǎng)橋(Linux Ethernet bridge)是一個(gè)虛擬的 2 層網(wǎng)絡(luò)設(shè)備,目的是把多個(gè)以太網(wǎng)段鏈接起來。網(wǎng)橋維護(hù)了一個(gè)轉(zhuǎn)發(fā)表,通過檢查轉(zhuǎn)發(fā)表通過它傳輸?shù)臄?shù)據(jù)包的目的地,并決定是否將數(shù)據(jù)包傳遞到連接到網(wǎng)橋的其他網(wǎng)段。網(wǎng)橋代碼通過查看網(wǎng)絡(luò)中每個(gè)以太網(wǎng)設(shè)備特有的 MAC 地址來決定是傳輸數(shù)據(jù)還是丟棄數(shù)據(jù)。
網(wǎng)橋?qū)崿F(xiàn)了 ARP 協(xié)議,根據(jù)給定的 IP 地址它會(huì)找到對應(yīng)機(jī)器的數(shù)據(jù)鏈路層的 MAC 地址。一開始轉(zhuǎn)發(fā)表為空,當(dāng)一個(gè)數(shù)據(jù)幀被網(wǎng)橋接受后,網(wǎng)橋會(huì)廣播該幀到所有的鏈接設(shè)備(除了發(fā)送方設(shè)備),并且把響應(yīng)這個(gè)廣播的設(shè)備記錄到轉(zhuǎn)發(fā)表中;隨后發(fā)往相同 IP 地址的流量會(huì)直接從轉(zhuǎn)發(fā)表查找正確的 MAC 地址,然后轉(zhuǎn)發(fā)到對應(yīng)的設(shè)備上。
如上圖顯示了兩個(gè) Pod 通過 veth 對鏈接到 root 網(wǎng)絡(luò)命名空間,并且通過網(wǎng)橋進(jìn)行通信。
?同一個(gè) Node?中的 Pod?之間的一次通信 ?
鑒于每個(gè) Pod 有自己獨(dú)立的網(wǎng)絡(luò)命名空間,我們可以使用虛擬以太網(wǎng)設(shè)備把多個(gè) Pod 的命名空間鏈接到 root 命名空間,并且使用網(wǎng)橋讓多個(gè) Pod 進(jìn)行通信,下面我們看如何在兩個(gè) Pod 之間進(jìn)行通信:
通過網(wǎng)橋把 veth0 和 veth1 組成為一個(gè)以太網(wǎng),它們直接是可以直接通信的,另外這里通過 veth 對讓 Pod1 的 eth0 和 veth0、Pod2 的 eth0 和 veth1 關(guān)聯(lián)起來,從而讓 Pod1 和 Pod2 相互通信;
Pod 1 通過自己默認(rèn)的以太網(wǎng)設(shè)備 eth0 發(fā)送一個(gè)數(shù)據(jù)包,eth0 把數(shù)據(jù)傳遞給 veth0,數(shù)據(jù)包到達(dá)網(wǎng)橋后,網(wǎng)橋通過轉(zhuǎn)發(fā)表把數(shù)據(jù)傳遞給 veth1,然后虛擬設(shè)備 veth1 直接把包傳遞給 Pod2 網(wǎng)絡(luò)命名空間中的虛擬設(shè)備 eth0。
?不同 Node?中的 Pod?之間通訊 ?
Kubernetes 網(wǎng)絡(luò)模型需要每個(gè) Pod 必須通過 IP 地址才可以進(jìn)行訪問,每個(gè) Pod 的 IP 地址總是對網(wǎng)絡(luò)中的其他 Pod 可見,并且每個(gè) Pod?看到的自己的 IP 與別的 Pod 看到的是一樣的(雖然它沒規(guī)定如何實(shí)現(xiàn)),下面我們看不同 Node 間 Pod 如何交互?
Kubernetes 中每個(gè)集群中的每個(gè) Node 都會(huì)被分配一個(gè) CIDR 塊(無類別域間路由選擇:把網(wǎng)絡(luò)前綴都相同的連續(xù)地址組成的地址組稱為 CIDR 地址塊)用來給該 Node 上的 Pod 分配 IP 地址。(保證 Pod 的 IP 不會(huì)沖突)另外還需要把 Pod 的 IP 與所在的?Node IP 關(guān)聯(lián)起來。
如上圖 Node1(vm1) 上的 Pod1 與 Node2(vm2) 上 Pod4 之間進(jìn)行交互;
首先 Pod1 通過自己的以太網(wǎng)設(shè)備 eth0 把數(shù)據(jù)包發(fā)送到關(guān)聯(lián)到 root 命名空間的 veth0 上,然后數(shù)據(jù)包被 Node1 上的網(wǎng)橋設(shè)備 cbr0 接受。網(wǎng)橋查找轉(zhuǎn)發(fā)表發(fā)現(xiàn)找不到 Pod4 的 MAC 地址,則會(huì)把包轉(zhuǎn)發(fā)到默認(rèn)路由(root 命名空間的 eth0 設(shè)備),然后數(shù)據(jù)包經(jīng)過 eth0 就離開了 Node1,被發(fā)送到網(wǎng)絡(luò);
數(shù)據(jù)包到達(dá) Node2 后,首先會(huì)被 root 命名空間的 eth0 設(shè)備進(jìn)行處理,然后通過網(wǎng)橋 cbr0 把數(shù)據(jù)路由到虛擬設(shè)備 veth1?上,最終數(shù)據(jù)表會(huì)被流轉(zhuǎn)到與 veth1 配對的另外一端(Pod4 的 eth0)。
每個(gè) Node 都知道如何把數(shù)據(jù)包轉(zhuǎn)發(fā)到其內(nèi)部運(yùn)行的 Pod。當(dāng)一個(gè)數(shù)據(jù)包到達(dá) Node 后,其內(nèi)部數(shù)據(jù)流就和 Node 內(nèi) Pod 之間的流轉(zhuǎn)類似了。
對于如何來配置網(wǎng)絡(luò),Kubernetes 在網(wǎng)絡(luò)這塊自身并沒有實(shí)現(xiàn)網(wǎng)絡(luò)規(guī)劃的具體邏輯,而是制定了一套 CNI(Container Network Interface)接口規(guī)范,開放給社區(qū)來實(shí)現(xiàn)。
例如 AWS,亞馬遜為 Kubernetes 維護(hù)了一個(gè)容器網(wǎng)絡(luò)插件 , 使用 CNI 插件來讓亞馬遜 VPC(虛擬私有云)環(huán)境中的 Node 與 Node 直接進(jìn)行交互。
CoreOS 的 Flannel 是 Kubernetes 中實(shí)現(xiàn) CNI 規(guī)范較為出名的一種實(shí)現(xiàn)。
VXLAN
UDP?幀格式
VLAN
VXLAN 協(xié)議格式 VXLAN 全稱是 Virtual eXtensible Local Area Network,虛擬可擴(kuò)展的局域網(wǎng)。它是一種 overlay 技術(shù),通過三層網(wǎng)絡(luò)來搭建虛擬二層網(wǎng)絡(luò),其幀格式為:
從這個(gè)報(bào)文中可以看到 3 部分:
最外層的 UDP 協(xié)議報(bào)文用來在底層網(wǎng)絡(luò)上傳輸,也就是 vtep 之間互相通信的基礎(chǔ);
中間是 VXLAN 頭部,vtep 接受到報(bào)文之后,去除前面的 UDP 協(xié)議部分。根據(jù)這部分來處理 VXLAN 的邏輯,主要是根據(jù) VNI 發(fā)送到最終的虛擬機(jī);
最里面是原始的報(bào)文,也就是虛擬機(jī)看到的報(bào)文內(nèi)容。
VTEP(VXLAN Tunnel Endpoints):VXLAN 網(wǎng)絡(luò)的邊緣設(shè)備,用來進(jìn)行 VXLAN 報(bào)文的處理(封包和解包)。vtep 可以是網(wǎng)絡(luò)設(shè)備(比如交換機(jī)),也可以是一臺(tái)機(jī)器(比如虛擬化集群中的宿主機(jī));
VNI(VXLAN Network Identifier):VNI 是每個(gè) VXLAN 的標(biāo)識(shí),是個(gè) 24 位整數(shù),一共有 2^24 = 16,777,216(一千多萬),一般每個(gè) VNI 對應(yīng)一個(gè)租戶,也就是說使用 VXLAN 搭建的公有云,理論上可以支撐千萬級別的租戶;
Tunnel:隧道是一個(gè)邏輯上的概念,在 VXLAN 模型中并沒有具體的物理實(shí)體相對應(yīng)。隧道可以看做是一種虛擬通道,VXLAN 通信雙方(圖中的虛擬機(jī))認(rèn)為自己是在直接通信,并不知道底層網(wǎng)絡(luò)的存在。從整體來說,每個(gè) VXLAN 網(wǎng)絡(luò)像是為通信的虛擬機(jī)搭建了一個(gè)單獨(dú)的通信通道,也就是隧道。
Flannel
Flannel 是 CoreOS 團(tuán)隊(duì)針對 Kubernetes 設(shè)計(jì)的一個(gè)網(wǎng)絡(luò)規(guī)劃實(shí)現(xiàn)。簡單來說,它的功能有以下幾點(diǎn):
使集群中的不同 Node 主機(jī)創(chuàng)建的 Docker 容器都具有全集群唯一的虛擬 IP 地址;
建立一個(gè)覆蓋網(wǎng)絡(luò)(overlay network),這個(gè)覆蓋網(wǎng)絡(luò)會(huì)將數(shù)據(jù)包原封不動(dòng)的傳遞到目標(biāo)容器中。覆蓋網(wǎng)絡(luò)是建立在另一個(gè)網(wǎng)絡(luò)之上并由其基礎(chǔ)設(shè)施支持的虛擬網(wǎng)絡(luò)。覆蓋網(wǎng)絡(luò)通過將一個(gè)分組封裝在另一個(gè)分組內(nèi)來將網(wǎng)絡(luò)服務(wù)與底層基礎(chǔ)設(shè)施分離。在將封裝的數(shù)據(jù)包轉(zhuǎn)發(fā)到端點(diǎn)后,將其解封裝;
創(chuàng)建一個(gè)新的虛擬網(wǎng)卡 flannel0 接收 docker 網(wǎng)橋的數(shù)據(jù),通過維護(hù)路由表,對接收到的數(shù)據(jù)進(jìn)行封包和轉(zhuǎn)發(fā)(VXLAN);
路由信息一般存放到 etcd 中:多個(gè) Node 上的 Flanneld 依賴一個(gè) etcd cluster 來做集中配置服務(wù),etcd 保證了所有 Node 上 Flannel 所看到的配置是一致的。同時(shí)每個(gè) Node 上的 Flannel 都可以監(jiān)聽 etcd 上的數(shù)據(jù)變化,實(shí)時(shí)感知集群中 Node 的變化;
Flannel 首先會(huì)在 Node 上創(chuàng)建一個(gè)名為 flannel0 的網(wǎng)橋(VXLAN 類型的設(shè)備),并且在每個(gè) Node 上運(yùn)行一個(gè)名為 Flanneld 的代理。每個(gè) Node 上的 Flannel 代理會(huì)從 etcd 上為當(dāng)前 Node 申請一個(gè) CIDR 地址塊用來給該 Node 上的 Pod 分配地址;
Flannel 致力于給 Kubernetes 集群中的 Node 提供一個(gè)三層網(wǎng)絡(luò),它并不控制 Node 中的容器是如何進(jìn)行組網(wǎng)的,僅僅關(guān)心流量如何在 Node 之間流轉(zhuǎn)。
如上圖, IP 為 10.1.15.2 的 Pod1 與另外一個(gè) Node 上 IP 為 10.1.20.3 的 Pod2 進(jìn)行通信;
首先 Pod1 通過 veth 對把數(shù)據(jù)包發(fā)送到 docker0 虛擬網(wǎng)橋,網(wǎng)橋通過查找轉(zhuǎn)發(fā)表發(fā)現(xiàn) 10.1.20.3 不在自己管理的網(wǎng)段,就會(huì)把數(shù)據(jù)包轉(zhuǎn)發(fā)給默認(rèn)路由(這里為 flannel0 網(wǎng)橋);
flannel0 網(wǎng)橋是一個(gè) VXLAN 設(shè)備,flannel0 收到數(shù)據(jù)包后,由于自己不是目的 IP 地址 10.1.20.3,也要嘗試將數(shù)據(jù)包重新發(fā)送出去。數(shù)據(jù)包沿著網(wǎng)絡(luò)協(xié)議棧向下流動(dòng),在二層時(shí)需要封二層以太包,填寫目的 MAC 地址,這時(shí)一般應(yīng)該發(fā)出 arp:”who is 10.1.20.3″。但 VXLAN 設(shè)備的特殊性就在于它并沒有真正在二層發(fā)出這個(gè) arp 包,而是由 linux kernel 引發(fā)一個(gè)”L3 MISS”事件并將 arp 請求發(fā)到用戶空間的 Flannel 程序中;
Flannel 程序收到”L3 MISS”內(nèi)核事件以及 arp 請求 (who is 10.1.20.3) 后,并不會(huì)向外網(wǎng)發(fā)送 arp request,而是嘗試從 etcd 查找該地址匹配的子網(wǎng)的 vtep 信息,也就是會(huì)找到 Node2 上的 flannel0 的 MAC 地址信息。Flannel 將查詢到的信息放入 Node1 host 的 arp cache 表中,flannel0 完成這項(xiàng)工作后,Linux kernel 就可以在 arp table 中找到 10.1.20.3 對應(yīng)的 MAC 地址并封裝二層以太包了:
由于是 Vlanx 設(shè)備,flannel0 還會(huì)對上面的包進(jìn)行二次封裝,封裝新的以太網(wǎng) MAC 幀:
Node 上 2 的 eth0 接收到上述 VXLAN 包,kernel 將識(shí)別出這是一個(gè) VXLAN 包,于是拆包后將 packet 轉(zhuǎn)給 Node 上 2 的 flannel0。flannel0 再將這個(gè)數(shù)據(jù)包轉(zhuǎn)到 docker0,繼而由 docker0 傳輸?shù)?Pod2 的某個(gè)容器里。
如上圖,總的來說就是建立 VXLAN 隧道,通過 UDP 把 IP 封裝一層直接送到對應(yīng)的節(jié)點(diǎn),實(shí)現(xiàn)了一個(gè)大的 VLAN。
Pod?與 Service?之間的網(wǎng)絡(luò)
上面展示了 Pod 之間如何通過它們自己的 IP 地址進(jìn)行通信,但是 Pod 的 IP 地址是不持久的,當(dāng)集群中 Pod 的規(guī)??s減或者 Pod 故障或者 Node 故障重啟后,新的 Pod 的 IP 就可能與之前的不一樣。所以 Kubernetes 就中衍生出了 Service 來解決這個(gè)問題。
Kubernetes 中 Service 管理了一系列的 Pod,每個(gè) Service 有一個(gè)虛擬的 IP,要訪問 Service 管理的 Pod 上的服務(wù)只需要訪問你這個(gè)虛擬 IP 就可以了。這個(gè)虛擬 IP 是固定的,當(dāng) Service 下的 Pod 規(guī)模改變、故障重啟、Node 重啟時(shí)候,對使用 Service 的用戶來說是無感知的,因?yàn)樗鼈兪褂玫?Service IP 是沒有變化的。
當(dāng)數(shù)據(jù)包到達(dá) Service 虛擬 IP 后,數(shù)據(jù)包會(huì)被通過 Kubernetes,給該 Servcie 自動(dòng)創(chuàng)建的負(fù)載均衡器路由到背后的 Pod 容器上。下面我們看看具體是如何做到的?
?netfilter??
為了實(shí)現(xiàn)負(fù)載均衡,Kuberntes 依賴 Linux 內(nèi)建的網(wǎng)絡(luò)框架 -netfilter。netfilter 是 Linux 提供的內(nèi)核態(tài)框架,允許使用者自定義處理接口實(shí)現(xiàn)各種與網(wǎng)絡(luò)相關(guān)的操作。 netfilter 為包過濾,網(wǎng)絡(luò)地址轉(zhuǎn)換和端口轉(zhuǎn)換提供各種功能和操作,以及提供禁止數(shù)據(jù)包到達(dá)計(jì)算機(jī)網(wǎng)絡(luò)內(nèi)敏感位置的功能。在 Linux 內(nèi)核協(xié)議棧中,有 5 個(gè)跟 netfilter 有關(guān)的鉤子函數(shù),數(shù)據(jù)包經(jīng)過每個(gè)鉤子時(shí),都會(huì)檢查上面是否注冊有函數(shù),如果有的話,就會(huì)調(diào)用相應(yīng)的函數(shù)處理該數(shù)據(jù)包。
NFIPPRE_ROUTING:接收的數(shù)據(jù)包剛進(jìn)來,還沒有經(jīng)過路由選擇,即還不知道數(shù)據(jù)包是要發(fā)給本機(jī)還是其它機(jī)器;
NFIPLOCAL_IN:已經(jīng)經(jīng)過路由選擇,并且該數(shù)據(jù)包的目的 IP 是本機(jī),進(jìn)入本地?cái)?shù)據(jù)包處理流程;
NFIPFORWARD:已經(jīng)經(jīng)過路由選擇,但該數(shù)據(jù)包的目的 IP 不是本機(jī),而是其它機(jī)器,進(jìn)入 forward 流程;
NFIPLOCAL_OUT:本地程序要發(fā)出去的數(shù)據(jù)包剛到 IP 層,還沒進(jìn)行路由選擇;
NFIPPOST_ROUTING:本地程序發(fā)出去的數(shù)據(jù)包,或者轉(zhuǎn)發(fā)(forward)的數(shù)據(jù)包已經(jīng)經(jīng)過了路由選擇,即將交由下層發(fā)送出去。
netfilter 是工作在哪一層?
?iptables??
iptables 是運(yùn)行在用戶態(tài)的用戶程序,其基于表來管理規(guī)則,用于定義使用 netfilter 框架操作和轉(zhuǎn)換數(shù)據(jù)包的規(guī)則。根據(jù) rule 的作用分成了好幾個(gè)表,比如用來過濾數(shù)據(jù)包的 rule 就會(huì)放到 Filter 表中,用于處理地址轉(zhuǎn)換的 rule 就會(huì)放到 NAT 表中,其中 rule 就是應(yīng)用在 netfilter 鉤子上的函數(shù),用來修改數(shù)據(jù)包的內(nèi)容或過濾數(shù)據(jù)包。目前 iptables 支持的表有下面這些:
Filter:從名字就可以看出,這個(gè)表里面的 rule 主要用來過濾數(shù)據(jù),用來控制讓哪些數(shù)據(jù)可以通過,哪些數(shù)據(jù)不能通過,它是最常用的表;
NAT(*):里面的 rule 都是用來處理網(wǎng)絡(luò)地址轉(zhuǎn)換的,控制要不要進(jìn)行地址轉(zhuǎn)換,以及怎樣修改源地址或目的地址,從而影響數(shù)據(jù)包的路由,達(dá)到連通的目的,這是家用路由器必備的功能;
Mangle:里面的 rule 主要用來修改 IP 數(shù)據(jù)包頭,比如修改 TTL 值,同時(shí)也用于給數(shù)據(jù)包添加一些標(biāo)記,從而便于后續(xù)其它模塊對數(shù)據(jù)包進(jìn)行處理(這里的添加標(biāo)記是指往內(nèi)核 skb 結(jié)構(gòu)中添加標(biāo)記,而不是往真正的 IP 數(shù)據(jù)包上加?xùn)|西);
Raw:在 netfilter 里面有一個(gè)叫做 connection tracking 的功能,主要用來追蹤所有的連接,而 Raw 表里的 rule 的功能是給數(shù)據(jù)包打標(biāo)記,從而控制哪些數(shù)據(jù)包不被 connection tracking 所追蹤;
Security:里面的 rule 跟 SELinux 有關(guān),主要是在數(shù)據(jù)包上設(shè)置一些 SELinux 的標(biāo)記,便于跟 SELinux 相關(guān)的模塊來處理該數(shù)據(jù)包。
在 Kubernetes 中,iptables 規(guī)則由 kube-proxy 控制器配置,該控制器監(jiān)視 Kubernetes API 服務(wù)器的更改。當(dāng)對 Service 或 Pod 的虛擬 IP 地址進(jìn)行修改時(shí),iptables 規(guī)則也會(huì)更新以便讓 Service 能夠正確的把數(shù)據(jù)包路由到后端 Pod。
iptables 規(guī)則會(huì)監(jiān)視發(fā)往 Service 虛擬 IP 的流量,并且在匹配時(shí),從可用 Pod 集合中選擇隨機(jī) Pod IP 地址,iptables 規(guī)則將數(shù)據(jù)包的目標(biāo) IP 地址從 Service 的虛擬 IP 更改為選定的 Pod 的 IP??偟膩碚f iptables 已在機(jī)器上完成負(fù)載均衡,并將指向 Servcie 的虛擬 IP 流量轉(zhuǎn)移到實(shí)際的 Pod IP 上。
在從 Service 到 Pod 的路徑上,IP 地址來自目標(biāo) Pod。 在這種情況下,iptables 再次重寫 IP 頭以將 Pod IP 替換為 Service 的 IP,以便 Pod 認(rèn)為它一直與 Service 的虛擬 IP 通信。
?IPVS??
Kubernetes 包括了用于集群內(nèi)負(fù)載均衡的第二個(gè)選項(xiàng):IPVS。 IPVS(IP Virtual Server)也構(gòu)建在 netfilter 之上,并實(shí)現(xiàn)傳輸層負(fù)載均衡(屬于 Linux 內(nèi)核的一部分)。 IPVS 包含在 LVS(Linux 虛擬服務(wù)器)中,它在主機(jī)上運(yùn)行,并在真實(shí)服務(wù)器集群前充當(dāng)負(fù)載均衡器。 IPVS 可以將對基于 TCP 和 UDP 的服務(wù)的請求定向到真實(shí)服務(wù)器中,并使真實(shí)服務(wù)器的服務(wù)在單個(gè) IP 地址上顯示為虛擬服務(wù)。這使得 IPVS 非常適合 Kubernetes 服務(wù)。
聲明 Kubernetes 服務(wù)時(shí),你可以指定是否要使用 iptables 或 IPVS 完成集群內(nèi)的負(fù)載均衡。 IPVS 專門用于負(fù)載均衡,并使用更高效的數(shù)據(jù)結(jié)構(gòu)(哈希表),與 iptables 相比,幾乎不限制規(guī)模。在創(chuàng)建 IPVS 負(fù)載時(shí),會(huì)發(fā)生以下事情:在 Node 上創(chuàng)建虛擬 IPVS 接口,將 Service 的 IP 地址綁定到虛擬 IPVS 接口,并為每個(gè) Service IP 地址創(chuàng)建 IPVS 服務(wù)器。
?Pod?到 Service?的一個(gè)包的流轉(zhuǎn)??
如上圖,當(dāng)從一個(gè) Pod 發(fā)送數(shù)據(jù)包到 Service 時(shí)候,數(shù)據(jù)包先從 Pod1 所在的虛擬設(shè)備 eth0 離開 Pod1, 并通過 veth 對的另外一端 veth0 傳遞給網(wǎng)橋 cbr0,網(wǎng)橋找不到 Service 對應(yīng) IP 的 MAC 地址,所以把包轉(zhuǎn)發(fā)給默認(rèn)路由,也就是 root 命名空間的 eth0;
在 root 命名空間的設(shè)備 eth0 接受到數(shù)據(jù)包前,數(shù)據(jù)包會(huì)經(jīng)過 iptables 進(jìn)行過濾,iptables 接受數(shù)據(jù)包后會(huì)使用 kube-proxy 在 Node 上安裝的規(guī)則來響應(yīng) Service 或 Pod 的事件,將數(shù)據(jù)包的目的地址從 Service 的 IP 重寫為 Service 后端特定的 Pod IP(本例子中是 Pod4);
現(xiàn)在數(shù)據(jù)包的目的 IP 就不再是 Service 的 IP 地址了,而是 Pod4 的 IP 地址;
iptables 利用 Linux 內(nèi)核的 conntrack 來記住所做的 Pod 選擇,以便將來的流量路由到同一個(gè) Pod(禁止任何擴(kuò)展事件)。 從本質(zhì)上講,iptables 直接在 Node 上進(jìn)行了集群內(nèi)的負(fù)載均衡,然后流量使用我們已經(jīng)檢查過的 Pod-to-Pod 路由流到 Pod。
?Service?到 Pod?的一個(gè)包的流轉(zhuǎn) ?
收到此數(shù)據(jù)包的 Pod 將會(huì)回發(fā)包到源 Pod,回包的源 IP 識(shí)別為自己的 IP(比如這里為 Pod4 的 IP),將目標(biāo) IP 設(shè)置為最初發(fā)送數(shù)據(jù)包的 Pod(這里為 Pod1 的 IP)。
數(shù)據(jù)包進(jìn)入目標(biāo) Pod(這里為 Pod1)所在節(jié)點(diǎn)后,數(shù)據(jù)包流經(jīng) iptables,它使用 conntrack 記住它之前做出的選擇,并將數(shù)據(jù)包的源 IP 重寫為 Service IP。 從這里開始,數(shù)據(jù)包通過網(wǎng)橋流向與 Pod1 的命名空間配對的虛擬以太網(wǎng)設(shè)備,并流向我們之前看到的 Pod1 的以太網(wǎng)設(shè)備。
Internet 與 Service 之間的網(wǎng)絡(luò)
到目前為止,我們已經(jīng)了解了如何在 Kubernetes 集群中路由流量。下面我們希望將服務(wù)暴露給外部使用(互聯(lián)網(wǎng))。 這需要強(qiáng)調(diào)兩個(gè)相關(guān)的問題:
從 Kubernetes 的 Service 訪問 Internet;
從 Internet 訪問 Kubernetes 的 Service。
?Kubernetes?流量到 Internet?
從 Node 到公有 Internet 的路由流量是特定于網(wǎng)絡(luò)的,實(shí)際上取決于網(wǎng)絡(luò)配置。為了使本節(jié)更具體,下面使用 AWS VPC 討論具體細(xì)節(jié)。
在 AWS 中,Kubernetes 集群在 VPC 內(nèi)運(yùn)行,其中每個(gè) Node 都分配了一個(gè)可從 Kubernetes 集群內(nèi)訪問的私有 IP 地址。要使集群外部的流量可訪問,需要將 Internet 網(wǎng)關(guān)連接到 VPC 中。 Internet 網(wǎng)關(guān)有兩個(gè)目的:在 VPC 路由表中提供可以路由到 Internet 的流量目標(biāo),以及為已分配的公有 IP 地址的任何實(shí)例執(zhí)行網(wǎng)絡(luò)地址轉(zhuǎn)換(NAT)。 NAT 轉(zhuǎn)換負(fù)責(zé)將集群專用的節(jié)點(diǎn)內(nèi)部 IP 地址更改為公有 Internet 中可用的外部 IP 地址。
通過 Internet 網(wǎng)關(guān),Node 可以將流量路由到 Internet 中。不幸的是,有一個(gè)小問題。 Pod 具有自己的 IP 地址,該 IP 地址與承載 Pod 的 Node 的 IP 地址不同,并且 Internet 網(wǎng)關(guān)上的 NAT 轉(zhuǎn)換僅適用于 Node 的 IP 地址。因?yàn)樗恢?Node 上正在運(yùn)行哪些 Pod(Internet 網(wǎng)關(guān)不是容器感知的)。讓我們再次看看 Kubernetes 如何使用 iptables 解決這個(gè)問題。
本質(zhì)都是使用 NAT 來做!
Node?到 Internet
如上圖中,數(shù)據(jù)包源自 Pod1 的網(wǎng)絡(luò)命名空間,并通過 veth 對連接到 root 命名空間:
一旦 root 命名空間,數(shù)據(jù)包就會(huì)從網(wǎng)橋 cbr0 傳遞到默認(rèn)設(shè)備 eth0 上。因?yàn)閿?shù)據(jù)包上的目的 IP 與連接到網(wǎng)橋的任何網(wǎng)段都不匹配,在到達(dá) root 命名空間的以太網(wǎng)設(shè)備 eth0 之前,iptables 會(huì)修改數(shù)據(jù)包;
在這種情況下,數(shù)據(jù)包的源 IP 地址是 Pod1 的 IP 地址,如果我們將源保持為 Pod1,則 Internet 網(wǎng)關(guān)將拒絕它,因?yàn)榫W(wǎng)關(guān) NAT 僅了解連接到 VM 的 IP 地址。解決方案是讓 iptables 執(zhí)行源 NAT,更改數(shù)據(jù)包源,以便數(shù)據(jù)包看起來來自 VM 而不是 Pod;
有了正確的源 IP,數(shù)據(jù)包現(xiàn)在可以離開 VM,并到達(dá) Internet 網(wǎng)關(guān)。 Internet 網(wǎng)關(guān)將執(zhí)行另一個(gè) NAT,將源 IP 從 VM 內(nèi)部 IP 重寫為 Internet IP。最后,數(shù)據(jù)包將到達(dá)公有互聯(lián)網(wǎng)。在回來數(shù)據(jù)包的路上,數(shù)據(jù)包遵循相同的路徑,任何源 IP 都會(huì)與發(fā)送時(shí)候做相同的修改操作,以便系統(tǒng)的每一層都接收它理解的 IP 地址:Node 以及 Pod 命名空間中的 Pod IP。
?Internet?到 Kubernetes??
讓 Internet 流量進(jìn)入 Kubernetes 集群,這特定于配置的網(wǎng)絡(luò),可以在網(wǎng)絡(luò)堆棧的不同層來實(shí)現(xiàn):
NodePort;
Service LoadBalancer;
Ingress 控制器。
第四層流量入口:NodePort
讓外網(wǎng)訪問 Kubernetes 內(nèi)部的服務(wù)的第一個(gè)方法是創(chuàng)建一個(gè) NodePort 類型的 Service,對于 NodePort 類型的 Service,Kubernetes 集群中的每個(gè) Node 都會(huì)打開一個(gè)端口(所有 Node 上的端口相同),并將該端口上收到的流量重定向到具體的 Service 上。
對于 NodePort 類型的 Service,我們可以通過任何 Node 的 IP 和端口號來訪問 NodePort 服務(wù)。
創(chuàng)建 NodePort 類型的服務(wù):
如下圖,服務(wù)暴露在兩個(gè)節(jié)點(diǎn)的端口 30123 上,到達(dá)任何一個(gè)端口的鏈接會(huì)被重定向到一個(gè)隨機(jī)選擇的 Pod。
如何做到的?
NodePort 是靠 kube-proxy 服務(wù)通過 iptables 的 NAT 轉(zhuǎn)換功能實(shí)現(xiàn)的,kube-proxy 會(huì)在運(yùn)行過程中動(dòng)態(tài)創(chuàng)建與 Service 相關(guān)的 iptables 規(guī)則。這些規(guī)則實(shí)現(xiàn)了 NodePort 的請求流量重定向到 kube-proxy 進(jìn)程上對應(yīng)的 Service 的代理端口上。
kube-proxy 接受到 Service 的請求訪問后,會(huì)從 Service 對應(yīng)的后端 Pod 中選擇一個(gè)進(jìn)行訪問(RR)。
但 NodePort 還沒有完全解決外部訪問 Service 的所有問題,比如負(fù)載均衡問題,假如我們的集群中有 10 個(gè) Node,則此時(shí)最好有一個(gè)負(fù)載均衡器,外部的請求只需訪問此負(fù)載均衡器的 IP 地址,由負(fù)載均衡器負(fù)責(zé)轉(zhuǎn)發(fā)流量到后面某個(gè) Node 的 NodePort 上。
第四層流量入口:LoadBalancer
該方式是 NodePort 方式的擴(kuò)展,這使得 Service 可以通過一個(gè)專用的負(fù)載均衡器來訪問,這個(gè)是由具體云服務(wù)提供商來提供的,負(fù)載均衡器會(huì)將流量重定向到所有節(jié)點(diǎn)的端口上。如果云提供商不支持負(fù)載均衡,則退化為 NodePort 類型。
創(chuàng)建 Kubernetes Service 時(shí),可以選擇指定 LoadBalancer。 LoadBalancer 的實(shí)現(xiàn)由云控制器提供,該控制器知道如何為你的 Service 創(chuàng)建負(fù)載均衡器。 創(chuàng)建 Service 后,它將公布負(fù)載均衡器的 IP 地址。 作為終端用戶,你可以將流量定向到負(fù)載均衡器以開始與提供的 Service 進(jìn)行通信。
創(chuàng)建一個(gè)負(fù)載均衡服務(wù):
借助 AWS,負(fù)載均衡器可以識(shí)別其目標(biāo)組中的 Node,并將平衡集群中所有節(jié)點(diǎn)的流量。 一旦流量到達(dá) Node,之前在整個(gè)集群中為 Service 安裝的 iptables 規(guī)則將確保流量到達(dá)感興趣的 Service 的 Pod 上。
下面看下 LoadBalancer 到 Service 的一個(gè)數(shù)據(jù)包的流轉(zhuǎn)過程:
LoadBalancer 在實(shí)踐中是如何運(yùn)作的?
部署 Service 后,正在使用的云提供商將會(huì)創(chuàng)建一個(gè)新的LoadBalancer(1);
由于?LoadBalancer?不能識(shí)別容器,因此一旦流量到達(dá)LoadBalancer?后,它就會(huì)把數(shù)據(jù)包發(fā)送到在構(gòu)成集群的某個(gè) Node 中(2)。每個(gè) Node 上的 iptables 規(guī)則將來自LoadBalancer?的傳入流量重定向到正確的 Pod(3)上;
這些 iptables 規(guī)則是在 Service 創(chuàng)建時(shí)候創(chuàng)建的。從 Pod 到請求方的響應(yīng)將返回 Pod 的 IP,但請求方需要具有LoadBalancer?的 IP 地址。這就需要 iptables 和 conntrack 用于在返回路徑上正確地重寫 IP。
上圖顯示了承載 Pod 的三個(gè) Node 前面的網(wǎng)絡(luò)?LoadBalancer。首先流量被傳到的 Service 的LoadBalancer(1)。一旦?LoadBalancer?收到數(shù)據(jù)包(2),它就會(huì)隨機(jī)選擇一個(gè) VM。這里我們故意選擇了沒有 Pod 運(yùn)行的 Node:Node 2。在這里,Node 上運(yùn)行的 iptables 規(guī)則將使用 kube-proxy 安裝在集群中的內(nèi)部負(fù)載均衡規(guī)則將數(shù)據(jù)包定向到正確的 Node 中的 Pod。 iptables 會(huì)執(zhí)行正確的 NAT 并將數(shù)據(jù)包轉(zhuǎn)發(fā)到正確的 Pod(4)中。
需要注意的是每個(gè)服務(wù)需要?jiǎng)?chuàng)建自己獨(dú)有的?LoadBalancer,下面要講解的一種方式所有服務(wù)只需要一個(gè)公開服務(wù)。
第七層流量入口:Ingress Controller
這是一個(gè)與上面提到的兩種方式完全不同的機(jī)制,通過一個(gè)公開的 IP 地址來公開多個(gè)服務(wù),第 7 層網(wǎng)絡(luò)流量入口是在網(wǎng)絡(luò)堆棧的 HTTP/HTTPS 協(xié)議范圍內(nèi)運(yùn)行,并建立在 Service 之上。
如上圖,不像負(fù)載均衡器每個(gè)服務(wù)需要一個(gè)公開 IP。Ingress 中的所有服務(wù)只需要一個(gè)公網(wǎng) IP, 當(dāng)客戶端向 Ingress 發(fā)送 HTTP 請求時(shí)候,Ingress 會(huì)根據(jù)請求的主機(jī)名和路徑?jīng)Q定請求轉(zhuǎn)發(fā)到哪個(gè)服務(wù)中。
創(chuàng)建 Ingress 資源:
如上定義了一個(gè)單一規(guī)則的 Ingress,請確保 Ingress 控制器接受的所有請求主機(jī) kubia.example.com 的 HTTP 請求被發(fā)送到端口 80 上的 kubia-nodeport 服務(wù)。
工作原理:如下圖,客戶端首先對 kubia.example.com 執(zhí)行 DNS 查找,DNS 服務(wù)器可以返回 Ingress 控制器的 IP??蛻舳四玫?IP 后,向 Ingress 控制器發(fā)送 HTTP 請求,并在 Host 投中指定的 kubia.example.com??刂破鹘邮艿秸埱蠛髸?huì)從 Host 頭部就知道該訪問哪個(gè)服務(wù),通過與該 Service 關(guān)聯(lián)的 endpoint 對象查詢 Pod IP,并將請求轉(zhuǎn)發(fā)到某一個(gè) Pod。
這里 Ingress 并沒把請求轉(zhuǎn)發(fā)給 Service,而是自己選擇一個(gè)一個(gè) Pod 來訪問。
第 7 層負(fù)載均衡器的一個(gè)好處是它們具有 HTTP 感知能力,因此它們了解 URL 和路徑。 這允許你按 URL 路徑細(xì)分服務(wù)流量。 它們通常還在 HTTP 請求的 X-Forwarded-For 標(biāo)頭中提供原始客戶端的 IP 地址。
本文參考了大量資料,并結(jié)合作者的理解,如有不對,歡迎討論 ^^。
--
參考文獻(xiàn)
http://www.linuxtcpipstack.com/685.html
https://www.ibm.com/developerworks/cn/linux/l-ntflt/index.html
https://www.ibm.com/developerworks/cn/linux/network/s-netip/index.html
https://yuque.antfin-inc.com/jialei.zjl/ylprqa/bsti0g
https://www.atatech.org/articles/134314
https://www.atatech.org/articles/113438
https://linux.cn/article-5595-1.html
https://tools.ietf.org/html/rfc7348
https://cizixs.com/2017/09/25/vxlan-protocol-introduction/
https://tonybai.com/2017/01/17/understanding-flannel-network-for-kubernetes/
https://github.com/containernetworking/plugins
https://www.kubernetes.org.cn/4105.html
https://ieevee.com/tech/2017/01/18/k8s-flannel.html
http://dockone.io/article/618
http://yizhanggou.top/kubernetes%E9%97%B4pod%E7%9A%84%E4%BA%92%E8%81%94%EF%BC%9Aflannel/
https://www.hi-linux.com/posts/30481.html
https://juejin.im/post/5bb45d63f265da0a9e532128?
作者簡介:
加多,某大型互聯(lián)網(wǎng)公司資深 Java 開發(fā)工程師,熱衷并發(fā)編程、微服務(wù)架構(gòu)設(shè)計(jì)、中間件基礎(chǔ)設(shè)施,著有《Java 并發(fā)編程之美》一書,個(gè)人微信公眾號:技術(shù)原始積累。