Kubernetes 学习笔记之 Calico CNI Plugin 源码解析 (二)
Overview
calico 插件代码仓库在 projectcalico/cni-plugin(https://github.com/projectcalico/cni-plugin/blob/release-v3.17/cmd/calico/calico.go#L29-L45) ,并且会编译两个二进制文件:calico 和 calico-ipam,其中 calico 会为 sandbox container 创建 route 和虚拟网卡 virtual interface,以及 veth pair 等网络资源,并且会把相关数据写入 calico datastore 数据库里;calico-ipam 会为当前 pod 从当前节点的 pod 网段内分配 ip 地址,当然当前节点还没有 pod 网段,会从集群网段 cluster cidr 中先分配出该节点的 pod cidr,并把相关数据写入 calico datastore 数据库里,这里 cluster cidr 是用户自己定义的,已经提前写入了 calico datastore,并且从 cluster cidr 中划分的 block size 也是可以自定义的(新版本 calico/node 容器可以支持自定义,老版本 calico 不支持),可以参考官网文档 change-block-size(https://docs.projectcalico.org/networking/change-block-size#concepts)。接下来重点看下 calico 二进制插件具体是如何工作的,后续再看 calico-ipam 二进制插件如何分配 ip 地址的。
calico plugin 源码解析
calico 插件是遵循 cni 标准接口,实现了 ADD 和 DEL 命令,这里重点看看 ADD 命令时如何实现的。calico 首先会注册 ADD 和 DEL 命令,代码在 L614-L677(https://github.com/projectcalico/cni-plugin/blob/release-v3.17/pkg/plugin/plugin.go#L614-L677):
ADD 命令里,主要做了三个逻辑:
查询 calico datastore 里有没有 WorkloadEndpoint 对象和当前的 pod 名字匹配,没有匹配,则会创建新的 WorkloadEndpoint 对象,该对象内主要保存该 pod 在 host network namespace 内的网卡名字和 pod ip 地址,以及 container network namespace 的网卡名字等等信息,对象示例如下。
创建一个 veth pair,并把其中一个网卡置于宿主机端网络命名空间,另一个置于容器端网络命名空间。在 container network namespace 内创建网卡如 eth0,并通过调用 calico-ipam 获得的 IP 地址赋值给该 eth0 网卡;在 host network namespace 内创建网卡,网卡名格式为 "cali" + sha1(namespace.pod)[:11] ,并设置 MAC 地址"ee:ee:ee:ee:ee:ee"。
在容器端和宿主机端创建路由。在容器端,设置默认网关为 169.254.1.1 ,该网关地址代码写死的;在宿主机端,添加路由如 10.217.120.85 dev calid0bda9976d5 scope link ,其中 10.217.120.85 是 pod ip 地址,calid0bda9976d5 是该 pod 在宿主机端的网卡,也就是 veth pair 在宿主机这端的 virtual ethernet interface 虚拟网络设备。
一个 WorkloadEndpoint 对象示例如下,一个 k8s pod 对象对应着 calico 中的一个 workloadendpoint 对象,可以通过 calicoctl get wep -o wide 查看所有 workloadendpoint。记得配置 calico datastore 为 kubernetes 的,为方便可以在 ~/.zshrc 里配置环境变量:
根据以上三个主要逻辑,看下 cmdAdd(https://github.com/projectcalico/cni-plugin/blob/release-v3.17/pkg/plugin/plugin.go#L105-L494)函数代码:
以上 cmdAdd()函数基本结构符合 cni 标准里的函数结构,最后会把结果打印到 stdout。看下 k8s.CmdAddK8s()(https://github.com/projectcalico/cni-plugin/blob/release-v3.17/pkg/k8s/k8s.go#L48-L482) 函数的主要逻辑:
以上代码最后会创建个 workloadendpoint 对象,同时 DoNetworking()函数很重要,这个函数里会创建路由和 veth pair。然后看下 linuxDataplane 对象的 DoNetworking()(https://github.com/projectcalico/cni-plugin/blob/release-v3.17/pkg/dataplane/linux/dataplane_linux.go#L52-L352) 函数,是如何创建 veth pair 和 routes 的。这里主要调用了 github.com/vishvananda/netlink golang 包来增删改查网卡和路由等操作,等同于执行 ip link add/delete/set xxx 等命令,该 golang 包也是个很好用的包,被很多主要项目如 k8s 项目使用,在学习 linux 网络相关知识时可以利用这个包写一写相关 demo,效率也高很多。这里看看 calico 如何使用 netlink 这个包来创建 routes 和 veth pair 的:
总结
至此,calico 二进制插件就为一个 sandbox container 创建好了网络资源,即创建了一个 veth pair,并分别为宿主机端和容器端网卡设置好对应 MAC 地址,以及为容器段配置好了 IP 地址,同时还在容器端配置好了路由默认网关,以及宿主机端配置好路由,让目标地址是 sandbox container ip 的进入宿主机端 veth pair 网卡,同时还为宿主机端网卡配置 arp proxy 和 packet forwarding 功能,最后,会根据这些网络数据生成一个 workloadendpoint 对象存入 calico datastore 里。
但是,还是缺少了一个关键逻辑,calico-ipam 是如何分配 IP 地址的,后续有空在学习记录。
评论