cilium:
- 节点级 Agent
- 支持隧道和直接路由
- eBPF 原生数据转发平面
- 可替换 kube-proxy
1. 隧道模式(vxlan, geneve)
- 不依赖底层网络: 只要节点能通信, Pod 就能通信
- 克服 IP 地址不足
- 自动配置: 新加入集群的新节点自动加入 overlay 网络
需要消耗 50 字节用于封装
2. 原生直接路由
- linux 内核原生路由
- SDN VPC(网卡子接口地址对)原生路由
pod 发出的数据包和节点本地进程发送的数据包一样都需要进行路由选择。
所以需要为 pod 到 pod 配置 路由:必须让内核知道如何转发
2.1 情景1:所有物理节点共享一个平面的 L2 网络
每个物理机需要对应一个 pod CIDR,这样的话可以将跨节点pod间流量需要的路由收敛到 node 对应的 CIDR 粒度。
2.2 VPC 内使用原生直接路由
配合 SDN 面的主网卡的虚拟子接口地址对模式,只需要走虚拟机默认网卡的默认路由即可实现,这也就是 native(vpc 原生)的另一个意思。
3. BPF LB
IPtables 和 Netfilter 是 kube-proxy 实现 Service 抽象的两项基础技术。它们继承了 20 多年开发积累的遗产,这些遗产植根于更传统的网络环境,这些环境通常比普通 Kubernetes 集群静态得多。
Kubernetes Without kube-proxy
增强 Kubernetes 集群的网络速度和效率将 Kubernetes 从 kube-proxy 和 IPtables 中解放出来。 在云原生时代,它们不再是完成工作的最佳工具,特别是在性能、可靠性、可扩展性和操作方面。
Cilium 的 kube-proxy 替换依赖于 socket-LB 功能:
-
这需要 v4.19.57、v5.1.16、v5.2.0 或更高版本的 Linux 内核。
-
Linux 内核 v5.3 和 v5.8 添加了其他功能,Cilium 可以使用这些功能来进一步优化 kube-proxy 替换实现。
另外 cilium 可以使用 BPF LB,而且可以使用 DSR
3.1 保留客户端源 IP
Cilium 的 eBPF kube-proxy replacement 实现了各种选项,以避免对 NodePort 请求执行 SNAT,否则客户端源 IP 地址将在通往服务端点的路径上丢失。
对于设置了 externalTrafficPolicy=Local 的服务,可以在集群内实现连接,并且即使从没有本地后端的节点上也可以访问这些服务,这意味着在不需要执行 SNAT 的情况下,所有服务端点都可以从集群内部进行负载均衡。通过这种方式,可以确保对具有 externalTrafficPolicy=Local 策略的服务的有效加载均衡,同时在集群内部实现连接。
externalTrafficPolicy=Cluster: 对于默认的 Cluster 策略,在服务创建时存在多种选项来实现外部流量的客户端源 IP 保留,即如果后者只有基于 TCP 的服务向外界公开,则可以在 DSR 或 Hybrid 模式下运行kube-proxy 替代功能。通过这种方式,可以有效地在集群中保留外部流量的客户端源 IP,特别是在面向外部的基于 TCP 的服务中实现这一点。
3.2 内部流量策略
-
对于 internalTrafficPolicy=Local 的服务,源自当前集群中 Pod 的流量仅路由到流量源自同一节点内的端点。
-
internalTrafficPolicy=Cluster 是默认设置,它不限制可以处理内部(集群内部)流量的端点。
这意味着所有的端点都可以处理集群内部的流量,而不对内部流量的处理进行限制。
3.3 Maglev 一致性哈希
Cilium 的 eBPF kube-proxy replacement 功能通过在其负载均衡器中实现The Maglev 哈希的变体,从而支持一致性哈希。
-
这提高了在发生故障时的可靠性。
-
此外,它还提供了更好的负载平衡属性: 因为添加到集群的节点将为给定的 5-tuple 在整个集群中做出一致的后端选择,而无需与其他节点同步状态。
-
类似地,在删除后端后,后端查找表将被重新编程,对给定服务的不相关后端的干扰最小(重新分配中最多有 1% 的差异)。
可以通过设置 --set loadBalancer.algorithm=maglev 来启用用于服务负载平衡的 Maglev 哈希。
3.3.1 maglev 哈希只适用于 南北向流量
请注意,Maglev 哈希仅适用于外部 (N-S) 流量。
对于集群内服务连接(E-W),套接字直接分配给服务后端,例如在 TCP 连接时,没有任何中间跃点,因此不受 Maglev 的影响。
Cilium 的 XDP 加速也支持 Maglev 哈希。
3.3.2 maglev
- maglev.tableSize:指定每个服务的 Maglev 查找表的大小。
- maglev.hashSeed
maglev.tableSize 可选值: 251 509 1021 2039 4093 8191 16381 32749 65521 131071
Maglev 建议表大小 (M) 明显大于最大预期后端数量 (N)。
实际上,这意味着 M 应该大于100 * N,以保证后端更改时重新分配最多有 1%差异的特性。 M 必须是质数。 Cilium 对 M 使用默认大小 16381。
4. 其他重要特性:
带有 IPv4 选项/IPv6 扩展标头的 DSR
在这种 DSR 调度模式中,服务 IP/port 信息通过 Cilium 特定的 IPv4 选项或 IPv6 目标选项扩展头传送到后端。这要求 Cilium 在 Native-Routing 模式下部署,即在 Encapsulation 模式下无法工作。这种方式确保了服务 IP/port 信息能够传递到后端,从而实现了一致性处理机制。
在某些公共云提供商环境中,由于底层网络架构可能会丢弃特定于 Cilium 的IP 选项,因此这种 DSR 模式可能无法正常工作。如果遇到连接到远程节点上的后端的服务的连接问题,首先检查 NodePort 请求是否实际上到达包含后端的节点。如果不是这种情况,请考虑切换到使用 Geneve 的 DSR 模式(如下所述),或者切换回默认的 SNAT 模式。这种方式可以帮助解决云环境下的连接问题,确保服务正常运行。 在启用了 DSR-only 模式的无 kube-proxy 环境中,上述 Helm 示例配置如下所示:
helm install cilium cilium/cilium --version 1.15.4 \
--namespace kube-system \
--set routingMode=native \
--set kubeProxyReplacement=true \
--set loadBalancer.mode=dsr \
--set loadBalancer.dsrDispatch=opt \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=${API_SERVER_PORT}
Geneve 的 DSR 模式
在默认情况下,Cilium 在 DSR 模式下会将服务 IP/port 编码到特定于Cilium 的 IPv4 option 或 IPv6 目标 option 扩展中,以便后端了解需要使用的服务 IP/port 来进行回复。
然而,一些数据中心路由器会将具有未知 IP 选项的数据包传递给称为 “第2层慢路径” 的软件处理。
如果具有 IP 选项的数据包数量超过给定阈值,这些路由器会丢弃这些数据包,这可能会显著影响网络性能。
为了避免这个问题,Cilium 提供了另一种调度模式,即带有 Geneve 的 DSR 模式。在该模式中,Cilium 通过 Geneve 头部(远比 vxlan 的 24 位大)将数据包封装到 Loadbalancer 中,并在 Geneve Option 中包含服务 IP/port 信息,然后将数据包重定向到后端。 在 kube-proxy-free 环境中启用 DSR 和 Geneve 调度的 Helm 示例配置如下:
helm install cilium cilium/cilium --version 1.15.4 \
--namespace kube-system \
--set routingMode=native \ # 这里应该是 linux 内核面向 VNI 的路由
--set tunnelProtocol=geneve \
--set kubeProxyReplacement=true \
--set loadBalancer.mode=dsr \
--set loadBalancer.dsrDispatch=geneve \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=${API_SERVER_PORT}
DSR 与 Geneve 封装模式(Encapsulation)兼容,可以与直接路由模式或 Geneve 隧道模式一起使用。
不幸的是,它与 vxlan 封装模式不兼容。 以下是在 DSR 与 Geneve 调度和隧道模式下的示例配置:
helm install cilium cilium/cilium --version 1.15.4 \
--namespace kube-system \
--set routingMode=tunnel \
--set tunnelProtocol=geneve \
--set kubeProxyReplacement=true \
--set loadBalancer.mode=dsr \
--set loadBalancer.dsrDispatch=geneve \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=${API_SERVER_PORT}
Hybrid DSR 和 SNAT 模式
Cilium 还支持 hybrid DSR 和 SNAT 模式:
- 即对于 TCP 连接执行 DSR
- 对于 UDP 连接执行 SNAT
这消除了在网络中手动更改 MTU 的需要,同时仍然受益于通过去除回复的额外跳数带来的延迟改进,特别是当 TCP 是工作负载的主要传输协议时。
通过设置 loadBalancer.mode 选项为 dsr、snat 或 hybrid,可以控制行为。
默认情况下,代理使用 snat 模式。
在启用了 DSR hybrid 模式的 kube-proxy-free 环境中,使用 Helm 的示例配置如下所示:
--namespace kube-system \
--set routingMode=native \
--set kubeProxyReplacement=true \
--set loadBalancer.mode=hybrid \ # 这里
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=${API_SERVER_PORT}
Socket LoadBalancer Bypass in Pod Namespace
Cilium 的 套接字级负载均衡器(sock-lb) 对低层数据路径操作是透明的,即在连接(TCP,已连接的 UDP )、sendmsg(UDP)或 recvmsg(UDP)系统调用时,会根据目标 IP 选择现有服务 IP 之一,并选择一个服务后端作为目标。
这意味着尽管应用程序认为自己连接到服务地址,但内核套接字实际上连接到后端地址,因此无需额外的下层 NAT。
Cilium 内置了绕过套接字级负载均衡器并在 veth 接口上返回到 tc(流量控制)负载均衡器的支持。
例如,KubeVirt,Kata Containers,gVisor 场景中
当自定义重定向或操作依赖于 pod 命名空间内的原始 ClusterIP(例如,Istio sidecar)或者由于 Pod 的特性套接字级负载均衡器无效时:
通过设置 socketLB.hostNamespaceOnly=true 来启用此绕过模式。启用后,这将绕过连接(connect())和发送消息(sendmsg())系统调用的 BPF 挂钩中的套接字重写,允许原始数据包继续到下一个操作阶段(例如,以每个端点路由模式在堆栈中)并重新启用 tc BPF 程序中的服务查找。
以下是在 kube-proxy-free 环境中具有套接字负载均衡器绕过的 Helm 示例配置:
helm install cilium cilium/cilium --version 1.15.4 \
--namespace kube-system \
--set routingMode=native \
--set kubeProxyReplacement=true \
--set socketLB.hostNamespaceOnly=true
负载均衡器和 NodePort XDP 加速
配置 BPF Map 大小
对于大型环境,Cilium 的 BPF 映射可以配置为具有更高的条目限制。可以使用覆盖 Helm 选项来调整这些限制。 要增加 Cilium 的 BPF LB 服务、后端和关联映射中的条目数,请考虑覆盖 bpf.lbMapMax Helm 选项。该 LB 映射大小的默认值为 65536。
helm install cilium cilium/cilium --version 1.15.4 \
--namespace kube-system \
--set kubeProxyReplacement=true \
--set bpf.lbMapMax=131072
容器 HostPort 支持
虽然不是 kube-proxy 的一部分,Cilium 的 eBPF kube-proxy replacement 方案也原生支持 hostPort 服务映射
通过指定 kubeProxyReplacement=true,原生 hostPort 支持会自动启用,因此不需要进一步操作。否则,可以使用 hostPort.enabled=true 来启用该设置。
会话亲和性
Cilium 的 eBPF kube-proxy replacement 方案支持 Kubernetes 服务会话亲和性。
来自同一 Pod 或主机到配置了 sessionAffinity: ClientIP 的服务的每个连接将始终选择相同的服务端点。
亲和性的默认超时时间为三小时(每个请求到服务时更新),但如果需要,可以通过 Kubernetes 的 sessionAffinityConfig 进行配置。
亲和性的源取决于请求的发起源。
-
如果从集群外部发送请求到服务,则使用请求的源 IP 地址来确定端点亲和性。
-
如果从集群内部发送请求,则源取决于是否使用 socket-LB 功能来负载平衡ClusterIP 服务。
如果是,则使用客户端的网络命名空间 cookie 作为源。 后者是在 5.7 版Linux 内核中引入的,用于在 socket-LB 操作的套接字层实现亲和性(在那里并没有可用的源 IP,因为端点选择发生在内核构建网络数据包之前)。
如果不使用 socket-LB(即在每个数据包接口上对 Pod 网络接口进行负载平衡),则使用请求的源 IP 地址作为源。
对于 Cilium 的 kube-proxy replacement 方案,默认情况下启用了会话亲和性支持。
对于不支持网络命名空间 cookie 的旧内核的用户,实施了一种基于固定cookie 值的集群内部模式作为权衡方案。
这使得主机上的所有应用程序对于配置了会话亲和性的给定服务都会选择同一个服务端点。
要禁用该功能,请设置 config.sessionAffinity=false。 当不使用固定的cookie 值时,具有多个端口的服务的会话亲和性是按照服务 IP 和端口进行的。
这意味着从相同源发送到相同服务端口的给定服务的所有请求将被路由到相同的服务端点;但同一服务的两个请求,从相同源发出但发送到不同服务端口,可能会被路由到不同的服务端点。
拓扑感知提示
kube-proxy replacement 实现了 K8s 服务拓扑感知提示。
这允许 Cilium 节点优先选择驻留在同一区域的服务端点。
要启用该功能,请设置 loadBalancer.serviceTopology=true。
Neighbor Discovery
当启用 kube-proxy replacement 方案时,Cilium 会对集群中的节点进行L2邻居发现。
这对于服务负载平衡是必要的,以便为后端填充 L2 地址,因为在快速路径上无法动态解析邻居。
在 Cilium 1.10 或更早版本中,代理本身包含一个 ARP 解析库,它触发了对加入集群的新节点的发现和定期刷新。
已解析的邻居条目被推送到内核并作为永久条目进行刷新。
在一些罕见情况下,Cilium 1.10 或更早版本可能会在邻居表中留下过时的条目,导致某些节点之间的数据包被丢弃。
要跳过邻居发现,而是依赖 Linux 内核来发现邻居,您可以向cilium-agent传递--enable-l2-neigh-discovery=false标志。
但是,请注意,依赖 Linux 内核可能也会导致某些数据包被丢弃。例如,NodePort请求可能会在中间节点上被丢弃(即接收到服务数据包并将其转发到运行所选服务端点的目标节点的节点)。这可能会发生,如果内核中没有 L2 邻居条目(由于条目被垃圾收集或由于内核没有进行邻居解析)。
从 Cilium 1.11 开始,邻居发现已完全重做,Cilium 内部的 ARP 解析库已从代理中移除。
代理现在完全依赖 Linux 内核来发现在同一 L2 网络中的网关或主机。
Cilium 代理支持 IPv4 和 IPv6 邻居发现。根据我们最近在 Plumbers 上展示的内核工作,“managed”邻居条目已被上游并将在 Linux 内核 v5.16 或更高版本中可用,Cilium 代理将检测并透明地使用这些条目。
在这种情况下,代理将新加入集群的节点的 L3 地址作为外部学习的 “managed” 邻居条目推送下去。
对于内省,iproute2 将它们显示为 “managed extern_learn”。“extern_learn” 属性阻止了内核邻居子系统对条目的垃圾收集。
这样的 “managed” 邻居条目由 Linux 内核自身动态解析并定期刷新,以防止一段时间内没有活动流量。
换句话说,内核试图始终将它们保持在可达状态。
对于较早的 Linux 内核版本 v5.15 或更早版本,在其中 “managed” 邻居条目不存在的情况下,Cilium 代理类似地将新节点的 L3 地址推送到内核进行动态解析,但通过代理触发的定期刷新。
在这种情况下,iproute2 仅将它们显示为 “extern_learn”。 如果一段时间内没有活动流量,则 Cilium 代理控制器会触发基于 Linux 内核的重新解析,以试图保持它们处于可达状态。
如果需要,刷新间隔可以通过传递 --arping-refresh-period=30s 标志给cilium-agent 来进行更改。默认周期为 30 秒,对应于内核的基本可达时间。
neighbor discovery 支持多设备环境,其中每个节点具有多个设备和到另一个节点的多个下一跳。
Cilium 代理为所有目标设备(包括直接路由设备)推送邻居条目。
目前,它支持每个设备一个下一跳。以下示例说明了邻居发现在多设备环境中的工作方式。
每个节点有两个连接到不同 L3 网络(10.69.0.64/26 和 10.69.0.128/26)的设备以及每个设备一个全局范围地址(分别为10.69.0.1/26和10.69.0.2/26)。
从节点1到节点2的下一跳可以是 10.69.0.66 dev eno1 或者 10.69.0.130 dev eno2。
在这种情况下,Cilium 代理为 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|
+---------------+ +---------------+
$ 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