本篇主要是简单了解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_NETNS
或prevResult
的话,插件应该尽量清除所有的资源(比如释放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插件输出接口。
{
"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:
{
"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字段可用于传递任意数据,如果不识别则忽略。
{
"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
字段。
{
"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" ]
}
}
}
{
"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命令的情况下):
{
"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
"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
"routes": [
{
"dst": "<ip-and-prefix-in-cidr>",
"gw": "<ip-of-next-hop>" (optional)
},
...
]
DNS
"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和插件,直接看看怎么使用:
首先我们要有插件对应的配置文件
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" } //网关
]
}
}
接下来编译插件
$ cd $GOPATH/src/github.com/containernetworking/plugins
$ ./build_linux.sh # or build_windows.sh
最后在该网络中运行ifconfig的操作,其中用到的脚本可以看下源码,没有难度。
$ 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的操作。
$ 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插件以后的规划
- 动态更新网络配置
- 网络带宽和防火墙规则的动态策略
4 条评论
文章的确不错啊https://www.cscnn.com/
看的我热血沸腾啊https://www.ea55.com/
想想你的文章写的特别好
叼茂SEO.bfbikes.com