介绍什么是 CNI 以及如何动手写一个 CNI-IPAM-plugin

CNI

  1. CNI – Container Network Interface - networking for Linux containers.
  2. CNCF(Cloud Native Computing Foundation) 项目.
  3. 包含了 spec 规范和一些制作 CNI 插件(plugin)所可选用的(golang)库.
  4. CNI 项目地址: containernetworking/cni
  5. 部分实现的 plugin 项目地址: containernetworking/plugins

cni-spec

  1. 因为篇幅受限/懒, 以及官方的文档写的挺简单/清晰的, 所以参见: Container Networking Interface Proposal (0.3.1)
  2. 0.3.0 版本的 spec 竟然出现了一个语义上的 typo, 在 0.3.1 上修复了, 就是 ip: [] -> ips: []. (笑

cni-plugin

  1. 项目地址 containernetworking/plugins
  2. main plugin 是拥有完整实现容器连通性功能的插件, (e.g. bridge/macvlan/ptp/ipvlan)
  3. ipam plugin 是 IPAM (IP Address Management) 的实现. To lessen the burden and make IP management strategy be orthogonal to the type of CNI plugin, (e.g. dhcp/host-local)
  4. meta plugin 是拥有部分功能的插件, 必须依赖其他的 main plugin 链式使用, 如 tuning 是为了 It reads in its own netconf, it does not create any network interface but just changes the network sysctl

bridge (main)

  1. 在 host 的 ns 下将各个容器连接到 bridge(virtual switch) 上, 然后给容器内的 veth peer 分配 IP.
  2. 可以给该 bridge 绑定 IP, 就变成所有容器的网关, 或者当做一个纯 L2 交换机(此模式下, 容器与容器之间无法连通 To-be-confirmed).
  3. 如果 ipam 没提供 ip 的 gw, 会默认用掩码+1 当做 gw.
  4. 目前源码中看到 PromiscMode 只能 up 不能 down

ipvlan (main)

  1. 一种 L3 虚拟化.
  2. macvlan 是表兄弟关系, Like its cousin macvlan, it virtualizes the host interface.
  3. 因为共享 mac, 目前不支持 dhcp ipam
  4. 虚拟 interface 不能和主机沟通 'ipvlan' does not allow virtual interfaces to communicate with the master interface.
  5. master interface 不能同时启用 ipvlanmacvlan

macvlan (main)

  1. 一种 L2 虚拟化.
  2. 所有 interface 共享物理网卡, 但每个 interface 有不同的 mac 地址
  3. 因为每个 interface 都有自己的 mac 地址, 所以可以很简单地使用 dhcp ipam
  4. 大部分无线网卡可能不支持
  5. ipvlan 是表兄弟关系
  6. master interface 不能同时启用 ipvlanmacvlan

ptp (main)

  1. 似乎是 bridge 的简化版, 直接用 host 当做 bridge.
  2. 所以似乎现在默认是用 host-local ipam.

dhcp (ipam)

  1. 依靠网络中现有的 DHCP server.
  2. 因为容器并不管/关心 IP 是怎么来的, 而 DHCP 的 IP 是需要续租的, 所以该插件有一个 daemon 模式, 除了给它当做 ipam 使用时候的 rpc 调用的服务器端, 还负责了 IP 的续租和释放.

host-local (ipam)

  1. 使用本地文件来保存 IP 的使用情况
  2. ranges 配置是个二层嵌套数组, 一般第一个是个 IPV4 子网可选列表, 第二个是 IPV6 子网可选列表, 每个列表中可以有多个子网, round-robin 算法来选 IP.
  3. 使用本地的 /etc/resolv.conf 中的 dns 配置(也可以另外指定文件)
  4. 支持 CNI_ARGS 参数, 可以指定分配某个 subnet 中的某几个 IP, 但也是哪个没用到就用哪个
  5. 数据存在 /var/lib/cni/networks/$NETWORK_NAME, 如果想重启主机后, 自动释放这些 IP, 就可以将 dataDir 指定在一些 tmpfs 的目录(e.g. /var/run/cni)

tuning (meta)

  1. 如前所述, meta plugin 不负责连通性 or IP 分配, 只是一些辅助性的插件.
  2. tuning 负责给容器修改内核参数.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    The following network configuration file
    {
    "name": "mytuning",
    "type": "tuning",
    "sysctl": {
    "net.core.somaxconn": "500"
    }
    }
    will set /proc/sys/net/core/somaxconn to 500.

flannel (meta)

  1. flannel 用的, 不多说了

以上小结

  1. ipam plugin 负责分配 IP, 而 main plugin 负责想办法给容器连通.
  2. 因为各个 main plugin 的实现具体原理, 并不是所有的 ipam pluginmain plugin 都可以两两配对.
  3. 在一个生产的 k8s 集群中, 一般很少会有 dhcp server, 因为这相当于完全放弃了 IP 的管理.
  4. host-local ipam 因为分配的 IP 不与 host IP 在一个段, 但会需要额外的方式来保证连通, 不论是 bridge 的 L2/L3 方式, 还是第三方的如 flannel(不是上面的 flannel meta plugin) 的封包发 udp 的方式.
  5. 注意上面的 cni-plugin, 对一个集群内的网络环境, 都有部分侵入, 如 dhcp 占用掉和主机同网段的 IP. 而 host-local 在多主机之间, 给容器分配 IP 是会重复的, 而且不确定多主机之间是如何实现连通的.

第三方 cni-plugin 实现

  1. 以上分析了官方实现的部分 cni-plugin, 因为都有部分对现有网络的侵入, 而第三方在实现 cni 的时候, 部分会避免这种 类似 的侵入, 而选择其他方式的 侵入.

calico

  1. 给容器 interface 是很简单地使用了 veth peer
  2. 给容器 IP 用的是和主机不同, 和 k8s service 也不同的独立网段
  3. 而容器之间的连通性是依靠在主机上写入路由表进行的
  4. 另外很独特的一点, cni 是个在单台主机上的可执行文件, 那在这台主机上新增/删除/修改了路由表, 别的主机上怎么知道呢? OK, 这里 calico 用了一个用在自治路由系统中的 BGP 协议来传递路由. (BGP 协议是个没个十几万字就解释不清楚的东西)
  5. 第 2 点说了, calico 用了和主机不同的独立的 IP 段, 那么如果 k8s 集群主机的 IP 就在不同的 IP 段怎么办呢? 而 calico 给这些不同子网主机上的容器还是用的同一个段. 这里 calico 用了一种 IP-in-IP 技术, 是一种 IP 层的标准协议, 在原有的 IP header 上再套一个 IP header. 那么在外层 header 上使用主机 IP, 内层 header 用容器 IP, 就实现了跨网段主机上容器的连通性了.

flannel

  1. TODO

write a CNI(IPAM)

  1. 要写一个 CNI 还是比较难的, 因为大部分可行的容器连通性的方案都有实现过, 要么是协议栈的虚拟化, 无论是二层虚拟/三层虚拟/桥接, 还是依赖封包解包, 还是靠 BGP 传递路由.
  2. 而 IPAM 在设计之初就是为了减轻 cni 的耦合程度, 并且可以将 IP 的分配功能单独区分出来. 另外国外工程师的想法似乎对 IP 的管理并没那么多的兴趣, 就是对 IP 的使用基本都是按网段掩码来分.
  3. 当然了, 我也觉得 IPAM 的将 IP 的分配和应用的属性/配置关联起来的想法很蠢, 但就是有企业对网络, 对流量的管理是依赖这种 死配置 的 IP.
  4. 至于如何写一个 IPAM 呢, 首先可以参考 cni-spec 的描述, main-pluginipam-plugin 分别是接受哪些输入/环境变量, 以及需要输出什么格式的结果.
  5. 其次参考 dhcp 和 host-local 的实现, 主要是实现两个函数 cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) ref: containernetworking/cni/pkg/skel/skel.go

References

containernetworking/cni
containernetworking/plugins
About Calico
flannel 0.9.0 Documentation