本文最后更新于: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 Defaulted container "cilium-agent" out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init) KubeProxyReplacement: Disabled
再检查一下集群中的kube-proxy组件状态,可以看到各个组件都工作正常。
$ kubectl get pods -n kube-system | grep kube-proxy kube-proxy-86g7b 1/1 Running 1 (18h ago) 19h kube-proxy-gqbd4 1/1 Running 1 (18h ago) 19h kube-proxy-gtcqc 1/1 Running 1 (18h ago) 19h kube-proxy-kdjr9 1/1 Running 1 (18h ago) 19h kube-proxy-pbj8s 1/1 Running 1 (18h ago) 19h kube-proxy-tvltv 1/1 Running 1 (18h ago) 19h $ kubectl get ds -n kube-system | grep kube-proxy kube-proxy 6 6 6 6 6 kubernetes.io/os=linux 45h $ kubectl get cm -n kube-system | grep kube-proxy kube-proxy 2 45h
1.2 删除kube-proxy 在删除kube-proxy
之前我们先备份一下相关的配置
$ kubectl get ds -n kube-system kube-proxy -o yaml > kube-proxy-ds.yaml $ kubectl get cm -n kube-system kube-proxy -o yaml > kube-proxy-cm.yaml $ iptables-save > kube-proxy-iptables-save.bak
接下来我们删除kube-proxy
相关的daemonset
、configmap
、iptables
规则和ipvs
规则。
$ kubectl -n kube-system delete ds kube-proxy $ kubectl -n kube-system delete cm kube-proxy $ iptables-save | grep -v KUBE | iptables-restore $ ipvsadm -C
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 configmap/cilium-config edited $ kubectl get cm -n kube-system cilium-config -o yaml | grep kube-proxy-replacement kube-proxy-replacement: strict $ kubectl rollout restart ds/cilium deployment/cilium-operator -n kube-system
另外默认情况下cilium不会允许集群外的机器访问clusterip,需要开启这个功能的话可以在配置中添加bpf-lb-external-clusterip: "true"
$ kubectl get cm -n kube-system cilium-config -o yaml | grep bpf-lb-external-clusterip 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 \ $ kubectl get cm -n kube-system cilium-config -o yaml | egrep "bpf-lb-external-clusterip|kube-proxy-replacement" bpf-lb-external-clusterip: "true" kube-proxy-replacement: strict
个人建议使用helm和configmap来管理cilium的配置这两种方式挑一种即可,不要两个方式混用,避免一些配置被覆盖
Cilium的参数比较多,在helm和configmap中的名字不一样,可以参考github上面的配置 和官方给出的helm说明
1.4 检测cilium cilium重启完成之后检测相关的服务状态:
$ kubectl -n kube-system exec ds/cilium -- cilium status | grep KubeProxyReplacement Defaulted container "cilium-agent" out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init) KubeProxyReplacement: Strict [eth0 10.31.80.4 (Direct Routing)]
注意这时候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 Defaulted container "cilium-agent" out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init) ID Frontend Service Type Backend 1 10.32.169.6:80 ClusterIP 1 => 10.32.3.93:80 (active) 2 => 10.32.3.85:80 (active) 3 => 10.32.5.204:80 (active) 4 => 10.32.4.25:80 (active) 2 10.32.192.192:80 LoadBalancer 1 => 10.32.3.93:80 (active) 2 => 10.32.3.85:80 (active) 3 => 10.32.5.204:80 (active) 4 => 10.32.4.25:80 (active) 3 10.32.176.164:8080 ClusterIP 1 => 10.32.3.93:80 (active) 2 => 10.32.3.85:80 (active) 3 => 10.32.5.204:80 (active) 4 => 10.32.4.25:80 (active) 4 10.31.80.3:30088 NodePort 1 => 10.32.3.93:80 (active) 2 => 10.32.3.85:80 (active) 3 => 10.32.5.204:80 (active) 4 => 10.32.4.25:80 (active) 5 0.0.0.0:30088 NodePort 1 => 10.32.3.93:80 (active) 2 => 10.32.3.85:80 (active) 3 => 10.32.5.204:80 (active) 4 => 10.32.4.25:80 (active) 6 10.32.131.127:443 ClusterIP 1 => 10.31.80.6:4244 (active) 2 => 10.31.80.3:4244 (active) 3 => 10.31.80.1:4244 (active) 4 => 10.31.80.5:4244 (active) 5 => 10.31.80.4:4244 (active) 6 => 10.31.80.2:4244 (active) 7 10.32.171.0:80 ClusterIP 1 => 10.32.4.114:4245 (active) 8 10.32.128.10:53 ClusterIP 1 => 10.32.5.239:53 (active) 2 => 10.32.5.21:53 (active) 9 10.32.128.10:9153 ClusterIP 1 => 10.32.5.239:9153 (active) 2 => 10.32.5.21:9153 (active) 10 10.32.184.206:80 ClusterIP 1 => 10.32.3.138:8081 (active) 11 10.31.80.3:30081 NodePort 1 => 10.32.3.138:8081 (active) 12 0.0.0.0:30081 NodePort 1 => 10.32.3.138:8081 (active) 13 10.32.172.208:80 ClusterIP 1 => 10.32.3.93:80 (active) 2 => 10.32.3.85:80 (active) 3 => 10.32.5.204:80 (active) 4 => 10.32.4.25:80 (active) 14 10.32.192.0:80 LoadBalancer 1 => 10.32.3.93:80 (active) 2 => 10.32.3.85:80 (active) 3 => 10.32.5.204:80 (active) 4 => 10.32.4.25:80 (active) 15 10.32.188.57:8080 ClusterIP 1 => 10.32.5.220:8080 (active) 16 10.31.80.3:31243 NodePort 1 => 10.32.5.220:8080 (active) 17 0.0.0.0:31243 NodePort 1 => 10.32.5.220:8080 (active) 18 10.32.142.224:8080 ClusterIP 1 => 10.32.4.248:8080 (active) 19 0.0.0.0:31578 NodePort 1 => 10.32.4.248:8080 (active) 20 10.31.80.3:31578 NodePort 1 => 10.32.4.248:8080 (active) 21 10.32.128.1:443 ClusterIP 1 => 10.31.80.1:6443 (active) 2 => 10.31.80.2:6443 (active) 3 => 10.31.80.3:6443 (active)
此时再查看ipvs规则可以看到为空,重启节点后对应的kube-ipvs0
网卡也会消失,iptables
中的相关规则也不会再生成。
$ ipvsadm -ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn $ ip link show kube-ipvs0 Device "kube-ipvs0" does not exist. $ iptables-save | grep KUBE-SVC [ empty line ]
此前我们已经配置了该集群的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 TCP测试:Y UDP测试:Y
ping测试:Y TCP测试:Y UDP测试:Y
集群内:Cluster IP
ping测试:Y TCP测试:Y UDP测试:Y
ping测试:N TCP测试:Y UDP测试:Y
集群内:LoadBalancer IP
ping测试:Y TCP测试:Y UDP测试:Y
ping测试:N TCP测试:Y UDP测试:Y
集群外:Pod IP
ping测试:Y TCP测试:Y UDP测试:Y
ping测试:Y TCP测试:Y UDP测试:Y
集群外:Cluster IP
ping测试:Y TCP测试:Y UDP测试:Y
ping测试:N TCP测试:Y UDP测试:Y
集群外:LoadBalancer IP
ping测试:Y TCP测试:Y UDP测试:Y
ping测试:N TCP测试:Y UDP测试:Y
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 djthA7ezcQmtolON $ kubectl get cm -n kube-system cilium-config -o yaml | egrep "bpf-lb-algorithm|bpf-lb-maglev-hash-seed|bpf-lb-maglev-table-size" bpf-lb-algorithm: maglev bpf-lb-maglev-hash-seed: djthA7ezcQmtolON bpf-lb-maglev-table-size: "65521" SEED=$(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-mode bpf-lb-mode: dsr $ kubectl rollout restart ds/cilium deployment/cilium-operator -n kube-system --set loadBalancer.mode=dsr
设置loadBalancer.mode
为hybrid
,将对TCP服务使用DSR模式,UDP服务使用SNT模式。
$ kubectl get cm -n kube-system cilium-config -o yaml | grep bpf-lb-mode bpf-lb-mode: hybrid $ kubectl rollout restart ds/cilium deployment/cilium-operator -n kube-system --set 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 --> Node --> Pod --> Node --> Client
此时客户端和pod之间的通信来回都是需要经过Node进行NAT转换。
$ curl 10.32.3.126 10.31.88.1:49940 $ curl 10.32.192.80 10.31.80.1:50066
开启了DSR模式之后的转发路径变成下面这样:
Client --> Node --> Pod --> Client
此时Pod响应客户端的请求不需要再经由Node转发,而是由Pod直接返回给客户端。
$ curl 10.32.3.126 10.31.88.1:34220 $ curl 10.32.192.80 10.31.88.1:52650
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" bpf-lb-sock-hostns-only: "true" $ kubectl rollout restart ds/cilium deployment/cilium-operator -n kube-system --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" bpf-lb-acceleration: native $ kubectl rollout restart ds/cilium deployment/cilium-operator -n kube-system --set loadBalancer.acceleration=native $ kubectl -n kube-system exec ds/cilium -- cilium status --verbose | grep XDP Defaulted container "cilium-agent" out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init) XDP Acceleration: Native
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" enable-ipv4-masquerade: "true" enable-ipv6-masquerade: "true" $ kubectl -n nginx-quic exec -it deployments/nginx-quic-deployment -- curl ipport.tinychen.com 10.31.80.6:44372 $ kubectl -n nginx-quic exec -it deployments/nginx-quic-deployment -- curl 10.32.192.80 10.32.5.96:42688 $ kubectl get cm -n kube-system cilium-config -o yaml | egrep "enable-ipv(4|6)-masquerade" enable-ipv4-masquerade: "false" enable-ipv6-masquerade: "false" $ kubectl -n nginx-quic exec -it deployments/nginx-quic-deployment -- curl ipport.tinychen.com 10.32.5.96:58512 $ kubectl -n nginx-quic exec -it deployments/nginx-quic-deployment -- curl 10.32.192.80 10.32.5.96:36324