3 分钟带你搞定 Kubernetes CNI 插件开发
本文介绍 CNI 插件调用的时机、CNI 插件配置的读取,以及 CNI 插件的调用、执行。读完此文,您将清楚 CNI 插件的运行机制、调用细节,并能够自信地写出一个简单的 CNI 插件。CRI:containerdCNI plugin:flannelCNI 的调用时机源码位置:containerd
本文介绍 CNI 插件调用的时机、CNI 插件配置的读取,以及 CNI 插件的调用、执行。读完此文,您将清楚 CNI 插件的运行机制、调用细节,并能够自信地写出一个简单的 CNI 插件。
- CRI:containerd
- CNI plugin:flannel
CNI 的调用时机
源码位置:containerd - pkg/cri/server/service.go#L61CRI 的RunPodSandbox()实现中。
RunPodSandbox的启动流程
- 生成 container id,名称;
- 确保 Infra 容器用的镜像存在,不存在的话就拉取;
- 确定 ociRuntime,比如 runC;
- 创建 Infra 容器将要托管的网络;
- 按照 ociRuntime 规范创建所要求的 spec;
- 生成 spec opts;
- 创建容器;
- 基于容器配置来创建 Task
- 启动 Task;
因此,Network Namespace 的创建,早于容器的创建。
CNI 配置文件的读取
源码位置:containerd - pkg/cri/server/service.go#L149先在 containerd 的配置文件中,配置 CNI 的配置路径,如下。
[plugins."io.containerd.grpc.v1.cri".cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
在 containerd 启动时读取该目录下的配置文件,封装成一个网络插件,最终保存在 libcni 里。
type libcni struct {
config
cniConfig cnilibrary.CNI
networkCount int // minimum network plugin configurations needed to initialize cni
networks []*Network
sync.RWMutex
}
type Network struct {
cni cnilibrary.CNI
config *cnilibrary.NetworkConfigList
ifName string
}
libcni 中有个 Network 数组,通常这个数组有两个元素,最终分别用来设置 loopback、eth0这两个网卡。
总结一下这里的过程,可以概述为以下 3 点:
containerd 会读取 conf_dir 下的内容,生成默认的网络插件(还可以添加其他路径,来生成其他网络插件);
如果 conf_dir 下面有 N 个配置文件,会生成 N + 1 个 Network;
Network 会生成相应的网络设备,默认名称为 eth0、eth1...。
CNI 配置文件示例 (flannel)
在 K8s 节点上的路径:/etc/cni/net.d/10-flannel.conflist
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
CNI 插件的调用
源码位置:containerd - pkg/cri/server/sandbox_run.go#L377总体流程
获取网络插件;
调用网络插件(libcni)的 Setup() 方法。
创建 namespace;
创建网络设备;
遍历所有的网络 Network;
- lo
- eth0
调用 CNI 接口 AddNetworkList。
遍历该 Network 中所有的 Plugins
- flannel
- portmap
- 检查 plugin 是否存在。(默认:
/opt/cni/bin/{type}) - 校验参数
- 执行插件命令。(环境变量
CNI_COMMAND值为ADD)
整理创建结果。
整理结果;
给 CNI 插件所传环境变量的分类:
CNI 保留字段
- CNI_COMMAND
- CNI_CONTAINERID
- CNI_NETNS
- CNI_ARGS
CNI 自定义参数由 map 转成 string 后的字符串 - CNI_IFNAME
- CNI_PATH
CNI 自定义参数
调用 CNI 插件时,详细的传参如下:
exec.ExecPlugin(ctx, pluginPath, netconf, args.AsEnv())
pluginPath插件的完整路径;netconfplugin 配置;args.AsEnv()将所有上述环境变量转换成 key=value 后得到的数组。
CNI 插件的执行
项目地址:github.com/flannel-io/cni-plugin插件作为二进制直接被调用,执行时,根据 CNI_COMMAND 取值不同,走不同的处理流程(由 switch 分发逻辑)。还有一套对这种操作的封装,只需提供 ADD、DEL、CHECK 的实现函数。
ADD 操作
总结为两个步骤:
- 填充
delegate字段的参数; - 以填充后的
delegate字段作为参数去调用其他的组件,来完成目标。
此时,插件拿到的网络配置数据
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
}
设置 delegate 将要调用的命令为 bridge,插件填充完数据之后,得到的 delegate 字段如下:
{
"cniVersion": "0.3.1",
"hairpinMode": true,
"ipMasq": false,
"ipam": {
"ranges": [
[
{
"subnet": "10.244.1.0/24"
}
]
],
"routes": [
{
"dst": "10.244.0.0/16"
}
],
"type": "host-local"
},
"isDefaultGateway": true,
"isGateway": true,
"mtu": 1450,
"name": "cbr0",
"type": "bridge"
}
调用其他组件来完成配置
ExecPluginWithResult(ctx, pluginPath, netconf, delegateArgs("ADD"), realExec)