天天看點

如何化解 Kubernetes 網絡的複雜性?

雲栖号資訊:【 點選檢視更多行業資訊

在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!

根據 CNCF 曆年釋出的 Kubernetes 生态報告,安全、存儲、網絡始終是開發者最關注的三大槽點。即便在虛拟網絡和請求路由方面有豐富經驗,很多開發者在處理 Kubernetes 叢集網絡時還是很混亂。

本文将以帶有兩個 Linux 節點的标準 Google Kubernetes Engine(GKE)叢集為例,通過跟蹤 HTTP 請求被傳送到叢集服務的整個過程,深度拆解 Kubernetes 網絡的複雜性。

請求的旅程

當一個人在浏覽網頁時,他首先單擊一個連結,發生了一些事,之後目标頁面就被加載出來。這讓人不免好奇,從單擊連結到頁面加載,中間到底發生了什麼?

如何化解 Kubernetes 網絡的複雜性?

對于這個問題,我們可以這樣了解。如下圖所示,使用者請求通過 Internet 被發送給一個非常大的雲提供商,然後再被發送到該雲提供商基礎架構中托管的 Kubernetes 叢集。

如何化解 Kubernetes 網絡的複雜性?

如果進一步放大 Kubernetes 叢集,我們可以看到雲提供商正向 Kubernetes Service 資源(SVC)發送請求,然後将請求路由到 Kubernetes ReplicaSet(RS)中的 Pod。

如何化解 Kubernetes 網絡的複雜性?

為了更直覺,我們可以部署 YAML 來建立 Kubernetes Service 和 ReplicaSet:

如何化解 Kubernetes 網絡的複雜性?

現在我們已經在 hello-world ReplicaSet 下建立了兩個 Pod,還建立了一個帶有負載均衡器的服務資源 hello-world(如果雲提供商和叢集網絡支援),以及一個在 host:port 中有兩個條目的 Kubernetes Endpoint 資源,每個 Pod 對應一個,以 Pod IP 作為主機值和端口 8080。

在 GKE 叢集上,我們 kubectl 一下會傳回以下内容:

如何化解 Kubernetes 網絡的複雜性?

叢集 IP 網絡資訊:

  • Node - 10.138.15.0/24
  • Cluster - 10.16.0.0/14
  • Service - 10.19.240.0/20

已知服務在叢集 CIDR 中的虛拟 IP 位址(VIP)是 10.19.240.1。現在,我們可以從負載均衡器開始,深入跟蹤請求進入 Kubernetes 叢集的整個“旅程”。

負載均衡器

Kubernetes 通過本地控制器和 Ingress 控制器提供了很多公開服務的方法,但這裡我們還是使用 LoadBalancer 類型的标準 Service 資源。

我們的 hello-world 服務需要 GCP 網絡負載均衡器。每個 GKE 叢集都有一個雲控制器,它在叢集和 API 端點之間進行接口,以自動建立叢集資源所需的 GCP 服務,包括我們的負載均衡器(不同雲提供商的負載均衡器在類型、特性上都有不同)。

通過從不同的角度觀察叢集,我們可以檢視外部負載均衡器的位置:

如何化解 Kubernetes 網絡的複雜性?

kube-proxy

每個節點都有一個 kube-proxy 容器程序(在 Kubernetes 參考架構中,kube-proxy 容器位于 kube-system 命名空間的 Pod 中),它負責把尋址到叢集 Kubernetes 服務對象虛拟 IP 位址的流量轉發到相應後端 Pod。kube-proxy 目前支援三種不同的實作方式:

  • User space:即使用者空間,服務路由是在使用者程序空間的 kube-proxy 中進行的,而不是核心網絡堆棧。這是kube-proxy 的最初版本,較為穩定,但是效率不太高;
  • iptables:這種方式采用 Linux 核心級 Netfilter 規則為 Kubernetes Services 配置所有路由,是大多數平台實作 kube-proxy 的預設模式。當對多個後端 Pod 進行負載均衡時,它使用未權重的循環排程;
  • IPVS:IPVS 基于 Netfilter 架構建構,在 Linux 核心中實作了 L4 負載均衡,支援多種負載均衡算法,連接配接最少,預期延遲最短。它從 Kubernetes v1.11 中開始普遍可用,但需要 Linux 核心加載 IPVS 子產品。它也不像 iptables 那樣擁有各種 Kubernetes 網絡項目的廣泛支援。

在我們的 GKE 叢集中,kube-proxy 以 iptables 模式運作,是以我們後續主要研究該模式的工作方式。

如果檢視建立好的 hello-world 服務,我們可以發現它已經被配置設定了一個節點端口 30510。節點網絡上動态配置設定的端口允許其中托管的多個 Kubernetes 服務在其端點中使用相同的面向 Internet 的端口。

如果服務已被部署到标準 Amazon EKS 叢集,它将由 Elastic Load Balance 提供服務,該服務會将傳入的連接配接發送到相應 Pod 節點上我們服務的節點端口。但是,Google Cloud Platform 網絡負載均衡器隻會将流量轉發到與負載均衡器的傳入端口位于同一端口的目标,例如,到負載均衡器上的端口 80 的流量會被發送到目标後端執行個體上的端口 80。

我們的 hello-world pods 絕對沒有在節點的端口 80 上監聽。是以如果在節點上運作 netstat,我們可以看到沒有程序正在監聽該端口。

如何化解 Kubernetes 網絡的複雜性?

那麼,通過負載均衡器的請求是如何成功建立連接配接的呢?如果 kube-proxy 在使用者空間模式下運作,它實際上是将連接配接代理到後端 Pod。但是,在 iptables 模式下,kube-proxy 配置了 Netfilter 鍊,是以該連接配接被節點的核心直接路由到了後端容器的端點。

iptables

在我們的 GKE 叢集中,如果登入到其中一個節點并運作 iptables,我們可以看到這些規則。

如何化解 Kubernetes 網絡的複雜性?

根據規則注釋,我們可以獲得與來自服務的負載均衡器到 hello-world 服務的傳入連接配接相比對的過濾器鍊的名稱,并遵循該鍊的規則(在沒有規則注釋的情況下,我們仍然可以将規則的源 IP 位址與服務的負載均衡器進行比對)。

如何化解 Kubernetes 網絡的複雜性?

我們還可以可視化網絡堆棧中用于評估和修改資料包的鍊和規則,檢視我們在叢集中建立的服務是怎麼把流量定向到副本內建員的。

如何化解 Kubernetes 網絡的複雜性?

KUBE-FW-33X6KPGSXBPETFQV 鍊有三個規則,每個規則都添加了另一個鍊來處理資料包。

  • KUBE-MARK-MASQ 向發送到 hello-world 服務的包(來自叢集網絡外部)添加一個 Netfilter 标記。帶有此标記的資料包将按照 POSTROUTING 規則進行更改,以使用源網絡位址轉換(SNAT),并将節點 IP 位址作為其源 IP 位址;
  • KUBE-SVC-33X6KPGSXBPETFQV 鍊适用于所有與 hello-world 服務相關的流量(與源無關),并且對每個服務端點(在本例中為兩個 Pod)提供規則。使用哪個端點鍊是完全随機确定的:
  • KUBE-SEP-ALRUKLHE5DT3R34X:如果需要,KUBE-MARK-MASQ 會再次向資料包中添加一個 Netfilter 标記用以 SNAT;DNAT 規則使用 10.16.0.11:8080 端點作為目标來設定目标 NAT
  • KUBE-SEP-X7DMMHFVFOT4JLHD:如果需要,KUBE-MARK-MASQ 會再次為資料包添加一個 Netfilter 标記用以 SNAT;DNAT 規則使用 10.16.1.8:8080 端點作為目标來設定目标 NAT
  • KUBE-MARK-DROP 向此時尚未啟用目标 NAT 的資料包添加 Netfilter 标記。這些資料包将在 KUBE-FIREWALL 鍊中被丢棄。

需要注意的是,盡管我們的叢集有兩個節點,每個節點都有一個 hello-world Pod,但這種路由方法并不存在優先級。如果我們将服務規範中的 externalTrafficPolicy 更改為 Local,那麼情況就會改變。假設此時存在請求,這個請求不僅會轉到接收請求的節點上的 Pod,還會導緻沒有服務 Pod 的節點拒絕連接配接。

是以,Local 政策通常需要與 Kubernetes daemon sets 一起使用,後者會在叢集中的每個節點上排程一個 Pod。雖然前者能明顯降低請求的平均網絡延遲,但它也可能導緻服務 Pods 之間的負載不均衡。

Pod 網絡

本文不會深入介紹 Pod 網絡,但是在我們的 GKE 叢集中,Pod 網絡有自己的 CIDR 塊,與節點網絡分開。Kubernetes 網絡模型要求叢集中的所有 Pod 能夠直接互相尋址(無視其主機節點)。GKE 群集使用 kubenet CNI,它在每個節點上建立到 Pod 網絡的網橋接口,為每個節點提供自己的 Pod IP 位址專用 CIDR 塊,以簡化配置設定和路由。Google Compute Engine(GCE)網絡可以在 VM 之間路由該 Pod 網絡流量。

請求

以下是是我們擷取 HTTP 200 響應代碼的方式:

如何化解 Kubernetes 網絡的複雜性?

本文提到了許多改變路由的方法,它們由不同 Kubernetes 平台提供,下面是一個簡單的清單:

  • 容器網絡接口(CNI)插件:每個雲提供商預設使用與其 VM 網絡模型相容的 CNI 實作。本文以預設設定的 GKE 叢集為例,但如果是 Amazon EKS,那會很不一樣,因為 AWS VPC CNI 把容器直接放在節點的 VPC 網絡上;
  • Kubernetes Network Policy:Calico 是實施網絡政策最受歡迎的 CNI 插件之一,它在節點上為每個 Pod 建立一個虛拟網絡接口,并使用 Netfilter 規則來實施其防火牆規則;
  • 盡管大多數情況下仍然使用 Netfilter,但 kube-proxy IPVS 路由模式通常會把服務路由和 NAT 移出 Netfilter 規則;
  • 外部負載均衡器或其他可以将流量直接發送到服務節點端口的源将比對 iptables 中的不同鍊(KUBE-NODEPORTS);
  • Kubernetes Ingress 控制器可以通過多種方式更改邊緣服務路由;
  • 諸如 Istio 之類的服務網格可能會繞過 kube-proxy,直接連接配接服務容器之間的内部路由。

保護服務

Kubernetes 網絡需要大量可移動部件,它非常複雜,但如果開發者對叢集中發生的事有基本了解,這會有助于開發者更有效地監控、保護它。

第一,對于 Kubernetes 服務資源建立的雲負載均衡器,添加防火牆限制的通用方法是不存在的。一些雲提供商會支援服務規範中的 loadBalancerSourceRanges 字段,這個字段允許開發者提供可以連接配接到負載均衡器的 IP CIDR 塊白名單。如果雲提供商不支援此字段,它就會被忽略,是以開發者需要驗證外部負載均衡器的網絡配置。

而對于不支援 loadBalancerSourceRanges 字段的雲提供商,除非已經在雲提供商級别采取措施鎖定了負載均衡器和運作它們的雲網絡,開發者還是應該假定負載均衡器上的服務端點是對全世界開放的。由于各種因素,雲提供商負載均衡器産品的預設防火牆設定千差萬别,一些雲提供商可能還支援對 Service 對象的注釋,以配置負載均衡器的安全性。

其次,請注意,我們沒有通過在 GKE 叢集中啟用 Kubernetes 網絡政策支援來安裝 Calico CNI,因為 Calico 建立了大量其他 iptables 規則,這給可視化跟蹤到 Pod 的虛拟路由時增加了額外步驟。盡管如此,我們還是建議開發者在生産叢集中實作 NetworkPolicy API 的 CNI,并建立限制 Pod 流量的政策。

第三,啟用 HostNetwork 屬性建立的 Pod 将共享節點的網絡空間。雖然存在一些這樣做的例子,但通常情況下,大多數 Pod 不需要在主機網絡上,尤其是對于有 root 特權的 Pod,這可能會導緻受攻擊的容器可以檢視網絡流量。如果開發者需要在節點網絡上公開容器端口,而使用 Kubernetes Service 節點端口無法滿足需求,一個穩妥的選擇是可以在 PodSpec 中為容器指定 hostPort。

最後,使用主機網絡的 Pod 不應使用 NET_ADMIN 功能運作,這将使它們能夠讀取和修改節點的防火牆規則。

【雲栖号線上課堂】每天都有産品技術專家分享!

課程位址:

https://yqh.aliyun.com/live

立即加入社群,與專家面對面,及時了解課程最新動态!

【雲栖号線上課堂 社群】

https://c.tb.cn/F3.Z8gvnK

原文釋出時間:2020-05-15

本文作者:翔宇

本文來自:“

dockone

”,了解相關資訊可以關注“dockone”

繼續閱讀