# CNI

随着容器技术的发展，跨主机容器间的网络互通已经成为基本要求，更高的要求包括容器固定 IP 地址、一个容器多个 IP 地址、多个子网隔离、ACL 控制策略、与 SDN 集成等。

CNI（Container Network Interface） 是一个抽象的接口层，将容器网络配置方案与容器平台方案解耦。它定义了一套接口标准，提供了规范文档以及一些标准实现。采用 CNI 规范来设置容器网络的容器平台不需要关注网络的设置的细节，只需要按 CNI 规范来调用 CNI 接口即可实现网络的设置。

CNI 的接口并不是指 HTTP，gRPC 接口，CNI 接口是指对可执行程序的调用（exec)。这些可执行程序称之为 CNI 插件，以 K8S 为例，K8S 节点默认的 CNI 插件路径为 `/opt/cni/bin`。

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

容器运行时通过设置环境变量以及从标准输入传入的配置文件来向插件传递参数。CNI 通过 JSON 格式的配置文件来描述网络配置，当需要设置容器网络时，由容器运行时负责执行 CNI 插件，并通过 CNI 插件的标准输入（stdin）来传递配置文件信息，通过标准输出（stdout）接收插件的执行结果。

CNI 配置发生改变后，不需要重启 kubelet。每次 kubelet 创建、销毁容器的时候都会读取 CNI 配置文件。如果改变 CNI 配置文件，只需要重启现有的 Pods。新建 Pod 会直接被应用新的网络配置。

## CNI 插件要求

* Pod 之间通信不需要使用 NAT
* 所有 node 和 容器之间通信不需要使用 NAT
* 容器自身所见的 IP 和外界所见 IP 相同

## 实现

1. 硬件交换机 Hardware Switches / 网络操作系统 Network OS，例如 Cumulus
2. 虚拟交换机，例如 OVS
3. Linux 内核网络扩展: Flannel, Calico, Cilium 等等

## 操作类型

1. ADD: 为容器所在的网络命名空间创建一个网络接口，或者修改容器所在网络命名空间中的指定网络接口。例如通过 ADD 将容器网络接口接入到主机的网桥中。
2. DEL: 为容器所在的网络命名空间删除一个网络接口，或者撤销 ADD 操作的修改。例如通过 DEL 将容器网络接口从主机网桥中删除。
3. CHECK: CHECK 操作是 v0.4.0 加入的类型，用于检查网络设置是否符合预期。容器运行时可以通过 CHECK 来检查网络设置是否出现错误，当 CHECK 返回错误时（返回了一个非 0 状态码），容器运行时可以选择 Kill 掉容器，通过重新启动来重新获得一个正确的网络配置。
4. VERSION: 查看插件支持的版本信息。

## 环境变量

* 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`。

## 链式调用

单个 CNI 插件的职责是单一的，比如 bridge 插件负责网桥的相关配置， firewall 插件负责防火墙相关配置， portmap 插件负责端口映射相关配置。因此，当网络设置比较复杂时，通常需要调用多个插件来完成。CNI 支持插件的链式调用，可以将多个插件组合起来，按顺序调用。例如先调用 bridge 插件设置容器 IP，将容器网卡与主机网桥连通，再调用 portmap 插件做容器端口映射。容器运行时可以通过在配置文件设置 plugins 数组达到链式调用的目的。

插件是通过标准输入进行参数的传递，只接受自己的配置内容和前一个插件执行的结果。进行链式调用的时候，需要对 plugins 数组做格式转换，同时需要将前一次插件的返回结果添加到配置文件中（通过 prevResult 字段）。这一项繁琐而重复的工作，libcni 已经为我们封装好了，容器运行时不需要关心如何转换配置文件，如何填入上一次插件的返回结果，只需要调用 libcni 的相关方法即可。

## CNI 插件

### Main

bridge: 网桥存在于主机网络空间。每个容器得到 veth 对的一端，另一端接到网桥上。容器这一端被分配了 IP 地址，接到网桥的这一端没有被分配 IP 地址。此时网桥仅工作在二层。如果配置的时候指定了 gateway，则将 gateway IP 地址分配给网桥，网桥也工作在三层，充当容器网络的网关。

macvlan: 相当于一个接在主机网口上的交换机，容器接在交换机上，有各自的 MAC 地址。配置了 macvlan 可以很方便的使用 DHCP 服务。

### IPAM

IPAM 管理容器 IP 地址（包括网关、路由等信息）的 IPAM Plugin，通常由 CNI Plugin 运行时调用来完成容器 IP 地址的分配。

dhcp: 运行在主机上的守护进程，代替容器发起 DHCP 请求。

host-local: 本地维护一个已分配 IP 的数据库。

static: 分配静态 IPv4/IPv6 地址给容器。
