CNI学习系列1 认识CNI

介绍CNI

什么是CNI?

CNI (Container Network Interface) 。容器网络接口。由CNCF出品,旨在配置linux 容器的网络规范的库。现在已经成最流行的网络标准,被包含k8s在内的众多容器解决方案采用。

CNI长啥样?

  • CNI的插件都以可执行文件的形式存放在集群节点上
    1
    2
    [root@node1 amd64]# ls /opt/cni/bin/
    bandwidth bridge dhcp firewall flannel host-device host-local ipvlan loopback macvlan portmap ptp sbr static tuning vlan vrf

CNI插件怎么生效

kubelet将按照NETCONFPATH的环境变量找相关的cni配置文件,比如我在配置文件中声明了flannelbandwidth 这两个插件,他将会到CNI_PATH 中查找相应的插件执行。

CNI规范要求所有参数要么以标准输入的json字符串的形式,要么以环境变量的形式给到插件。同时插件也应json形式标准输出结果。

一个官方的CNI NETCONF 例子如下

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
{
"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}
}
]
}

插件的分类?

Main:接口创建

  • bridge:创建网桥,并添加主机和容器到该网桥
  • ipvlan:在容器中添加一个 ipvlan 接口
  • loopback:创建一个回环接口
  • macvlan:创建一个新的 MAC 地址,将所有的流量转发到容器
  • ptp:创建 veth 对
  • vlan:分配一个 vlan 设备

IPAM:IP 地址分配

  • dhcp:在主机上运行守护程序,代表容器发出 DHCP 请求
  • host-local:维护分配 IP 的本地数据库

Meta:其它插件

  • flannel:根据 flannel 的配置文件创建接口
  • tuning:调整现有接口的 sysctl 参数
  • portmap:一个基于 iptables 的 portmapping 插件。将端口从主机的地址空间映射到容器。
    单个CNI插件的职责是单一的,比如bridge插件负责网桥的相关配置, firewall插件负责防火墙相关配置, portmap 插件负责端口映射相关配置。

命令看起来像是这样:

1
2
3
4
# CNI_COMMAND=ADD 顾名思义表示创建。
# XXX=XXX 其他参数定义见下文。
# < config.json 表示从标准输入传递配置文件
CNI_COMMAND=ADD XXX=XXX ./bridge < config.json

插件有那些参数?

  • CNI_COMMAND :定义期望的操作,可以是ADD,DEL,CHECK或VERSION。通常用于添加网络还是删除网络。
  • CNI_CONTAINERID : 容器ID,由容器运行时管理的容器唯一标识符。
  • CNI_NETNS:容器网络命名空间的路径。(形如 /run/netns/[nsname] )。
  • CNI_IFNAME :需要被创建的网络接口名称,例如eth0。
  • CNI_ARGS :运行时调用时传入的额外参数,格式为分号分隔的key-value对,例如 FOO=BAR;ABC=123
  • CNI_PATH : CNI插件可执行文件的路径,例如/opt/cni/bin。

项目结构

  • cnitool 一个用于执行插件的工具。工具执行依赖两个环境变量:NETCONFPATH 配置文件路径,默认是在/etc/cni/net.d目录下搜索 *.conflist文件。CNI_PATH 这个环境变量保存插件。
  • libcni 这里面就是CNI规范中的接口,里面是golang的interface。
  • plugins 这里有些自己开发插件的模板项目

一个例子

我们下面演示下内置的一些简单插件的使用,我们以bridge为例:

  1. 首先在linux内部 /opt/cni/bin 下部署插件组

  2. 以无网络模式启动docker 容器

    1
    2
    3
    4
    contid=$(docker run -d --net=none --name nginx nginx) # 容器ID
    pid=$(docker inspect -f '{{ .State.Pid }}' $contid) # 容器进程ID
    netnspath=/proc/$pid/ns/net # 命名空间路径

nsenter进入相应namespace可以看到只有回环网卡。

1
2
3
4
5
nsenter -t $pid -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
  1. 配置参数json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"cniVersion": "0.4.0",
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"isDefaultGateway": true,
"forceAddress": false,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
}

执行ADD操作

1
CNI_COMMAND=ADD CNI_CONTAINERID=$contid CNI_NETNS=$netnspath CNI_IFNAME=eth0 CNI_PATH=~/cni/bin ~/cni/bin/bridge < bridge.json

结果返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"cniVersion": "0.4.0",
"interfaces": [
....
],
"ips": [
{
"version": "4",
"interface": 2,
"address": "10.10.0.2/16", //给容器分配的IP地址
"gateway": "10.10.0.1"
}
],
"routes": [
.....
],
"dns": {}
}

这是执行nsenter到容器命名空间已经可以看到我们的网卡了,这里我们还配置ipam为host-local ,所以这里插件只会为我们分配本机上 子网唯一的 ip地址。显然这种简单的思路是不能满足生产中全局唯一的clusterip需求的。

后续我们将看下flannel在这块是如何实现的。