前面学习了CNI为容器网络创建定义的配置格式及基本的操作执行流程。 本节将从源码加深对CNI接口的理解。

CNI项目https://github.com/containernetworking/cnilibcni下包含CNI接口,供容器运行时调用,转发调用具体的CNI插件。 libcni.CNI就是具体的go语言的接口,源码如下:

 1type CNI interface {
 2	AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
 3	CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
 4	DelNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
 5	GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
 6	GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
 7
 8	AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
 9	CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
10	DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
11	GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
12	GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
13
14	ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)
15	ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error)
16}
  • ValidateNetworkListValidateNetwork主要用来对CNI网络配置列表和CNI网络配置进行校验。
  • AddNetworkListCheckNetworkListDelNetworkList通过网络配置列表调用一组插件将容器添加到网络、检查网络、从网络中删除
  • AddNetworkCheckNetworkDelNetwork通过网络配置列表调用一个插件将容器添加到网络、检查网络、从网络中删除

libcni.CNI接口的各个Add, Del, Check方法中都有一个RuntimeConf参数,包含了一次CNI调用中,除了网络配置之外的参数信息。通常由容器运行时根据上下文来创建,下面是RuntimeConf这个struct的源码:

 1// A RuntimeConf holds the arguments to one invocation of a CNI plugin
 2// excepting the network configuration, with the nested exception that
 3// the `runtimeConfig` from the network configuration is included
 4// here.
 5type RuntimeConf struct {
 6	ContainerID string
 7	NetNS       string
 8	IfName      string
 9	Args        [][2]string
10	// A dictionary of capability-specific data passed by the runtime
11	// to plugins as top-level keys in the 'runtimeConfig' dictionary
12	// of the plugin's stdin data.  libcni will ensure that only keys
13	// in this map which match the capabilities of the plugin are passed
14	// to the plugin
15	CapabilityArgs map[string]interface{}
16
17	// DEPRECATED. Will be removed in a future release.
18	CacheDir string
19}

NetworkConfigNetworkConfigList两个struct对应CNI规范中的网络配置和网络配置列表,每个NetworkConfig对应一个插件Plugin,源码如下:

 1type NetworkConfig struct {
 2	Network *types.NetConf
 3	Bytes   []byte
 4}
 5
 6type NetworkConfigList struct {
 7	Name         string
 8	CNIVersion   string
 9	DisableCheck bool
10	Plugins      []*NetworkConfig
11	Bytes        []byte
12}

每个NetworkConfig对应一个Plugin实现了libcni.CNI接口,所以容器运行时调用接口时会转发到具体的插件(二进制执行)。 这个可以从容器运行时containerd的源码中找到踪迹,containerd的源码依赖它自己封装的https://github.com/containerd/go-cni, 在这个项目中https://github.com/containerd/go-cni/blob/main/namespace.go中直接对AddNetworkListCheckNetworkList的代码调用。

每个具体的CNI插件开发通过依赖pkg/skel, pkg/invoke等包提供的骨架代码实现其功能。每个CNI插件必须实现为一个可执行文件,由容器管理系统或容器运行时调用。插件负责将一个网络接口插入到容器网络名称空间(例如,veth对的一端),并在主机上做任何必要的配置(例如,将veth的另一端连接到网桥bridge上)。然后,CNI插件还应该为网络接口分配IP,并通过调用合适的IPAM插件来设置与IP地址管理部分一致的路由规则。

通过前面的分析可知,容器运行时、容器管理系统、cnitool内部实现上会依赖libcni,直接调用libcni的接口执行add, check, delete操作,libcni会把操作转发到具体的插件二进制文件执行上。

参考