欢迎关注微信公众号*“云原生手记”***
文章目录
源码地址:https://github.com/k8snetworkplumbingwg/multus-cni.git
源码解析
解析之前,先看下机器上的/opt/cni/bin目录下的可执行文件,这些文件都有可能被cni插件调用:
[root@zhounanjun-test01 ~]# ls /opt/cni/bin/
bandwidth calico dhcp flannel host-local kube-ovn macvlan portmap sbr static vlan
bridge calico-ipam firewall host-device ipvlan loopback multus ptp sriov tuning whereabouts
这些 CNI 的基础可执行文件,按照功能可以分为以下三类:
- 第一类,叫做 Main 插件,它是用来创建具体网络设备的二进制文件。比如,bridge(网桥设备)、ipvlan、loopback(lo设备)、macvlan、ptp(Veth Pari 设备)、以及 vlan。
- 第二类,叫做 IPAM(IP Address Management)插件,它是负责分配 IP 地址的二进制文件。比如
- dhcp 会向 DHCP 服务器发起请求;
- host-local 会使用预先配置的 IP 地址段来进行分配;
- calico-ipam是clalico cni自己的ip地址分配插件,是一种集中式ip分配插件;
- whereabouts也是一个集中式ip分配插件,用的比较少,我也是因为使用了sriov设备才用到的,所以节点上有这个文件, 这个是k8snetworkplumbingwg社区开源的。
- 第三类,是由 CNI 社区维护的内置 CNI 插件,比如
- flannel,这就是专门为 Flannel 项目提供的 CNI 插件;
- tunning,是一个通过 sysctl 调整网络设备参数的二进制文件;
- portmap 是一个通过 iptables 配置端口映射的二进制文件;
- bandwidth 是一个使用 Token Bucket Filter(TBF)来进行限流的二进制文件;
- calico是专门为Calico项目提供的CNI插件。
cmdAdd解析
源码解析的过程,也就是实现cni插件的过程,希望读者在本文解析后可以实现自己的cni插件。
multus-cni插件的启动代码在cmd/main.go文件中
main函数如下:
func main() {
// Init command line flags to clear vendored packages' one, especially in init()
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
// add version flag
versionOpt := false
flag.BoolVar(&versionOpt, "version", false, "Show application version")
flag.BoolVar(&versionOpt, "v", false, "Show application version")
flag.Parse()
if versionOpt == true {
fmt.Printf("%s\n", multus.PrintVersionString())
return
}
skel.PluginMain( // 实现cni插件的三个函数cmdAdd, cmdCheck, cmdDel
func(args *skel.CmdArgs) error {
// 用于创建容器时构建网络环境
result, err := multus.CmdAdd(args, nil, nil)
if err != nil {
return err
}
return result.Print()
},
func(args *skel.CmdArgs) error {
return multus.CmdCheck(args, nil, nil)
},
// 用于删除容器时回收 网络环境
func(args *skel.CmdArgs) error { return multus.CmdDel(args, nil, nil) },
cniversion.All, "meta-plugin that delegates to other CNI plugins")
}
skel.PluginMain中传入的三个函数类型,就是cmdAdd, cmdCheck, cmdDel函数。那么先看cmdAdd函数,就是下面这段:
func(args *skel.CmdArgs) error {
// 用于创建容器时构建网络环境
result, err := multus.CmdAdd(args, nil, nil)
if err != nil {
return err
}
return result.Print()
}
调用的是multus.CmdAdd,multus.CmdAdd函数的内容如下,稍加缩减:
func CmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) (cnitypes.Result, error) {
// 是/etc/cni/net.d/00-multus.conf文件的配置
n, err := types.LoadNetConf(args.StdinData) // 从stdinData中反序列化配置,
logging.Debugf("CmdAdd: %v, %v, %v", args, exec, kubeClient)
if err != nil {
return nil, cmdErr(nil, "error loading netconf: %v", err)
}
// 生成client
kubeClient, err = k8s.GetK8sClient(n.Kubeconfig, kubeClient)
if err != nil {
return nil, cmdErr(nil, "error getting k8s client: %v", err)
}
k8sArgs, err := k8s.GetK8sArgs(args) // 参数中包含的是kubelet传递过来的pod信息,比如pod名称,pod所属的namespace等信息
if err != nil {
return nil, cmdErr(nil, "error getting k8s args: %v", err)
}
...
pod, err := getPod(kubeClient, k8sArgs, false) // 从k8s获取pod对象
if err != nil {
return nil, err
}
...
// 获取/etc/cni/net.d/00-multus.conf 配置中的delegates字段数据,该字段中放着的是默认cni插件的配置,比如calico的
// 这个函数深入看下,适用于获取pod要使用的网络插件信息的,可能存在多个网络插件要使用。
_, kc, err := k8s.TryLoadPodDelegates(pod, n, kubeClient, resourceMap) // n中的Delegates数组已经更新完毕
if err != nil {
return nil, cmdErr(k8sArgs, "error loading k8s delegates k8s args: %v", err)
}
// cache the multus config 将Delegates数组内容保存在CNIDir中,
// CNIDir默认路径是:/var/lib/cni/multus
if err := saveDelegates(args.ContainerID, n.CNIDir, n.Delegates); err != nil {
return nil, cmdErr(k8sArgs, "error saving the delegates: %v", err)
}
var result, tmpResult cnitypes.Result
var netStatus []nettypes.NetworkStatus
cniArgs := os.Getenv("CNI_ARGS")
for idx, delegate := ran