背景
在云原生相对成熟的今天,对于Kubernetes本身的能力需求,逐渐变得不那么迫切,因为常见的能力都已经成熟稳定了。但是在周边领域还处于不断发展的过程,特别是容器的网络方案,存储方案,安全领域等。
重点是网络方案,举例来说,一个企业要想成功落地容器平台,首先要解决的问题就是网络方案的选型。选型的参考没有标准化。有根据易用性的;有根据规模的;有根据性能的;有根据应用亲和的(固定IP),有根据企业内部网络要求的,有根据网络安全要求的,有根据稳定性的(相对而言),有根据可运维性的,等等。
在选型的时候选型的条件大多数是组合条件,这些条件对容器的网络方案就提出了很多的挑战。不同的厂商或者开源项目都在构建不同场景下的网络方案。
从容器的网络发展路线来看,包括以下几个阶段:
基于Linuxbridge以及基于ovs实现的overlay的网络。
基于bgp/hostgw等基于路由能力的网络。
基于macvlan,ipvlan等偏物理网络的underlay的网络。
基于Kernel的eBPF的技术实现的网络。
基于dpdk/sriov/vpp/virtio-user/offload/af-xdp实现的用户态的网络。
第一/二/三种网络的方案有一个共同的特性就是完全使用内核成熟的技术,以及对于netfilter,也就是iptables的能力,就是拿来用就可以,不会考虑bypass它。
第四种的网络方案中也会用到iptables,但是它的设计思想是尽量的bypassnetfilter/iptables,以及基于内核的eBPF技术实现网络设备之间redirect的方式bypassnetfilte,让网络数据处理的路径尽量的短,进而达到高性能的目的,不是说eBPF不稳定,而是想要实现更好的性能,对内核的uapi的依赖性比较高,往往要开发feature,并合并到内核去,然后再使用,前三种方案使用的内核技术都是非常成熟的,不会有太多的功能会依赖开发新的内核特性。
第五种网络的方案的主要场景就是希望最快的网络,不同的实现方案,会有硬件的依赖,会有用户态协议栈的技术,内存网络加速的技术,有的方案还会对应用本身有侵入性,而且此方案也是相对最复杂的。
从技术路线来看,不难看出的是,对容器的网络方案的基本诉求就是要越来越快。
从功能的角度看和使用的角度看,基本是类似的,原因是使用的接口都是标准的CNI标准接口。
从发展的阶段看,理论上用户态的网络能力是最快的,因为bypass了Kernel,但是也意味着对硬件和应用是有一定的要求的,技术的通用性稍微显得有点不太兼容。
其余4种方案都是基于内核技术,通用性比较好。方案没有好坏之分,适合自己的才是好的。那接下来就要看看谁的性能是比较好的。
本文不对所有的网络方案做分析,主要针对最近比较热门的基于eBPF实现的Cilium网络技术,做一些相关的技术分享。
技术术语
为了更好的理解Cilium实现的能力,首先需要对一些常见的内核技术和网络技术的相关概念有一个初步的认识之后,会更好的帮助理解原理。以下整理一些技术术语,不是包含所有的,主要列出来一些主要的点。
CNI:Kubernetes的网络插件的接口规范的定义,主要能力是对接Kubelet完成容器网卡的创建,申请和设置ip地址,路由设置,网关设置。核心接口就是cmdAdd/cmdDel,调用接口期间,会传递Pod相关的信息,特别是容器网卡的name,以及Pod的networknamespace等信息。
IPAM:容器的网卡对应的ip分配就是由IPAM完成,从全称可以看出来,IPAddressManagement。这个组件是由CNI调用来完成为Pod申请ip地址。
Vxlan:用于在实现overlay网络中,跨主机通信的能力,基于封包和隧道的能力,实现2层虚拟网络。不同主机互相维护对端的隧道地址,通过主机的内核路由,实现数据包跨机器组建overlay网络。
NetworkPolicy:用于定义Kubernetes中,容器访问控制,容器安全等能力的定义规范。可以实现能不能从容器出去,能不能访问某一个容器,能不能访问某一个服务的port等等,包括L3/L4/L7以及自定义扩展的协议的安全能力。
LinuxVeth:Linux提供的一种虚拟网络技术,可以实现跨networknamespace通信能力。一端在主机网络空间,一端在容器的网络空间。
Netfilter/Iptables:实现对网络数据包的处理能力。它也是LinuxKernel的一种hook机制,可以理解成在数据包经过内核网络协议栈处理的过程中,需要经过很多的方法处理,在特定的方法中定义了hook,所以这些hook会在内核方法执行前/执行后被调用和执行。Linux提供了5个这样的hook,分别是pre-routing、input、forword、output、post-routing。处理数据包的能力主要包括修改、跟踪、打标签、过滤等。
CT:这里的ct指的是连接跟踪conntrack,主要完成接受连接和发起连接的时候,记录连接的状态,主要用在nat/reversenat(snat/dnat),iptables连接处理操作等,作为数据包处理依据。网络连接讲究的是,连接从哪来回哪去。
BGP:是一种路由协议,用于3层网络的路由发布能力,让Pod的地址可以在物理网络设备之间被识别,从而完成跨主机的可路由的能力。
NAT/ReverseNAT:地址转换在网络连接的过程中是常见的操作。举例,在路由器上就会对地址进行转换,具体是snat(源地址转换)/dnat(目的地址转换)就看处理连接的阶段,如果是数据包从外部进来,就会进行dnat操作,作用是将数据包的目的地址,转换成真正要访问的服务的地址,如通过NodePort访问应用,那肯定会将目的地址转换成要访问的Pod的ip地址;如果是数据包要从主机出去的时候,一般会进行snat的操作,因为要让数据包的源地址是路由可达的,而一般路由可达的地址应该是主机的物理网卡的地址,所以一般会snat成主机的物理网络地址。有NAT,就会有反向NAT,主要是完成reply包的处理。
Linux路由子系统/Neigh子系统:用于根据ip地址,在fib中寻找下一条的地址,以及由Neigh子系统来查找对应的mac地址。在基于二层交换机的场景下,srcmac是可以不设置的,但是dstmac是一定要设置的,对于三层的网络通信是一定要srcmac和dstmac都要设置正确,才可以完成数据包的正确处理。
LinuxNetworkNamespace:这是Linux提供的网络隔离的能力,每一个NetworkNamespace有独立的网络协议栈,如Netfilter/Iptables。
eBPF:是一种可以不改变Kernel的前提下,开发Kernel相关能力的一种技术。开发eBPF的程序要使用C语言,因为Kernel是C语言开发的,eBPF在开发的过程中会依赖Kernel的uapi以及Linux的Hepler方法来完成处理。Linux是基于事件模型的系统,也支持了eBPF类型hook,在一些hook点执行挂载的eBPF程序。
一致性Hash:一致性Hash本身是一种算法,这里的提到的作用是为了说明在新1.11版本实现的基于XDP的LB中,解决访问过程中,运行BackendPod的主机出现故障之后,为了保证正常的访问,引入了一致性Hash的方式选择Pod。具体就是在客户端访问的时候,选择后端可用Pod的时候,使用一致性Hash的算法来决定由哪一个Pod提供服务,这样可以保证选择的Pod是固定的,以及当提供服务的Pod机器出现故障,不会出现接手的Pod所在机器无法处理本次客户端的连接的问题。
SNAT/DSR:这两个概念是讲的是在完成LB的过程中,采用的地址处理策略,选择不同的策略会影响数据包的流向,以及网络的延迟。SNAT的方式在数据包被代理到Backend之后,reply的包还是会回到代理服务器的节点,然后再由代理服务器的节点再返回给Client。而DSR是直接在Backend节点直接返回给Client,性能更好。
SessionAffinity:在访问Backends的时候,当需要同一个Client一直保持和之前选择的Backend连接的时候,就会需要SessionAffinity的特性,也就是常说的粘性会话。
EgressGateway:这个概念不是Cilium特有的,而是一个特定的场景问题。主要解决的是,一个K8s集群要和外部进行通信的时候,需要经过出口的代理网关,这样的好处是可以在被访问端开启特定的防火墙规则,来控制K8s集群中的什么样的出口地址是可以访问墙内的什么样的服务,一般会在网络安全要求比较高的场景下会使用到这个特性。
东西向LB:指的是集群内部,通过内部负载均衡实现的跨主机的容器网络通信。如Pod访问ClusterIP类型的Service。
南北向LB:外部客户端通过Kubernetes暴露的支持负载均衡且主机可达的服务,访问Kubernetes服务的方式。如Client访问NodePort类型的Service。
直接路由模式/隧道模式:在Cilium中,Pod之间的路由方式分为直接路由和隧道的模式。直接路由推荐使用BGP协议完成Pod路由能力,隧道模式使用基于vxlan的技术完成Pod之间的路由能力。
Endpoint:Cilium网络的端点(CiliumEndpoint),也可以理解成Cilium网络中的一个参与者,或者实体,和K8s里的Endpoint不是一个概念。其中不仅仅是Pod本身是一个Endpoint;Host也是一种Endpoint;负责overlay网络模式的cilium_vxlan也是一个Endpoint;而对于外部网络这样的一个虚拟不存在的参与者也是一个Endpoint,它叫WORLD_ID。
Identity:用于标记参与Cilium网络的端点(Endpoint)的安全身份,用标签的方式来标记。有相同Identity标签的Endpoints是具有相同安全身份的,在处理安全策略的时候,是同样的验证逻辑。这样有一个好处是,不会随着Pod重启导致Podip变化,而让policy需要重新生成或者配置的问题。Cilium本身不仅仅完成了容器的网络的数据包处理和传输,其中安全也是Cilium的重要能力,在设计和实现的时候都是统一考虑和实现的。不像其它的网络方案都是在外围来实现。
Service:Cilium中的Service和K8s里的Service不是一个定义,但是也是类似的概念,也是用来表述一组服务的访问入口,也有对应的Backend。只是这个Service的模型是Cilium自己定义的。在完成ct,nat/reverse-nat,粘性会话的时候,都会用到Service。
KubeProxyReplacement:使用基于eBPF的数据平面去替代kube-proxy实现的功能,进一步提升K8s的网络相关的性能。
SocketLB:基于eBPF实现的东西向的LB,可以在Socket级别完成数据包的发送和接受,而不是Per-Packet的处理方式。可以实现跨主机的Pod-Pod的直接连接,直接通信,而传统的Kube-Proxy是实现的Per-Packet的,且需要基于ipvs/iptables的方式实现的LB的转发能力。对于Per-Packet的处理,需要对每一个需要出去的数据包都要进行DNAT的处理,而Socket级的方案只需要设置一次,那就是在建立连接和Socket的时候,设置一次的四元组里的目的地址就可以,这样可以提升更好的网络传输速度。
HostReachableService:传统的LB工具,都会在代理请求的时候,进行NAT相关的操作,但是HostReachableService能够完成连接K8sservice的时候,真正建立连接的时候是直接连接BackendPodip的能力,减少了每次数据包处理的NAT开销。这样网络性能就自然会有所提高。
HostRouting/FastPath:看到名字就可以想到是快速的意思,这个就是Kernel在5.10里新增加的能力,主要包含ctxredirectpeer和ctxredirectneigh。在没有HostRouting能力之前,所有数据包要想进入到容器都需要Kernel的路由能力和邻居子系统,以及需要经过cilium_host虚拟网络设备才可能进入到容器。而在有了HostRouting之后,可以实现网络设备到网络设备之间的直接redirect,进一步缩短数据包的传输路径。所以ctx_redirect_peer是ctx_redirect的升级版本,当然对Kernel的版本要求也更高。
TProxy透明代理:可以完成LB场景下,模拟Client→Backend的直接连接的能力,从连接状态看,和直接连接一样,但是其实中间是有一层代理在处理数据包,完成Client到Backend的代理能力。
Envoy/EnvoyFilter:Envoy本身就是一个可以完成L4/L7等能力的代理工具。同时Envoy有很好的扩展性,可以对Envoy的能力进行扩展,完成业务的需要。这里的扩展能力,主要指的就是Envoy的Filters,这里的Filters主要包含常见的ListenerFilters,NetworkFilters,以及HttpConnectionManager的Filters。Cilium的开源版本就是基于Envoy的能力完成了L7的NetworkPolicy和VisibilityPolicy,以及一些自定义的L4的Policy。
Hubble/Relay/Hubble-OTEL:这里的三个组件都是为了完成Cilium的可观测能力。其中Hubble是CiliumAgent内置的能力,每一个节点都会有。Relay是可以完成对所有单机的Hubble的数据进行收集和整合成统一的数据视图,但是在早一点的版本中,这些观测数据都是没有持久化的,都是能查看最近的一些数据,就类似于cAdvisor中对容器数据的监控一样,都是保存在特定的内存中的,在新版本中,有一个Hubble-OTEL的组件,可以完成Hubble的数据对接到OpenTelemetry中去,由于OpenTelemetry是有很多后端持久化插件,这样就可以完成观测数据的历史分析能力。
OpenTelemetry:是CNCF的用于完成观测能力的项目,支持Traces、Metrics、Logs能力的观测技术。具体可以参见官方文档或参见云原生观测性–OpenTelemetry
架构介绍
CiliumAgent:以DaemonSet的方式运行在所有的节点上,并且提供API。负责全局eBPF程序的编辑和挂载,以及为Pod编译和挂载Pod对应的eBPF程序,当有Pod被创建的时候,CNI就会在完成容器本身网卡,地址等设置之后,调用CiliumAgent的EndpointCreate方法完成eBPF/Identity相关数据路径以及安全策略的创建;负责相关iptables的初始化和维护,因为在内核版本还不是足够高的情况下,还是需要iptables来完成一些工作,如在支持NetworkPolicy的时候,使用到基于iptables的tproxy,以及基于Envoy实现的NetworkPolicy时,需要的iptableschains等;负责eBPF程序所需要的全局的Maps的初始化和维护以及Pod相关的Maps的创建和维护,因为有一些Maps是全局性的,所以需要启动的时候初始化和恢复,有一些是随着Pod的创建而创建的;内置HubbleServer,提供Metrics和Trace的能力,并且提供API。同时CiliumAgent也内置实现了基于透明代理的DNSNetworkPolicy能力的DNSProxyServer。
CiliumOperator:负责部分的IPAM的工作,主要是给主机节点分配CIDR,接下来给Pod分配ip地址由主机上的CiliumAgent来完成;负责对存储Provider进行健康检查,现在支持Cilium的存储Provider有K8sCRD,还有外置的etcd,当发现存储设施不健康会触发CiliumAgent重启;优化K8s的Endpoint的发现机制,在早期一点的版本,CiliumAgent要处理的Endpoint不是使用的EndpointSlice的方式,会导致数据量很大,增加CiliumAgent的压力,现在基于Operator进行转换,让CiliumAgent只处理Operator转换出来的EndpointSlice,极大的减小了数据量,这样支持的集群的节点规模就可以增大,这里的EndpointSlice是K8s的特性,所以需要开启该特性。
CiliumCli:用于和本地的CiliumAgent通信,提供操作Cilium的能力。虽然只和本地的CiliumAgent通信,但是可以拿到完整的整个Cilium网络方案内的数据。因为每一个CiliumAgent是会和控制平面通信。
CiliumProxy:用于完成L7/Proxylib类型的NetworkPolicy的合法检查,检查通过就会继续处理数据包,不通过就返回。实现的方式是使用C++扩展Envoy实现的一整套filters,包括ListenerFilter,NetworkFilter,HttpFilter。可以支持K8sNetworkPolicy,支持Kafka的Policy等都是在这里完成的。CiliumAgent会负责处理Proxy相关的控制流,包括启动一个包括定制化配置的Envoy。这里的Proxy是完全只是和Envoy相关的,为了支持Policy能力的部分。对于L3/L4层的NetworkPolicy是在eBPF的程序中直接完成验证的。
CiliumDNSProxy:拦截和验证访问的DNS是不是可以访问,这里的DNSProxy不是独立的组件,而是CiliumAgent内置实现的一个独立的Server,在这里单独提出来,只是为了强调能力的独立性。主要的实现方式是基于tproxy的能力将请求转发到DNSProxyServer,完成DNS请求的验证。
CiliumCNI:这个比较熟悉了,就是实现了CNI接口的组件,由Kubelet调用在Pod的网络命名空间中完成容器网络的管理,包括容器网卡的创建,网关的设置,路由的设置等,因为依赖eBPF的网络能力,这里还会调用CiliumAgent的接口,完成Pod所需要的eBPF程序的编译,挂载,配置,以及和Pod相关的eBPFMaps的创建和管理。
HubbleServer:主要负责完成Cilium网络方案中的观测数据的服务。在Cilium运行的数据路径中,会对数据包的处理过程进行记录,最后统一由HubbleServer来完成输出,同时提供了API来访问这些观测数据。
HubbleRelay:因为HubbleServer提供了API接口,而Relay就是调用所有集群节点的HubbleServer的API接口,对观测数据进行汇总,然后由HubbleUI统一展示。
HubbleUI:用于展示HubbleServer收集的观测数据,这里会直接连接到Relay去查询数据。
HubbleOTEL:用于将HubbleServer的观测数据以OTLP的协议导出到OpenTelemetry框架中。由于OpenTelemetry有丰富的生态,可以很好地对Cilium的观测数据进行管理和分析。
eBPF程序:使用eBPF可以开发出对应不同类型的Linux子系统的能力扩展。这里主要使用最多的是和网络相关的能力。主要包括如下几个场景:在XDP和TC的hook处,对数据包进行处理,完成对数据包的验证和转发,这里的转发主要依赖Kernle的redirect(ifindex,flags)/redirect_peer(ifindex,flags)能力;使用cgroup类型的eBPF程序完成SocketLB能力,提供更高网络性能的东西向通信;使用sockmap类型和socketops类型的eBPF程序完成SocketRedirect能力,加速本地Socket之间的通信。
eBPFMaps:Linux提供了很多种类型的eBPFMaps供eBPF程序使用,理论上传统开发语言中的Map只是一种数据结构,eBPF也一样,只是eBPF的Maps的类型更偏向场景或是功能,举例来说,要想完成socketredirect,这里常用就是BPF_MAP_TYPE_SOCKHASH类型的Map,配合msg_redirect_hash方法,完成socket的数据包的redirect。Maps的类型有很多种,暂不列出具体的类型。
iptables:用于完成在内核版本不支持的某一些功能下的时候,还是需要iptables来支撑,举例在Kernel的版本低于5.7的时候,tproxy还是要依赖于iptables来完成,但是在高的版本里就直接可以用基于eBPF实现的tproxy了。再比如,在使用Envoy实现NetworkPolicy的能力,以及处理完成之后将数据包reply到Cilium的网络的过程中,依赖于iptables和iprule将数据包定向传输到cilium_host。
Grafana:用于展示Cilium暴露出来的Prometheus的Metrics数据。
Prometheus:用于收集Cilium暴露出来的Prometheus的Metrics数据。
OpenTelemetry:用于收集Cilium通过OTLP协议暴露出来的Trace的数据。
K8sapiserver:用于Cilium的datastore,保存Cilium的数据。如Identity数据,Endpoint数据。CiliumAgent会与其通信,完成数据的发现。
etcd:另一种可用于Cilium的datastore,保存Cilium的数据。如Identity数据,Endpoint数据。CiliumAgent会与其通信,完成数据的发现。
观测能力
Cilium默认提供了一些观测的能力,支持基于CiliumAgent内置的Prometheus来获取Metrics数据,配合Grafana实现监控数据的展示;支持基于数据包访问路径的Trace,包括L4/L7的观测。这里有一个点比较特别是L7的观测,默认是不打开的,在需要观测L7的Pod上打上annotation就可以拿到这个Pod的L7的观测数据,对于L4是默认就实现了观测,不需要额外配置。
L7Trace实现:
基于Pod的annotation,annotation的name是“io.cilium.proxy-visibility”,value是“{TrafficDirection}/{L4Port}/{L4Protocol}/{L7Protocol}”。
举例:
kubectlannotatepodfoo-nbario.cilium.proxy-visibility=”Egress/53/UDP/DNS,Egress/80/TCP/HTTP”。
实现方式:
1.CiliumAgent会watchPod的一些annotation的变化,其中就是包括了ProxyVisibility,也就是“io.cilium.proxy-visibility”这个annotation。
2.CiliumAgent会为VisibilityPolicy创建redirect对象,在经过proxy处理之后,会记录下相关的Trace信息,最后通过Hubble组件暴露出来。
Trace:
Metrics:
性能
TCPThroughput(TCP_STREAM)
Request/ResponseRate(TCP_RR)
ConnectionRate(TCP_CRR)
TestConfigurations
社区发展
除了Cilium关键的容器网络能力之外,还包括在发展的:
CiliumClusterMesh多集群的能力,主要包括跨集群的服务发现,负载均衡,网络策略,透明加密,Pod路由等。
CiliumServiceMesh服务网格的能力,主要包括基于NoSidecar形式运行代理。对于L7的服务,使用每台机器一个Envoy的机制完成微服务治理的相关能力。
本文作者熊中祥「DaoCloud道客」技术合伙人云原生技术专家