深圳幻海软件技术有限公司 欢迎您!

认识一下容器网络接口 CNI

2023-02-28

在最前,周末写到这篇的时候我就发现可能是给自己挖了很大的坑,整个Kubernetes网关相关的内容会非常复杂且庞大。深入探索Kubernetes网络模型和网络通信认识一下容器网络接口CNI(本篇)源码分析:从kubelet、容器运行时看CNI的使用从Flannel学习KubernetesVXLAN网

在最前,周末写到这篇的时候我就发现可能是给自己挖了很大的坑,整个 Kubernetes 网关相关的内容会非常复杂且庞大。

深入探索 Kubernetes 网络模型和网络通信

认识一下容器网络接口 CNI(本篇)

源码分析:从 kubelet、容器运行时看 CNI 的使用

从 Flannel 学习 Kubernetes VXLAN 网络

Cilium CNI 与 eBPF

...

看自己能学到哪一步~


在 《深入探索 Kubernetes 网络模型和网络通信》 文章中,我们介绍了网络命名空间(network namespace) 如何在 Kubernetes 网络模型中工作,通过示例分析 pod 间的流量传输路径。整个传输过程需要各种不同组件的参与才完成,而这些组件与 pod 相同的生命周期,跟随 pod 的创建和销毁。容器的维护由 kubelet 委托给容器运行时(container runtime)来完成,而容器的网络命名空间则是由容器运行时委托网络插件完成。

创建 pod(容器)的网络命名空间

创建接口

创建 veth 对

设置命名空间网络

设置静态路由

配置以太网桥接器

分配 IP 地址

创建 NAT 规则

...

上篇我们也提到不同网络插件对 Kubernetes 网络模型有不同的实现,主要集中在跨节点的 pod 间通信的实现上。用户可以根据需要选择合适的网络插件,这其中离不开 CNI(container network interface)。这些网络插件都实现了 CNI 标准,可以与容器编排系统和运行时良好的集成。

runtime-with-cni

CNI 是什么

CNI 是 CNCF 下的一个项目,除了提供了最重要的 规范[1] 、用来 CNI 与应用集成的 库[2]、实行 CNI 插件的 CLI `cnitool`[3],以及 可引用的插件[4]。本文发布时,最新版本为 v1.1.2。

CNI 只关注容器的网络连接以及在容器销毁时清理/释放分配的资源,也正因为这个,即使容器发展迅速,CNI 也依然能保证简单并被 广泛支持[5]。

CNI 规范

CNI 的规范涵盖了以下几部分:

网络配置文件格式

容器运行时与网络插件交互的协议

插件的执行流程

将委托其他插件的执行流程

返回给运行时的执行结果数据类型

1. 网络配置格式

这里贴出规范中的配置示例,规范[6] 中定义了网络配置的格式,包括必须字段、可选字段以及各个字段的功能。示例使用定义了名为 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}
    }
  ]
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.

2. 容器运行时与网络插件交互的协议

CNI 为容器运行时提供 四个不同的操作[7]:

ADD - 将容器添加到网络,或修改配置

DEL - 从网络中删除容器,或取消修改

CHECK - 检查容器网络是否正常,如果容器的网络出现问题,则返回错误

VERSION - 显示插件的版本

规范对操作的输入和输出内容进行了定义。主要几个核心的字段有:

CNI_COMMAND:上面的四个操作之一

CNI_CONTAINERID:容器 ID

CNI_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-plugin-execution-flow

除了定义单次操作中插件的执行顺序,CNI 还对操作的并行操作、重复操作等进行了说明[8]。

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"}
  ]
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

5. 执行结果

插件可以返回一下三种结果之一,规范对 结果的格式[9] 进行了定义。

Success:同时会包含PrevResult​ 信息,比如 ADD​ 操作后的 PrevResult 返回给容器运行时。

Error:包含必要的错误提示信息。

Version:这个是VERSION 操作的返回结果。

CNI 的库是指 `libcni`[10],用于 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)  
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

以添加网络的部分代码为例:

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)  
}
  • 1.
  • 2.
  • 3.
  • 4.

执行的逻辑简单来说就是:

查找可执行文件

加载网络配置

执行ADD 操作

结果处理

总结

这篇学习了 CNI 规范的内容、网络插件的执行流程,对 CNI 抽象的网络管理接口有了大致的了解。

下一篇将结合源码的分析,了解 kubelet、容器运行时、CNI 网络插件之间如何进行交互。

参考

https://www.tigera.io/learn/guides/kubernetes-networking/kubernetes-cni/

https://github.com/containernetworking/cni

https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/

参考资料

[1] 

规范: https://github.com/containernetworking/cni/blob/main/SPEC.md

[2] 

库: https://github.com/containernetworking/cni/blob/main/libcni

[3] 

cnitool​: https://github.com/containernetworking/cni/blob/main/cnitool

[4] 

可引用的插件: https://github.com/containernetworking/plugins

[5] 

广泛支持: https://github.com/containernetworking/cni/blob/main/README.md#who-is-using-cni

[6] 

规范: https://github.com/containernetworking/cni/blob/main/SPEC.md#section-1-network-configuration-format

[7] 

四个不同的操作: https://github.com/containernetworking/cni/blob/master/SPEC.md#cni-operations

[8] 

CNI 还对操作的并行操作、重复操作等进行了说明: https://github.com/containernetworking/cni/blob/main/SPEC.md#lifecycle--ordering

[9] 

结果的格式: https://github.com/containernetworking/cni/blob/main/SPEC.md#section-5-result-types

[10] 

libcni​: https://github.com/containernetworking/cni/tree/main/libcni