本文最后更新于:December 22, 2022 pm
                
              
            
            
              本文主要介绍在使用了Cilium的K8S集群中如何开启KubeProxyReplacement功能来替代K8S集群原生的kube-proxy组件,同时还会介绍Cilium的几个特色功能如Maglev一致性哈希、DSR模式、Socket旁路和XDP加速等。
关于本文实操使用的K8S集群的部署过程可以参考上一篇文章k8s系列10-使用kube-router和cilium部署BGP模式的k8s集群 。此前写的一些关于k8s基础知识和集群搭建的一些方案 ,有需要的同学可以看一下。
1、配置KubeProxyReplacement 1.1 检查集群状态 关于使用cilium替代kube-proxy的官方文档可以参考这里 ,需要注意的是我们这里是在已经部署好cilium和kube-proxy的K8S集群上面操作,因此会和官方的全新初始化安装稍有不同,但是大致原理类似。
首先检查一下我们现在的cilium状态,可以看到KubeProxyReplacement参数默认是设置为Disabled
$ kubectl -n kube-system exec  ds/cilium -- cilium status | grep KubeProxyReplacement"cilium-agent"  out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init)
再检查一下集群中的kube-proxy组件状态,可以看到各个组件都工作正常。
$ kubectl get pods -n kube-system | grep kube-proxy
1.2 删除kube-proxy 在删除kube-proxy之前我们先备份一下相关的配置
接下来我们删除kube-proxy相关的daemonset、configmap、iptables规则和ipvs规则。
1.3 配置cilium 删除kube-proxy之后此时的K8S集群应该会处于不正常工作的状态,不用紧张,现在我们开启cilium的kube-proxy-replacement功能来替代kube-proxy。
因为我们集群已经部署了cililum,因此我们可以直接通过修改configmap的方式开启kube-proxy-replacement;修改cilium-config,将kube-proxy-replacement: disabled修改为kube-proxy-replacement: strict
$ kubectl edit cm -n kube-system cilium-config
另外默认情况下cilium不会允许集群外的机器访问clusterip,需要开启这个功能的话可以在配置中添加bpf-lb-external-clusterip: "true"
$ kubectl get cm -n kube-system cilium-config -o yaml | grep bpf-lb-external-clusterip"true" 
如果是使用helm来初始化安装或者是更新的话可以考虑添加下面的这两个参数,最后的效果应该是一致的:
需要特别注意如果一开始就是用helm部署的话,这里要加上参数手动指定k8s的apiserver地址和端口。
 
set  kubeProxyReplacement=strict \set  bpf.lbExternalClusterIP=true  \set  k8sServiceHost=k8s-cilium-apiserver.tinychen.io \set  k8sServicePort=8443 \"bpf-lb-external-clusterip|kube-proxy-replacement" "true" 
个人建议使用helm和configmap来管理cilium的配置这两种方式挑一种即可,不要两个方式混用,避免一些配置被覆盖
Cilium的参数比较多,在helm和configmap中的名字不一样,可以参考github上面的配置 和官方给出的helm说明 
 
1.4 检测cilium cilium重启完成之后检测相关的服务状态:
$ kubectl -n kube-system exec  ds/cilium -- cilium status | grep KubeProxyReplacement"cilium-agent"  out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init)
注意这时候cilium里面能够看到的Service Type应该还包含了LoadBalancer和NodePort等类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 $ kubectl -n kube-system exec  ds/cilium -- cilium service list"cilium-agent"  out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init)
此时再查看ipvs规则可以看到为空,重启节点后对应的kube-ipvs0网卡也会消失,iptables中的相关规则也不会再生成。
$ ipvsadm -ln link  show kube-ipvs0"kube-ipvs0"  does not exist.
此前我们已经配置了该集群的podIP、clusterIP和loadbalancerIP均为集群外路由可达,即可ping通,可正常请求。此时已经开启了cilium的kube-proxy-replacement模式之后,只有pod IP是能正常ping通并请求的;loadbalancerIP和clusterIP都是无法ping通,但是能正常请求;主要原因是cilium本身的eBPF代码默认并没有对loadbalancerIP和clusterIP的icmp数据包进行处理,导致ping请求无法被响应。
kube-proxy + kube-router 
without kube-proxy + kube-router 
 
 
集群内:Pod IP 
ping测试:Y 
ping测试:Y 
 
集群内:Cluster IP 
ping测试:Y 
ping测试:N 
 
集群内:LoadBalancer IP 
ping测试:Y 
ping测试:N 
 
集群外:Pod IP 
ping测试:Y 
ping测试:Y 
 
集群外:Cluster IP 
ping测试:Y 
ping测试:N 
 
集群外:LoadBalancer IP 
ping测试:Y 
ping测试:N 
 
2、一致性哈希 Cilium官方声称已经实现了完整的四七层代理,因此在切换到它的KubeProxyReplacement模式之后我们可以使用一些cilium的特有功能,比如一致性哈希。
在负载均衡算法中使用传统的哈希算法时,增减一个后端节点都会导致几乎所有的哈希规则进行重新映射,使得原先的客户端会被转发到新的后端节点,这对于缓存服务器来说是非常不友好的。因此在这种场景下一般会使用一致性哈希算法,其特点是当哈希表槽位数(大小)的改变平均只需要对K/n 个关键字重新映射,其中K是关键字的数量,n是槽位数量。也就是说使用了一致性哈希算法之后,可以大幅度减小后端节点变化带来的哈希映射关系变动,从而使得请求转发更加的均匀稳定。
一致性哈希算法有很多种具体实现,而Cilium主要是通过实现谷歌此前开源的Maglev 的一种变体来达到一致性哈希 的效果,这提高了发生故障时的弹性并提供了更好的负载平衡属性。因为添加到集群的节点将在整个集群中为给定的 5 元组做出一致的后端选择,而无需与其他节点同步状态 。Cilium声称可以在后端出现变动的时候将影响控制到1%以内。
这里需要解释一下这两个特定于 Maglev 的参数:maglev.tableSize 和maglev.hashSeed。
maglev.tableSize用来指定maglev查找单个服务的表的大小,maglev建议该值(M) 应远大于实际的最大后端节点数量 (N),实际上我们为了实现后端出现变动的时候将影响控制到1%以内的目标,需要将M的值设置成大于N的100倍才比较合理,另外M必须是一个质数,Cilium默认将M设置为16381(对应最大后端节点数量在160左右)。下表是cilium给出的一些可以使用的参考值,我们可以根据自己的实际业务进行调整:
maglev.tableSize value 
 
251 
 
509 
 
1021 
 
2039 
 
4093 
 
8191 
 
16381 
 
32749 
 
65521 
 
131071 
 
maglev.hashSeed则是用来设置maglev算法的seed值,官方推荐设置,这样就不需要依赖内置的seed值。该值每台机器需要一致,这样才能保证每台机器的哈希结果一致。maglev.hashSeed应该是一个base64编码的12位随机字符串,可以使用head -c12 /dev/urandom | base64 -w0命令生成。
配置maglev算法和hash相关参数,主要配置三个值:
head  -c12 /dev/urandom | base64  -w0"bpf-lb-algorithm|bpf-lb-maglev-hash-seed|bpf-lb-maglev-table-size" "65521" head  -c12 /dev/urandom | base64  -w0)set  loadBalancer.algorithm=maglev \set  maglev.tableSize=65521 \set  maglev.hashSeed=$SEED  \
注意开启了maglev一致性哈希之后,因为需要维护哈希表,所以cilium的ds进程会占用更多的内存;同时需要注意的是maglev一致性哈希只会对集群外部的流量生效 (即通过nodeport、externalIP等方式进来的流量),因为对于集群内部的东西流量,往往都是直接转发到对应的pod上面,不需要经过中间的选择转发环节,也就没有maglev一致性哈希的工作环节了。当然,Cilium的一致性哈希支持XDP加速 。
3、Direct Server Return (DSR) 默认情况下,Cilium 的 eBPF NodePort转发是通过SNAT模式实现的,也就是说,当节点外部的流量通过诸如LoadBalancer、NodePort等方式进入集群内,并且需要转发到非本node的pod上面时,该node将通过执行SNAT转换将请求转发到对应的后端pod上面。这不需要对数据包的MTU进行变更,代价是后端pod回复数据包的时候需要再次经由node进行reverse SNAT再把请求发回给客户端。
Cilium提供了一个DSR模式 来对此场景进行优化,即当数据包转发给后端pod之后,由pod直接返回给客户端,而不再经由node进行转发回复,这样可以减少一跳的转发,并且减少了一次NAT转换。DSR模式需要cilium工作在Native-Routing 模式,如果是使用了隧道模式,那么将无法正常工作。
DSR模式相较于SNAT模式的另一个优势就是能够保留客户端的源IP,即后端服务能够根据客户端IP进行更灵活的控制策略。考虑到一个后端pod实际上可能会被多个SVC同时使用,Cilium会在IPv4 Options/IPv6 Destination Option extension header中编码特定的cilium信息,将对应的service IP/port信息传递给后端pod,对应的代价就是报文用来传递实际业务数据的MTU会减小。对于 TCP 服务,Cilium 只对 SYN 数据包的service IP/port进行编码,而不会对后续数据包进行编码。
Cilium还支持混合DSR和SNAT模式 ,即对TCP连接进行DSR,对UDP连接进行SNAT。当workload主要使用TCP进行数据传输的时候,这可以有效地折中优化转发链路 和减小MTU 两者的影响。
注意在某些公有云环境中DSR模式可能会不生效,因为底层网络可能会丢弃Cilium特定的IP数据包。如果处理请求的pod不在接受nodeport请求的node上面,那么出现连接问题的时候优先确定数据包是否转发到了对应pod所在的节点上。如果不是这种情况,则建议切换回默认 SNAT 模式作为解决方法。此外,在某些实施源/目标 IP 地址检查(例如 AWS)的公共云提供商环境中,必须禁用检查才能使 DSR 模式工作。
 
设置loadBalancer.mode为dsr,将对所有服务(TCP+UDP)都使用DSR模式。
$ kubectl get cm -n kube-system cilium-config -o yaml | grep bpf-lb-modeset  loadBalancer.mode=dsr
设置loadBalancer.mode为hybrid,将对TCP服务使用DSR模式,UDP服务使用SNT模式。
$ kubectl get cm -n kube-system cilium-config -o yaml | grep bpf-lb-modeset  loadBalancer.mode=hybrid 
设置完成之后我们部署一个测试服务,直接返回remote_addr和remote_port,用来验证DSR模式是否正常工作:
key 
value 
 
 
PodIP 
10.32.3.126 
 
LoadBalanceIP 
10.32.192.80 
 
ClientIP 
10.31.88.1 
 
NodeIP 
10.31.80.1-6 
 
首先我们查看snat模式下的情况:
当在集群外 通过LoadBalanceIP、ClusterIP、NodePort等方式访问的时候,转发路径如下:
Client  -->ode  -->od  -->ode  -->
此时客户端和pod之间的通信来回都是需要经过Node进行NAT转换。
开启了DSR模式之后的转发路径变成下面这样:
Client  -->ode  -->od  -->
此时Pod响应客户端的请求不需要再经由Node转发,而是由Pod直接返回给客户端。
4、Socket 旁路&XDP加速 4.1 Socket LoadBalancer Bypass in Pod Namespace Cilium对Scoket旁路的全称是Socket LoadBalancer Bypass in Pod Namespace ,即对于同一个namspace下的服务,如果namespace中的某个pod通过LoadBalancer来访问同namespace下的另一个服务,实际上请求是会被转发到同namespace中的另一个pod,但是在底层的socket看来,还是由该客户端pod对LoadBalancerIP发起请求产生的socket连接,以及后续需要的NAT转换等操作。
开启了旁路功能之后,可以绕过上述的流程,直接把请求转发给对应的后端pod,缩短转发链路从而提高性能。
$ kubectl get cm -n kube-system cilium-config -o yaml | egrep "bpf-lb-sock-hostns-only" "true" set  socketLB.hostNamespaceOnly=true  
4.2 LoadBalancer & NodePort XDP Acceleration Cilium还支持对外部流量(ExternalIP、NodePort等)服务使用XDP加速 从而提高性能表现,由于XDP加速本身依赖Linux内核,同时也对网卡的型号和驱动有要求,因此最好先确定机器对应的网卡驱动(使用ethtool -i eth0查看)和内核版本是否符合需求。官方有给出一份支持列表 ,对于绝大部分高速网卡和大多数的虚拟机网卡驱动(virtio_net)都是支持的。
$ kubectl get cm -n kube-system cilium-config -o yaml | egrep "bpf-lb-sock-hostns-only" set  loadBalancer.acceleration=native exec  ds/cilium -- cilium status --verbose | grep XDP"cilium-agent"  out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init)
5、Native-Routing-masquerade 注意该功能与是否开启KubeProxyReplacement模式无关,只需要集群开启了Native-routing 模式,即确保podIP在集群外路由可达,即可配置该功能。
默认情况下Cilium会开启IP伪装功能,即pod在访问集群外的服务时,源IP会被伪装成所在node节点的IP而不是本身的真实IP,我们可以根据实际需求来进行调整,通过调整enable-ipv(4|6)-masquerade参数可以分别控制IPv4/IPv6网络下的IP伪装功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ kubectl get cm -n kube-system cilium-config -o yaml | egrep "enable-ipv(4|6)-masquerade" "true" "true" exec  -it deployments/nginx-quic-deployment -- curl ipport.tinychen.comexec  -it deployments/nginx-quic-deployment -- curl 10.32.192.80"enable-ipv(4|6)-masquerade" "false" "false" exec  -it deployments/nginx-quic-deployment -- curl ipport.tinychen.comexec  -it deployments/nginx-quic-deployment -- curl 10.32.192.80