认识一下容器网络接口 CNI
写在最前,周末写到这篇的时候我就发现可能是给自己挖了很大的坑,整个 Kubernetes 网关相关的内容会非常复杂且庞大。
- 深入探索 Kubernetes 网络模型和网络通信
- 认识一下容器网络接口 CNI(本篇)
- 源码分析:从 kubelet、容器运行时看 CNI 的使用
- 从 Flannel 学习 Kubernetes VXLAN 网络
- Kubernetes 网络学习之 Cilium 与 eBPF
- …
看自己能学到哪一步~
在 《深入探索 Kubernetes 网络模型和网络通信》 文章中,我们介绍了网络命名空间(network namespace) 如何在 Kubernetes 网络模型中工作,通过示例分析 pod 间的流量传输路径。整个传输过程需要各种不同组件的参与才完成,而这些组件与 pod 相同的生命周期,跟随 pod 的创建和销毁。容器的维护由 kubelet 委托给容器运行时(container runtime)来完成,而容器的网络命名空间则是由容器运行时委托网络插件共同完成。
- 创建 pod(容器)的网络命名空间
- 创建接口
- 创建 veth 对
- 设置命名空间网络
- 设置静态路由
- 配置以太网桥接器
- 分配 IP 地址
- 创建 NAT 规则
- …
上篇我们也提到不同网络插件对 Kubernetes 网络模型有不同的实现,主要集中在跨节点的 pod 间通信的实现上。用户可以根据需要选择合适的网络插件,这其中离不开 CNI(container network interface)。这些网络插件都实现了 CNI 标准,可以与容器编排系统和运行时良好的集成。
CNI 是什么
CNI 是 CNCF 下的一个项目,除了提供了最重要的 规范 、用来 CNI 与应用集成的 库、实行 CNI 插件的 CLI cnitool
,以及 可引用的插件。本文发布时,最新版本为 v1.1.2。
CNI 只关注容器的网络连接以及在容器销毁时清理/释放分配的资源,也正因为这个,即使容器发展迅速,CNI 也依然能保证简单并被 广泛支持。
CNI 规范
CNI 的规范涵盖了以下几部分:
- 网络配置文件格式
- 容器运行时与网络插件交互的协议
- 插件的执行流程
- 将委托其他插件的执行流程
- 返回给运行时的执行结果数据类型
1. 网络配置格式
这里贴出规范中的配置示例,规范 中定义了网络配置的格式,包括必须字段、可选字段以及各个字段的功能。示例使用定义了名为 dbnet
的网络,配置了插件 bridge
和 tuning
,这两个插件。
CNI 的插件一般分为两种:
- 接口插件(interface plugin):用来创建网络接口,比如示例中的
bridge
。 - 链式插件(chained):用来调整已创建好的网络接口,比如示例中的
tuning
。
{
"cniVersion": "1.0.0",
"name": "dbnet",
"plugins": [
{
"type": "bridge",
// plugin specific parameters
"bridge": "cni0",
"keyA": ["some more", "plugin specific", "configuration"],
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1",
"routes": [
{"dst": "0.0.0.0/0"}
]
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
},
{
"type": "tuning",
"capabilities": {
"mac": true
},
"sysctl": {
"net.core.somaxconn": "500"
}
},
{
"type": "portmap",
"capabilities": {"portMappings": true}
}
]
}
2. 容器运行时与网络插件交互的协议
CNI 为容器运行时提供 四个不同的操作:
- ADD - 将容器添加到网络,或修改配置
- DEL - 从网络中删除容器,或取消修改
- CHECK - 检查容器网络是否正常,如果容器的网络出现问题,则返回错误
- VERSION - 显示插件的版本
规范对操作的输入和输出内容进行了定义。主要几个核心的字段有:
CNI_COMMAND
:上面的四个操作之一CNI_CONTAINERID
:容器 IDCNI_NETNS
:容器的隔离域,如果用的网络命名空间,这里的值是网络命名空间的地址CNI_IFNAME
:要在容器中创建的接口名,比如eth0
CNI_ARGS
:执行参数时传递的参数CNI_PATH
:插件可执行文件的朝招路径
3. 插件的执行流程
CNI 将容器上网络配置的 ADD
、DELETE
和 CHECK
操作,成为附加(attachment)。
容器网络配置的操作,需要一个或多个插件的共同操作来完成,因此插件有一定的执行顺序。比如前面的示例配置中,要先创建接口,才能对接口进行调优。
拿 ADD
操作为例,首先执行的一般是 interface plugin
,然后在执行 chained plugin
。以前一个插件的 输出 PrevResult
与下一个插件的配置会共同作为下一个插件的 输入。 如果是第一个插件,会将网络配置作为输入的一部分。插件可以将前一个插件的 PrevResult
最为自己的输出,也可以结合自身的操作对 PrevResult
进行更新。最后一个插件的输出 PrevResult
作为 CNI 的执行结果返回给容器运行时,容器运行时会保存改结果并将其作为其他操作的输入。
DELETE
的执行与 ADD
的顺序正好相反,要先移除接口上的配置或者释放已经分配的 IP,最后才能删除容器网络接口。DELETE
操作的输入就是容器运行时保存的 ADD
操作的结果。
除了定义单次操作中插件的执行顺序,CNI 还对操作的并行操作、重复操作等进行了说明。
4. 插件委托
有一些操作,无论出于何种原因,都不能合理地作为一个松散的链接插件来实现。相反,CNI 插件可能希望将某些功能委托给另一个插件。一个常见的例子是 IP 地址管理(IP Adress Management,简称 IPAM),主要是为容器接口分配/回收 IP 地址、管理路由等。
CNI 定义了第三种插件 – IPAM 插件。CNI 插件可以在恰当的时机调用 IPAM 插件,IPAM 插件会将执行的结果返回给委托方。IPAM 插件会根据指定的协议(如 dhcp)、本地文件中的数据、或者网络配置文件中 ipam
字段的信息来完成操作:分配 IP、设置网关、路由等等。
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1",
"routes": [
{"dst": "0.0.0.0/0"}
]
}
5. 执行结果
插件可以返回一下三种结果之一,规范对 结果的格式 进行了定义。
- Success:同时会包含
PrevResult
信息,比如ADD
操作后的PrevResult
返回给容器运行时。 - Error:包含必要的错误提示信息。
- Version:这个是
VERSION
操作的返回结果。
库
CNI 的库是指 libcni
,用于 CNI 和应用程序集成,定义了 CNI 相关的接口和配置。
type CNI interface {
AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
DelNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)
ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error)
}
以添加网络的部分代码为例:
func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
...
return invoke.ExecPluginWithResult(ctx, pluginPath, newConf.Bytes, c.args("ADD", rt), c.exec)
}
执行的逻辑简单来说就是:
- 查找可执行文件
- 加载网络配置
- 执行
ADD
操作 - 结果处理
总结
这篇学习了 CNI 规范的内容、网络插件的执行流程,对 CNI 抽象的网络管理接口有了大致的了解。
下一篇将结合源码的分析,了解 kubelet、容器运行时、CNI 网络插件之间如何进行交互。