雲原生網絡利器 --Cilium Service Mesh

微服務在技術領域已經算是比較 “歷史悠久” 的話題,不管是在沒有容器的傳統領域,還是在雲原生時代的今天,微服務都是軟件領域一個重要的版塊。但無論是在傳統領域,還是在雲原生背景下,微服務的體系建設和能力都是爲了解決應用服務的治理能力,問題域是一致的,只是使用到技術會有區別,整體主要包含服務發現,服務註冊、配置中心、負載均衡、斷路器、智能路由等等。接下來,**本篇技術文章主要從 Cilium 的技術視角出發,通過分析 Service Mesh 的相關技術,來了解一下基於 eBPF 技術的網絡方案 Cilium,擁有怎樣處理微服務治理的相關能力。**對 Cilium 的一些前置瞭解,可以參見之前的系列文章,《雲原生網絡利器 --Cilium 總覽》《雲原生網絡利器 --Cilium 之 eBPF 篇》

常見方案

SpringCloud: Java 體系的微服務框架。

Dubbo: Java 體系的微服務框架。

Istio: 基於 Envoy 數據面能力的服務網格。

Linkerd: 基於 Envoy 數據面能力的服務網格。

技術語言

Service Mesh (服務網格): 服務網格的概念中,突出的是網格這個概念。在服務網格中,服務本身是獨立的業務的運行單元,可以是運行在物理機,虛擬機,容器裏。這些服務本身運行的位置是複雜的拓撲,服務和服務之間的通信不是直接連接的,而是通過每一個服務自己的邊車程序,來完成流量的攔截和轉發,數據包在所有的邊車之間傳遞,從而看起來像是構成一個網狀的拓撲。目前還有一種模式是無邊車模式的服務網格,這個也是本篇重點介紹的模式。

邊車模式/無邊車模式: 邊車模式是指的 Pod 都被動態注入了一個 Sidecar Proxy,通信線路爲 ServiceA → Sidecar Proxy → Sidecar Proxy → ServiceB。無邊車模式是指不會爲 Pod 注入 Sidecar Proxy,同一個機器上的 Pod 共享一個 Proxy,通信線路爲 ServiceA → Proxy → ServiceB。

Envoy (數據面 LB): 基於 C++ 實現的負載均衡軟件。也是目前比較流行的服務網格的數據面的負載均衡組件。主要作用是從控制平面同步配置信息到自己的配置中,完成服務之間流量代理和治理能力。這裏有一個需要注意的是,Cilium 本身對 Envoy 是進行深度集成的,有一個 Envoy 內置的能力被去除了,同時也擴展了一些和 Cilium 服務網格業務相關的能力,以及和 Cilium 網絡能力進行了集成。

XDS (配置發現服務): XDS 是 Envoy 中基於 grpc stream 實現的動態配置發現能力框架,其中具體的 DS 包含了 LDS (Listener Discovery Service) 、RSD (Route Discovery Service)、CDS (Cluster Discovery Service)、EDS (Endpoint Discovery Service)、SDS (Secret Discovery Service) 等,具體 Listener、Route、Cluster、Endpoint、Secret 在後面會提到。

Ingress (訪問入口): Ingress 本身是 Kubernetes 中定義的資源對象,也就是數據模型的規範,不同的提供商可以根據自己的技術棧實現自己的 Ingress 能力,來提供容器平臺中南北向流量的能力,暴露容器服務的訪問地址到容器環境以外。

Listener (監聽器): 這是 Envoy 中的負責接收數據流量的入口,它會在主機上監聽一個端口,這個監聽的端口可以主機上沒有被佔用的端口。

Listener Filter (監聽器過濾器): 顧名思義就是對監聽器本身做一些處理能力的過濾器,常用在處理連接的元數據信息。如在客戶端連接建立的過程中做一些處理。Envoy 內置了一些監聽器過濾器,如獲取連接的目的地址的 OriginalDst 和源地址的 OriginalSrc 等。也支持自定義監聽器過濾器,如 Cilium 擴展出了 cilium.BpfMetadata 這個監聽器過濾器。

Filter Chains (過濾器鏈): 在監聽器過濾器處理結束,接下來就要進入網絡過濾器的處理階段,網絡過濾器包含很多種類型的 Filters,而這些 Filter 是以 Chains 的方式一個一個執行下去,Chains 中的每一個節點都是一個 Filter,這裏包含常見的 Dubbo proxy、Connection Limit Filter、Local rate limit、Redis Proxy、Dubbo Proxy、Tcp Proxy、Mongo Proxy、Zookeeper Proxy、HttpConnectionManager 等等。

Filter (過濾器): 過濾器是 Envoy 中提供的擴展能力之一,可以在接收到數據包之後,對其進行數據包的一些處理,其支持插件機制,可以根據自己對數據的處理能力去擴展新的 Filter。同時 Envoy 的 Chains 中也內置了一些常用的 Filter,如包含常見的處理 http 請求的 7 層協議的 HttpConnectionManager,也有處理 4 層協議的 DubboProxy,RedisProxy 等。也有自定義擴展的 Filter,如 Cilium 基於 Envoy 擴展的 cilium.network。

Http Filter (Http 過濾器): Envoy 常見的使用場景是 7 層代理的能力比較多,在 Envoy 中內置了處理 http 請求的 7 層協議的 HttpConnectionManager,對於 7 層協議的處理功能點有很多,所以 HttpConnectionManager 這個過濾器本身也包含了很多 Filter,如常見的 Router、Rate limit、Admission Control、Fault Injection、gRPC HTTP/1.1 bridge、Lua、OAuth2、Stateful session、Health check、Compressor、Wasm 、CORS 等等。Cilium 基於 Envoy 擴展了 cilium.l7policy 這個 http Filter。

Route (路由): 路由是用來在 Envoy 中控制代理的服務訪問的一些路由配置信息。如負責的 domains 是什麼;匹配什麼樣的 path 後,流量被什麼集羣去處理,這裏的集羣是 Envoy 中的概念。

Cluster (集羣): Envoy 的 Cluster 在 Kubernetes 中看,可以理解成在一個 Kubernetes 中的 Service,Service 對應的 Endpoints 就是 Cluster 的集羣成員。

Service (服務): 這裏指的就是 Kubernetes 中的 Service 資源對象。

LoadBalancer (負載均衡): 這裏指的就是 Kubernetes 中的 LoadBalancer 類型的 Service 資源對象。可以提供一個固定不變的訪問地址,這也是公有云常見的 Kubernetes 暴露服務訪問的方式。當然私有云也有一些實現,如使用的比較多的 MetalLB。

Endpoint (端點): 實現邏輯服務的網絡節點。它們組成集羣。在 Kubernetes 中就是指的是 Pod,在 Envoy 中,集羣中的端點就是 Envoy 代理的上游。在 Kubernetes 的發現模式下,是基於動態發現的 EDS,同時在 Envoy 中也支持給集羣配置靜態的 Endpoints。

TProxy (透明代理): 這裏用於在 Cilium 中完成流量攔截後,數據包進入到 Envoy 的網絡路徑。從連接信息來看,就像客戶端和服務端直接連接是一樣的,但實際中間是有一個代理在處理數據包,可以基於 iptables 的方式來達到這個能力,也可以使用 Cilium 使用 eBPF 實現的透明代理的能力。

MetalLB: 這裏是用於提供實現了 LoadBalancer 類型的 Service 的具體的 Provider 實現,用於在 Cilium 中的南北向的 Ingress 中會需要用到。MetalLB 支持 layer2 方式的 arp 和 bgp 的協議來達到廣播被暴露的地址,不管是哪種方式,目的都是爲了引流數據包到 Kubernetes 的集羣中。這裏有一個注意點,很多 LoadBalancer 的 Provider 在實現的時候,都會爲每一個 Service 同時申請一個 NodePort,比如 MetalLB、AWS 的都是這樣,但是也有例外的,比如 GCP 平臺就沒有 NodePort。但是最終目的都是引流,如果是有 NodePort 的就引流到 Kubernetes 的主機的 NodePort,如果沒有 NodePort,那就引流到對應的 Pod,這些要根據 Provider 自身的設計和實現。

eBPF Section from-container: 處理容器出口流量的 eBPF 程序,在這個場景中,可以根據訪問服務是不是需要走 Envoy 完成服務治理,來發送數據包 Envoy。

eBPF Section from-netdev: 處理主機外部的入口流量的 eBPF 程序,在這個場景中,可以根據訪問服務是不是需要走 Envoy 完成服務治理,來發送數據包 Envoy。

eBPF Section from-host: 處理主機上程序發起服務訪問的的流量處理的 eBPF 程序,在這個場景中,可以根據訪問服務是不是需要走 Envoy 完成服務治理,來發送數據包 Envoy。

eBPF Section to-netdev: 處理從當前主機要出去的數據包的這樣一個 eBPF 程序。,主要完成一些 SNAT 等相關的操作,後經過物理網卡發往遠程的主機。

eBPF ipv4_local_delivery: 處理本地的數據包到 Pod 的核心方法。不管是 Pod 到 Pod 的,還是 cilium_host 到 Pod 的,還是 overlay 到 Pod 的,還是物理網卡到 Pod 的,只要是確定了是本地的 Endpoint,那數據包要進入 Pod 就是要這個方法來處理的,主要的作用就是校驗 policy,看能不能進入到 Pod,方向對於 Pod 來說是 ingress 方向。

流量攔截: 服務網格的重要特點之一就是要應用無感知的情況下對應用的服務進行管理,而在其中重要的問題域就是怎麼解決流量攔截的技術。在 istio 的實現中,是基於 iptables 對流量進行 redirect 到 Envoy 的指定監聽的 Port 上,在出口的地方同樣攔截流量到負責出口的 Envoy 的指定監聽的 Port 上。在 Cilium 中是基於本身的網絡的 datapath 上進行根據是不是要攔截的策略進行流量的 redirect 到 Envoy 的某一個監聽的 Port 上,具體的監聽 Port 會根據不同的 CiliumEnvoyConfig 而不同。

Upstream / Downstream: ServiceA→ Proxy→ ServiceB, 那這裏的 ServiceB 就是 Upstream,ServiceA 就是 Downstream。

本地 Endpoint / 遠程 Endpoint: 本地 Endpoint 指的是請求的後端服務恰好在被訪問的機器上,反之遠程 Endpoint 是請求的後端服務不在被訪問的機器上,這個時候需要將請求的數據包轉發到遠程的 Endpoint 所在的機器,去完成請求處理。

控制平面

最近 Cilium 發佈的 1.12.0 版本,在其中比較重點的部分是提出了 Cilium Service Mesh 能力。本篇文章先不考慮和 Istio 集成或者對接的場景。Cilium Service Mesh 提供了服務網格中的南北向和東西向的能力,主要包括南北向的 Ingress 和 CiliumEnvoyConfig。CiliumEnvoyConfig 不但可以完成東西向的能力,也可以完成基於 NodePort 的方式的南北向能力。這裏主要從對比常見服務網格的視角來理解。

架構介紹

下圖包含了 Cilium Service Mesh 中的南北入口的 Ingress,也包含了東西方向的 CiliumClusterwideEnvoyConfig/CiliumEnvoyConfig。以下以 Native Routing 的模式展示數據流部分,所以不會有 Overlay 相關的部分。

Ingress 控制流:1 → 2 → 3 → 4 → 5 → 6 → 7。包含 CiliumEnvoyConfig 的處理和 LoadBalancer 類似的 Service 的處理。下文會詳細介紹。

Ingress 數據流:

CiliumEnvoyConfig 控制流: 3 → 4 → 6 → 7。包含 CiliumEnvoyConfig 的處理,下文會詳細介紹。

CiliumEnvoyConfig 數據流:

  1. 本地 Endpoint:12 → 10→ 11。從 Pod 內部發出訪問服務的請求,首先會由處理容器出口流量的 from-container eBPF 程序處理,發現是需要轉發到 Envoy 的數據包,經過 TProxy 代理 Envoy,再由 Envoy 代理,轉發到真正的 Pod 中去。其中從主機上進入到 Cilium 中,需要經過 from-host eBPF 程序處理之後,通過 eBPF 的 tail call 方式調用 ipv4_local_delivery eBPF 程序,最後由 ipv4_local_delivery eBPF 程序處理,主要是對進入容器的數據包做一些 policy 驗證類的工作,判斷數據包能不能進入到 Pod 中去,通過驗證通過了就 ctx redirect 數據包到 Pod 的 lxc-xxx,可以理解成數據包被轉發到了 Pod 了。

  2. 遠程 Endpoint:12 → 13 → 15 → 16。從 Pod 內部發出訪問服務的請求,首先會由處理容器出口流量的 from-container eBPF 程序處理,發現是需要轉發到 Envoy 的數據包,經過 TProxy 代理 Envoy,Envoy 經過 CDS 和 EDS 的能力,找到遠程的 Endpoint 的 IP 地址後,數據包經過內核進行路由的查找,經過 to-netdev eBPF 程序處理後發送到遠程的主機,數據包到達遠程主機,由遠程主機的 from-netdev eBPF 程序處理之後,通過 eBPF 的 tail call 方式調用 ipv4_local_delivery eBPF 程序,最後由 ipv4_local_delivery eBPF 程序處理,主要是對進入容器的數據包做一些 policy 驗證類的工作,判斷數據包能不能進入到 Pod 中去,驗證通過了就 ctx redirect 數據包到 Pod 的 lxc-xxx,可以理解成數據包被轉發到了 Pod 了。

  1. 本地 Endpoint:14 → 9 → 10→ 11。從主機上發送服務的請求,如直接訪問 ClusterIP,首先需要知道是,所有由主機的進程發出的請求,都是首先需要被 from-host eBPF 程序處理,發現是需要轉發到 Envoy 的數據包,經過 TProxy 代理 Envoy,再由 Envoy 代理,轉發到真正的 Pod 中去。其中從主機上進入到 Cilium 中,需要經過 from-host eBPF 程序處理之後,通過 eBPF 的 tail call 方式調用 ipv4_local_delivery eBPF 程序,最後由 ipv4_local_delivery eBPF 程序處理,主要是對進入容器的數據包做一些 policy 驗證類的工作,判斷數據包能不能進入到 Pod 中去,通過驗證通過了就 ctx redirect 數據包到 Pod 的 lxc-xxx,可以理解成數據包被轉發到了 Pod 了。

  2. 遠程 Endpoint:14 → 9 → 13 → 15 → 16。從主機上發送服務的請求,如直接訪問 ClusterIP,首先需要知道是,所有由主機的進程發出的請求,都是首先需要被 from-host eBPF 程序處理,發現是需要轉發到 Envoy 的數據包,經過 TProxy 代理 Envoy,Envoy 經過 CDS 和 EDS 的能力,找到遠程的 Endpoint 的 IP 地址後,數據包經過內核進行路由的查找,經過 to-netdev eBPF 程序處理後發送到遠程的主機,數據包到達遠程主機,由遠程主機的 from-netdev eBPF 程序處理之後,通過 eBPF 的 tail call 方式調用 ipv4_local_delivery eBPF 程序,最後由 ipv4_local_delivery eBPF 程序處理,主要是對進入容器的數據包做一些 policy 驗證類的工作,判斷數據包能不能進入到 Pod 中去,通過驗證通過了就 ctx redirect 數據包到 Pod 的 lxc-xxx,可以理解成數據包被轉發到了 Pod 了。

  1. 本地 Endpoint:8→ 9 → 10→ 11。對於集羣外部訪問 NodePort 類型的 Service,會經過負責處理外部流量的 from-netdev eBPF 程序,經過 TProxy 代理 Envoy,再由 Envoy 代理,轉發到真正的 Pod 中去。其中從主機上進入到 Cilium 中,需要經過 from-host eBPF 程序處理之後,通過 eBPF 的 tail call 方式調用 ipv4_local_delivery eBPF 程序,最後由 ipv4_local_delivery eBPF 程序處理,主要是對進入容器的數據包做一些 policy 驗證類的工作,判斷數據包能不能進入到 Pod 中去,驗證通過了就 ctx redirect 數據包到 Pod 的 lxc-xxx,可以理解成數據包被轉發到了 Pod 了。

  2. 遠程 Endpoint:8 → 9 → 13 → 15 → 16。對於集羣外部訪問 NodePort 類型的 Service,會經過負責處理外部流量的 from-netdev 的 eBPF 程序,經過 TProxy 代理到 Envoy,Envoy 經過 CDS 和 EDS 的能力,找到遠程的 Endpoint 的 IP 地址後,數據包經過內核進行路由的查找,經過 to-netdev eBPF 程序處理後發送到遠程的主機,數據包到達遠程主機,由遠程主機的 from-netdev eBPF 程序處理之後,通過 eBPF 的 tail call 方式調用 ipv4_local_delivery eBPF 程序,最後由 ipv4_local_delivery eBPF 程序處理,主要是對進入容器的數據包做一些 policy 驗證類的工作,判斷數據包能不能進入到 Pod 中去,驗證通過了就 ctx redirect 數據包到 Pod 的 lxc-xxx,可以理解成數據包被轉發到了 Pod 了。

備註:具體的數據路徑會隨着內核的版本而使用不同的 eBPF 能力,導致數據流會有稍微的不同。

CiliumEnvoyConfig實現

總體流程: 整體上包含了 Envoy 配置下發和 Cilium 流量攔截兩大部分。方法 UpsertEnvoyResources() 完成 Envoy 配置的轉換和下發動作。addK8sServiceRedirects() 方法負責給 Service 對象配置對應的標誌,用於在 Cilium 的 eBPF 程序中判斷當前的 Service 被訪問時,是需要被流量攔截,以及對應的 Proxy Port。對於這個 Proxy Port 是怎麼來的,後面會介紹。

Envoy 配置下發:首先會根據 CRD CiliumClusterwideEnvoyConfig / CiliumEnvoyConfig 的 cr 對象的定義,將 resources 部分的定義轉變成 Envoy 可以認識的各種對象,包含 Listener、Route、Cluster、Endpoint、Secret。轉換成功之後,保存到 xDS 的 cache 中,這個 cache 後面會通過 xDS 的 grpc 的 stream 下發到 Envoy 中去。

Cilium 流量攔截 - 控制面邏輯: 主要邏輯分爲幾個步驟。步驟一:registerL7LBService() 方法完成 Service 對象的 Proxy Port 的設置,以及對應的 Envoy 的 Listener 是哪一個;步驟二:UpsertService() 方法將設置好的 Service,通過 go 程序和 eBPF 的通信,將 Service 信息寫入到 eBPF 程序會訪問的 lb map 中,這裏的 lb map 就是 eBPF 支持的數據結構,用於保存和 Service 相關的信息,很多 eBPF 程序中都會用到這個 lb map 的數據。

Cilium 流量攔截 - 數據面邏輯: 主要邏輯是判斷 Service 是不是一個需要被攔截流量,redirect 到 Envoy 中去,是的話就通過透明代理的方式代理流量到 Envoy。lb4_svc_is_l7loadbalancer() 方法用於判斷 Service 是不是一個需要被攔截流量;ctx_redirect_to_proxy_hairpin_ipv4() 方法完成代理流量到 Envoy。ipv4_l3() 方法用於設置 ctx 的 ip 的 ttl,用於定期檢查 icmp 的有效性,以及設置源 mac 地址和目的 mac 地址。ctx_redirect() 方法的能力是基於 eBPF,由內核提供的核心方法,主要作用就是將數據包轉發到某一個網絡設備,可以是虛擬的網絡設備,也可以是物理的網卡。

Envoy 的監聽端口: Cilium 在引入了 Envoy 之後,給 Envoy 分配了 10000-20000 的一個端口範圍,用於爲 Envoy 分配 Listener 端口用的。在沒有開始支持 Service Mesh 的時候,這個能力已經存在,主要用於 NetworkPolicy 和觀測能力。NetworkPolicy 可以理解成使用 Envoy 的一些配置來控制訪問。至於爲什麼能觀測,後面會提到。對於每一個 CiliumClusterwideEnvoyConfig / CiliumEnvoyConfig 的 cr 對象,Cilium 都會爲其動態的自動分配對應的 Port,用於 Envoy 的 Listener 端口。每一個機器的 cilium agent 都會爲 CiliumClusterwideEnvoyConfig / CiliumEnvoyConfig 的 cr 對象分配一個 Port,而且不同的機器上爲其分配的 Port 都是根據每個機器的端口分配情況,各自分配的,所以不同的機器上爲 CiliumClusterwideEnvoyConfig / CiliumEnvoyConfig 的 cr 對象分配的端口會不同。

支持的 Envoy 能力: Cilium 深度集成了 Envoy,去掉很多 Envoy 默認自帶的很多能力,目前是保留了一下的能力,如果要使用的時候,需要關注支持的能力,目前 Cilium 對於下發的資源對象的格式沒有驗證,不會在下發的時候進行檢查,只會在下發到 Envoy 之後,從底層查看錯誤的日誌,纔會知道哪裏配置有問題,這個地方也是目前版本做的不是很友好的地方。後期會根據客戶的反饋,逐步的添加回這些被刪除的能力。

Ingress 實現

這裏的 Ingress 就是指的是 Kubernetes 的 Ingress 資源對象,Cilium 的 Ingress 的實現主要依賴於兩部分,一部分就是上面重點分析的 CiliumEnvoyConfig,基於 CiliumEnvoyConfig 可以完成內部的東西向的代理能力;一部分是 LoadBalancer 的 Kubernetes 的 Service。那接下來就要解決的是南北向的流量怎樣引流到 Kubernetes 的集羣中。在 Cilium 中使用了 LoadBalancer 的 Kubernetes 的 Service 方式來引流到 Kubernetes 集羣中。這裏的 MetalLB 是 Cilium 使用了 MetalLB 的程序包自己實現和集成了 MetalLB 的 LoadBalancer 能力。 

對比社區 Istio

本篇文章先不考慮和社區的 Istio 集成或者對接的場景。主要聚焦在 Cilium 自己的控制平面 CiliumEnvoyConfig 和社區標準的 Istio 作一些對比。

總結

從整體上,Cilium Service Mesh 目前的能力更多體現在數據平面上,同時提供了 Ingress 和 CiliumEnvoyConfig 兩個控制平面的能力,但是相比於 Istio 控制平面以及業務 API 的能力還是比較欠缺。但是 Cilium 本身和 Istio 不衝突,可以藉助於 Cilium 的網絡能力,在 Cilium 之上運行 Istio 也是一種選擇。

相關鏈接:

https://github.com/cilium/cilium

https://docs.cilium.io/en/v1.12/gettingstarted/servicemesh/l7-traffic-management/

https://isovalent.com/blog/post/cilium-service-mesh/

本文作者 

熊中祥

「DaoCloud 道客」技術合夥人

雲原生技術專家

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/taPzBp6uDKRv9FGbbVVPhA