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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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)
}
  • ValidateNetworkListValidateNetwork主要用来对CNI网络配置列表和CNI网络配置进行校验。
  • AddNetworkListCheckNetworkListDelNetworkList通过网络配置列表调用一组插件将容器添加到网络、检查网络、从网络中删除
  • AddNetworkCheckNetworkDelNetwork通过网络配置列表调用一个插件将容器添加到网络、检查网络、从网络中删除

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

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

	// DEPRECATED. Will be removed in a future release.
	CacheDir string
}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type NetworkConfig struct {
	Network *types.NetConf
	Bytes   []byte
}

type NetworkConfigList struct {
	Name         string
	CNIVersion   string
	DisableCheck bool
	Plugins      []*NetworkConfig
	Bytes        []byte
}

每个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会把操作转发到具体的插件二进制文件执行上。

参考