Serverless容器的服务发现

2020年9月,UCloud上线了Serverless容器产品Cube,它具备了虚拟机级别的平安隔离、轻量化的零碎占用、秒级的启动速度,高度自动化的弹性伸缩,以及简洁明了的易用性。联合虚构节点技术(Virtual Kubelet),Cube能够和UCloud容器托管产品UK8S无缝对接,极大地丰盛了Kubernetes集群的弹性能力。如下图所示,Virtual Node作为一个虚构Node在Kubernetes集群中,每个Cube实例被视为VK节点上的一个Pod。

然而,Virtual Kubelet仅仅实现了集群中Cube实例的弹性伸缩。要使得Cube实例正式成为K8s集群小家庭的一员,运行在Cube中的利用须要能利用K8s的服务发现能力,即拜访Service地址。

为什么不是kube-proxy?

家喻户晓, kube-proxy为K8s实现了service流量负载平衡。kube-proxy一直感知K8s内Service和Endpoints地址的对应关系及其变动,生成ServiceIP的流量转发规定。它提供了三种转发实现机制:userspace, iptables和ipvs, 其中userspace因为较高的性能代价已不再被应用。

然而,咱们发现,间接把kube-proxy部署在Cube虚拟机外部并不适合,有如下起因:

1 、kube-proxy采纳go语言开发,编译产生的指标文件体积宏大。以K8s v1.19.5 linux环境为例,经strip过的kube-proxy ELF可执行文件大小为37MB。对于一般K8s环境来说,这个体积能够忽略不计;但对于Serverless产品来说,为了保障秒起轻量级虚拟机,虚拟机操作系统和镜像须要高度裁剪,寸土寸金,咱们想要一个部署体积不超过10MB的proxy控制程序。

2 、kube-proxy的运行性能问题。同样因为应用go语言开发,绝对于C/C++和Rust等无gc、具备精密管制底层资源能力的高级语言来说,要付出更多的性能代价。Cube通常存在较细粒度的资源交付配额,例如0.5C 500MiB,咱们不心愿kube-proxy这类辅助组件喧宾夺主。

3 、ipvs的问题。在eBPF被广为周知之前,ipvs被认为是最正当的K8s service转发面实现。iptables因为扩展性问题被鞭尸已久,ipvs却能随着services和endpoints规模增大仍然保持稳定的转发能力和较低的规定刷新距离。

但事实是,ipvs并不完满,甚至存在重大的问题。

例如,同样实现nat , iptables是在PREROUTING或者OUTPUT实现DNAT;而ipvs须要经验INPUT和OUTPUT,链路更长。因而,较少svc和ep数量下的service ip压测场景下,无论是带宽还是短连贯申请提早,ipvs都会取得全场最低分。此外,conn_reuse_mode的参数为1导致的滚动公布时服务拜访失败的问题至今(2021年4月)也解决的不太洁净。

4 、iptables的问题。扩大差,更新慢,O(n)工夫复杂度的规定查找(这几句话背不进去是找不到一份K8s相干的工作的), 同样的问题还会呈现在基于iptables实现的NetworkPolicy上。1.6.2以下iptables甚至不反对full_random端口抉择,导致SNAT的性能在高并发短连贯的业务场景下雪上加霜。

eBPF能为容器网络带来什么?

eBPF近年来被视为linux的革命性技术,它容许开发者在linux的内核里动静实时地加载运行本人编写的沙盒程序,无需更改内核源码或者加载内核模块。同时,用户态的程序能够通过bpf(2)零碎调用和bpf map构造与内核中的eBPF程序实时替换数据,如下图所示。

编写好的eBPF程序在内核中以事件触发的模式运行,这些事件能够是零碎调用入进口,网络收发包的要害门路点(xdp, tc, qdisc, socket),内核函数入进口kprobes/kretprobes和用户态函数入进口uprobes/uretprobes等。加载到网络收发门路的hook点的eBPF程序通常用于管制和批改网络报文, 来实现负载平衡,安全策略和监控观测。

cilium的呈现使得eBPF正式进入K8s的视线,并正在粗浅地扭转k8s的网络,平安,负载平衡,可观测性等畛域。从1.6开始,cilium能够100%替换kube-proxy,真正通过eBPF实现了kube-proxy的全副转发性能。让咱们首先考查一下ClusterIP(货色流量)的实现。

ClusterIP的实现


无论对于TCP还是UDP来说,客户端拜访ClusterIP只须要实现针对ClusterIP的DNAT,把Frontend与对应的Backends地址记录在eBPF map中,这个表的内容即为前面执行DNAT的根据。那这个DNAT在什么环节实现呢?

对于个别环境,DNAT操作能够产生在tc egress,同时在tc ingress中对回程的流量进行反向操作,行将源地址由实在的PodIP改成ClusterIP, 此外实现NAT后须要从新计算IP和TCP头部的checksum。

如果是反对cgroup2的linux环境,应用cgroup2的sockaddr hook点进行DNAT。cgroup2为一些须要援用L4地址的socket零碎调用,如connect(2), sendmsg(2), recvmsg(2)提供了一个BPF拦挡层(BPF_PROG_TYPE_CGROUP_SOCK_ADDR)。这些BPF程序能够在packet生成之前实现对目标地址的批改,如下图所示。

对于tcp和有连贯的udp的流量(即针对udp fd调用过connect(2))来说, 只须要做一次正向转换,即利用bpf程序,将出向流量的目标地址改成Pod的地址。这种场景下,负载平衡是最高效的,因为开销一次性的,作用成果则继续贯通整个通信流的生命周期。

而对于无连贯的udp流量,还须要做一次反向转换,行将来自Pod的入向流量做一个SNAT,将源地址改回ClusterIP。如果缺了这一步操作,基于recvmsg的UDP利用会无奈收到来自ClusterIP的音讯,因为socket的对端地址被改写成了Pod的地址。流量示意图如下所示。

综述,这是一种用户无感知的地址转换。用户认为本人连贯的地址是Service, 但理论的tcp连贯间接指向Pod。一个能阐明问题的比照是,当你应用kube-proxy的时候,在Pod中进行tcpdump时,你能发现目标地址仍然是ClusterIP,因为ipvs或者iptables规定在host上;当你应用cilium时,在Pod中进行tcpdump,你曾经能发现目标地址是Backend Pod。NAT不须要借助conntrack就能实现,绝对于ipvs和iptables来说,转发门路缩小,性能更优。而比照方才提到的tc-bpf,它更轻量,无需从新计算checksum。

Cube的Service服务发现


Cube为每个须要开启ClusterIP拜访性能的Serverless容器组启动了一个叫cproxy的agent程序来实现kube-proxy的外围性能。因为Cube的轻量级虚拟机镜像应用较高版本的linux内核,cproxy采纳了上述cgroup2 socket hook的形式进行ClusterIP转发。cproxy应用Rust开发,编译后的指标文件只有不到10MiB。运行开销相比kube-proxy也有不小劣势。部署构造如下所示。

以下是一些测试状况比照。咱们应用wrk对ClusterIP进行2000并发HTTP短连贯测试,别离比拟svc数量为10和svc数量为5000,察看申请耗时状况(单位ms)。

论断是cproxy无论在svc数量较少和较多的状况下,都领有最好的性能;ipvs在svc数量较大的状况下性能远好于iptables,但在svc数量较小的状况下,性能不如iptables。svc数量=10

svc数量=10

svc数量=5000

后续咱们会持续欠缺基于eBPF实现LoadBalancer(南北流量)转发,以及基于eBPF的网络拜访策略(NetworkPolicy)。

UCloud容器产品拥抱eBPF


eBPF正在扭转云原生生态, 将来UCloud容器云产品 UK8S与Serverless容器产品Cube将紧密结合业内最新进展,开掘eBPF在网络,负载平衡,监控等畛域的利用,为用户提供更好的观测、定位和调优能力。


如果您对Cube产品感兴趣,欢送扫码退出Cube测试交换群!