微服务在技术领域已经算是比较 “历史悠久” 的话题,不管是在没有容器的传统领域,还是在云原生时代的今天,微服务都是软件领域一个重要的版块。
但无论是在传统领域,还是在云原生背景下,微服务的体系建设和能力都是为了解决应用服务的治理能力,问题域是一致的,只是使用到技术会有区别,整体主要包含服务发现,服务注册、配置中心、负载均衡、断路器、智能路由等等。
接下来,本篇技术文章主要从 Cilium 的技术视角出发,通过分析 Service Mesh 的相关技术,来了解一下基于 eBPF 技术的网络方案 Cilium,拥有怎样处理微服务治理的相关能力。
1
常见方案
  • SpringCloud:Java 体系的微服务框架。
  • Dubbo:Java 体系的微服务框架。
  • Istio:基于 Envoy 数据面能力的服务网格。
  • Linkerd:自带数据面能力的服务网格。
2
技术语言
  • 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 所在的机器,去完成请求处理。
3
控制平面
最近 Cilium 发布的 1.12.0 版本,在其中比较重点的部分是提出了 Cilium Service Mesh 能力。本篇文章先不考虑和 Istio 集成或者对接的场景。Cilium Service Mesh 提供了服务网格中的南北向和东西向的能力,主要包括南北向的 Ingress 和 CiliumEnvoyConfig。CiliumEnvoyConfig 不但可以完成东西向的能力,也可以完成基于 NodePort的方式的南北向能力。这里主要从对比常见服务网格的视角来理解。
  • Ingress:Kubernetes 标准的资源对象。只是 Cilium 基于 ingressClassName 扩展实现了自己的 Ingress 南北向流量的能力。Ingress 创建成功之后,可以基于 Ingress 的 external IP 去访问 Kubernetes 的服务。
  • CiliumClusterwideEnvoyConfig/CiliumEnvoyConfig:这两个数据结构和作用基本是相同的,区别是一个是 namespace 级别的,一个是 cluster 级别的。
    主要的作用包含两个大的部分能力。一部分是对一些指定的 Kubernetes Service,让其在 Cilium 网络层面访问这些 Service 的时候会走到 Envoy 的能力。这部分的定义是在 spec 的 services 中指定,这里的 services 中包含的 services 会被 Cilium Service Mesh 认为是需要服务治理能力,所有这些服务的访问都会被 redirect 到 Cilium 内置的 Envoy 中,由 Envoy 中配置的各种规则完成服务的治理之后,代理到真正的 Backend,所以前面提到也可以基于 NodePort 的方式提供南北向。
    同时服务还可以有服务治理的能力,作用类似 Istio 中的 istio-injection=enabled,只是 Istio 这种是基于 namespace 级别的动态注入,Cilium 这种是以更小的 Service 为管理单位。
    还有一部分的能力是给 Envoy 下发配置信息。如上述的 Listener、Filter、Route、Cluster、Endpoint 等,其中 Cluster 就是 Kubernetes 的 Service,这个和 Istio 是类似的,这部分的定义是在 spec 的 resources 中指定。这两部分的中间的网络桥梁就是上述提到的透明代理 TProxy。
4
架构介绍
下图包含了 Cilium Service Mesh 中的南北入口的 Ingress,也包含了东西方向的 CiliumClusterwideEnvoyConfig/CiliumEnvoyConfig。以下以 Native Routing 的模式展示数据流部分,所以不会有 Overlay 相关的部分。
Ingress 控制流:1 → 2 → 3 → 4 → 5 → 6 → 7。包含 CiliumEnvoyConfig 的处理和 LoadBalancer 类似的 Service 的处理。下文会详细介绍。
Ingress 数据流:
  • 本地 Endpoint:8 → 9 → 10 → 11 。对于集群外部访问 Ingress 入口,会经过负责处理外部流量的 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 了。
  • 远程 Endpoint:8 → 9 → 13 → 15 → 16 。对于集群外部访问 Ingress 入口,会经过负责处理外部流量的 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 了。
CiliumEnvoyConfig 控制流:3 → 4 → 6 → 7。包含 CiliumEnvoyConfig 的处理,下文会详细介绍。
CiliumEnvoyConfig 数据流:
  • 从容器内访问服务:
  • 本地 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 了。
  • 远程 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 了。
  • 从主机上访问服务:
  • 本地 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 了。
  • 远程 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 了。
  • 从机器外访问服务(NodePort):
  • 本地 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 了。
  • 远程 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 能力,导致数据流会有稍微的不同。
5
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 之后,从底层查看错误的日志,才会知道哪里配置有问题,这个地方也是目前版本做的不是很友好的地方。后期会根据客户的反馈,逐步的添加回这些被删除的能力。
6
Ingress 实现
这里的 Ingress 就是指的是 Kubernetes 的 Ingress 资源对象,Cilium 的 Ingress 的实现主要依赖于两部分,一部分就是上面重点分析的 CiliumEnvoyConfig,基于 CiliumEnvoyConfig 可以完成内部的东西向的代理能力;一部分是 LoadBalancer 的 Kubernetes 的 Service。
那接下来就要解决的是南北向的流量怎样引流到 Kubernetes 的集群中。在 Cilium 中使用了 LoadBalancer 的 Kubernetes 的 Service 方式来引流到 Kubernetes 集群中。这里的 MetalLB 是 Cilium 使用了 MetalLB 的程序包自己实现和集成了 MetalLB 的 LoadBalancer 能力。 
  • 处理 CiliumEnvoyConfig 相关:1 → 2 → 3 → 4
  • 处理 LoadBalancer Service相关:5→ 6 → 7 → 8
  • Ingress 对象处理:这里有 Cilium Operator 组件完成 Ingress 资源对象的 watch,当有 Ingress 对象被添加,会为 Ingress 对象创建,CiliumEnvoyConfig cr 对象,这个 cr 对象最终由 cilium agent 去 watch 并生成 Envoy 的配置后,下发到 cilium agent 机器所在的 Envoy 中,由于每台机器都有 cilium agent。
    所以每一个 CiliumEnvoyConfig 的定义都会在所有的机器上的 Envoy 中配置。接下来,会创建一个 LoadBalancer 类型的 Service,这里中间件还有一步创建一个 Endpoint 的步骤,在下面说,但是这个 Endpoint是为 LoadBalancer 类型的 Service 创建的 Endpoint。
  • CiliumEnvoyConfig 创建:首先根据配置生成 Ingress 类型对应的 CiliumEnvoyConfig 配置,调用 Kubernetes 的 API,如果有就更新,没有就创建。
  • Service 的 Endpoint:由于这里创建的 Service 是没有 selector 的,所有 Service 是没有 Endpoint 的。这里需要创建一个 dummy 的 Endpoint,目的就是为了让 Service 有一个 Endpoint,这样 Service 的信息才可以被写到 eBPF 的 lb map 中,这里的 lb map 就是在 Cilium 中保存了 Kubernetes Service 对应信息的一种 map 类型的数据结构。
    这个 lb map,Cilium 的控制平面的 go 程序可以读写,eBPF 的数据面处理程序也可以读写。只有写到 lb map 中的 Service 才会根据是不是需要代理到 Envoy 中而进行处理数据包,不会有真正的数据包会经过这个 Endpoint 处理。
  • LoadBalancer 类型的 Service:这里就是创建一个 LoadBalancer 类型的 Service。这里要注意的是,Service 的 name 和创建的 Endpoint 的 name 是一样的,这样就对上述的 Endpoint 关联到了自己的 Service 中。
    接下来会被写到 eBPF 的 lb map 中,在数据包达到 eBPF 的处理程序中的时候,会根据这个 Service 是不是被设置了需要服务治理的标记,来判断是不是要将数据包通过 TProxy 代理到 Envoy 中,从而完成 Ingress 南北向进入到 Cilium 的能力。
7
对比社区 Istio
本篇文章先不考虑和社区的 Istio 集成或者对接的场景。主要聚焦在 Cilium 自己的控制平面 CiliumEnvoyConfig 和社区标准的 Istio 作一些对比。
  • 在对接 Envoy 方面,Cilium 内置了 Envoy,同时对 Envoy 进行的大量的定制化,来适配 Cilium 的网络。删除了一些能力,也增加了一些能力。
  • 在运行模式方面,Cilium Service Mesh 采用了无边车模式的模式,也就是不会在为每一个 Pod 去动态的注入一个 Envoy 这样的 Sidecar Proxy,而是每台机器上的所有 Pod 共享一个 Envoy。
  • 在实现方面,Cilium 对业务流量的拦截不再基于 iptables 的各种规则来 redirect 到 Envoy 的监听 Port。而是基于 Cilium 网络数据面的 code 中内置实现了基于 TProxy 的透明代理的方式 redirect 数据包到 Envoy 的监听 Port,而且 Envoy 的监听 Port 也是可以自己手动指定的,而在 Istio 是不允许指定的,固定在 15006 上。
  • 在安全方面,Cilium 内置实现了 NetworkPolicy 来管理服务的访问控制。不仅可以完成 L3/L4 的安全控制,还可以完成 L7 的安全控制,可以在整体上更完整的控制网络的完全。
  • 在使用方面,Cilium 还没有定义像 Istio 中提供的 VirtualService、DestinationRule、Gateway、WorkloadEntry、ServiceEntry 等面向用户友好的服务治理的业务 API。只有 Ingress 和 CiliumEnvoyConfig 两种类型。
  • 在性能方面,由于 Cilium 中使用无边车模式模式,本身是少了一次代理,每一次代理基本会加大 400-500us 左右的延迟,对于延迟要求很高的系统是无法接受的。
    以及 eBPF 传递数据的 CTX Redirect / CTX Redirect Peer / CTX Redirect Neight 的能力(eBPF 加速 packet 传递,bypass 内核网络协议栈,不会使用 iptables,加速网络,降低延时)等。所以性能上 Cilium Service Mesh 是比默认没有任何加速方案的 Istio(基于 iptables 和内核网络栈的方案)要好。
  • 在观测性上,由于 Cilium 内置了 Hubble 这样的观测能力,天然支持了 L3/L4 层,同时在集成 Envoy 的情况下,支持了 L7 层的观测,整体上观测的数据比较完整。而 Istio 重点是 L7 层的观测数据比较偏重。同时从额外带来的延迟上看,Cilium Service Mesh 是比 Istio 中延迟要低,毕竟少了一次代理。
  • 在资源消耗方面,在边车模式下,每一个需要服务治理的 Pod 都会注入一个 Envoy 的容器,Pod 很多情况下,对资源消耗是不可小觑。但是 Cilium Service Mesh 的无边车模式,不会为 Pod 注入 Envoy 容器,可以大量的节省资源。
  • 在配置下发方法,在边车模式下,每一个需要服务治理的 Pod 都会注入一个 Envoy 的容器,Pod 很多情况下,所有的 Envoy Sidecar 都需要下发 Envoy 的配置,这部分的网络开销和压力也是不可小觑。
    但是 Cilium Service Mesh 的无边车模式,不会为 Pod 注意 Envoy,也就不会为每一个 Pod 去下发配置,而是以机器为单位的下发,不是 Pod 为单位下发配置,可以节省这部分的网络消耗和减少这部分的服务压力。
  • 在 CNI 依赖方面,Cilium Service Mesh 是强依赖 Cilium 本身的 CNI 网络能力的。Istio 是 CNI 独立的 Service Mesh,理论上都支持,具体要看 CNI 是不是有和 Istio 能力冲突的地方,特别是 eBPF 类型,用户态类型的 CNI,因为这些类型的 CNI 会影响到 Istio 中基于 iptables 规则拦截流量的方式。
8
总结
从整体上,Cilium Service Mesh 目前的能力更多体现在数据平面上,同时提供了 Ingress 和 CiliumEnvoyConfig 两个控制平面的能力,但是相比于 Istio 控制平面以及业务 API 的能力还是比较欠缺。但是 Cilium 本身和 Istio 不冲突,可以借助于 Cilium 的网络能力,在 Cilium 之上运行 Istio 也是一种选择。
本文源自公众号道客船长,分布式实验室已获完整授权。
推荐阅读:

现在Kubernetes已经成了容器编排事实上的标准,会Kubernetes现在已经成了很多公司招聘后台工程师与架构师的基本要求。为了满足在职人员短时间内提升Kubernetes技能的要求,我们特别组织了本次Kubernetes线下培训。3天封闭式培训,15人小班授课,考不过免费复训,10月21日在上海开课,扫描下方二维码咨询详情。
继续阅读
阅读原文