共计 3535 个字符,预计需要花费 9 分钟才能阅读完成。
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 测试交换群!