通过kubeadm init初始化控制平面节点,并跳过kube-proxy插件的安装:
根据您使用的CRI实现,您可能需要在kubeadm init…命令中使用--cri-socket选项定制CRI。例如:如果您使用的是Docker CRI,您将使用--cri-socket unix:///var/run/cri-dockerd.sock。
kubeadm init --skip-phases=addon/kube-proxy
然后,通过指定控制平面节点IP地址和kubeadm init返回的令牌来加入工作节点(对于本教程,至少要向集群添加一个工作节点):
kubeadm join <..>
如果您有多个网络接口,请确保在每个worker上正确设置kubelet的--node-ip。否则,Cilium的kube-proxy 替换可能无法正常工作。您可以通过运行kubectl get nodes-owide来验证这一点,以查看每个节点是否具有分配给每个节点上具有相同名称的设备的InternalIP。
对于已经通过DaemonSet安装了kube-proxy的集群,请使用以下命令将kube-proxy其删除。注意:该操作断开现有的服务连接,停止与服务相关的通信,直到安装了cilium替换:
$ kubectl -n kube-system delete ds kube-proxy
$ # Delete the configmap as well to avoid kube-proxy being reinstalled during a Kubeadm upgrade (works only for K8s 1.19 and newer)
$ kubectl -n kube-system delete cm kube-proxy
$ # Run on each node with root permissions:
$ iptables-save | grep -v KUBE | iptables-restore
注意请使用helm3完成本教程的安装操作,helm2是不兼容的。
设置helm仓库:
helm repo add cilium https://helm.cilium.io/
接下来,生成所需的YAML文件并部署它们。重要:请通过kubeadm init报告的kube-apiserver IP地址和端口号确认你设置的 API_SERVER_IP和API_SERVER_PORT正确(kubeadm默认使用端口6443)。
因为kubeadm init是在没有kube-proxy的情况下直接运行,所以必须要设置这两个环境变量。尽管kubeadm将KUBERNETES_SERVICE_HOST和KUBERNETES_SERVICE_PORT使用ClusterIP的模式导出到环境变量中。但没有kube-proxy,网络是不通的。因此,需要通过以下配置使cilium了解该信息:
API_SERVER_IP=
# Kubeadm default is 6443
API_SERVER_PORT=
helm install cilium cilium/cilium --version 1.12.1 \
--namespace kube-system \
--set kubeProxyReplacement=strict \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=${API_SERVER_PORT}
Cilium将自动在默认路径路径/run/cilium/cgroupv2上挂载附加BPF cgroupv2程序所需的cgroup2文件系统。为此,它需要daemonset的init容器挂载主机/proc路径。通过 --set cgroup.autoMount.enabled=false 可禁用自动挂载,并通过使用--set cgroup.hostRoot设置一个已经在主机挂载的cgroup v2文件系统。例如,如果尚未安装,可以通过在主机上运行mount -t cgroup2 none /sys/fs/cgroup命令来安装cgroup v2文件系统,然后找helm安装命令设置参数--set cgroup.hostRoot=/sys/fs/cgroup。
helm最终将cilium安装为一个CNI插件,并使用eBPF kube-proxy replacement来实现实现对ClusterIP、NodePort、LoadBalancer类型的Kubernetes服务和具有ExternalIP的服务的处理。此外,eBPF kube-proxy replacement还支持容器的hostPort,因此不再需要使用portmap插件。
最后,验证cilium正确地在集群中安装,并准备好提供网络服务。
$ kubectl -n kube-system get pods -l k8s-app=cilium
NAME READY STATUS RESTARTS AGE
cilium-fmh8d 1/1 Running 0 10m
cilium-mkcmb 1/1 Running 0 10m
注意,在上述Helm配置中,kubeProxyReplacement已设置为严格(strict)模式。这意味着,如果缺少底层Linux内核支持,Cilium agent将直接退出。
默认情况下,Helm将kubeProxyReplacement设置为disabled,这意味着cilium仅对集群内的ClusterIP服务启用的负载平衡。
Cilium在直接路由和隧道模式中都支持eBPF kube-proxy replacement。
在使用上述快速入门指南部署Cilium之后,我们可以首先验证Cilium代理是否以所需模式运行:
$ kubectl -n kube-system exec ds/cilium -- cilium status | grep KubeProxyReplacement
KubeProxyReplacement: Strict [eth0 (Direct Routing), eth1]
使用--verbose选项可以查看更多详细信息。
$ kubectl -n kube-system exec ds/cilium -- cilium status --verbose
[...]
KubeProxyReplacement Details:
Status: Strict
Socket LB: Enabled
Protocols: TCP, UDP
Devices: eth0 (Direct Routing), eth1
Mode: SNAT
Backend Selection: Random
Session Affinity: Enabled
Graceful Termination: Enabled
NAT46/64 Support: Enabled
XDP Acceleration: Disabled
Services:
- ClusterIP: Enabled
- NodePort: Enabled (Range: 30000-32767)
- LoadBalancer: Enabled
- externalIPs: Enabled
- HostPort: Enabled
[...]
下一步,我们将创建一个Nginx部署。然后,我们将创建一个新的NodePort服务,并验证Cilium是否能正确处理网络服务。
以下yaml用于创建后端Pod:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
验证nginx pod启动并进入running阶段。
$ kubectl get pods -l run=my-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-756fb87568-gmp8c 1/1 Running 0 62m 10.217.0.149 apoc
my-nginx-756fb87568-n5scv 1/1 Running 0 62m 10.217.0.107 apoc
接下来,为两个pod创建NodePort类型的service。
$ kubectl expose deployment my-nginx --type=NodePort --port=80
service/my-nginx exposed
验证NodePort类型的service是否创建成功。
$ kubectl get svc my-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx NodePort 10.104.239.135 80:31940/TCP 24m
通过cilium service list命令,我们可以验证cilium的eBPF kube-proxy replacement创建了新的NodePort服务。在本例中,创建了具有端口31940的服务(设备eth0和eth1各一个):
$ kubectl -n kube-system exec ds/cilium -- cilium service list
ID Frontend Service Type Backend
[...]
4 10.104.239.135:80 ClusterIP 1 => 10.217.0.107:80
2 => 10.217.0.149:80
5 0.0.0.0:31940 NodePort 1 => 10.217.0.107:80
2 => 10.217.0.149:80
6 192.168.178.29:31940 NodePort 1 => 10.217.0.107:80
2 => 10.217.0.149:80
7 172.16.0.29:31940 NodePort 1 => 10.217.0.107:80
2 => 10.217.0.149:80
创建一个用于node port测试的变量:
node_port=$(kubectl get svc my-nginx -o=jsonpath='{@.spec.ports[0].nodePort}')
同时我们可以验证,宿主机上iptables应该查不到相关规则。
$ iptables-save | grep KUBE-SVC
[ empty line ]
最后通过一个简单的curl测试显示暴露的NodePort和ClusterIP的连通性。
$ curl 127.0.0.1:$node_port
Welcome to nginx!
[....]
$ curl 192.168.178.29:$node_port
welcome to nginx!
[....]
$ curl 172.16.0.29:$node_port
welcome to nginx!
[....]
$ curl 10.104.239.135:80
Welcome to nginx!
[....]
如上,eBPF kube-proxy replacement 就正确设置并验证完成了。
cilium eBPF kube-proxy replacement 可以通过多个选项来禁止访问NodePort时执行SNAT,避免数据发到对端时源IP地址丢失。
cilium eBPF kube-proxy replacement 使用了The Maglev paper 论文介绍的变种hash算法来实现在负载均衡后端endpoint一致性hash。这种算法提高了更好的负载平衡和故障恢复能力。集群中的某个节点在不用考虑同步其它节点的状态时就可以对某个5元组做出一致的后端选择。而在某个后端被删除后,后端查找表重新排列时对不相关的后端实例影响最小(重新分配后的一致性hash差异率约为1%)。
通过下面的选项可以开启Maglev hash。loadBalancer.algorithm=maglev
Maglev 是来自于google的负载均衡软件
helm install cilium cilium/cilium --version 1.12.1 \
--namespace kube-system \
--set kubeProxyReplacement=strict \
--set loadBalancer.algorithm=maglev \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=${API_SERVER_PORT}
请注意,Maglev hash仅适用于外部(N-S)流量。对于集群内服务连接(E-W),套接字直接分配给服务后端,例如在TCP连接时,没有任何中间跳,因此不受Maglev的影响。Cilium的XDP加速也支持Maglev hash。
使用Maglev hash时有两个额外的配置:maglev.tableSize和 maglev.hashSeed。
maglev.tableSize |
251 |
509 |
1021 |
2039 |
4093 |
8191 |
16381 |
32749 |
65521 |
131071 |
例如大小16381适用于集群任意一个服务最多有160个后端实例。如果服务有更多的后端,则后端更改的重新分配差异将增加。
下面的部署示例生成种子后传递给Helm,同时将Maglev表大小设置为65521,允许给定服务的最大后端数为650(后端重新分配的属性差异最多为1%):
SEED=$(head -c12 /dev/urandom | base64 -w0)
helm install cilium cilium/cilium --version 1.12.1 \
--namespace kube-system \
--set kubeProxyReplacement=strict \
--set loadBalancer.algorithm=maglev \
--set maglev.tableSize=65521 \
--set maglev.hashSeed=$SEED \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=${API_SERVER_PORT}
注意相对于默认的负载均衡算法(loadBalancer.algorithm=random),Maglev一致性算法会消耗更多的内存。
默认情况下,Cilium eBPF NodePort的实现使用SNAT模式。也就是说,当节点外部流量到达并且节点确定负LoadBalancer、NodePort或具有externalIP的服务的后端位于其它节点时,则执行SNAT伪装源地址其将请求重定向到远程后端。这不需要对数据包的内容更改。但是来自后端的响应需要在将数据包直接返回到外部客户端之前,额外跳回到该童工负载均衡器的节点以在执行反向SNAT转换。
Helm 选项loadBalancer.mode=dsr 可以设置Cilium的eBPF NodePort在dsr模式下运行。在这种模式下,后端直接回复外部客户端,而不需要额外的跃点,这意味着后端通过使用service IP/port作为源进行回复。DSR只在Native-Routing模式下工作,隧道模式下无法工作。
DSR模式的另一个优点是保留了客户端的源IP,因此可以在后端节点上对其进行策略匹配。。如果一个特定的后端可以被多个服务使用,那么后端需要知道它们回复数据包时应使用的service IP/port。因此,cilium在定制了专属的IPv4选项(IPv6目的地选项),并把service的IP/port信息编码在扩展报头中,代价是MTU值变小。对于TCP服务,Cilium仅对SYN数据包的service IP/port进行编码,而不对后续数据包进行编码。如果想避免MTU减少,后面还介绍了混合DSR模式。如下一小节所述,其中DSR用于TCP,SNAT用于UDP。
请注意,DSR模式的使用在某些公共云提供商环境中可能不起作用,因为Cilium特定的IP选项可能会被底层网络结构丢弃。如果通过NodePort服务请求的远程节点上的服务存在连接问题,请首先检查数据包是否实际到达NodePort service包含的后端节点。如果数据包无法传递,建议将切换回默认SNAT模式。
此外,在一些实现源/目的地IP地址检查(例如AWS)的公共云提供商环境中,为了使DSR模式工作,必须禁用该检查。
启用DSR模式的配置如下:
helm install cilium cilium/cilium --version 1.12.1 \
--namespace kube-system \
--set tunnel=disabled \
--set autoDirectNodeRoutes=true \
--set kubeProxyReplacement=strict \
--set loadBalancer.mode=dsr \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=${API_SERVER_PORT}
Cilium还支持混合DSR和SNAT模式,例如对TCP连接执行DSR,对UDP执行SNAT。这样就不影响网络的MTU了,同时通过减少了额外的应答跃点(尤其是当TCP是工作负载的主要传输时),获得改进延迟收益。
loadBalancer.mode选项可以有三个可选值:snat(默认值)、dsr和hybrid。
下面是一个通过helm配置混合模式的示例:
helm install cilium cilium/cilium --version 1.12.1 \
--namespace kube-system \
--set tunnel=disabled \
--set autoDirectNodeRoutes=true \
--set kubeProxyReplacement=strict \
--set loadBalancer.mode=hybrid \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=${API_SERVER_PORT}
套接字级负载均衡器对cilium的下层数据路径透明,因为在应用程序发起 connect(TCP或连接的UDP),sendmsg(UDP)或者recvmsg(UDP)系统调用时,会检查目标IP是否在现有的service IP中,并会从对应的Endpoint中选择一个后端作为实际目标。这意味着虽然应用程序假设它连接到服务地址,但相应的内核套接字实际上连接到的是后端的地址,因此不需要额外的底层NAT。
当应用自定义的重定向或流量操作依赖了pod命名空间中的原始ClusterIP时,或者由于pod的性质导致套接字级负载均衡器无法生效(如在KubeVirt,Kata容器环境下)时,cilium会自动降级为在veth接口上使用tc负载均衡器。
socketLB.hostNamespaceOnly=true选项可以开启绕过模式。这将绕过bpf钩子对connect()和sendmsg()系统调用套接字重写,并将原始数据包传递到下一个操作阶段(例如,per-endpoint-routing下的堆栈),并重新启用tc bpf程序中的服务查找。
下面示例展示了如何配置在 kube-proxy-free环境中绕过Socket LB:
helm install cilium cilium/cilium --version 1.12.1 \
--namespace kube-system \
--set tunnel=disabled \
--set autoDirectNodeRoutes=true \
--set kubeProxyReplacement=strict \
--set socketLB.hostNamespaceOnly=true
cilium 1.8 引入了XDP(eXpress Data Path),当 cilium 需要转发请求,且后端位于远端节点时,cilium 内置了对NodePort、LoadBlancer和ExternalIP服务加速的功能。XDP的eBPF程序直接在网卡上运行,而不是在更高层运行。
选项loadBalancer.acceleration值为native可以开启加速,默认使用disable禁用加速。大多数10GB或更高速率的网卡驱动程序都在新版的内核上支持了nativeXDP。在云上大多数驱动程序都支持SR-IOV变体,也支持nativeXDP。对于私有化部署,cilium XDP加速可以与kubernetes负载均衡器(如 MetalLB)配合使用。加速器仅能在支持直接路由的单个设备上使用。
在大规模的集群环境,应该注意考虑调整eBPF默认Map的大小。例如通过config.bpfMapDynamicSizeRatio可以在cilium agent启动时根据系统内存来动态设置几个占用空间较大的eBPF Map的大小。
loadBalancer.acceleration选项支持与DSR、SNAT和混合模式配合使用。下面是结合混合模式负载均衡器使用的例子:
helm install cilium cilium/cilium --version 1.12.1 \
--namespace kube-system \
--set tunnel=disabled \
--set autoDirectNodeRoutes=true \
--set kubeProxyReplacement=strict \
--set loadBalancer.acceleration=native \
--set loadBalancer.mode=hybrid \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=${API_SERVER_PORT}
在多个设备的环境中,可以通过指定设备或者通过cilium设备自动检测机制选择多个设备来暴露节点端口,所有被选中的设备都会执行XDP加速。这意味着需要每个设备的驱动都支持nativeXDP,此外处于性能的原因,推荐在使用多设备加速环境的内核版本>=5.5。
下表给出了支持nativeXDP加速的驱动列表。通过ethtool -i eth0可以查看设备使用的驱动。
# ethtool -i eth0
driver: nfp
[...]
Vendor | Driver | XDP Support |
Amazon | ena | >= 5.6 |
Broadcom | bnxt_en | >= 4.11 |
Cavium | thunderx | >= 4.12 |
Freescale | dpaa2 | >= 5.0 |
Intel | ixgbe | >= 4.12 |
ixgbevf | >= 4.17 | |
i40e | >= 4.13 | |
ice | >= 5.5 | |
Marvell | mvneta | >= 5.5 |
Mellanox | mlx4 | >= 4.8 |
mlx5 | >= 4.9 | |
Microsoft | hv_netvsc | >= 5.6 |
Netronome | nfp | >= 4.10 |
Others | virtio_net | >= 4.10 |
tun/tap | >= 4.14 | |
Qlogic | qede | >= 4.10 |
Socionext | netsec | >= 5.3 |
Solarflare | sfc | >= 5.5 |
Texas Instruments | cpsw | >= 5.3 |
通过cilium status命令可以反映出cilium kube-proxy XDP模式。
$ kubectl -n kube-system exec ds/cilium -- cilium status --verbose | grep XDP
XDP Acceleration: Native
注意,由于数据包的处理在网络堆栈的更底层,所以在XDP层处理NodePort服务并推回设备的数据包通过tcpdump不可见。通过cilium的监视器命令和度量计数器可用于观察XDP处理的数据包。
当运行Cilium eBPF kube-proxy replacement时,NodePort 、LoadBalancer和externalIP service通过主机默认路由和kubernetes已分配InternalIP或ExternalIP的本地IP地址是可以访问的(InternalIP优于ExternalIP)。要更改绑定的设备,可以通过Helm devices选项。例如: devices='{eth0,eth1,eth2}'。列出的设备名必须在每个cilium管理的节点都存在。如果设备在不同节点之间不一直,则可以使用通配符选项,例如, devices=eth+,这将匹配以前缀eth开头的任何设备。如果没有设备可以匹配,cilium将尝试执行自动检测。
当使用多个设备时,只有一个设备可以用于cilium节点之间的直接路由。默认情况下,如果通过设备检测到或指定了单个设备,则Cilium将使用该设备进行直接路由。否则,Cilium将使用设置了Kubernetes InternalIP或ExternalIP的设备。如果两者都存在,则内部IP优先于外部IP。要更改直接路由设备,请设置nodePort.directRoutingDeviceHelm选项,例如:directRoutingDevice=eth+。如果有多个设备匹配通配符选项,Cilium将按照字母数字递增的顺序对它们进行排序,并选择第一个。如果设备中不存在直接路由设备,Cilium会将该设备添加到后一个列表中。直接路由设备也用于NodePort XDP加速(如果启用)。
此外,基于Socket-LB功能,可以从集群内的主机或pod通过其公共、本地(docker*前缀名称除外)或环回地址(例如127.0.0.1:NODE_PORT)访问NodePort服务。
如果kube-apiserver配置为使用非默认NodePort范围(30000-32766),则必须通过nodePort.range选项将相同范围传递给Cilium。例如,作为节点端口。范围为10000-32767的范围nodePort.range="10000\,32767"。
如果节点端口范围与临时端口范围(net.ipv4.ip_local_port_range)重叠,Cilium会将节点端口范围附加到保留端口(net.ipv4.ip_ local_ reserved_ports)。这是为了防止节点端口服务劫持源端口与服务端口匹配的主机本地应用程序的流量。要禁用保留端口,请设置nodePort.autoProtectPortRanges=false。
默认情况下,NodePort实现防止应用程序bind(2)对NodePort服务端口的请求。在这种情况下,应用程序通常会看到bind: Operation not permitted的错误。默认情况下,对于较旧的内核,或者从v5.7内核开始,只针对主机名称空间,因此不再影响任何应用程序pod bind(2)请求。专家用户可以通过nodePort.bindProtection=false来更改此设置。
Cilium eBPF kube-proxy replacement与FHRP(如VRRP)或Cisco的HSRP和VPC(也称为多机箱以太网通道)结合使用时,Cilium默认的FIB优化配置将绕过对FIB表的查找,使用目的地为NodePort的ingress向的源IP地址对应的mac地址作为应答时转发L2帧的目的地址,导致数据路径出现问题。这种环境中,最好关闭Cilium对FIB查找优化。确保应答数据包能在忽略来源地址,始终转发到当前活动的对端FHRP对应的mac地址。
通过设置bpf.lbBypassFIBLookup=false可以禁用优化:
helm install cilium cilium/cilium --version 1.12.1 \
--namespace kube-system \
--set kubeProxyReplacement=strict \
--set bpf.lbBypassFIBLookup=false
在大规模的集群环境,应该注意考虑调整eBPF默认Map的大小。使用Helm选项可以调节这些配置。
bpf.lbMapMax默认大小是65535,调节该参数可以增加BPF LB service条目,后端和关联的映射的大小。
helm install cilium cilium/cilium --version 1.12.1 \
--namespace kube-system \
--set kubeProxyReplacement=strict \
--set bpf.lbMapMax=131072
尽管主机端口映射不是kube-proxy的能力,但Cilium eBPF kube-proxy replacement 还是选择了在无需配置Helm CNIcni.chainingMode=portmap选项时,原生的支持主机端口映射。
kubeProxyReplacement=strict模式下,将自动启用主机端口映射支持能力。如果不在此模式下,则需要通过hostPort.enabled=true启用该功能。
如果指定的hostPort没有额外的hostIP,则Pod将同NodePort类型的service一样自动发现主机的地址暴露给外部,例如使用Kubernetes InternalIP或ExternalIP暴露服务地址(如果设置)。此外,还可以通过节点上的环回地址(例如127.0.0.1:hostPort)访问Pod。如果除了hostPort之外,还为Pod指定了hostIP,则Pod将仅在给定的hostIP上公开。0.0.0.0的hostIP与未指定hostIP行为相同。注意hostPort不应在NodePort端口范围内,以避免冲突。
下面在没有kube-proxy的环境下使用deployment工作负载展示一个示例:
helm install cilium cilium/cilium --version 1.12.1 \
--namespace kube-system \
--set kubeProxyReplacement=strict \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=${API_SERVER_PORT}
确认每个节点都通过 INTERNAL-IP 或 EXTERNAL-IP知道自身的ip地址。
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP [...]
apoc Ready master 6h15m v1.17.3 192.168.178.29 [...]
tank Ready 6h13m v1.17.3 192.168.178.28 [...]
如果没有发现node ip,则可以通过kubelet启动参数 --node-ip来指定。假设eth0是默认网卡名,下面脚本可以用于发现node ip。
echo KUBELET_EXTRA_ARGS=\"--node-ip=$(ip -4 -o a show eth0 | awk '{print $4}' | cut -d/ -f1)\" | tee -a /etc/default/kubelet
然后更新/etc/default/kubelet文件,重启kubelet。
为了验证是否已在Cilium中启用HostPort功能,通过cilium status CLI命令的KubeProxyReplacement信息行可以观察到相关信息。如果已成功启用,HostPort将显示为已启用,例如:
$ kubectl -n kube-system exec ds/cilium -- cilium status --verbose | grep HostPort
- HostPort: Enabled
下面的yaml指定hostPort: 8080验证端口映射。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
hostPort: 8080
部署后,我们可以验证Cilium eBPF kube-proxy replacement 是否为容器暴露HostPort 8080端口。
$ kubectl exec -it -n kube-system cilium-fmh8d -- cilium service list
ID Frontend Service Type Backend
[...]
5 192.168.178.29:8080 HostPort 1 => 10.29.207.199:80
类似地,我们可以通过主机名称空间中的iptables检查主机端口服务是否存在iptables规则:
$ iptables-save | grep HOSTPORT
[ empty line ]
最后使用curl测试联通性:
$ curl 192.168.178.29:8080
Welcome to nginx!
[....]
删除deployment后,对应的HostPort也被删除。
kubectl delete deployment my-nginx
Cilium eBPF kube-proxy replacement 支持多种配置模式。它可以完全替换kube-proxy,当linux内核不支持完整的 kube-proxy replacement时也可以与kube-proxy共存。
注意:在使用 kube-proxy replacement和kube-proxy共存时,这两个组件各自独立运行。这意味着如果需要在集群中添加或删除 kube-proxy replacement,如果需要将服务流量委托给kube-proxy,则现有的服务流量会中断。这是因为两个NAT表彼此之间数据并不一样。
下面详述 kube-proxy replacement的选项:
下面的Helm选项相当于kubeProxyReplacement=strict:
helm install cilium cilium/cilium --version 1.12.1 \
--namespace kube-system \
--set kubeProxyReplacement=partial \
--set socketLB.enabled=true \
--set nodePort.enabled=true \
--set externalIPs.enabled=true \
--set hostPort.enabled=true \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=${API_SERVER_PORT}
下面的Helm选项相当于kube-proxy环境中使用v1.6或更早版本Cilium处理service,即为Pod提供ClusterIP:
helm install cilium cilium/cilium --version 1.12.1 \
--namespace kube-system \
--set kubeProxyReplacement=partial
以下Helm选项将优化Cilium的NodePort、 LoadBalancer 和 externalIPs service,并在kube-proxy环境中处理外部ingress到cilium管理的节点的流量:
helm install cilium cilium/cilium --version 1.12.1 \
--namespace kube-system \
--set kubeProxyReplacement=partial \
--set nodePort.enabled=true \
--set externalIPs.enabled=true
通过命令行工具查看当前cilium kube-proxy replacement的模式:
$ kubectl -n kube-system exec ds/cilium -- cilium status | grep KubeProxyReplacement
KubeProxyReplacement: Strict [eth0 (DR)]
cilium kube-proxy replacement支持服务后端pod优雅退出,该功能需要kubernetes 版本大于等于1.20,且开启了EndpointSliceTerminatingCondition特性。cilium agent默认会检测pod的Terminating状态,如果不需要可以通过选项enable-k8s-terminating-endpoint来关闭该特性。
cilium status命令可以查看cilium的特性标识:
$ kubectl -n kube-system exec ds/cilium -- cilium status --verbose
[...]
KubeProxyReplacement Details:
[...]
Graceful Termination: Enabled
[...]
当cilium agent接收到端点Terminating的Kubernetes更新事件时,该端点的数据路径状态将被删除,这样它将不会为新连接提供服务,但端点已有的活动连接要能正常终止。当agent收到端点的Kubernetes删除事件时,端点状态被完全删除。Kubernetes pod termination文档包含terminationGracePeriodSeconds的行为和配置的更多信息。
Cilium eBPF kube-proxy replacement支持kubernetes Service Session亲和。从同一pod或主机到配置了sessionAffinity: ClusterIP的服务的每个连接,将始终选择相同的服务端点。亲和性的默认超时时间为三小时(根据服务的每个请求更新)。如果有需要,可以通过Kubernetes的sessionAffinityConfig进行配置。
亲和关联取决于请求的来源。如果从集群外部向服务发送请求,则该请求的源IP地址用于确定端点关联。如果请求是从集群内部发送的,则源取决于使用使用socket-LB特性用于ClusterIP服务的负载均衡。如果是,则客户端的网络名称空间cookie用作源。后者是在5.7 Linux内核中引入的,用于在套接字层实现亲和性,socket-LB在该层运行(不能使用源IP,因为端点选择发生在内核构建网络包之前)。如果未使用socket-LB(即在pod网络接口上对每个数据包实现负载均衡),则将请求的源IP地址用作源。
cilium 默认启用了Session亲和特性支持,对于不支持网络名称空间cookie的旧内核,将在集群内场景中降级为基于固定cookie值的。主机上的所有应用程序为访问配置了Session亲和服务选择的服务端点相同。要禁用该功能,请将config.sessionAffinity设置为false。
当不使用固定cookie值时,具有多个端口的服务Session亲和的依据是每个服务IP和端口。这意味着从相同源发送到相同服务端口的给定服务的所有请求都将路由到相同的服务端点,从相同源发送到不同服务端口的相同服务的两个请求可以路由到不同的服务端点。
对于使用kube-proxy运行的用户(即,禁用Cilium的kube-proxy replacement),当从在非主机网络命名空间中运行的pod发送请求时,ClusterIP服务负载平衡仍在pod网络接口上执行(直到修复GH#16197)。在这种情况下,默认情况下禁用Session亲和支持。要启用该功能,请设置config.sessionAffinity=true。
要为kube-proxy Replacement启用健康检查服务,必须设置kubeProxyReplacementHealthzBindAddr选项(默认情况下禁用)。该选项接受健康检查服务器服务的IP地址和端口。例如,要启用IPv4设置kubeproxyReplacementHealthzBindR='0.0.0.0:10256',IPv6则设置为kubeProxyReplacementHealthzBindAddr='[::]:10256'。可以通过HTTP /healthz端点访问健康检查服务器。
当使用spec.loadBalancerSourceRanges配置LoadBalancer服务时,Cilium的eBPF kube-proxy Replacement将从外部(例如集群外流量)对该服务的访问限制为该字段中指定的白名单CIDR。如果该字段为空,则不会配置访问限制。
当从集群内部访问服务时,kube-proxy Replacement将忽略该字段,无论是否设置。这意味着集群中的任何pod或任何主机进程都将能够在内部访问LoadBalancer服务。
默认情况下,负载平衡器源地址范围检查功能已启用,可以通过设置config.svcSourceRangeCheck=false来禁用。在某些特殊云环境下禁用检查是有意义的,例如 Amazon NLB原生支持源地址范围检查,因此可以禁用cilium的功能。
kube-proxy都支持配置service.kubernetes.io/service-proxy-name选项,代表该kube-proxy只管理匹配service-proxy-name的service。Cilium通过Helmk8s.serviceProxyName选项也能做到同样的功能。该选项默认为空字符串,代表Cilium不处理任何带有service.kubernetes.io/service-proxy-name标签的service。
关于 service.kubernetes.io/service-proxy-name标签和其工作流程,请参阅https://github.com/kubernetes/enhancements/blob/3ad891202dab1fd5211946f10f31b48003bf8113/keps/sig-network/2447-Make-kube-proxy-service-abstraction-optional/README.md。
如果Cilium设置了服务代理名字,请确认kube-dns和kubernetes这种必须的service设置了代理标签。
Cilium eBPF kube-proxy replacement支持kubernetes service 拓扑逻辑感应提示,要开启该特性请配置选项loadBalancer.serviceTopology=true。
当启用kube-proxy替换时,Cilium会主动对集群中的节点进行L2邻居发现。这是因为负载均衡器需要为后端填充L2地址,而快速数据路径中不支持动态按需解析邻居。
在Cilium 1.10或更早版本中,agent本身包含一个ARP解析库,它在其中定时触发发现并刷新加入集群的新节点。解析后的邻居条目被推入内核并刷新为永久条目。在某些少见的场景下,Cilium 1.10或更早版本可能会在邻居表中留下过期的条目,导致某些节点之间的数据包丢失。要跳过Cilium邻居发现回退到依赖Linux内核来发现邻居,可以将--enable-l2-neigh-discovery=false选项传递给cilium agent。
请注意,依赖Linux内核也可能导致某些数据包丢失。例如,在中继节点上可能发生的丢弃NodePort请求(中继节点即接收到服务分组并将其转发到服务端点的节点)。如果内核中没有L2邻居条目(由于该条目被垃圾收集,或者由于内核没有执行邻居解析),则可能会发生丢包的情况。这是因为在BPF快速路径程序中不支持解析L2地址,例如在XDP层。
从Cilium 1.11开始,完全重新设计了邻居发现,并且Cilium内部ARP解析库已从agnet中删除。agent现在完全依赖Linux内核来发现同一L2网络上的网关或主机。cilium agent中支持IPv4和IPv6邻居发现。根据我们最近在Plumbers上介绍的内核工作,“managed”邻居条目提案已被接受,并将在Linux内核v5.16或更高版本中可用,Cilium agent将检测并透明使用托管邻居。在这种情况下,agnet将加入集群的新节点的L3地址向下推,作为外部学习的“托管”邻居条目。在状态展示方面,iproute2将托管邻居显示为“managed extern_learn”。“extern_learn”属性防止内核的相邻子系统对条目进行垃圾收集。如果在一段时间内没有活动流量,Linux内核会动态解析并定期刷新这些“受管理”邻居条目。也就是说,内核试图始终将它们保持在可达状态。对于不存在“托管”邻居条目的Linux内核v5.15或更早版本,Cilium agent将新节点的L3地址推送到内核中进行动态解析,使用agnet触发的定期刷新。在状态展示上,iproute2仅显示为“extern_learn”。如果在某一段时间内没有活动流量,则Cilium agent控制器会触发基于Linux内核的重新解析,以尝试将它们保持在可访问状态。如果需要,可以通过cilium agent的--arping-refresh-period=30s选项来改变刷新间隔。默认周期为30秒,对应内核的可达性管理时间周期。
邻居发现支持多设备环境,其中每个节点具有多个设备和到另一节点的下一跳。Cilium agent会管理推送所有目标设备(包括直接路由设备)的邻居条目。目前,它支持为每个设备都配置一个 next-hops下一跳信息。以下示例说明了邻居发现如何在多设备环境中工作。每个节点都有两个设备连接到不同的L3网络(10.69.0.64/26和10.69.0.128/26两个不同的子网),全局范围地址分别是10.69.0.1/26 和 10.69.0.2/26。从node1到node2的下一跳是10.69.0.66 dev eno1或10.69.0.130 dev eno2。在这种情况下,cilium agent推送10.69.0.66 dev eno1和10.69.0.130 dev eno2的邻居条目。
+---------------+ +---------------+
| node1 | | node2 |
| 10.69.0.1/26 | | 10.69.0.2/26 |
| eno1+-----+eno1 |
| | | | | |
| 10.69.0.65/26 | |10.69.0.66/26 |
| | | |
| eno2+-----+eno2 |
| | | | | |
| 10.69.0.129/26| | 10.69.0.130/26|
+---------------+ +---------------+
在node1上:
$ ip route show
10.69.0.2
nexthop via 10.69.0.66 dev eno1 weight 1
nexthop via 10.69.0.130 dev eno2 weight 1
$ ip neigh show
10.69.0.66 dev eno1 lladdr 96:eb:75:fd:89:fd extern_learn REACHABLE
10.69.0.130 dev eno2 lladdr 52:54:00:a6:62:56 extern_learn REACHABLE
Cilium默认禁止从集群外部访问ClusterIP类型的service,通过bpf.lbExternalClusterIP=true选项可以关闭该限制。
Cilium 连接BPF cgroup程序以实现基于socket的负载均衡(也被称为host-reachable 服务)。如果你的ClusterIP服务连接出现连接问题,请检查操BPF程序是否连接到了主机cgroup根目录。默认的cgroup root路径为/run/cilium/cgroupv2。注意如果容器运行时以cgroup namespace模式运行,cilium agent pod可能将BPF cgroup程序附加到virtualized cgroup root。在这种情况下,基于Cilium eBPF kube-proxy replacement的负载均衡可能失效,导致连接问题。有关详细信息,请确保您有修复拉取请求。在运行cilium agent pod的kubernetes节点上运行以下命令可以查看cgroup的状态。
$ mount | grep cgroup2
none on /run/cilium/cgroupv2 type cgroup2 (rw,relatime)
$ bpftool cgroup tree /run/cilium/cgroupv2/
CgroupPath
ID AttachType AttachFlags Name
/run/cilium/cgroupv2
10613 device multi
48497 connect4
48493 connect6
48499 sendmsg4
48495 sendmsg6
48500 recvmsg4
48496 recvmsg6
48498 getpeername4
48494 getpeername6
下面的资料更详细的说明了Cilium eBPF kube-proxy replacement 内部工作流程:
留言与评论(共有 0 条评论) “” |