7.6 CNI网络模型

随着容器技术在企业生产系统中的逐步落地,用户对容器云的网络特性要求也越来越高。跨主机容器间的网络互通已经成为基本要求,更高的要求包括容器固定IP地址、一个容器多个IP地址、多个子网隔离、ACL控制策略、与SDN集成等。目前主流的容器网络模型主要有Docker公司提出的Container Network Model(CNM)和CoreOS公司提出的Container Network Interface(CNI)。

7.6.1 CNM网络模型简介

CNM模型现已被Cisco Contiv、Kuryr、Open Virtual Networking(OVN)、Project Calico、VMware、Weave和Plumgrid等项目所采纳。另外,Weave、Project Calico、Kuryr和Plumgrid等项目也为CNM提供了网络插件的具体实现。

CNM模型主要通过Network Sandbox、Endpoint和Network这3个组件进行实现,如图7.17所示。

图7.17 CNM模型示意图

◎ Network Sandbox:容器内部的网络栈,包括网络接口、路由表、DNS等配置的管理。Sandbox可通过Linux网络命名空间、FreeBSD Jail等机制进行实现。一个Sandbox可以包含多个Endpoint。

◎ Endpoint:用于将容器内的Sandbox与外部网络相连的网络接口。可以使用Veth设备对、Open vSwitch的内部port等技术进行实现。一个Endpoint仅能加入一个Network。

◎ Network:可以直接互连的Endpoint的集合。可以通过Linux网桥、VLAN等技术进行实现。一个Network包含多个Endpoint。

7.6.2 CNI网络模型详解

CNI是由CoreOS公司提出的另一种容器网络规范,现在已经被Kubernetes、rkt、Apache Mesos、Cloud Foundry和Kurma等项目采纳。另外,Contiv Networking、Project Calico、Weave、SR-IOV、Cilium、Infoblox、Multus、Romana、Plumgrid和Midokura等项目也为CNI提供了网络插件的具体实现。图7.18描述了容器运行环境与各种网络插件通过CNI进行连接的模型。

图7.18 CNI模型示意图

CNI定义了容器运行环境与网络插件之间的简单接口规范,通过一个JSON Schema定义CNI插件提供的输入和输出参数。一个容器可以通过绑定多个网络插件加入多个网络中。

本节将对CNI规范、CNI插件、网络配置、IPAM等概念和配置进行详细说明。

1.CNI规范概述

CNI提供了一种应用容器的插件化网络解决方案,定义对容器网络进行操作和配置的规范,通过插件的形式对CNI接口进行实现。CNI是由rkt Networking Proposal发展而来的,尝试提供一种普适的容器网络解决方案。CNI仅关注在创建容器时分配网络资源与在销毁容器时删除网络资源,这使得CNI规范非常轻巧、易于实现,得到了广泛的支持。

在CNI模型中只涉及两个概念:容器和网络。

◎ 容器:是拥有独立Linux网络命名空间的环境,例如使用Docker或rkt创建的容器。关键之处是容器需要拥有自己的Linux网络命名空间,这是加入网络的必要条件。

◎ 网络:表示可以互连的一组实体,这些实体拥有各自独立、唯一的IP地址,可以是容器、物理机或者其他网络设备(比如路由器)等。可以将容器添加到一个或多个网络中,也可以从一个或多个网络中删除。

对容器网络的设置和操作都通过插件(Plugin)进行具体实现,CNI插件包括两种类型:CNI Plugin和IPAM(IP Address Management)Plugin。CNI Plugin负责为容器配置网络资源,IPAM Plugin负责对容器的IP地址进行分配和管理。IPAM Plugin作为CNI Plugin的一部分,与CNI Plugin一起工作。

2. 容器运行时与CNI插件的关系和工作机制

将容器添加到网络中或者删除某个网络是由容器运行时(runtime)和CNI插件完成的,容器运行时与CNI插件之间的关系和工作机制通常遵循下面的原则。

◎ 容器运行时必须在调用任意插件前为容器创建一个新的网络命名空间。

◎ 容器运行时必须确定此容器所归属的网络(一个或多个),以及每个网络必须执行哪个插件。

◎ 网络配置为JSON格式,便于在文件中存储。网络配置包括必填字段,例如name和type,以及插件(类型)特有的字段。网络配置允许在调用时更改字段的值。为此,必须在可选字段args中包含需要变更的信息。

◎ 容器运行时必须按照先后顺序为每个网络运行插件将容器添加到每个网络中。

◎ 容器生命周期结束后,容器运行时必须以反向顺序(相对于添加容器执行顺序)执行插件,以使容器与网络断开连接。

◎ 容器运行时一定不能为同一个容器的调用执行并行(parallel)操作,但可以为多个不同容器的调用执行并行操作。

◎ 容器运行时必须对容器的ADD和DEL操作设置顺序,以使得ADD操作最终跟随相应的DEL操作。DEL操作后面可能会有其他DEL操作,但插件应自由处理多个DEL操作(即多个DEL操作应该是幂等的)。

◎ 容器必须由ContainerID进行唯一标识。存储状态的插件应使用联合主键(network name、CNI_CONTAINERID、CNI_IFNAME)进行存储。

◎ 容器运行时不得为同一个实例(由联合主键network name、CNI_CONTAINERID、CNI_IFNAME进行标识)调用两次ADD操作(无相应的DEL操作)。对同一个容器(ContainerID),仅在每次ADD操作都使用不同的网络接口名称时,才可以多次添加到特定的网络中。

◎ 除非明确标记为可选配置,CNI结构中的字段(例如Network Configuration和CNI Plugin Result)都是必填字段。

3.CNI Plugin详解

CNI Plugin必须是一个可执行程序,由容器管理系统(如Kubernetes)调用。

CNI Plugin负责将网络接口(network interface)插入容器网络名称空间(例如Veth设备对的一端),并在主机上进行任意必要的更改(例如将Veth设备对的另一端连接到网桥),然后调用适当的IPAM插件,将IP地址分配给网络接口,并设置正确的路由规则。

CNI Plugin需要支持的操作包括ADD(添加)、DELETE(删除)、CHECK(检查)和VERSION(版本查询)。这些操作的具体实现均由CNI Plugin可执行程序完成。

(1)ADD:将容器添加到某个网络中,主要过程为在Container Runtime创建容器时,先创建好容器内的网络命名空间,然后调用CNI插件为该netns完成容器网络的配置。ADD操作的参数如下。

◎ Container ID:容器ID,为容器的唯一标识。

◎ Network namespace path:容器的网络命名空间路径,例如/proc/[pid]/ns/net。

◎ Network configuration:网络配置JSON文档,用于描述容器待加入的网络。

◎ Extra arguments:其他参数,提供了另一种以每个容器为基础的CNI插件简单配置机制。

◎ Name of the interface inside the container:容器内的虚拟网卡名称。

ADD操作的结果信息包含以下参数。

◎ Interfaces list:网络接口列表,根据Plugin的实现,可能包括Sandbox Interface名称、主机Interface名称、每个Interface的地址等信息。

◎ IP configuration assigned to each interface:分配给每个网络接口的IPv4或IPv6地址、网关地址和路由信息。

◎ DNS information:DNS相关信息,包括域名服务器(name server)、域名信息(domain)、搜索后缀(search domains)、DNS选项(options)。

(2)DEL:在容器销毁时将容器从某个网络中删除。DEL操作的参数如下。

◎ Container ID:容器ID。

◎ Network namespace path:容器的网络命名空间路径,例如/proc/[pid]/ns/net。

◎ Network configuration:网络配置JSON文档,用于描述容器待加入的网络。

◎ Extra arguments:其他参数。

◎ Name of the interface inside the container:容器内的网卡名。

执行DEL操作时需要注意如下事项。

◎ 所有参数都必须与执行ADD操作时相同。

◎ DEL操作应该释放容器(ContainerID)占用的所有网络资源。

◎ 如果前一个操作是ADD,则应在网络插件的配置文件JSON中补充prevResult字段,以标明前一个操作的结果(容器运行时可能会缓存ADD的结果)。

◎ 如果没有提供CNI_NETNS或prevResult,则CNI Plugin应该尽可能释放容器相关的所有网络资源(例如释放通过IPAM分配的IP地址),并返回成功。

◎ 如果容器运行时对ADD结果进行了缓存,则在执行DEL操作后必须删除之前的缓存内容。

◎ CNI Plugin在执行DEL操作后通常应返回成功,即使在某些资源缺失的情况下。例如,当容器的网络命名空间不存在时,IPAM插件也应该对执行IP地址释放返回成功。

(3)CHECK:检查容器网络是否正确设置,其结果为空(表示成功)或错误信息(表示失败)。CHECK操作的参数如下。

◎ Container ID:容器ID。

◎ Network namespace path:容器的网络命名空间路径,例如/proc/[pid]/ns/net。

◎ Network configuration:网络配置JSON文档,用于描述容器待加入的网络,必须通过prevResult字段将其设置为前一个ADD操作的结果。

◎ Extra arguments:其他参数。

◎ Name of the interface inside the container:容器内的网卡名。

执行CHECK操作时需要注意如下事项。

◎ 必须设置prevResult字段,标明需要检查的网络接口和网络地址。

◎ 插件必须允许插件链中靠后的插件对网络资源进行修改,例如修改路由规则。

◎ 如果prevResult中的某个资源(如网络接口、网络地址、路由)不存在或者处于非法状态,则插件应该返回错误。

◎ 如果未在Result中跟踪的其他资源(例如防火墙规则、流量整形(traffic shaping)、IP保留等)不存在或者处于非法状态,则插件应该返回错误。

◎ 如果插件得知容器不可达,则应该返回错误。

◎ 插件应该在执行ADD操作后立刻执行CHECK操作。

◎ 插件应该在执行其他代理插件(例如IPAM)后立刻执行CHECK操作,并将错误的结果返回给调用者。

◎ 容器运行时不得在调用ADD操作前调用CHECK操作,也不得在调用DEL操作后再调用CHECK操作。

◎ 如果在网络配置中明确设置了“disableCheck”,则容器运行时不得调用CHECK操作。

◎ 容器运行时应在调用ADD操作后,在网络配置中补充prevResult信息。

◎ 容器运行时可以选择在一个插件链中某一个插件返回错误时停止执行CHECK操作。

◎ 容器运行时可以在成功执行ADD操作后立刻执行CHECK操作。

◎ 容器运行时可以假设一次失败的CHECK操作意味着容器永远处于错误配置状态。

(4)VERSION:查询网络插件支持的CNI规范版本号,无参数,返回值为网络插件支持的CNI规范版本号。例如:

容器运行时必须使用CNI Plugin网络配置参数中的type字段标识的文件名在环境变量CNI_PATH设定的路径下查找同名的可执行文件。一旦找到,容器运行时就将调用该可执行程序,并传入以下环境变量设置的网络配置参数,供该插件完成容器网络资源和参数的设置。

需要传入的环境变量参数如下。

◎ CNI_COMMAND:操作方法,包括ADD、DEL、CHECK和VERSION。

◎ CNI_CONTAINERID:容器ID。

◎ CNI_NETNS:容器的网络命名空间路径,例如/proc/[pid]/ns/net。

◎ CNI_IFNAME:待设置的网络接口名称。

◎ CNI_ARGS:其他参数,为key=value格式,多个参数之间用分号分隔,例如"FOO=BAR;ABC=123"。

◎ CNI_PATH:可执行文件的查找路径,可以设置多个。

网络配置参数由一个JSON报文组成,以标准输入(stdin)的方式传递给可执行程序。

下面对CNI Plugin操作的返回结果进行说明。

首先,成功操作的返回码应设置为0,在失败的情况下设置为非0,并返回如下JSON格式的错误信息到标准输出(stdout)中:

CNI规范设计返回码0~99为系统保留,100及以上的返回码可由插件的具体实现按需任意使用。另外,标准错误输出(stderr)也可以用于非结构化的输出内容,例如详细的日志信息。

对于ADD操作来说,成功的结果应以如下格式的JSON报文发送到标准输出:

其中,ips和dns段落的内容应与IPAM插件的返回结果完全一致,并补充interface字段为网络接口的序号(这是因为IPAM插件不关心网络接口的信息)。

对各字段信息说明如下。

◎ cniVersion:CNI规范的版本号,CNI Plugin可以支持多个版本。

◎ interfaces:CNI Plugin创建的网络接口(network interface)信息,如果设置了环境变量CNI_IFNAME,则应使用CNI_IFNAME指定的网络接口名称。网络接口的信息如下。

· mac:网络接口的MAC地址,对于不关心二层地址的容器来说,可以不设置。

· sandbox:对于容器环境,应返回容器的网络命名空间路径,例如/proc/[pid]/ns/net;对于Hypervisor或虚拟机环境,应返回虚拟沙箱的唯一ID。

◎ ips:IP地址信息,包括地址类型是否是IPv4或IPv6、IP地址、网关地址、网络接口序号等信息。

◎ routes:路由信息。

◎ dns:DNS相关信息,包括域名服务器、本地域名、搜索后缀、DNS选项等。

4.CNI网络配置详解

CNI网络配置(Network Configuration)以JSON格式进行描述。这个配置可以以文件的形式保存在磁盘上,或者由容器运行时自动生成。

目前,CNI规范的网络配置参数包括如下几个。

◎ cniVersion(string):CNI版本号。

◎ name(string):网络名称,应在一个Node或一个管理域内唯一。

◎ type(string):CNI Plugin可执行文件的名称。

◎ args(dictionary):其他参数,可选。

◎ ipMasq(boolean):是否设置IP Masquerade(需插件支持),适用于主机可作为网关的环境中。

◎ ipam:IP地址管理的相关配置。

· type(string):IPAM可执行的文件名。

◎ dns:DNS服务的相关配置。

· nameservers(list of strings):域名服务器列表,可以使用IPv4或IPv6地址。

· domain(string):本地域名,用于短主机名查询。

· search(list of strings):按优先级排序的域名搜索后缀列表。

· options(list of strings):传递给域名解析器的选项列表。

下面看几个网络配置示例。

(1)bridge类型,IP地址管理(IPAM)使用host-local插件进行设置:

(2)ovs类型,IP地址管理(IPAM)使用dhcp插件进行设置:

(3)macvlan类型,IP地址管理(IPAM)使用dhcp插件进行设置:

5.CNI网络配置列表

CNI网络配置列表(Network Configuration List)通过将多个网络配置按顺序配置,为容器提供连接到多个网络的机制。每个CNI Plugin执行后的结果将作为下一个插件的输入信息。网络配置列表也以JSON格式进行描述,内容由多个网络配置组成,主要包括以下字段。

◎ cniVersion(string):CNI的版本号。

◎ name(string):网络名称,应在一个Node或一个管理域内唯一。

◎ disableCheck(string):可设置为“true”或“false”,设置为“true”表示容器运行时不得调用CHECK操作,可用于在某些插件可能返回虚假错误的情况下跳过检查。

◎ plugins(list):一组网络配置列表,每个网络配置的内容请见上节的说明。

下面是由两个网络配置组成的网络配置列表示例,第1个为bridge,第2个为tuning:

容器运行时将按先后顺序依次调用各CNI Plugin的二进制文件并执行。

如果某个插件执行失败,容器运行时就必须停止后续的执行,返回错误信息给调用者。对于ADD操作的失败情况,容器运行时应反向执行全部插件的DEL操作,即使某些插件从未执行过ADD操作。

下面看看容器运行时在执行ADD、CHECK、DEL操作时,CNI网络配置内容的变化。

(1)容器运行时在执行ADD操作时,将按以下过程逐步完成。

调用bridge插件,以如下配置进行ADD操作:

调用tuning插件,以如下配置进行ADD操作,其中,将上一步bridge插件的运行结果设置在prevResult字段中:

(2)容器运行时在执行CHECK操作时,将按以下过程逐步完成。

调用bridge插件,以如下配置进行CHECK操作,其中,将bridge插件执行ADD的结果设置在prevResult字段中:

调用tuning插件,以如下配置进行ADD操作,其中,将bridge插件执行ADD的结果设置在prevResult字段中:

(3)容器运行时在执行DEL操作时,将以ADD的反向过程逐步完成,如下所述。

调用tuning插件,以如下配置进行DEL操作,其中,将tuning插件执行ADD的结果设置在prevResult字段中:

调用bridge插件,以如下配置进行ADD操作,其中,将bridge插件执行ADD的结果设置在prevResult字段中:

6.IP地址分配和IPAM Plugin详解

为了减轻CNI Plugin在IP地址管理方面的负担,CNI规范设置了一个独立的插件IPAM Plugin来专门管理容器的IP地址。CNI Plugin应负责在运行时调用IPAM Plugin完成容器IP地址的管理操作。IPAM Plugin负责为容器分配IP地址、网关、路由和DNS,并负责将IP地址操作结果返回给主CNI Plugin,典型实现包括host-local插件和dhcp插件。

与CNI Plugin类似,IPAM Plugin也以在CNI_PATH路径中可执行程序的形式完成具体操作。IPAM可执行程序也处理传递给CNI插件的环境变量和通过标准输入传入的网络配置参数。

IPAM Plugin操作的返回码在成功时应被设置为0,在失败时应被设置为非0。

IPAM Plugin在ADD操作成功时,应完成容器IP地址的分配,并返回以下JSON格式的报文到标准输出中:

在以上代码中主要包括ips、routes和dns等内容,与CNI Plugin执行ADD操作的结果不同的是,它不包括interfaces信息,因为IPAM Plugin不关心网络接口信息。

◎ ips:IP地址信息,包括地址类型是否是IPv4或IPv6、IP地址、网关地址、网络接口序号等信息。

◎ routes:路由信息。

◎ dns:DNS相关信息,包括域名服务器、本地域名、搜索后缀、DNS选项等。

7. 错误返回码说明

CNI规范系统保留的错误返回码范围为1~99,目前已规范的错误返回码如表7.1所示。

表7.1 CNI已规范的错误返回码

7.6.3 在Kubernetes中使用网络插件

Kubernetes目前支持两种网络插件的实现。

◎ CNI插件:根据CNI规范实现其接口,以与插件提供者进行对接。

◎ kubenet插件:使用bridge和host-local CNI插件实现一个基本的cbr0。

为了在Kubernetes集群中使用网络插件,需要在kubelet服务的启动参数上设置下面两个参数。

◎ --network-plugin-dir:kubelet启动时扫描网络插件的目录。

◎ --network-plugin:网络插件名称,对于CNI插件,将其设置为cni即可,无须关注--network-plugin-dir的路径。对于kubenet插件,将其设置为kubenet即可,目前仅实现了一个简单的cbr0 Linux网桥。

在设置--network-plugin="cni"时,kubelet还需设置下面两个参数。

◎ --cni-conf-dir:CNI插件的配置文件目录,默认为/etc/cni/net.d。该目录下配置文件的内容需要符合CNI规范。

◎ --cni-bin-dir:CNI插件的可执行文件目录,默认为/opt/cni/bin。

目前已有多个开源项目支持以CNI网络插件的形式部署到Kubernetes集群中,进行Pod的网络设置和网络策略的设置,包括Calico、Weave、Contiv、Cilium、Infoblox、Multus、Flannel、Romana等。关于CNI规范和第三方插件的更多信息,请参考CNI在GitHub项目库中的说明。