理解CNI网络模型
CNI网络模型
随着容器技术在企业生产系统中的逐步落地,跨主机容器间的网络互通已经成为基本要求,更高的要求包括容器固定IP地址、一个容器多个IP地址、多个子网隔离、ACL控制策略、与SDN集成等。目前主流的容器网络模型主要有Docker公司提出的Container Network Model(CNM)模型和CoreOS公司提出的Container Network Interface(CNI)模型。
CNM模型
CNM模型主要通过Network Sandbox、Endpoint和Network这三个组件进行实现。
- Network Sandbox:容器内部的网络栈,包括网络接口、路由 表、DNS等配置的管理。Sandbox可用Linux网络命名空间、FreeBSD Jail等机制进行实现。一个Sandbox可以包含多个Endpoint。
- Endpoint:用于将容器内的Sandbox与外部网络相连的网络接 口。可以使用veth对、Open vSwitch的内部port等技术进行实现。一个 Endpoint仅能够加入一个Network。
- Network:可以直接互连的Endpoint的集合。可以通过Linux网 桥、VLAN等技术进行实现。一个Network包含多个Endpoint。
CNI模型
CNI是由CoreOS公司提出的另一种容器网络规范,让各个容器管理平台(k8s,mesos等)都可以通过相同的接口调用各式各样的网络插件来为容器配置网络。
CNI定义的是容器运行环境与网络插件之间的简单接口规范,通过 一个JSON Schema定义CNI插件提供的输入和输出参数。一个容器可以通过绑定多个网络插件加入多个网络中。
CNI规范概述
CNI提供了一种应用容器的插件化网络解决方案,定义对容器网络进行操作和配置的规范,通过插件的形式对CNI接口进行实现。CNI仅关注在创建容器时分配网络资源,和在销毁容器时删除 网络资源,这使得CNI规范非常轻巧、易于实现,得到了广泛的支持。
CNI规范的一些要点:
- CNI规范为一个容器定义一个Linux网络命名空间
- CNI的网络定义存储为JSON格式
- 网络定义通过STDIN输入流传输到插件,这意味着宿主机上不会存储网络配置文件
- 其他的配置参数通过环境变量传递给插件
- CNI插件为可执行文件
- CNI插件负责连通容器网络,它要完成所有的工作才能使容器连入网络
- CNI插件负责调用IPAM插件
CNI模型只涉及两个概念:容器和网络
- 容器(Container):是拥有独立Linux网络命名空间的环境, 例如使用Docker或rkt创建的容器。关键之处是容器需要拥有自己的 Linux网络命名空间,这是加入网络的必要条件。
- 网络(Network):表示可以互连的一组实体,这些实体拥有 各自独立、唯一的IP地址,可以是容器、物理机或者其他网络设备(比 如路由器)等。
对容器网络的设置和操作都通过插件(Plugin)进行具体实现, CNI插件包括两种类型:CNI Plugin和IPAM(IP Address Management) Plugin。CNI Plugin负责为容器配置网络资源,IPAM Plugin负责对容器 的IP地址进行分配和管理。IPAM Plugin作为CNI Plugin的一部分,与 CNI Plugin一起工作。
CNI Plugin插件详解
CNI Plugin包括3个基本接口的定义:添加(ADD)、删除 (DELETE)、检查(CHECK)和版本查询(VERSION)。这些接口的具体实现要求插件提供一个可执行的程序,在容器网络添加或删除时进行调用,以完成具体的操作。比如添加接口的参数有Version、ContanierID、Network namespace、Network configuration、Extra arguments、Name of the interface inside the container。
网络配置参数(Network configuration)则由一个JSON报文组成,以标准输入(stdin)的方 式传递给可执行程序。下面例子定义了一个名为dbnet的网络配置参数:
{
"cniVersion": "0.4.0", //cni版本号
"name": "dbnet", //网络名称,应在一个管理域内唯一
"type": "bridge", //CNI插件的可执行文件的名称
"args": "", //其他参数
"ipam": { //IP地址管理的相关配置
"type": "host-local", //IPAM可执行文件名
"subnet": "10.1.0.0.0/16",
"gateway": "10.1.0.1"
},
"dns": { //dns服务相关配置
"nameservers": ["10.1.0.1"],
"domain": "" //本地域名
"search": "" //按优先级排序的域名查询列表
"options": "" //传递给resolver的选项列表
}
}
CNI插件应能够支持通过环境变量和标准输入传入参数。可执行文 件通过网络配置参数中的type字段标识的文件名在环境变量CNI_PATH 设定的路径下进行查找
IPAM Plugin插件
为了减轻CNI Plugin对IP地址管理的负担,在CNI规范中设置了一个 新的插件专门用于管理容器的IP地址(还包括网关、路由等信息),被称为IPAM Plugin。通常由CNI Plugin在运行时自动调用IPAM Plugin完 成容器IP地址的分配。
如果成功完成了容器IP地址的分配,则IPAM插件应该通过标准输 出(stdout)返回以下JSON报文:
{
"cniVerison": "0.4.0",
"ips": [
{
"version": "<4-or-6>",
"address": "<ip-and-prefix-in-CIDR>"
"gateway": "<ip-address-of-the-gateway>" //optional
},
//......
],
"routes": [
{
"dst": "ip-and-prefix-in-cidr",
"gw": "<ip-of-next-hop>" //optional
},
//......
],
"dns": {
"nameservers": "<list-of-nameservers>" //optional
"domain": "<name-of-local-domain>" //optional
"search": "<list-of-search-domains>" //optional
"options": "<list-of-options>" //optional
}
}
多网络插件
在很多情况下,一个容器需要连接多个网络,CNI规范支持为一个 容器运行多个CNI Plugin来实现这个目标。多个网络插件将按照网络配置列表中的顺序执行,并将前一个网络配置的执行结果传递给后面的网络配置。
下面的例子定义了两个网络配置参数,分别作用于两个插件,第1 个为bridge,第2个为tuning。CNI将首先执行第1个bridge插件设置容器的网络,然后执行第2个tuning插件:
{
"cniVersion": "0.4.0",
"name": "dbnet",
"plugin": [
{
"type": "bridge",
"args": {
"labels": {
"appVersion": "1.0"
}
},
"ipam": {
"type": "host-local",
"subnet": "10.1.0.0/16"
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": ["10.1.0.1"]
}
},
{
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
}
}
]
}
在Kubernetes中使用网络插件
CNI插件是可执行文件,会被kubelet调用。Kubernetes目前支持两种网络插件的实现:
- CNI插件:根据CNI规范实现其接口,以与插件提供者进行对接。
- kubenet插件:使用bridge和host-local CNI插件实现一个基本的cbr0。
为了在k8s集群中使用网络插件,需要在kubelet服务的启动参数上设置下面两个参数
- --network-plugin-dir:kubelet启动时扫描网络插件的目录
- --network-plugin:网络插件名称,对于CNI插件为cni(无须关注--network-plugin-dir路径),kubenet插件为kubenet。
设置为cni时,还需要两个参数--cni-conf-dir
(默认/etc/cni/net.d)和--cni-bin-dir
(默认/opt/cni/bin),作用看名字就知道。
目前已有多个开源项目支持以CNI网络插件的形式部署到Kubernetes集群中,进行Pod的网络设置和网络策略的设置。
使用CNI插件的简单示例
Docker有自己的CNM标准,那我们可以将CNI与Docker一起使用吗?答案是肯定的,但这不是个完整的解决方案。CNI插件负责连接容器,因此有可能只是用Docker的容器运行时,而不调用Docker的网络端的工作。
CNI的工作是从容器管理系统处获取运行时信息,包括network namespace的路径,容器ID以及network interface name,再从容器网络的配置文件中加载网络配置信息,再将这些信息传递给对应的插件,由插件进行具体的网络配置工作,并将配置的结果再返回到容器管理系统中。
下载CNI二进制文件
# -O 参数来告诉 curl 保存为文件,使用 -L 参数来允许 curl 跟随重定向
curl -O -L https://github.com/containernetworking/cni/releases/download/v0.4.0/cni-amd64-v0.4.0.tgz
tar -xzvf cni-amd64-v0.4.0.tgz
让我们聚焦到网络插件bridge文件,Bridge是CNI官网插件之一,它的工作是将容器依附到网桥接口上。网络配置是通过STDIN流传输到插件中的,其他信息通过环境变量传递到插件。
首先定义一个网桥的网络配置文件
cat > mybridge.conf <<"EOF"
{
"cniVersion": "0.2.0",
"name": "mybridge",
"type": "bridge",
"bridge": "cni_bridge0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.15.20.0/24",
"routes": [
{ "dst": "0.0.0.0/0" },
{ "dst": "1.1.1.1/32", "gw":"10.15.20.1"}
]
}
}
EOF
除了可以设置系统级别的环境变量外,我们还可以把环境变量直接传递给命令。
sudo CNI_COMMAND=ADD CNI_CONTAINERID=xingns CNI_NETNS=/var/run/netns/xingns CNI_IFNAME=eth12 CNI_PATH=`pwd` ./bridge < mybridge.conf
- CNI_COMMAND = ADD:告诉CNI要添加连接
- CNI_CONTAINER = xingns:CNI要使用xingns网络命名空间
- CNI_NETNS = /var/run/netns/xingns:网络命名空间路径
- CNI_IFNAME = eth12:命名空间里使用的网络接口名
- CNI_PATH =
pwd
:CNI插件的可执行文件路径
在运行命令前,我们还需要创建插件将要配置的网络命名空间,通常容器运行时会自动创建命名空间,但由于我们是自己手动实验,所以得自己创建。
ip netns add xingns
创建完成后,运行插件
执行完后返回两部分输出
- 由于IPAM找不到本地存储的保留IP分配信息文件,因此返回错误。如果我们对其他网络命名空间再次运行此命令,则不会出现此错误了,因为该文件在我们首次运行插件时创建了。
- 其次是返回一个JSON格式的IP配置,在本例中,网桥本身配置为10.15.20.1/24的IP,而网络命名空间接口将会分配到10.15.20.2/24,它还设置了默认网关和我们在网络配置JSON中定义的1.1.1.1/32路由。
我们现在又了一个名为cni_bridge0
的网桥接口,接口IP也和我们预期的一致,注意底部有veth设备的一端。我们还启用了ipMasq,(是否为该网络配置出站地址转换)如果我们查看主机的iptables,将看到如下规则...
在看一下网络命名空间:
![截屏2021-05-27 上午11.10.56](https://tva1.sinaimg.cn/large/008i3skNly1gqwt8mkvksj30kj02jaa8.jpg)
网络配置和预期的也一致,命名空间有一个名为”eth12”的网络接口,其IP地址为10.15.20.2/24,我们之前定义的路由也在那里。
参考:
《Kubernetes权威指南》
http://www.dasblinkenlichten.com/understanding-cni-container-networking-interface/