一、基础知识:eBPF和XDP

1.1 BPF

全称为“Berkeley Packet Filter”,于1997 年自Linux 2.1.75版本的内核引入。基于寄存器(CPU之上的小型存储空间)的虚拟机,运行于内核空间。主要功能包括:

  • 负责运行从用户空间(通过系统调用)注入的代码而无须对内核进行编程(开发内核模块)。
  • 使用自定义的64 位RISC指令集。
  • 能够在Linux内核内部运行即时本地编译的 “BPF 程序”,并能访问内核功能和内存的子集

tcpdump命令其实就是借助BPF把后面的选项转换为BPF程序并注入到BPF虚拟机上运行(即使用BPF作为网络数据包过滤技术)。

1.2 eBPF

eBPF,即“extended BPF”,由Alexei Straovoitov于2014年实现,最早出现在Linux 3.15版本的内核中。针对现代硬件进行了优化,较之BPF执行速度更快。
       2014年6月,eBPF扩展到用户空间,从而演进成了一个通用执行引擎,应用场景不再局限于数据包过滤,而是逐步扩展到内核各子模块,随后被广泛应用在观测、网络和安全等领域。目前已然发展成为Linux内核的顶级子系统。
       ePBF为用户提供了通过简单程序扩展或定制内核功能的接口。用户可编写eBPF程序与内核中的目标事件相关联,并由这些事件触发执行。
       eBPF的工作逻辑如下:进程通过系统调用将eBPF代码送到内核空间。待校验通过后由JIT Compiler(Just-in-Time,即时编译器)把它编译为eBPF的可执行程序,在eBPF的执行引擎上执行。执行结果可通过SystemCall(linux系统接口)送给进程以了解执行情况。这里的可执行程序可控制Socket通信、TCP/IP的各类属性、网络接口的配置等。
       eBPF通常用于SDN(软件定义网络)配置、入侵检测、可观测性、防火墙、容器安全、DDOS泛洪防范、设备驱动等功能。

1.3 XDP

XDP的全称为“express Data Path”,在内核级提供可编程数据包处理机制。它是Linux内核中基于eBPF提供的高性能数据路径,支持绕过内核的网络栈直接在网络驱动程序的级别处理数据包。

二、Cilium

2.1 简介

Cilium是一款基于eBPF和XDP的Kubernetes高性能网络插件,支持可观测性和安全机制。也是ServiceMesh的数据平面。

Cilium网络插件_Cilium

其功能如下:

  • 容器网络解决方案和负载均衡器:CNI、Kubernetes Service、Multi-cluster。Service的功能原本需要kube-proxy组件支持,有了Cilium,就无需kube-proxy组件了。
  • 网络安全:Network Policy、Identity-based(基于每个Pod的身份识别,下发证书建立TLS连接,实现端到端的加密通信)、Encryption(链路加密)。
  • 可观测性:Metrics、Flow Visibility(请求流)、Service Dependency(服务依赖性)。

Kubernetes的基础网络功能,包括iptables/ipvs、服务发现、同一节点/跨节点的Pod间的通信,这些Cilium都能实现。它是节点级Agent,支持隧道和直接路由的方式通信。它是eBPF的原生数据平面,可替换由kube-proxy所支撑的Service的服务发现和负载均衡功能,完全取代kube-proxy。使用Cilium代替kube-proxy,网络请求报文无需遍历iptables/ipvs表,使得集群性能更加高效。

Cilium网络插件_Cilium_02

2.2 Cilium的关键组件

2.2.1 Cilium Agent

  • 由DaemonSet编排运行集群中的每个节点上。
  • 里面运行的程序用于从Kubernetes或API接口接受配置:网络、LB、Network Policy等。将这些配置转换为eBPF程序并送到节点内核中的eBPF引擎上。
  • 基于SDK调用节点上的eBPF实现相关的功能。

2.2.2 Cilium Client

  • 命令行工具,通过Rest API同当前节点上的Cilium Agent进行交互去了解Cilium Agent的各类状态信息。
  • 常用于检查本地Cilium Agent的状态。
  • 也可用于直接访问eBPF Map。
  • 另外还有一个客户端工具称为Cilium CLI,负责管理整个Cilium集群,而非当前Cilium Agent。

2.2.3 Cilium Operator

负责监视和维护整个集群。

2.2.4 Cilium Plugin

Cilium自身即为Kubernetes CNI插件,同时还可以完全取代Kube Proxy的功能。

三、Kubernetes使用Cilium插件

3.1 Pod网络模式

3.1.1 隧道封装

  • VXLAN:默认模式,8472/UDP
  • Geneve:6081/UDP

3.1.2 直接路由

  • 将所有非本地目标地址报文都交由内核的路由子系统,处理逻辑等同于本地进程生成的报文。类似于Calico的BGP模式和Flannel的host-gw模式。
  • 节点上的路由表将决定如何路由Pod报文。
  • 开启方式:--set tunnel=disabled --set routing-mode=native

3.2 部署Kubernetes集群和Cilium网络插件

部署Kubernetes集群时,在kubeadm init命令上使用“--skip-phases=addon/kube-proxy”选项,以跳过Kube Proxy的部署。

kubeadm init --control-plane-endpoint="kubeapi.wxd.com" --kubernetes-versinotallow=v1.29.8 --pod-network-cidr=10.244.0.0/16 --service-cidr=10.96.0.0/12 --token-ttl=0 --upload-certs --image-repository=registry.aliyuncs.com/google_containers --cri-socket=unix:///var/run/cri-dockerd.sock --skip-phases=addon/kube-proxy

部署Cilium网络插件,方式有helm和Cilium cli。这里使用Cilium cli。
       Cilium cli安装参考https://github.com/cilium/cilium-cli

wget https://github.com/cilium/cilium-cli/releases/download/v0.16.16/cilium-linux-amd64.tar.gz
tar -xf cilium-linux-amd64.tar.gz
mv cilium /usr/local/bin
#显示 Cilium 的状态信息,包括节点、代理、网络等信息。进入cilium agent的Pod内部使用
cilium status
#列出可用的Cilium版本,默认为目前最新的稳定版
cilium install --list-versions
#使用默认的VXLAN模式,并自定义要使用的子网
cilium install --set kubeProxyReplacement=true --set ipam.mode=kubernetes --set routingMode=tunnel --set tunnelProtocol=vxlan --set ipam.operator.clusterPoolIPv4PodCIDRList=10.244.0.0/16 --set ipam.Operator.ClusterPoolIPv4MaskSize=24
#卸载
cilium uninstall
#使用原生路由(直接路由)模式
cilium install  --set kubeProxyReplacement=strict --set ipam.mode=kubernetes  --set routingMode=native --set ipam.operator.clusterPoolIPv4PodCIDRList=10.244.0.0/16  --set ipam.Operator.ClusterPoolIPv4MaskSize=24  --set ipv4NativeRoutingCIDR=10.244.0.0/16  --set autoDirectNodeRoutes=true --set bpf.masquerade=true

3.3 Cilium网络插件的实现

Cilium会在宿主机上创建四个虚拟网络接口。

  • cilium_host和cilium_net。一组veth pair,由Cilium Agent创建。cilium_host会被配置为该主机分配到的PodCIDR中的第一个地址,并作为该子网的默认网关(例如下面的10.244.3.57)。CNI插件负责创建BPF规则,以便于在内核接通veth pair的两端。
  • cilium_vxlan。负责在vxlan模式下,封装或解封vxlan报文。
  • lxc_health。节点健康状态检测。

Cilium网络插件_Cilium_03

关于Pod网络接口。Cilium会为每个Pod创建一组veth pair。一端作为Pod中的网络接口存在,网关指向cilium_host的地址。另一端表现为宿主机上名字形如“lxcXXXX”的虚拟接口。LXC接口的MAC地址用于响应ARP请求。

Cilium网络插件_Cilium_04

Cilium网络插件_Cilium_05

#以当前节点(这里是K8S-Node2节点)为中心显示Cilium的隧道状态信息。需要进入Cilium agent的Pod内执行
cilium bpf tunnel list

Cilium网络插件_Cilium_06

3.4 Cilium网络插件的配置要点

3.4.1 Cilium在Kubernetes上的运行模式

  • 与kube-proxy共存:通常在内核版本较低时采用的模式,替换kube-proxy的部分功能
  • kubeProxyReplacement=probe:自动探测内核中支持BPF特性,并自行决定如何替换
  • kubeProxyReplacement=partial:由用户手动指定由BPF替换的特性
  • 完全替换kube-proxy:kubeProxyReplacement=strict

如上配置可通过kubectl edit cm -n kube-system cilium-config命令修改。

3.4.2 Masquerading(地址伪装)

将来自集群外部流量转发至集群上的Pod时依赖的功能,传统方式是使用iptables nat机制(称为iptables mode)。eBPF提供了更加高效的masquerading机制(eBPF mode),但仅在4.19及以上版本的Linux内核上支持。启用方式:“bpf.masquerade=true”。

3.4.3 Host-Routing(主机路由)

eBPF模式下,网络报文仍会通过常规网络栈中的某些部分,例如netfilter框架,这会增加网络开销。Cilium自v1.9版本引入的基于eBPF的Host-Routing机制,可让报文完全绕过netfilter和上层网络栈。该功能存在Legacy Mode和BPF Mode两种模式,后一种依赖于多个条件:Kernel >= 5.10、eBPF-based kube-proxy replacement、eBPF-based masquerading。

四、Hubble及Cilium指标

4.1 关于Hubble

Hubble是建立在Cilium和eBPF之上、分布式的网络可观察性平台。存储、展示从Cilium获取的相关数据。Hubble还可以对接像Prometheus、Grafana等主流的云原生监控体系,实现可扩展的监控策略。

4.2 配置Cilium启用Hubble

通过cilium命令启用:cilium hubble enable --ui

部署cilium时直接启用,在cilium命令上使用如下选项即可:

--set hubble.enabled="true" --set hubble.listenAddress=":4244" --set hubble.relay.enabled="true" --set hubble.ui.enabled="true"

同Prometheus对接:

--set prometheus.enabled=true --set operator.prometheus.enabled=true --set hubble.metrics.port=9665 --set hubble.metrics.enableOpenMetrics=true --set metrics.enabled="{dns:query;ignoreAAAA;destinatinotallow=pod-short,drop:sourceCnotallow=pod;destinatinotallow=pod,tcp,flow,port-distribution,icmp,http}"
上面的设置,表示开启了hubble的metrics输出模式,并输出以上这些信息。默认情况下,Hubble daemonset会自动暴露metrics API给Prometheus。

五、Cilium Ingress Controller

5.1 简介

Cilium内置支持用于Kubernetes Cluster的Ingress Controller。支持通过将ingressClassName的值设定为“cilium”来解析Ingress资源,也兼容 “kubernetes.io/ingress.class”注解方式。支持各类Ingress,包括TLS类型。启用Kube Proxy时,设置了“nodePort.enabled=true”;未启用Kube Proxy,由Cilium代替其功能。
       Cilium Ingress的负载均衡器模式:

  • dedicated:独占模式,即Cilium会为每个Ingress资源在同一个namespace下创建一个专用的LoadBalancer类型的Service资源。
  • shared:共享模式,多个Ingress资源共享使用Cilium默认创建的LoadBalancer类型的Service资源(位于Cilium所在的名称空间,默认为kube-system)。类似于Ingress Nginx。

5.2 启用Cilium Ingress Controller

# 启用Ingress Controller 
cilium install \ 
… 
--set kubeProxyReplacement=true \ 
--set ingressController.enabled=true \ # 启用Ingress Controller
--set ingressController.loadbalancerMode=dedicated \ # 设定默认的LoadBalancer模式为dedicated
--set ingressController.default=true # 可选项,设定Cilium作为默认使用的Ingress Controller

当然,也可以采用配置Ingress的方式来完成。在ingress资源上,将“spec.ingressClassName”字段或者“kubernetes.io/ingress.class”注解的值设置为“cilium”。

ingress资源的LoadBalancer类型默认继承Cilium的全局设定,也可通过注解“ingress.cilium.io/loadbalancer-mode”注解单独配置。dedicated模式下,还可通过注解“ingress.cilium.io/service-type”指定专用Service的类型,可用值有“LoadBalancer”和 “NodePort”。

对于dedicated模式的ingress,cilium会自动为其创建一个Service作为Ingress Controller入口,名称格式为“cilium-ingress-INGRESS_NAME”。客户端要通过Ingress中声明的主机名,和专用Service的入口 (NodePort或LB_IP)来访问对应的Ingress中要暴露的服务。

5.3 共享模式

以下命令采用共享模式创建Cilium Ingress Controller。

cilium install --set kubeProxyReplacement=true --set ipam.mode=kubernetes --set routingMode=tunnel --set tunnelProtocol=vxlan --set ipam.operator.clusterPoolIPv4PodCIDRList=10.244.0.0/16 --set ipam.Operator.ClusterPoolIPv4MaskSize=24 --set ingressController.enabled=true --set ingressController.loadbalancerMode=shared

之后部署MetalLB,部署成功后就可以让Cilium作为Ingress为服务暴露于集群外部提供支持。服务就以之前的hubble-ui为例进行说明。

Cilium网络插件_Cilium_07

Cilium网络插件_Cilium_08

此时,kube-system名称空间下的hubble-ui服务需要通过cilium-ingress这个Ingerss控制器去对外开放服务。

#创建ingress规则,ingressclass为cilium,把kube-system名称空间下hubble-ui:80这个服务通过主机名hubble.wxd.com开放出去。
kubectl create ingress hubble-ui --class='cilium' --rule='hubble.wxd.com/*=hubble-ui:80' -n kube-system

Cilium网络插件_Cilium_09

在物理机做如下解析,查看访问效果:

192.168.131.201 hubble.wxd.com

Cilium网络插件_Cilium_10

5.4 独占模式

先创建好对应的Deployment和clusterip类型的Service

kubectl create deploy demoapp --image=ikubernetes/demoapp:v1.0 --replicas=3
kubectl create svc clusterip demoapp --tcp=80:80

创建相应的Ingress

vim demoapp.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demoapp
  annotations:
    ingress.cilium.io/loadbalancer-mode: 'dedicated'
    ingress.cilium.io/service-type: 'LoadBalancer'
spec:
  ingressClassName: cilium
  rules:
  - host: demoapp.wxd.com
    http:
      paths:
      - backend:
          service:
            name: demoapp
            port:
              number: 80
        path: /
        pathType: Prefix
status:
  loadBalancer: {}

kubectl apply -f demoapp.yaml

Cilium网络插件_Cilium_11

apply清单后,发现demoapp.wxd.com被分配一个为192.168.131.202的外部IP地址,此地址隶属cilium-ingress-demoapp这个svc的。这个Service的意思是专门用于通过 cilium Ingress开放demoapp服务的Service。用域名demoapp.wxd.com即可访问demoapp服务,访问效果为负载均衡。

Cilium网络插件_Cilium_12

注意:192.168.131.202只能被demoapp这个ingress所使用,尽管为此创建了专有的Service,但该Service只是被Ingress作为LoadBalancer的流量入口来使用。