k8s網絡通信初探

前言

近年,Docker 已成爲最主流的容器技術,已逐漸成爲虛擬化主流。相比傳統虛擬化方案,具有更輕量、易於移植、環境一致性等優勢。爲實現Docker 的集羣化、規模化管理, 谷歌推出了Kubernetes——一個對容器化應用自動部署、伸縮和管理的開源系統。而Kubernetes框架中的網絡問題是系統運行的重中之重。

本文概要分析Kubernetes系統網絡通信模式,介紹必要的網絡通信基礎、Docker容器技術通信原理、Kubernetes幾種典型網絡通信架構,更加清晰的闡述其網絡問題,爲日後相關文件的解決提供理論支撐。

1、Linux基礎(namespace+cgroup)

Linux爲了實現網絡協議棧的多實例,在網絡棧中引入了命名空間(Network Namespace),這些獨立的協議棧被隔離到不同的命名空間中。處於不同命名空間的網絡棧是完全隔離的,彼此之間無法通信,就好像兩個 “平行宇宙”。通過這種對網絡資源的隔離,就能在一個宿主機上虛擬多個不同的網絡環境。  Docker 也正是利用了網絡的命名空間特性,實現了不同容器之間網絡的隔離。linux支持多種類型的namespace,包括Network,IPC,PID, Mount, UTC, User。創建不同類型的namespace就相當於從不同資源維度在主機上作隔離。在Linux 的網絡命名空間內可以有自己獨立的路由表及獨立的 iptables/Netfilter 設置來提供包轉發、NAT 及 IP 包過濾等功能。

爲了隔離出獨立的協議棧,需要納入命名空間的元素有進程、套接字、網絡設備等。進程創建的套接字必須屬於某個命名空間,套接字的操作也需要在命名空間內進行。同樣,網絡設計也必須屬於某個命名空間。因爲網絡設備屬於公共資源,所以可以通過修改屬性實現在命名空間之間移動。當然,是否允許移動和設備的特徵有關。

Linux CGroup全稱Linux Control Group,是Linux內核爲了不讓某個進程一家獨大,而其他進程餓死,所以它的作用就是控制各進程分配的CPU,Memory,IO等。

   

2、Veth 設備對

前面提到,由於命名空間代表的是一個獨立的協議棧,所以它們之間無法通信,爲了讓處於不同命名空間的網絡互相通信,甚至和外部的網絡進行通信,所以引入 Veth 設備對是爲了在不同的網絡命名空間之間進行通信,利用它可以直接將兩個網絡命名空間連接起來。由於要連接兩個網絡命名空間,所以Veth 設備都是成對出現的,很像一對以太網卡,並且中間有直連網線。既然是一對網卡,那麼將其中一端稱爲另一端的peer。在 Veth 設備的一端發送數據時,它會將數據直接發送到另一端,並觸發另一端的接收操作。

 

3、網橋

Linux 可以支持很多不同的端口,這些端口之間可以進行通訊,這就是網橋的功能,網橋是一個二層網絡設備,可以解析收發的報文,讀取目標 MAC 地址的信息,和自己記錄的 MAC 表結合,來決策報文的轉發端口。爲了實現這些功能,網橋會學習源 MAC 地址(二層網橋轉發的依據就是 MAC 地址)。在轉發報文的時候,網橋只需要向特定的網絡接口進行轉發,從而避免不必要的網絡交互。如果它遇到一個自己從未學習到的地址,就無法知道這個報文應該從哪個網口設備轉發,於是只好將報文廣播給所有的網絡設備端口(報文來源的端口除外)。

在實際網絡中,網絡拓撲不可能永久不變,如果設備移動到一個端口上,而它沒用發送任何數據,那麼網橋設備就無法範知道這個變化,結果網橋還是向原來的端口轉發數據包,在這種情況下數據就會丟失,所以網絡還要對學習到的 MAC 地址表加上超時時間(默認爲5 分鐘)。如果網橋收到了對應端口 MAC 地址返回的包,則重設超時時間,否則過了超時時間後,就認爲這個設備已經不在那個端口上了,就會重新發送廣播。

在 Linux 的內部網絡棧裏面實現的網橋設備,作用和上面的描述相同。過去 Linux 主機一般都是隻有一個網卡,對於接收到的報文,要不轉發,要不丟棄。運行着 Linux 內核的機器本身更就是一臺主機,有可能是網絡報文的目的地,其收到的報文除了轉發和丟棄,還可能被髮送到網絡協議棧的上層(網絡層),從而被自己(這臺主機本身的協議棧)消化,所以我們既可以把網橋看做一個二層設備,也可以看作一個三層設備。

Linux 內核時通過一個虛擬的網橋設備(Net Device)來實現橋接的。這種虛擬設備可以綁定若干個以網絡橋接設備,從而將它們橋接起來。如下圖,這種 Net Device 網橋和普通的設備不同,最明顯的一個特性是它還可以有一個 IP 地址。

上圖中,網橋設備 br0 綁定了 eth0 和 eth1.對於網絡協議棧的上層來說,只看得到 br0。因爲橋接是在數據鏈路層實現的,上層不需要關心網橋的細節,於是協議棧上層需要發送的報文被送到 br0,網橋設備的處理代碼判斷報文被轉發到 eth0 還是 eth1,或者兩者皆轉發;反過來,從 eth0 或者從 eth1 接收到的報文被提交給網橋的處理代碼,在這裏會判斷報文應該被轉發、丟棄還是提交到協議棧上層。而有時 eth0、eth1 也可能會作爲報文的源地址或目的地址,直接參與報文的發送與接收,從而繞過網橋。

 

4、容器

容器實際上是結合了namespace 和 cgroup 的一般內核進程,注意,容器就是個進程

所以,當我們使用Docker起一個容器的時候,Docker會爲每一個容器創建屬於他自己的namespaces,即各個維度資源都專屬這個容器了,此時的容器就是一個孤島,也可以說是一個獨立VM就誕生了。當然他不是VM,網上關於二者的區別和優劣有一對資料.更進一步,也可以將多個容器共享一個namespace,比如如果容器共享的是network 類型的namespace,那麼這些容器就可以通過 localhost:[端口號]  來通信了。因爲此時的兩個容器從網絡的角度看,和宿主機上的兩個內核進程沒啥區別。

1、Docker概述

容器的英文爲Container,原始的含義是集裝箱。其實容器技術正是來源於集裝箱運輸的思想。過去海上運輸貨物類型很多,運輸和裝卸船效率低。爲了提高運輸效率,集裝箱都誕生了。在一艘船上,無論是衣服、奶粉、甚至汽車,只要裝在標準的集裝箱中,船東無需關心集裝箱內的貨物,用標準的運輸方式進行託運和裝卸作業,極大提高了運輸效率。正是借鑑箱運輸的思想,容器技術也誕生了。在此之前,部署一個應用至少先需要物理環境,然後是操作系統,之後還有各種中間件環境,最後才能部署應用,部署人員和開發人員都需要費時費力的管理。後來的虛擬化部署一定程度上解決了硬件資源的最大化利用,但並沒有解決應用層面的問題,每次應用部署還是需要搭建一套依賴的應用環境。由於虛擬技術使得各自還擁有自己的OS,導致性能也並不理想。Container容器技術的誕生解決了技術領域的“集裝箱運輸”的問題,利用NameSpace做隔離,對應用進行打包,通過容器引擎,共享一個操作系統,達到了良好的性能指標,實現了Build Once,Run Everywhere,節省了巨大的人力成本,提高運維效率。

標準的 Docker 支持一下 4 類網絡模式。(1)host模式:使用 --net=host 指定。()2container模式:使用 --net=container:NAME_or_ID 指定。(3)none模式:使用 --net=none 指定。(4)bridge模式:使用 --net=bridge 指定,爲默認設置。

 

2、bridge模式

一般的,在 Kubernetes 管理模式下,通常只會使用 bridge 模式。在該模式下,Docker Daemon 第 1 次啓動時會創建一個虛擬的網橋,默認的名字是 docker0,然後按照 RPC1918 的模型,在私有網絡空間中給這個網橋分配一個子網。針對由 Docker 創建出來的每一個容器,都會創建一個虛擬的以太網設備(Veth 設備對),其中一端關聯到網橋上,另一端使用 Linux 的網絡命名空間技術,映射到容器內的 eth0 設備,然後從網橋的地址段內給 eth0 接口分配一個 IP 地址。

其中 ip1 是網橋的 IP 地址,Docker Daemon 會在幾個備選地址段給它選擇一個,通常是 172 開頭的一個地址。這個地址和主機的 IP 地址是不重疊的。ip2 是 Docker 給啓動容器時,在這個網址段隨機選擇的一個沒有使用的 IP 地址,Docker 佔用它並分配給了被啓動的容器。相應的 MAC 地址也根據這個 IP 地址,在 02:42:ac:11:00:00 和 02:42:ac:11:ff:ff 的範圍內生成,這樣做可以確保不會有 ARP 的衝突。啓動後,Docker 還將 Veth 對的名字映射到了 eth0 網絡接口。ip3 就是主機的網卡地址。

美中不足的是,從 Docker 對 Linux 網絡協議棧的操作可以看到,Docker 一開始沒有考慮到多主機互聯的網絡解決方案。Docker 一直以來的理念都是 “簡單爲美”,幾乎所有嘗試 Docker 的人,都被它 “用法簡單,功能強大” 的特性所吸引,這也是 Docker 迅速走紅的一個原因。

 

3、Kubemetes網絡模式

    k8s組網要求所有的Pods之間可以在不使用NAT網絡地址轉換的情況下相互通信,所有的Nodes之間可以在不使用NAT網絡地址轉換的情況下相互通信,每個Pod自己看到的自己的ip和其他Pod看到的一致。

    k8s網絡模型在設計時遵循,每個Pod都擁有一個獨立的 IP地址,而且 假定所有 Pod 都在一個可以直接連通的、扁平的網絡空間中;不管它們是否運行在同 一 個 Node (宿主機)中,都要求它們可以直接通過對方的 IP 進行訪問。

    設計這個原則時因爲用戶不需要額外考慮如何建立 Pod 之間的連接,也不需要考慮將容器端口映射到主機端口等問題。由於 Kubemetes 的網絡模型假設 Pod 之間訪問時使用的是對方 Pod 的實際地址,所以一個Pod 內部的應用程序看到的自己的 IP 地址和端口與集羣內其他 Pod 看到的一樣。它們都是 Pod 實際分配的IP地址 (從dockerO上分配的)。將IP地址和端口在Pod內部和外部都保持一致, 我們可以不使用 NAT 來進行轉換,地址空間也自然是平的。

 

1、概述

在實際的業務場景中,組件之間的管理十分複雜,提別是隨着微服務理念逐步深入人心,應用部署的粒度更加細小和靈活。爲了支持業務應用組件的通信聯繫,Kubernetes 網絡的設計主要致力解決以下場景。

(1)容器到容器之間的直接通信。

(2)抽象的 Pod 到 Pod 之間的通信。

(3)Pod 到 Service 之間的通信。

(4)集羣外部與內部組件之間的通信。

2、容器到容器之間的直接通信

在同一個 Pod 內部的容器(Pod 內的容器是不會垮宿主機的)共享同一個網絡命名空間,共享一個 Linux 協議棧。pod中每個docker容器和pod在一個網絡命名空間內,所以ip和端口等等網絡配置都和pod一樣,主要通過docker的一種網絡模式:container,新創建的Docker容器不會創建自己的網卡,配置自己的 IP,而是和一個指定的容器共享 IP、端口範圍,來實現容器間的直接通信。這麼做的結果安全和高效,也能減少將已經存在的程序從物理機或者虛擬機移植到容器下運行的難度。

在下圖中的虛線部分就是 Node 上運行着的一個 Pod 實例。在我們的例子中,容器就是圖中容器1 和容器 2。容器 1 和容器 2 共享了一個網絡的命名空間,它們就好像在一臺機器上運行似的,它們打開的端口不會有衝突,可以直接使用 Linux 的本地 IPC 進行通信。它們之間的互相訪問只需要使用 localhost 就可以。例如,如果容器 2 運行的是 MySQL,那麼容器 1 使用 localhost:3306 就能直接訪問這個運行在容器 2 上的 MySQL 了。

3、抽象的 Pod 到 Pod 之間的通信

接下來再看看 Pod 之間的通信情況。每一個 Pod 都有一個真實的全局 IP 地址,同一個 Node 內的不同 Pod 之間可以直接採用對方 Pod 的 IP 地址通信,而且不需要使用其他發現機制,例如 DNS、Consul 或者 etcd。

Pod 容器即由可能在同一個 Node 上運行,也有可能在不同的 Node 上運行,所以通信也分爲兩類:同一個 Node 內的 Pod 之間的通信和不同 Node 上的 Pod 之間的通信

3.1同Node下Pod的通信

可以看出,Pod 1 和 Pod 2 都是通過 Veth 連接在同一個 docker0 網橋上的,它們的 IP 地址 IP1、IP2 都是從 docker0 的網段上動態獲取的,它們和網橋本身的 IP3 是同一個網段的。另外,在 Pod 1、Pod 2 的 Linux 協議棧上,默認路由都是 docker0 的地址,也就是說所有非本地地址的網絡數據,都會被默認發送到 docker0 網橋上,由 docker0 網橋直接中轉。

綜上所述,由於它們都關聯在同一個 docker0 網橋上,地址段相同,所以它們之間是能直接通信的。

 

3.2不同Node下Pod的通信

Pod 的地址是與 docker0 在同一個網段內的,我們知道 docker0 網段與宿主機網卡是兩個完全不同的 IP 網段,並且不同 Node 之間的通信只能通過宿主機的物理網卡進行,因此要想實現位於不同 Node 上的 Pod 容器之間的通信,就必須想辦法通過主機的這個 IP 地址來進行尋址和通信。

另一方面,這些動態分配且藏在 docker0 之後的所謂 “私有” IP 地址也是可以找到的。Kubernetes 會記錄所有正在運行 Pod 的 IP 分配信息,並將這些信息保存在 etcd 中(作爲 Service 的 Endpoint)。這些私有 IP 信息對於 Pod 到 Pod 的通信也是十分重要的,因爲我們的網絡模型要求 Pod 到 Pod 使用私有 IP 進行通信。所以首先要知道這些 IP 是什麼。之前提到,Kubernetes 的網絡對 Pod 的地址是平面的和直達的,所以這些 Pod 的 IP 規劃也很重要,不能由衝突。只要沒有衝突,我們就可以想辦法在整個 Kubernetes 的集羣中找到它。

綜上所述,要想支持不同 Node 上的 Pod 之間的通信,就要達到兩個條件:

(1)在整個 Kubernetes 集羣中對 Pod 的 IP 分配進行規劃,不能有衝突;

(2)找到一種辦法,將 Pod 的 IP 和所在 Node 的 IP 關聯起來,通過這個關聯讓 Pod 可以互相訪問。

根據條件 1 的要求,需要在部署 Kubernetes 時,對 docker0 的 IP 地址進行規劃,保證每一個 Node 上的 docker0 地址沒有衝突。可以在規劃後手工配置到每個 Node 上,或者做一個分配規則,由安裝的程序自己去分配佔用。例如 Kubernetes 的網絡增強開源軟件 Flannel 就能夠管理資源池的分配。

根據條件 2 的要求,Pod 中的數據在發出時,需要有一個機制能夠知道對方 Pod 的 IP 地址掛載哪個具體的 Node 上。也就是說先要找到 Node 對應宿主機的 IP 地址,將數據發送到這個宿主機的網卡上,然後在宿主機上將應用的數據轉到具體的 docker0 上。一旦數據達到宿主機 Node,則那個 Node 內部的 docker0 便知道如何將數據發送到 Pod。

在上圖中,IP 1 對應的是 Pod 1、IP 2 對應的是 Pod 2。Pod 1 在訪問 Pod 2 時,首先要將數據從源 Node 的 eth0 發送出去,找到併到達 Node 2 的 eth0。也就是說先要從 IP 3 到 IP 4,之後纔是 IP 4 到 IP 2 的遞送。

在 Google 的 GEC 環境下,Pod 的 IP 管理(類似 docker0)、分配及它們之間的路由打通都是由 GCE 完成的。Kubernetes 作爲主要在 GCE 上面運行的框架,它的設計是假設底層已經具備這些條件,所以它分配完地址並將地址記錄下來就完成了它的工作。在實際的 GCE 環境宏,GCE 的網絡組建會讀取這些信息,實現具體的網絡打通。

而在實際的生產中,因爲安全、費用、合規等種種原因,Kubernetes 的客戶不可能全部使用 Google 的 GCE 環境,所以在實際的私有云環境中,除了部署 Kubernetes 和 Docker,還需要額外的網絡配置,甚至通過一些軟件來實現 Kubernetes 對網絡的要求。做到這些後,Pod 和 Pod 之間才能無差別地透明通信。

 

4、Pod 到 Service 之間的通信

pod的ip地址是不持久的,當集羣中pod的規模縮減或者pod故障或者node故障重啓後,新的pod的ip就可能與之前的不一樣的。所以k8s中衍生出來Service來解決這個問題。

Service管理了多個Pods,每個Service有一個虛擬的ip,要訪問service管理的Pod上的服務只需要訪問你這個虛擬ip就可以了,這個虛擬ip是固定的,當service下的pod規模改變、故障重啓、node重啓時候,對使用service的用戶來說是無感知的,因爲他們使用的service的ip沒有變。當數據包到達Service虛擬ip後,數據包會被通過k8s給該servcie自動創建的負載均衡器路由到背後的pod容器。

    在k8s裏,iptables規則是由kube-proxy配置,kube-proxy監視APIserver的更改,因爲集羣中所有service(iptables)更改都會發送到APIserver上,所以每臺kubelet-proxy監視APIserver,當對service或pod虛擬IP進行修改時,kube-proxy就會在本地更新,以便正確發送給後端pod。

4.1、pod到service包的流轉:

 

1)數據包從pod1所在eth0離開,通過veth對的另一端veth0傳給網橋cbr0,網橋找不到service的ip對應的mac,交給了默認路由,到達了root命名空間的eth0

2)root命名空間的eth0接受數據包之前會經過iptables進行過濾,iptables接受數據包後使用kube-proxy在node上配置的規則響應service,然後數據包的目的ip重寫爲service後端指定的pod的ip了

 

4.2service到pod包的流轉

收到包的pod會迴應數據包到源pod,源ip是發送方ip,目標IP是接收方,數據包進行回覆時經過iptables,iptables使用內核機制conntrack記住它之前做的選擇,又將數據包源ip重新爲service的ip,目標ip不變,然後原路返回至pod1的eth0

 

5、集羣外部與內部組件之間的通信

將k8s集羣服務暴露給互聯網上用戶使用,有兩個問題;從k8s的service訪問Internet,以及從Internet訪問k8s的service兩個方面。node通過Internet網關時可以將流量路由到Internet,但是pod具有自己的IP地址,Internet王冠上的NAT轉換並不適用。下邊以node主機通過iptables的nat爲例說明。node到internet包的流轉

數據包源自pod1網絡命名空間,通過veth對連接到root網絡命名空間,緊接着,轉發表裏沒有IP對應的mac,會發送到默認路由,到達root網絡命名空間的eth0

那麼在到達root網絡明明空間之前,iptables會修改數據包,現在數據包源ip是pod1的,繼續傳輸會被Internet網關拒絕掉,因爲網關NAT僅轉發node的ip,解決方案:使iptables執行源NAT更改數據包源ip,讓數據包看起來是來自於node而不是pod

iptables修改完源ip之後,數據包離開node,根據轉發規則發給Internet網關,Internet網關執行另一個NAT,內網ip轉爲公網ip,在Internet上傳輸。

數據包迴應時,也是按照:Internet網關需要將公網IP轉換爲私有ip,到達目標node節點,再通過iptables修改目標ip並且最終傳送到pod的eth0虛擬網橋。

 

    在當前形勢下,越來越多的企業選擇Drocker+Kubernetes模式來實現虛擬化平臺的管理,k8s採用的是扁平化的網絡模型,每個 Pod 都有自己的 IP,並且可以直接通信。與一些主流技術結合,使得k8s功能更加強大。如CNI規範使得 Kubernetes可以靈活選擇多種 Plugin 實現集羣網絡;Network Policy則賦予了 Kubernetes 強大的網絡訪問控制機制。

通過對該課題的探究,深深體會到了k8s的強大,在日後的場景中將會有更加廣泛的應用。Docker+Kubernetes的網絡通信方式通過將每個軟件應用在單獨隔離的容器中,提高了軟硬件的使用效率,也拓展了軟件應用的可移植性,並且能夠對大規模數量的容器進行有效的管理部署。K8s網絡通信隨着各種方案的出現還在不斷的發展強大中,大浪淘沙,最後流傳的必將是最優秀的網絡通信方案,本文只是淺談k8s的網絡通信,更深的技術還需要更加深入的探究。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章