01
Overview
我们生产K8s使用容器网络插件 Cilium 来创建 Pod network,下发 eBPF 程序实现 service 负载均衡来替换 kube-proxy,并且使用 BGP 协议来宣告路由给交换机,使得 pod ip 在内网可达。
目前 BGP speaker 使用 bird 软件, 不过随着 Cilium 最近新版本已经集成 MetalLB 库(https://github.com/cilium/metallb),可以使用 MetalLB 自带的 BGP speaker 来宣告路由,后续只需要部署 cilium-operator deployment 和 cilium-agent daemonset 两个组件,无需部署 bird daemonset 组件,运维成本更低,而且可以通过 cilium metrics 来获取 BGP 相关可观测性数据,具体详情可见 #16525 (https://github.com/cilium/cilium/pull/16525/),代码可见 pod_cidr.go(https://github.com/cilium/cilium/blob/master/pkg/bgp/speaker/pod_cidr.go)
Cilium 作为容器网络插件遵循 CNI 标准,部署时主要部署两个组件:cilium-operator deployment 和 cilium-agent daemonset。
cilium-operator 组件会根据选择的 ipam mode 不同选择不同的 ipam 逻辑,一般默认选择 cluster-pool 模式,这样 cilium-operator 会给每一个 v1.Node 对象创建对应的 CiliumNode 对象,且根据 cluster-pool-ipv4-cidr 和 cluster-pool-ipv4-mask-size 两个配置计算出每一个节点的 pod cidr subnet 值,并存储在该 CiliumNode 对象 spec.ipam.podCIDRs 字段中。比如给集群设置两个集群网段 cluster-pool-ipv4-cidr 为 10.20.30.40/24 和 50.60.70.80/24,cluster-pool-ipv4-mask-size 设置为 26,cilium-operator 组件可以支持一个网段消耗完了可以从下一个网段继续分配 pod cidr net,代码见 clusterpool.go#L107-L141(https://github.com/cilium/cilium/blob/master/pkg/ipam/allocator/clusterpool/clusterpool.go#L107-L141 )。根据 cluster-pool ipam 逻辑会依次把 cluster-pool-ipv4-cidr 10.20.30.40/24 分成 2^(26-24)=4 个子网段 pod cidr subnet,如果 10.20.30.40/24 消耗完了,则继续消费 50.60.70.80/24 网段,一个子网段的切分可见代码 cidr_set.go(https://github.com/cilium/cilium/blob/master/vendor/github.com/cilium/ipam/cidrset/cidr_set.go),这个逻辑也是复用的 k8s nodeipam 逻辑 cidr_set.go(https://github.com/kubernetes/kubernetes/blob/v1.22.3/pkg/controller/nodeipam/ipam/cidrset/cidr_set.go)总之,cilium-operator 组件会根据 v1.node 的添加和删除,来添加和删除 CiliumNode 对象,同时使用 IPAM 来管理每一个 node 的子网 allocate/release 操作。
cilium-agent 组件会从其对应的子网段出再去划分出每一个 pod ip,上文已经说过 cilium-agent 或者 bird 会把每一个子网段 pod cidr subnet 通过 BGP 协议宣告给交换机,本机 node ip 作为下一跳,所以使得每一个 pod ip 内网可达。kubelet 在创建 pod sandbox container 时会调用 cilium cni 二进制文件来为当前 pod 创建 network,同时 cilium cni 作为客户端会调用服务端 cilium-agent pod 来分配 pod ip。
那 cilium-agent 做了哪些工作呢?以及默认不像 calico 那样每一个 pod 的宿主机端网卡,都会创建对应的路由,cilium 如何下发哪些 eBPF 程序做到这一点的?这是本文重点讨论的问题。
02
Cilium 工作原理
Cilium CNI 在为 pod 创建网络资源的过程,粗略说起来不复杂,主要分为三步:
为 pod 创建网卡 veth pair,并配置 mac 以及容器侧的路由等等网络资源
Cilium IPAM 从该节点的子网段 pod cidr 中分配出一个 ip,并配置到 pod 网卡
为该 pod 创建对应的 CiliumEndpoint/CiliumIdentity 对象,计算并下发 network policy 规则,以及下发 eBPF 程序到 pod 网卡上。
限于篇幅以及为了精简,本文暂不考虑 network policy 逻辑,以及只考虑 create pod 时 cilium 的处理逻辑,不考虑 delete pod 时的逻辑。
01 Cilium 创建网络资源
在创建 pod 时,kubelet 会调用 Cilium 二进制文件,该二进制文件路径在启动 kubelet 时通过参数 --cni-bin-dir 传进来的,一般默认为 /opt/cni/bin/ ,
比如在宿主机该目录下存在 /opt/cni/bin/cilium-cni 二进制文件,kubelet 启动参数 --cni-conf-dir 包含 cni 配置文件路径,一般默认为 /etc/cni/net.d/05-cilium.conf ,如文件内容为:
{
"cniVersion": "0.3.1",
"name": "cilium",
"type": "cilium-cni",
"enable-debug": false
}
kubelet 和 cni 插件交互的具体内容可以参见之前的文章 《Kubernetes学习笔记之Calico CNI Plugin源码解析(一)》(https://juejin.cn/post/6916326439851655176)
该文件内容将会被 json 反序列化为 NetConf (https://github.com/cilium/cilium/blob/master/plugins/cilium-cni/types/types.go#L22-L33)对象,cni 代码中 args.StdinData 参数(https://github.com/cilium/cilium/blob/master/plugins/cilium-cni/cilium-cni.go#L278) 即为该文件内容。