赵占旭的博客

CNI初识

本篇主要是简单了解CNI,代码链接在此

概览

CNI(Container Network Interface)是用来配置Linux容器中的网络接口,它包含了一套规范和一些列的库文件,可以通过各种插件实现所需要的功能。

首先是规范和库文件,他是为了使所有的插件都能统一API,保证了插件的规范性。
接下来就是插件了,这个一般是我们最关注的,能够根据我们的需求实现容器网络的管理。官方文档列出了一系列的第三方插件,实现了不少场景下的容器网络管理,这里我们不列出了,有兴趣可以自己去看。

规范

规范文档在此

我们以版本0.4.0为准熟悉一下。该版本引入了CHECK命令和在DEL命令中添加prevResult的参数传入。

有以下几点需要注意(我比较关注的):

  • 不能为同一容器调用并行操作,但允许为不同的容器调用并行操作。
  • 必须要有ADD和DEL操作,ADD总是最后跟随相应的DEL。 DEL可以跟随其他DEL,但插件应该允许多个DEL。
  • ContainerID唯一标识。存储状态的插件应使用关键字(网络名称,CNI_CONTAINERID,CNI_IFNAME)执行此操作。
  • 对于相同的(网络名称,容器ID,容器内的接口名称),不能再调用ADD两次(没有相应的DEL)。
  • 除了标记可选的,CNI结构中的字段(如网络配置和CNI插件结果)是必需的。

CNI插件

CNI插件都必须是可执行文件,它负责将网络接口插入容器namespace中并在主机上进行一些操作(比如将veth pair的一端插入namespace,并且将另一端连接到bridge),然后调用IPAM插件将IP分配给容器接口,并且配置IP地址相关的路由等信息。

插件参数

之前说过必须支持的操作有ADD和DEL,接下来看一下ADD和DEL分别支持的参数。

  • ADD:给容器添加网络
    • 参数:
      • Container ID,运行容器时分配的唯一明文ID,不能为空。
      • Network namespace path,表示要添加的容器的网络namespace路径,比如/proc/$pid/ns/net或者bind-mount/link到namespace路径的。
      • Network configuration,这是一个json格式的文档,描述网络信息。
      • Extra arguments,提供了替代机制,可以为每个容器进行简单配置。
      • Name of the interface inside the container,容器中的网口名称。
    • Result
      • Interfaces list,容器内的端口和宿主机的端口列表以及端口的硬件地址等信息
      • IP configuration assigned to each interface,容器和宿主机的端口上配置的IP地址,网关和路由信息。
      • DNS information,nameservers、domain、search domains等DNS信息
  • DEL:删除网络
    • 参数
      • Container ID,同上。
      • Network namespace path,同上。
      • Network configuration,同上。
      • Extra arguments,同上。
      • Name of the interface inside the container,同上。
    • DEL操作的所有参数都必须与ADD相同。
    • DEL操作应该释放containerid配置的所有网络资源。
    • 如果有一个已知的ADD操作,那么执行DEL操作时,必须将之前ADD的返回值放入prevResult的字段,期望使用libcni支持缓存Result。
    • 当没有提供CNI_NETNSprevResult的话,插件应该尽量清除所有的资源(比如释放IPAM的所有申请资源)并且返回成功。
    • 如果缓存了之前ADD操作的返回值Result,那么执行DEL操作时也要删除缓存的Result。

即使缺少一些资源,插件通常也应该完成DEL操作,而不会出错。
例如,IPAM插件通常应该释放IP并返回成功,即使容器namespace不存在。
再举一个例子,即使容器namespace或容器网络接口不存在,bridge插件也应该将DEL操作委托给IPAM插件并清理自己的资源。

接下来再看一下可选项CHECK和VERSION

  • CHECK:检测容器的网络配置是否达预期
    • 参数
      • Container ID,和ADD一致。
      • Network namespace path,和ADD一致。
      • Network configuration,和ADD一致。必须要有prevResult字段。
      • Extra arguments,和ADD一致。
      • Name of the interface inside the container,和ADD一致。
    • Result
      • 插件无返回或者返回错误。
    • 插件比如要有prevResult字段来确定端口和地址是否符合预期。
    • 插件比如允许后续的插件来修改网络资源,比如route。
    • 如果一个资源包含了如下CNI的返回类型(interface, address or route),那么插件应该返回一个错误
      • 已经被插件创建了
      • 已经在prevResult
      • 不存在或者无效状态
    • 如果其他资源没有检测到如下的Result类型,或者处于无效状态,那么插件也应该返回一个错误
      • Firewall rules
      • Traffic shaping controls
      • IP reservations
      • External dependencies such as a daemon required for connectivity
    • 如果插件知道容器无法访问的情况,则应该返回错误。
    • 插件必须处理在ADD之后立即调用的CHECK,因此应该允许任何异步资源的合理收敛延迟。
    • 该插件应该在任何委派的(例如IPAM)插件上调用CHECK,并将任何错误传递给其调用者。
    • 运行时不能为尚未添加ADD的容器调用CHECK,或者在上一次ADD之后已经被DEL。
    • 如果在配置列表中将disableCheck设置为true,则运行时不能调用。
  • VERSION: Report version
    • Parameters: NONE
    • Result: CNI当前的版本以及支持的版本。

运行时必须使用网络类型作为要调用的可执行文件的名称(比如bridge或者sriov)。运行时应在预定义目录列表中查找此可执行文件。一旦找到,它必须使用以下环境变量调用可执行文件以进行参数传递:

  • CNI_COMMAND:表示所需的操作:ADD,DEL,CHECK或VERSION。
  • CNI_CONTAINERID:容器ID。
  • CNI_NETNS:网络命名空间文件的路径。
  • CNI_IFNAME:要设置的接口名称,如果插件无法使用此接口名称,则必须返回错误。
  • CNI_ARGS:用户在调用时传入的额外参数。由分号分隔的key-value,例如”FOO = BAR; ABC = 123”。
  • CNI_PATH:搜索CNI插件可执行文件的路径列表。路径由特定于OS的列表分隔符分隔,例如Linux上的’:’和Windows上的’;’。

必须通过stdin将JSON格式的网络配置流式传输到插件。这意味着它不依赖于磁盘上的特定文件,并且可能包含在调用之间更改的信息。

返回值

请注意,IPAM插件应返回缩写的Result结构,如IP分配中所述。

插件必须指示成功,返回代码为零,并且在ADD命令的情况下,将以下JSON打印到stdout。 ips和dns项应该与IPAM插件返回的输出相同,但插件应适当填写接口索引,因为IPAM插件应该不知道IPAM插件输出接口。

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
{
"cniVersion": "0.4.0",
"interfaces": [ (this key omitted by IPAM plugins)
{
"name": "<name>",
"mac": "<MAC address>", (required if L2 addresses are meaningful)
"sandbox": "<netns path or hypervisor identifier>" (required for container/hypervisor interfaces, empty/omitted for host interfaces)
}
],
"ips": [
{
"version": "<4-or-6>",
"address": "<ip-and-prefix-in-CIDR>",
"gateway": "<ip-address-of-the-gateway>", (optional)
"interface": <numeric index into 'interfaces' list>
},
...
],
"routes": [ (optional)
{
"dst": "<ip-and-prefix-in-cidr>",
"gw": "<ip-of-next-hop>" (optional)
},
...
]
"dns": { (optional)
"nameservers": <list-of-nameservers> (optional)
"domain": <name-of-local-domain> (optional)
"search": <list-of-additional-search-domains> (optional)
"options": <list-of-options> (optional)
}
}

cniVersion指定插件使用的CNI版本。 插件可能支持多个CNI规范版本(因为它通过VERSION命令报告),此处插件返回的结果中的cniVersion必须与网络配置中指定的cniVersion一致。 如果插件不支持网络配置中的cniVersion,则插件应返回错误代码1。

interfaces描述了插件创建的特定网络接口。 如果存在CNI_IFNAME变量,则插件必须使用该名称作为容器/管理程序接口,否则返回错误。

  • mac:接口的硬件地址。如果L2地址对插件没有意义,则此字段是可选的。
  • sandbox:基于容器/namespace的环境应该返回该sandbox的网络命名空间的完整文件系统路径。必须为创建或移动到sandbox中的接口提供此项。

ips字段是IP配置信息的列表。

routes字段是路由配置信息的列表。

dns字段包含由公共DNS信息组成的字典。

错误必须由非零返回码表示,并且以下JSON将打印到stdout:

1
2
3
4
5
6
{
"cniVersion": "0.4.0",
"code": <numeric-error-code>,
"msg": <short-error-message>,
"details": <long-error-message> (optional)
}

Error 0-99 是预留的错误类型,100以后是可以自定义的。

网络配置

网络配置用json格式描述。配置可以存储在磁盘上,也可以由容器运行时从其他源生成。以下是字段介绍:

  • cniVersion:CNI规范的版本。
  • name:网络名称。应该是唯一的。
  • type:CNI插件可执行文件的文件名。
  • args(可选):容器运行时提供的附加参数。
  • ipMasq(可选):如果插件支持,则在该主机的主机上设置IP伪装。如果主机将充当到无法路由到分配给容器的IP的子网的网关,则必须执行此操作。
  • ipam(可选):具有IPAM特定值的字典。
    • type:指IPAM插件可执行文件的文件名。
  • dns(可选):具有DNS特定值的字典。
    • nameservers(可选):此网络知道的DNS名称服务器的优先级排序列表。列表中的每个条目都是包含IPv4或IPv6地址。
    • domain(可选):用于短主机名查找的本地域。
    • search(可选):用于短主机名查找的优先级排序搜索域列表。大多数解析器都会优先考虑域名。
    • options(可选):可以传递给解析程序的选项列表

插件可以定义它们接受的其他字段,如果使用未知字段调用,则可能会生成错误。例外情况是args字段可用于传递任意数据,如果不识别则忽略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"cniVersion": "0.4.0",
"name": "pci",
"type": "ovs",
// type (plugin) specific
"bridge": "ovs0",
"vxlanID": 42,
"ipam": {
"type": "dhcp",
"routes": [ { "dst": "10.3.0.0/16" }, { "dst": "10.4.0.0/16" } ]
},
// args may be ignored by plugins
"args": {
"labels" : {
"appVersion" : "1.0"
}
}
}

网络配置列表

网络配置列表提供了一种机制,可以按照定义的顺序为单个容器运行多个CNI插件,并将每个插件的结果传递给下一个插件。

该列表以JSON格式描述,可以存储在磁盘上,也可以由容器运行时从其他源生成。字段介绍如下:

  • cniVersion:此配置列表和所有单个配置符合的CNI规范版本。
  • name:网络名称,应该是唯一的。
  • disableCheck:true or false。如果disableCheck为true,则运行时不得为此网络配置列表调用CHECK。
  • plugins:标准CNI网络配置字典列表。

对于ADD操作,还必须在第一个插件之后的任何插件的配置JSON中添加prevResult字段,该插件必须是JSON格式的前一个插件的Result。
对于CHECK和DEL操作,必须将prevResult字段添加到每个插件的配置JSON中,它必须是JSON格式的前一个ADD操作的结果。
对于ADD操作,插件应该将prevResult字段的内容回显到它们的stdout,以允许后续插件接收结果,除非他们希望修改或抑制先前的结果。允许插件修改或禁止全部或部分prevResult。但是,支持包含prevResult字段的CNI规范版本的插件必须通过传递,修改或明确禁止它来处理prevResult。
对于DEL操作,运行时必须以反向顺序执行插件。

当在插件列表上执行操作时发生错误时,必须停止执行列表。

如果ADD操作失败,当运行时决定处理失败时,它应该对列表中的所有插件执行DEL操作(与上面指定的ADD相反的顺序),即使在ADD操作期间未调用某些插件也是如此。

接下来查看一个示例,首先调用tuning的插件,里面包含了ADD操作返回的prevResult字段。然后调用bridge的插件,里面也包含了ADD操作返回的prevResult字段。

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
{
"cniVersion": "0.4.0",
"name": "dbnet",
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
},
"prevResult": {
"ips": [
{
"version": "4",
"address": "10.0.0.5/32",
"interface": 2
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55",
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11",
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue",
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}
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
39
40
41
42
43
44
45
46
47
{
"cniVersion": "0.4.0",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
"args": {
"labels" : {
"appVersion" : "1.0"
}
},
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
},
"prevResult": {
"ips": [
{
"version": "4",
"address": "10.0.0.5/32",
"interface": 2
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55",
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11",
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue",
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}

IP申请

CNI插件为接口分配(和维护)IP地址,然后配置与该接口相关的路由。这为CNI插件提供了极大的灵活性,但也给它带来了很大的负担。许多CNI插件需要具有相同的代码以支持用户想要的若干IP管理方案(例如,dhcp,host-local)。

为了减轻负担并使IP管理策略,我们定义了第二种类型的插件–IP地址管理插件(IPAM)。CNI插件的责任是在适当时刻调用IPAM插件。 IPAM插件必须确定接口IP/子网,网关和路由,并将此信息返回到main插件。 IPAM插件可以通过协议(例如dhcp),存储在本地文件系统上的数据,网络配置文件的ipam部分或上述的组合来获得信息。

###

与CNI插件一样,通过可执行文件来调用IPAM插件。 通过CNI_PATH指示CNI插件。 IPAM插件必须接收传递给CNI插件的所有相同环境变量。 就像CNI插件一样,IPAM插件通过stdin接收网络配置。

必须通过零返回代码指示成功,并将以下JSON打印到stdout(在ADD命令的情况下):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"cniVersion": "0.4.0",
"ips": [
{
"version": "<4-or-6>",
"address": "<ip-and-prefix-in-CIDR>",
"gateway": "<ip-address-of-the-gateway>" (optional)
},
...
],
"routes": [ (optional)
{
"dst": "<ip-and-prefix-in-cidr>",
"gw": "<ip-of-next-hop>" (optional)
},
...
]
"dns": { (optional)
"nameservers": <list-of-nameservers> (optional)
"domain": <name-of-local-domain> (optional)
"search": <list-of-search-domains> (optional)
"options": <list-of-options> (optional)
}
}

请注意,IPAM插件应返回不包含interfaces字段的Result结构,因为IPAM插件应该不知道其父插件配置的接口,除了IPAM特别需要的接口(例如,像dhcp IPAM插件) )。

IPAM插件示例:

  • host-local:在指定范围内选择未使用的(由同一主机上的其他容器)IP。
  • dhcp:使用DHCP协议获取和维护租约。 DHCP请求将通过创建的容器接口发送;因此,相关网络必须支持广播。

一些通用数据结构

IPs

1
2
3
4
5
6
7
8
9
"ips": [
{
"version": "<4-or-6>",
"address": "<ip-and-prefix-in-CIDR>",
"gateway": "<ip-address-of-the-gateway>", (optional)
"interface": <numeric index into 'interfaces' list> (not required for IPAM plugins)
},
...
]

ips字段是由插件确定的IP配置信息的列表。

version:对应于条目中IP地址的IP地址。
address:CIDR表示法中的IP地址(例如192.168.1.3/24)。
gateway:此子网的默认网关。它不指示CNI插件使用此网关添加任何路由:要添加的路由通过routes字段单独指定。使用此值的一个示例是CNI网桥插件将此IP地址添加到Linux网桥以使其成为网关。
interface:CNI插件的接口列表索引结果,指示应该将此IP配置应用于哪个接口。 IPAM插件不应返回此密钥,因为它们没有关于网络接口的信息。

Routes

1
2
3
4
5
6
7
"routes": [
{
"dst": "<ip-and-prefix-in-cidr>",
"gw": "<ip-of-next-hop>" (optional)
},
...
]

DNS

1
2
3
4
5
6
"dns": {
"nameservers": <list-of-nameservers> (optional)
"domain": <name-of-local-domain> (optional)
"search": <list-of-additional-search-domains> (optional)
"options": <list-of-options> (optional)
}

nameservers:此网络知道的DNS名称服务器的优先级排序列表。 列表中的每个条目都是包含IPv4或IPv6地址的字符串。
domain:用于短主机名查找的本地域。
search:用于短主机名查找的优先级排序搜索域列表。 大多数解析器都会优先考虑域名。
options:可以传递给解析程序的选项列表。

常见错误码

除此处指定外,不得使用错误代码1-99。

1 - 不兼容的CNI版本
2 - 网络配置中不支持的字段。 错误消息必须包含不受支持的字段的key和value。
3 - 容器未知或不存在。 此错误意味着运行时不需要执行任何容器网络清理(例如,在容器上调用DEL操作)。
11 - 稍后再试。 如果插件检测到应该清除的某些瞬态条件,它可以使用此代码通知运行时它应该稍后重新尝试操作。

库文件

使用

CNI规范与语言无关,但是目前实现中使用了Golang,参考插件我们后续再看。

我们这里跳过安装cni和插件,直接看看怎么使用:

首先我们要有插件对应的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cat >/etc/cni/net.d/10-mynet.conf <<EOF
{
"cniVersion": "0.2.0", //cni规范版本
"name": "mynet", //插件名称
"type": "bridge", //类型
"bridge": "cni0", //bridge名称
"isGateway": true, //是否网关
"ipMasq": true, //防火墙
"ipam": {
"type": "host-local", //ipam类型
"subnet": "10.22.0.0/16", //子网
"routes": [
{ "dst": "0.0.0.0/0" } //网关
]
}
}

接下来编译插件

1
2
$ cd $GOPATH/src/github.com/containernetworking/plugins
$ ./build_linux.sh # or build_windows.sh

最后在该网络中运行ifconfig的操作,其中用到的脚本可以看下源码,没有难度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ CNI_PATH=$GOPATH/src/github.com/containernetworking/plugins/bin
$ cd $GOPATH/src/github.com/containernetworking/cni/scripts
$ sudo CNI_PATH=$CNI_PATH ./priv-net-run.sh ifconfig
eth0 Link encap:Ethernet HWaddr f2:c2:6f:54:b8:2b
inet addr:10.22.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::f0c2:6fff:fe54:b82b/64 Scope:Link
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:1 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:1 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:90 (90.0 B) TX bytes:0 (0.0 B)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

在容器中运行ifconfig的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ CNI_PATH=$GOPATH/src/github.com/containernetworking/plugins/bin
$ cd $GOPATH/src/github.com/containernetworking/cni/scripts
$ sudo CNI_PATH=$CNI_PATH ./docker-run.sh --rm busybox:latest ifconfig
eth0 Link encap:Ethernet HWaddr fa:60:70:aa:07:d1
inet addr:10.22.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::f860:70ff:feaa:7d1/64 Scope:Link
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:1 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:1 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:90 (90.0 B) TX bytes:0 (0.0 B)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

CNI插件以后的规划

  • 动态更新网络配置
  • 网络带宽和防火墙规则的动态策略

注意:所有文章非特别说明皆为原创。为保证信息与源同步,转载时请务必注明文章出处!谢谢合作 :-)

原始链接:http://zhaozhanxu.com/2019/06/12/K8S/2019-06-12-CNI/

许可协议: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。