轉載:Kubernetes 網絡模型解析

轉載:Kubernetes 網絡模型解析


原創:加多(某大型互聯網公司資深 Java 開發工程師)

編輯:小君君(才云)

來源:技術原始積累

計算、網絡、存儲、安全一直是 Kubernetes 繞不開的話題。今天,我們就詳細了解一下,Kubernetes 網絡模型的那些事。

*注:萬字長文,建議收藏后閱讀!

Kubernetes 對 Pod 之間如何進行組網通信提出了要求,Kubernetes 對集群網絡有以下要求:

所有的 Pod 之間可以在不使用 NAT 網絡地址轉換的情況下相互通信;

所有的 Node 之間可以在不使用 NAT 網絡地址轉換的情況下相互通信;

每個 Pod 看到的自己的 IP 和其他 Pod 看到的一致。

Kubernetes 網絡模型設計基礎原則:每個 Pod 都擁有一個獨立的 IP 地址,而且假定所有 Pod 都在一個可以直接連通的、扁平的網絡空間中。所以不管它們是否運行在同一個 Node (宿主機) 中,都要求它們可以直接通過對方的 IP 進行訪問。設計這個原則的原因是,用戶不需要額外考慮如何建立 Pod 之間的連接,也不需要考慮將容器端口映射到主機端口等問題。

由于 Kubernetes 的網絡模型是假設 Pod 之間訪問時使用的是對方 Pod 的實際地址,所以一個 Pod 內部的應用程序看到自己的 IP 地址和端口與集群內其他 Pod 看到的一樣。它們都是 Pod 實際分配的 IP 地址 (從 docker0 上分配的)。這個 IP 地址和端口在 Pod 內部和外部都保持一致,我們可以不使用 NAT 來進行轉換。

鑒于上面這些要求,我們需要解決 4 個不同的網絡問題:

容器和容器之間的網絡;

Pod 與 Pod 之間的網絡;

Pod 與 Service 之間的網絡;

Internet 與 Service 之間的網絡。

下面我們對每種網絡問題以及解決方法進行一一討論。

容器和容器之間的網絡

在 Kubernetes 中每個 Pod 中管理著一組 Docker 容器,這些 Docker 容器共享同一個網絡命名空間;

Pod 中的每個 Docker 容器擁有與 Pod 相同的 IP 和 port 地址空間,并且由于它們在同一個網絡命名空間,它們之間可以通過 localhost 相互訪問。什么機制讓同一個 Pod 內的多個 Docker 容器相互通信呢?那是因為使用了 Docker 的一種網絡模型:–net=container。

container 模式指定新創建的 Docker 容器和已經存在的一個容器共享一個網絡命名空間,而不是和宿主機共享。新創建的 Docker 容器不會創建自己的網卡,配置自己的 IP,而是和一個指定的容器共享 IP、端口范圍等。

每個 Pod 容器都有一個 Pause 容器,它有獨立的網絡命名空間,在 Pod 內啟動 Docker 容器的時候,我們使用 –net=container 就可以讓當前的 Docker 容器加入到 Pod 容器擁有的網絡命名空間中(Pause 容器)。

Pod?與 Pod?之間的網絡

在 Kubernetes 中,每個 Pod 擁有一個 IP 地址,不同的 Pod 之間可以直接更改 IP 與彼此進行通訊;

在同一個 Node 上,從 Pod 視角來看,它存在于自己的網絡命名空間中,并且需要與該 Node 上的其他網絡命名空間上的 Pod 進行通信。

那么是如何做到的?這多虧了我們可以使用 Linux 虛擬以太網設備,或者說是由兩個虛擬接口組成的 veth 對使不同的網絡命名空間鏈接起來,這些虛擬接口分布在多個網絡命名空間上(這里是指多個 Pod 上)。

為了讓多個 Pod 的網絡命名空間鏈接起來,我們可以讓 veth 對的一端鏈接到 root 網絡命名空間(宿主機的),另一端鏈接到 Pod 的網絡命名空間。

每對 veth 就像一根接插電纜,連接兩側并允許流量在它們之間流動;這種 veth 對可以推廣到同一個 Node 上任意多的 Pod 上。上圖展示了使用 veth 對鏈接每個 Pod 到虛擬機的 root 網絡命名空間。

接下來,我們應該如何使用網橋設備來讓通過 veth 對鏈接到 root 命名空間的多個 Pod 進行通信呢?

Linux 以太網橋(Linux Ethernet bridge)是一個虛擬的 2 層網絡設備,目的是把多個以太網段鏈接起來。網橋維護了一個轉發表,通過檢查轉發表通過它傳輸的數據包的目的地,并決定是否將數據包傳遞到連接到網橋的其他網段。網橋代碼通過查看網絡中每個以太網設備特有的 MAC 地址來決定是傳輸數據還是丟棄數據。

網橋實現了 ARP 協議,根據給定的 IP 地址它會找到對應機器的數據鏈路層的 MAC 地址。一開始轉發表為空,當一個數據幀被網橋接受后,網橋會廣播該幀到所有的鏈接設備(除了發送方設備),并且把響應這個廣播的設備記錄到轉發表中;隨后發往相同 IP 地址的流量會直接從轉發表查找正確的 MAC 地址,然后轉發到對應的設備上。

如上圖顯示了兩個 Pod 通過 veth 對鏈接到 root 網絡命名空間,并且通過網橋進行通信。

?同一個 Node?中的 Pod?之間的一次通信 ?

鑒于每個 Pod 有自己獨立的網絡命名空間,我們可以使用虛擬以太網設備把多個 Pod 的命名空間鏈接到 root 命名空間,并且使用網橋讓多個 Pod 進行通信,下面我們看如何在兩個 Pod 之間進行通信:

通過網橋把 veth0 和 veth1 組成為一個以太網,它們直接是可以直接通信的,另外這里通過 veth 對讓 Pod1 的 eth0 和 veth0、Pod2 的 eth0 和 veth1 關聯起來,從而讓 Pod1 和 Pod2 相互通信;

Pod 1 通過自己默認的以太網設備 eth0 發送一個數據包,eth0 把數據傳遞給 veth0,數據包到達網橋后,網橋通過轉發表把數據傳遞給 veth1,然后虛擬設備 veth1 直接把包傳遞給 Pod2 網絡命名空間中的虛擬設備 eth0。

?不同 Node?中的 Pod?之間通訊 ?

Kubernetes 網絡模型需要每個 Pod 必須通過 IP 地址才可以進行訪問,每個 Pod 的 IP 地址總是對網絡中的其他 Pod 可見,并且每個 Pod?看到的自己的 IP 與別的 Pod 看到的是一樣的(雖然它沒規定如何實現),下面我們看不同 Node 間 Pod 如何交互?

Kubernetes 中每個集群中的每個 Node 都會被分配一個 CIDR 塊(無類別域間路由選擇:把網絡前綴都相同的連續地址組成的地址組稱為 CIDR 地址塊)用來給該 Node 上的 Pod 分配 IP 地址。(保證 Pod 的 IP 不會沖突)另外還需要把 Pod 的 IP 與所在的?Node IP 關聯起來。

如上圖 Node1(vm1) 上的 Pod1 與 Node2(vm2) 上 Pod4 之間進行交互;

首先 Pod1 通過自己的以太網設備 eth0 把數據包發送到關聯到 root 命名空間的 veth0 上,然后數據包被 Node1 上的網橋設備 cbr0 接受。網橋查找轉發表發現找不到 Pod4 的 MAC 地址,則會把包轉發到默認路由(root 命名空間的 eth0 設備),然后數據包經過 eth0 就離開了 Node1,被發送到網絡;

數據包到達 Node2 后,首先會被 root 命名空間的 eth0 設備進行處理,然后通過網橋 cbr0 把數據路由到虛擬設備 veth1?上,最終數據表會被流轉到與 veth1 配對的另外一端(Pod4 的 eth0)。

每個 Node 都知道如何把數據包轉發到其內部運行的 Pod。當一個數據包到達 Node 后,其內部數據流就和 Node 內 Pod 之間的流轉類似了。

對于如何來配置網絡,Kubernetes 在網絡這塊自身并沒有實現網絡規劃的具體邏輯,而是制定了一套 CNI(Container Network Interface)接口規范,開放給社區來實現。

例如 AWS,亞馬遜為 Kubernetes 維護了一個容器網絡插件 , 使用 CNI 插件來讓亞馬遜 VPC(虛擬私有云)環境中的 Node 與 Node 直接進行交互。

CoreOS 的 Flannel 是 Kubernetes 中實現 CNI 規范較為出名的一種實現。

VXLAN

UDP?幀格式

VLAN

VXLAN 協議格式 VXLAN 全稱是 Virtual eXtensible Local Area Network,虛擬可擴展的局域網。它是一種 overlay 技術,通過三層網絡來搭建虛擬二層網絡,其幀格式為:

從這個報文中可以看到 3 部分:

最外層的 UDP 協議報文用來在底層網絡上傳輸,也就是 vtep 之間互相通信的基礎;

中間是 VXLAN 頭部,vtep 接受到報文之后,去除前面的 UDP 協議部分。根據這部分來處理 VXLAN 的邏輯,主要是根據 VNI 發送到最終的虛擬機;

最里面是原始的報文,也就是虛擬機看到的報文內容。

VTEP(VXLAN Tunnel Endpoints):VXLAN 網絡的邊緣設備,用來進行 VXLAN 報文的處理(封包和解包)。vtep 可以是網絡設備(比如交換機),也可以是一臺機器(比如虛擬化集群中的宿主機);

VNI(VXLAN Network Identifier):VNI 是每個 VXLAN 的標識,是個 24 位整數,一共有 2^24 = 16,777,216(一千多萬),一般每個 VNI 對應一個租戶,也就是說使用 VXLAN 搭建的公有云,理論上可以支撐千萬級別的租戶;

Tunnel:隧道是一個邏輯上的概念,在 VXLAN 模型中并沒有具體的物理實體相對應。隧道可以看做是一種虛擬通道,VXLAN 通信雙方(圖中的虛擬機)認為自己是在直接通信,并不知道底層網絡的存在。從整體來說,每個 VXLAN 網絡像是為通信的虛擬機搭建了一個單獨的通信通道,也就是隧道。

Flannel

Flannel 是 CoreOS 團隊針對 Kubernetes 設計的一個網絡規劃實現。簡單來說,它的功能有以下幾點:

使集群中的不同 Node 主機創建的 Docker 容器都具有全集群唯一的虛擬 IP 地址;

建立一個覆蓋網絡(overlay network),這個覆蓋網絡會將數據包原封不動的傳遞到目標容器中。覆蓋網絡是建立在另一個網絡之上并由其基礎設施支持的虛擬網絡。覆蓋網絡通過將一個分組封裝在另一個分組內來將網絡服務與底層基礎設施分離。在將封裝的數據包轉發到端點后,將其解封裝;

創建一個新的虛擬網卡 flannel0 接收 docker 網橋的數據,通過維護路由表,對接收到的數據進行封包和轉發(VXLAN);

路由信息一般存放到 etcd 中:多個 Node 上的 Flanneld 依賴一個 etcd cluster 來做集中配置服務,etcd 保證了所有 Node 上 Flannel 所看到的配置是一致的。同時每個 Node 上的 Flannel 都可以監聽 etcd 上的數據變化,實時感知集群中 Node 的變化;

Flannel 首先會在 Node 上創建一個名為 flannel0 的網橋(VXLAN 類型的設備),并且在每個 Node 上運行一個名為 Flanneld 的代理。每個 Node 上的 Flannel 代理會從 etcd 上為當前 Node 申請一個 CIDR 地址塊用來給該 Node 上的 Pod 分配地址;

Flannel 致力于給 Kubernetes 集群中的 Node 提供一個三層網絡,它并不控制 Node 中的容器是如何進行組網的,僅僅關心流量如何在 Node 之間流轉。

如上圖, IP 為 10.1.15.2 的 Pod1 與另外一個 Node 上 IP 為 10.1.20.3 的 Pod2 進行通信;

首先 Pod1 通過 veth 對把數據包發送到 docker0 虛擬網橋,網橋通過查找轉發表發現 10.1.20.3 不在自己管理的網段,就會把數據包轉發給默認路由(這里為 flannel0 網橋);

flannel0 網橋是一個 VXLAN 設備,flannel0 收到數據包后,由于自己不是目的 IP 地址 10.1.20.3,也要嘗試將數據包重新發送出去。數據包沿著網絡協議棧向下流動,在二層時需要封二層以太包,填寫目的 MAC 地址,這時一般應該發出 arp:”who is 10.1.20.3″。但 VXLAN 設備的特殊性就在于它并沒有真正在二層發出這個 arp 包,而是由 linux kernel 引發一個”L3 MISS”事件并將 arp 請求發到用戶空間的 Flannel 程序中;

Flannel 程序收到”L3 MISS”內核事件以及 arp 請求 (who is 10.1.20.3) 后,并不會向外網發送 arp request,而是嘗試從 etcd 查找該地址匹配的子網的 vtep 信息,也就是會找到 Node2 上的 flannel0 的 MAC 地址信息。Flannel 將查詢到的信息放入 Node1 host 的 arp cache 表中,flannel0 完成這項工作后,Linux kernel 就可以在 arp table 中找到 10.1.20.3 對應的 MAC 地址并封裝二層以太包了:

由于是 Vlanx 設備,flannel0 還會對上面的包進行二次封裝,封裝新的以太網 MAC 幀:

Node 上 2 的 eth0 接收到上述 VXLAN 包,kernel 將識別出這是一個 VXLAN 包,于是拆包后將 packet 轉給 Node 上 2 的 flannel0。flannel0 再將這個數據包轉到 docker0,繼而由 docker0 傳輸到 Pod2 的某個容器里。

如上圖,總的來說就是建立 VXLAN 隧道,通過 UDP 把 IP 封裝一層直接送到對應的節點,實現了一個大的 VLAN。

Pod?與 Service?之間的網絡

上面展示了 Pod 之間如何通過它們自己的 IP 地址進行通信,但是 Pod 的 IP 地址是不持久的,當集群中 Pod 的規模縮減或者 Pod 故障或者 Node 故障重啟后,新的 Pod 的 IP 就可能與之前的不一樣。所以 Kubernetes 就中衍生出了 Service 來解決這個問題。

Kubernetes 中 Service 管理了一系列的 Pod,每個 Service 有一個虛擬的 IP,要訪問 Service 管理的 Pod 上的服務只需要訪問你這個虛擬 IP 就可以了。這個虛擬 IP 是固定的,當 Service 下的 Pod 規模改變、故障重啟、Node 重啟時候,對使用 Service 的用戶來說是無感知的,因為它們使用的 Service IP 是沒有變化的。

當數據包到達 Service 虛擬 IP 后,數據包會被通過 Kubernetes,給該 Servcie 自動創建的負載均衡器路由到背后的 Pod 容器上。下面我們看看具體是如何做到的?

?netfilter??

為了實現負載均衡,Kuberntes 依賴 Linux 內建的網絡框架 -netfilter。netfilter 是 Linux 提供的內核態框架,允許使用者自定義處理接口實現各種與網絡相關的操作。 netfilter 為包過濾,網絡地址轉換和端口轉換提供各種功能和操作,以及提供禁止數據包到達計算機網絡內敏感位置的功能。在 Linux 內核協議棧中,有 5 個跟 netfilter 有關的鉤子函數,數據包經過每個鉤子時,都會檢查上面是否注冊有函數,如果有的話,就會調用相應的函數處理該數據包。

NFIPPRE_ROUTING:接收的數據包剛進來,還沒有經過路由選擇,即還不知道數據包是要發給本機還是其它機器;

NFIPLOCAL_IN:已經經過路由選擇,并且該數據包的目的 IP 是本機,進入本地數據包處理流程;

NFIPFORWARD:已經經過路由選擇,但該數據包的目的 IP 不是本機,而是其它機器,進入 forward 流程;

NFIPLOCAL_OUT:本地程序要發出去的數據包剛到 IP 層,還沒進行路由選擇;

NFIPPOST_ROUTING:本地程序發出去的數據包,或者轉發(forward)的數據包已經經過了路由選擇,即將交由下層發送出去。

netfilter 是工作在哪一層?

?iptables??

iptables 是運行在用戶態的用戶程序,其基于表來管理規則,用于定義使用 netfilter 框架操作和轉換數據包的規則。根據 rule 的作用分成了好幾個表,比如用來過濾數據包的 rule 就會放到 Filter 表中,用于處理地址轉換的 rule 就會放到 NAT 表中,其中 rule 就是應用在 netfilter 鉤子上的函數,用來修改數據包的內容或過濾數據包。目前 iptables 支持的表有下面這些:

Filter:從名字就可以看出,這個表里面的 rule 主要用來過濾數據,用來控制讓哪些數據可以通過,哪些數據不能通過,它是最常用的表;

NAT(*):里面的 rule 都是用來處理網絡地址轉換的,控制要不要進行地址轉換,以及怎樣修改源地址或目的地址,從而影響數據包的路由,達到連通的目的,這是家用路由器必備的功能;

Mangle:里面的 rule 主要用來修改 IP 數據包頭,比如修改 TTL 值,同時也用于給數據包添加一些標記,從而便于后續其它模塊對數據包進行處理(這里的添加標記是指往內核 skb 結構中添加標記,而不是往真正的 IP 數據包上加東西);

Raw:在 netfilter 里面有一個叫做 connection tracking 的功能,主要用來追蹤所有的連接,而 Raw 表里的 rule 的功能是給數據包打標記,從而控制哪些數據包不被 connection tracking 所追蹤;

Security:里面的 rule 跟 SELinux 有關,主要是在數據包上設置一些 SELinux 的標記,便于跟 SELinux 相關的模塊來處理該數據包。

在 Kubernetes 中,iptables 規則由 kube-proxy 控制器配置,該控制器監視 Kubernetes API 服務器的更改。當對 Service 或 Pod 的虛擬 IP 地址進行修改時,iptables 規則也會更新以便讓 Service 能夠正確的把數據包路由到后端 Pod。

iptables 規則會監視發往 Service 虛擬 IP 的流量,并且在匹配時,從可用 Pod 集合中選擇隨機 Pod IP 地址,iptables 規則將數據包的目標 IP 地址從 Service 的虛擬 IP 更改為選定的 Pod 的 IP??偟膩碚f iptables 已在機器上完成負載均衡,并將指向 Servcie 的虛擬 IP 流量轉移到實際的 Pod IP 上。

在從 Service 到 Pod 的路徑上,IP 地址來自目標 Pod。 在這種情況下,iptables 再次重寫 IP 頭以將 Pod IP 替換為 Service 的 IP,以便 Pod 認為它一直與 Service 的虛擬 IP 通信。

?IPVS??

Kubernetes 包括了用于集群內負載均衡的第二個選項:IPVS。 IPVS(IP Virtual Server)也構建在 netfilter 之上,并實現傳輸層負載均衡(屬于 Linux 內核的一部分)。 IPVS 包含在 LVS(Linux 虛擬服務器)中,它在主機上運行,并在真實服務器集群前充當負載均衡器。 IPVS 可以將對基于 TCP 和 UDP 的服務的請求定向到真實服務器中,并使真實服務器的服務在單個 IP 地址上顯示為虛擬服務。這使得 IPVS 非常適合 Kubernetes 服務。

聲明 Kubernetes 服務時,你可以指定是否要使用 iptables 或 IPVS 完成集群內的負載均衡。 IPVS 專門用于負載均衡,并使用更高效的數據結構(哈希表),與 iptables 相比,幾乎不限制規模。在創建 IPVS 負載時,會發生以下事情:在 Node 上創建虛擬 IPVS 接口,將 Service 的 IP 地址綁定到虛擬 IPVS 接口,并為每個 Service IP 地址創建 IPVS 服務器。

?Pod?到 Service?的一個包的流轉??

如上圖,當從一個 Pod 發送數據包到 Service 時候,數據包先從 Pod1 所在的虛擬設備 eth0 離開 Pod1, 并通過 veth 對的另外一端 veth0 傳遞給網橋 cbr0,網橋找不到 Service 對應 IP 的 MAC 地址,所以把包轉發給默認路由,也就是 root 命名空間的 eth0;

在 root 命名空間的設備 eth0 接受到數據包前,數據包會經過 iptables 進行過濾,iptables 接受數據包后會使用 kube-proxy 在 Node 上安裝的規則來響應 Service 或 Pod 的事件,將數據包的目的地址從 Service 的 IP 重寫為 Service 后端特定的 Pod IP(本例子中是 Pod4);

現在數據包的目的 IP 就不再是 Service 的 IP 地址了,而是 Pod4 的 IP 地址;

iptables 利用 Linux 內核的 conntrack 來記住所做的 Pod 選擇,以便將來的流量路由到同一個 Pod(禁止任何擴展事件)。 從本質上講,iptables 直接在 Node 上進行了集群內的負載均衡,然后流量使用我們已經檢查過的 Pod-to-Pod 路由流到 Pod。

?Service?到 Pod?的一個包的流轉 ?

收到此數據包的 Pod 將會回發包到源 Pod,回包的源 IP 識別為自己的 IP(比如這里為 Pod4 的 IP),將目標 IP 設置為最初發送數據包的 Pod(這里為 Pod1 的 IP)。

數據包進入目標 Pod(這里為 Pod1)所在節點后,數據包流經 iptables,它使用 conntrack 記住它之前做出的選擇,并將數據包的源 IP 重寫為 Service IP。 從這里開始,數據包通過網橋流向與 Pod1 的命名空間配對的虛擬以太網設備,并流向我們之前看到的 Pod1 的以太網設備。

Internet 與 Service 之間的網絡

到目前為止,我們已經了解了如何在 Kubernetes 集群中路由流量。下面我們希望將服務暴露給外部使用(互聯網)。 這需要強調兩個相關的問題:

從 Kubernetes 的 Service 訪問 Internet;

從 Internet 訪問 Kubernetes 的 Service。

?Kubernetes?流量到 Internet?

從 Node 到公有 Internet 的路由流量是特定于網絡的,實際上取決于網絡配置。為了使本節更具體,下面使用 AWS VPC 討論具體細節。

在 AWS 中,Kubernetes 集群在 VPC 內運行,其中每個 Node 都分配了一個可從 Kubernetes 集群內訪問的私有 IP 地址。要使集群外部的流量可訪問,需要將 Internet 網關連接到 VPC 中。 Internet 網關有兩個目的:在 VPC 路由表中提供可以路由到 Internet 的流量目標,以及為已分配的公有 IP 地址的任何實例執行網絡地址轉換(NAT)。 NAT 轉換負責將集群專用的節點內部 IP 地址更改為公有 Internet 中可用的外部 IP 地址。

通過 Internet 網關,Node 可以將流量路由到 Internet 中。不幸的是,有一個小問題。 Pod 具有自己的 IP 地址,該 IP 地址與承載 Pod 的 Node 的 IP 地址不同,并且 Internet 網關上的 NAT 轉換僅適用于 Node 的 IP 地址。因為它不知道 Node 上正在運行哪些 Pod(Internet 網關不是容器感知的)。讓我們再次看看 Kubernetes 如何使用 iptables 解決這個問題。

本質都是使用 NAT 來做!

Node?到 Internet

如上圖中,數據包源自 Pod1 的網絡命名空間,并通過 veth 對連接到 root 命名空間:

一旦 root 命名空間,數據包就會從網橋 cbr0 傳遞到默認設備 eth0 上。因為數據包上的目的 IP 與連接到網橋的任何網段都不匹配,在到達 root 命名空間的以太網設備 eth0 之前,iptables 會修改數據包;

在這種情況下,數據包的源 IP 地址是 Pod1 的 IP 地址,如果我們將源保持為 Pod1,則 Internet 網關將拒絕它,因為網關 NAT 僅了解連接到 VM 的 IP 地址。解決方案是讓 iptables 執行源 NAT,更改數據包源,以便數據包看起來來自 VM 而不是 Pod;

有了正確的源 IP,數據包現在可以離開 VM,并到達 Internet 網關。 Internet 網關將執行另一個 NAT,將源 IP 從 VM 內部 IP 重寫為 Internet IP。最后,數據包將到達公有互聯網。在回來數據包的路上,數據包遵循相同的路徑,任何源 IP 都會與發送時候做相同的修改操作,以便系統的每一層都接收它理解的 IP 地址:Node 以及 Pod 命名空間中的 Pod IP。

?Internet?到 Kubernetes??

讓 Internet 流量進入 Kubernetes 集群,這特定于配置的網絡,可以在網絡堆棧的不同層來實現:

NodePort;

Service LoadBalancer;

Ingress 控制器。

第四層流量入口:NodePort

讓外網訪問 Kubernetes 內部的服務的第一個方法是創建一個 NodePort 類型的 Service,對于 NodePort 類型的 Service,Kubernetes 集群中的每個 Node 都會打開一個端口(所有 Node 上的端口相同),并將該端口上收到的流量重定向到具體的 Service 上。

對于 NodePort 類型的 Service,我們可以通過任何 Node 的 IP 和端口號來訪問 NodePort 服務。

創建 NodePort 類型的服務:

如下圖,服務暴露在兩個節點的端口 30123 上,到達任何一個端口的鏈接會被重定向到一個隨機選擇的 Pod。

如何做到的?

NodePort 是靠 kube-proxy 服務通過 iptables 的 NAT 轉換功能實現的,kube-proxy 會在運行過程中動態創建與 Service 相關的 iptables 規則。這些規則實現了 NodePort 的請求流量重定向到 kube-proxy 進程上對應的 Service 的代理端口上。

kube-proxy 接受到 Service 的請求訪問后,會從 Service 對應的后端 Pod 中選擇一個進行訪問(RR)。

但 NodePort 還沒有完全解決外部訪問 Service 的所有問題,比如負載均衡問題,假如我們的集群中有 10 個 Node,則此時最好有一個負載均衡器,外部的請求只需訪問此負載均衡器的 IP 地址,由負載均衡器負責轉發流量到后面某個 Node 的 NodePort 上。

第四層流量入口:LoadBalancer

該方式是 NodePort 方式的擴展,這使得 Service 可以通過一個專用的負載均衡器來訪問,這個是由具體云服務提供商來提供的,負載均衡器會將流量重定向到所有節點的端口上。如果云提供商不支持負載均衡,則退化為 NodePort 類型。

創建 Kubernetes Service 時,可以選擇指定 LoadBalancer。 LoadBalancer 的實現由云控制器提供,該控制器知道如何為你的 Service 創建負載均衡器。 創建 Service 后,它將公布負載均衡器的 IP 地址。 作為終端用戶,你可以將流量定向到負載均衡器以開始與提供的 Service 進行通信。

創建一個負載均衡服務:

借助 AWS,負載均衡器可以識別其目標組中的 Node,并將平衡集群中所有節點的流量。 一旦流量到達 Node,之前在整個集群中為 Service 安裝的 iptables 規則將確保流量到達感興趣的 Service 的 Pod 上。

下面看下 LoadBalancer 到 Service 的一個數據包的流轉過程:

LoadBalancer 在實踐中是如何運作的?

部署 Service 后,正在使用的云提供商將會創建一個新的LoadBalancer(1);

由于?LoadBalancer?不能識別容器,因此一旦流量到達LoadBalancer?后,它就會把數據包發送到在構成集群的某個 Node 中(2)。每個 Node 上的 iptables 規則將來自LoadBalancer?的傳入流量重定向到正確的 Pod(3)上;

這些 iptables 規則是在 Service 創建時候創建的。從 Pod 到請求方的響應將返回 Pod 的 IP,但請求方需要具有LoadBalancer?的 IP 地址。這就需要 iptables 和 conntrack 用于在返回路徑上正確地重寫 IP。

上圖顯示了承載 Pod 的三個 Node 前面的網絡?LoadBalancer。首先流量被傳到的 Service 的LoadBalancer(1)。一旦?LoadBalancer?收到數據包(2),它就會隨機選擇一個 VM。這里我們故意選擇了沒有 Pod 運行的 Node:Node 2。在這里,Node 上運行的 iptables 規則將使用 kube-proxy 安裝在集群中的內部負載均衡規則將數據包定向到正確的 Node 中的 Pod。 iptables 會執行正確的 NAT 并將數據包轉發到正確的 Pod(4)中。

需要注意的是每個服務需要創建自己獨有的?LoadBalancer,下面要講解的一種方式所有服務只需要一個公開服務。

第七層流量入口:Ingress Controller

這是一個與上面提到的兩種方式完全不同的機制,通過一個公開的 IP 地址來公開多個服務,第 7 層網絡流量入口是在網絡堆棧的 HTTP/HTTPS 協議范圍內運行,并建立在 Service 之上。

如上圖,不像負載均衡器每個服務需要一個公開 IP。Ingress 中的所有服務只需要一個公網 IP, 當客戶端向 Ingress 發送 HTTP 請求時候,Ingress 會根據請求的主機名和路徑決定請求轉發到哪個服務中。

創建 Ingress 資源:

如上定義了一個單一規則的 Ingress,請確保 Ingress 控制器接受的所有請求主機 kubia.example.com 的 HTTP 請求被發送到端口 80 上的 kubia-nodeport 服務。

工作原理:如下圖,客戶端首先對 kubia.example.com 執行 DNS 查找,DNS 服務器可以返回 Ingress 控制器的 IP??蛻舳四玫?IP 后,向 Ingress 控制器發送 HTTP 請求,并在 Host 投中指定的 kubia.example.com。控制器接受到請求后會從 Host 頭部就知道該訪問哪個服務,通過與該 Service 關聯的 endpoint 對象查詢 Pod IP,并將請求轉發到某一個 Pod。

這里 Ingress 并沒把請求轉發給 Service,而是自己選擇一個一個 Pod 來訪問。

第 7 層負載均衡器的一個好處是它們具有 HTTP 感知能力,因此它們了解 URL 和路徑。 這允許你按 URL 路徑細分服務流量。 它們通常還在 HTTP 請求的 X-Forwarded-For 標頭中提供原始客戶端的 IP 地址。

本文參考了大量資料,并結合作者的理解,如有不對,歡迎討論 ^^。

--

參考文獻

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?

作者簡介:

加多,某大型互聯網公司資深 Java 開發工程師,熱衷并發編程、微服務架構設計、中間件基礎設施,著有《Java 并發編程之美》一書,個人微信公眾號:技術原始積累。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容