美文网首页
cni设计简析

cni设计简析

作者: cloudFans | 来源:发表于2022-05-13 17:38 被阅读0次

cni设计

参考: https://github.com/containernetworking/cni/blob/main/SPEC.md

1. 插件式使用的cni

术语理解:

container: 在cni这里对应linux network ns
network: 指一组endpoint,可以用于获取到唯一的地址
runtime: 一个负责执行CNI plugin的程序
plugin: 负责配置网络

这个文档会仅对runtime 和 plugin进行说明

第一部分: 网络配置格式

cniVersion: 版本,版本号越大支持的特性越丰富
name: 网络名
disableCheck: 对应cni的check (Add|Del|check)
plugins: cni list

plugin 配置对象

type: 对应cni二进制的名字,比如macvlan,ipvlan
capabilities: 稍后补充
ipMasq: 通过host上的一个ip来伪装整个网络,所有pod用的该网段的pod ip都会使用该host ip进行地址伪装(nat)
ipam: 地址管理字典,其内的type字段即是ipam plugin
dns: 一个字典,包括,nameservers,domain,search,options


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

第二部分, 执行协议

cni protocol协议基于容器运行时调用cni(二进制),cni定义了cni二进制脚本和容器运行时交互的协议。
plugin分两种。

"Interface" plugins: 常见的cni macvlan ipvlan等
"Chained" plugins: 基于cni配置好的接口,做qos,安全组等配置,比如terway 的ipvlan和之后的cilium链式调用

容器运行时,通过将配置和环境变量以stdin的形式传递给cni插件。cni插件配置完容器接口以及网络之后,基于stdout 反馈成功。 如果有错误,基于strerr反馈。
stdin的配置和stdout的结果都是JSON格式。

成功是状态码 必须是0
失败时 非0

容器运行时是在root network ns中执行cni plugin的。

传递给cni的json包括:

CNI_COMMAND: ADD, DEL, CHECK, or VERSION
CNI_CONTAINERID: 容器id
CNI_NETNS: 容器所在网络命名空间 /run/netns/[nsname]
CNI_IFNAME: 容器内部的网卡
CNI_ARGS: 额外的参数,格式为 "FOO=BAR;ABC=123"
CNI_PATH: CNI插件所在路径

cni 的操作

ADD 将容器连接到网络,或者执行变更

  1. 在CNI_NETNS内部, 基于CNI_IFNAME 创建网卡
  2. 或者在CNI_NETNS内部, 基于CNI_IFNAME 调整接口的配置

如果ADD成功,那么返回的json 也就是 prevResult 对象会包括这些内容: https://github.com/containernetworking/cni/blob/main/SPEC.md#Success

cniVersion:
容器网卡: name, mac, samdbox(CNI_NETNS)
ips: ipcidr 192.168.1.3/24, gw, 网卡index
路由: dst cidr, gw
dns: nameservers domain search options

如果容器内有同样的接口,肯定会报错,而且,容器运行时不会连续执行ADD两次(中间没有删除动作),仅执行一次。

输入:

容器运行时会提供一个json 对象作为cni的标输入:

必须包含的字段:

  1. CNI_COMMAND
  2. CNI_CONTAINERID
  3. CNI_NETNS
  4. CNI_IFNAME

可选字段:
CNI_ARGS
CNI_PATH

DEL是add的反操作,暂不写
CHECK 用于检查和确认,暂不写

ipam 代理插件

Delegated plugins (IPAM)

ipam 插件必须反馈一个缩略的的成功对象的结构体。 比如没有接口数组以及接口ip等。

ipam插件式cni设计的一部分,ipam和cni二级制协调工作,才能完成网络配置

第三部分, 网络配置的执行

容器运行时会在容器内部执行 add, del, check 等网络操作。

  1. 网络配置如何转化
  1. 转化后的网络配置如何提供给插件

网络配置的操作统一叫做 attachment, 容器id 和 接口id, 是attachment的唯一标识

(操作)周期 和 顺序

  1. 在调用任何插件(cni ipam)之前,容器运行时必须已经创建了一个新的网络命名空间。
  2. 对于同一个容器,容器运行时不能调用多个并行的操作。 多个容器的操作可以并行。
  3. 不同的容器,插件可以并行执行,必要的话,需要实现一定的锁机制,必须ipam 的地址分配。
  4. 容器运行时必须保证,add 和 delete的先后顺序, add 结束,delete才能执行,即使add失败,del必须成功执行。
  5. delete保证幂等,无论delete多少次,结果一致。
  6. add 和 delete 之间的时间段, 网络配置不能发生变化。
  7. 在不同的attachments之间,网络配置不能发生变化。
  8. 容器运行时负责ns的清理。

Attachment的参数:

  1. 容器id
  2. ns
  3. 容器网卡名
  4. 原生参数, 额外参数,键值对格式,在CNI_ARGS 里面
  5. Capability Arguments, 键值对,json 格式: https://github.com/containernetworking/cni/blob/main/CONVENTIONS.md

 "capabilities": {"portMappings": true}
  "runtimeConfig": {
    "portMappings": [
      {"hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
    ]
  }

添加一个attachment

#  cat /etc/cni/net.d/00-multus.conf | jq
{
  "cniVersion": "0.4.0",
  "name": "multus-cni-network",
  "type": "multus",
  "capabilities": {
    "portMappings": true
  },
  "kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig",
  "delegates": [
    {
      "name": "kube-ovn",
      "cniVersion": "0.3.1",
      "plugins": [
        {
          "type": "kube-ovn",
          "server_socket": "/run/openvswitch/kube-ovn-daemon.sock"
        },
        {
          "type": "portmap",
          "capabilities": {
            "portMappings": true
          }
        }
      ]
    }
  ]
}

# 比如当前的k8s集群环境
# plugins包含两部分 cni是multus,ipam是kube-ovn-daemon

  1. 查看type 字段,不存在,则失败,报错
  2. 基于plugin 配置,去请求配置,包括如下参数

如果这个plugin在list中的第一位, 不需要提供之前的结果,应该指的是默认cni。
其他的plugins, 之前的结果就是上一个plugin处理过的输出。

这里说明,主plugin和其他plugin是有一定的顺序链式调用关系的。

  1. 执行plugin 二进制(比如macvlan cni), 基于CNI_COMMAND=ADD 参数,二进制可以通过环境变量读入参数,stdin也有包含一些配置项(ip mac dns 网关 svc localdns等)。

  2. 一旦plugin 执行出错,会立刻停止并失败,返回错误。

容器运行时,必须存储最后一个plugin返回的结果,并持久化存储(pod的状态),用于做pod状态检查以及是否删除。

Deleting an attachment: 是add的反向操作,暂不写

从 plugin 配置中,获取(待)执行配置项

网络配置的格式: 就是一个list,包含所有需要执行的plugin配置内容。

必须被转化为单个plugin能够理解的格式,因为即使是多网卡场景,也要一个一个的执行。

单个plugin调用时需要的配置也是一个json。

容器运行时必须将以下配置项传递给cni二进制

  1. cniVersion: cni 版本
  2. name: 取自网络配置中的name字段
  3. runtimeConfig: json 对象, plugin中的capabilities 字段
  4. prevResult: 一个json对象,上一个运行过的plugin(add/delete/check)返回的结果

plugin 中定义的 capabilities会被容器运行时移除。 其他field都毫无更改的直接传递。

runtimeConfig

然而 当CNI_ARGS 被传递给所有plugins后,没有任何指示表示这些信息会被消费。
所以,Capability需要在plugins字段中明确定义。 那么 容器运行时,就可以基于此来确定是否一个给定的网络配置支持某些特定的capability(能力)。


{
  "type": "myPlugin",
  "capabilities": {
    "portMappings": true  // 表示支持端口映射的能力
  }
}

// 将 capabilities 格式化为 runtimeConfig

{
  "type": "myPlugin",
  "runtimeConfig": {
    "portMappings": [ { "hostPort": 8080, "containerPort": 80, "protocol": "tcp" } ]
  }
  ...
}

runtimeConfig 的内容来自于 网络配置中的capabilities域

第四部分 插件代理(Plugin Delegation)

有一些操作,无论出于何种原因,都不能合理地实现为一个离散的链式插件。
更确切的说,一个CNI plugin 希望能代理别的插件执行一些功能。 一个最常用的例子就是代理执行ip 地址分配(ipam)

作为网络配置的一部分,CNI 插件需要能够分配并维护容器内网卡的ip地址,也包括配置必要的路由。

这让CNI插件具有极大的灵活性,但也变得更重(ipam 路由 dns 这些东西的维护)。

很多CNI插件需要的ipam的代码几乎都是类似的,比如dhcp host-local。

所以, 一个CNI 插件需要设计为可以用来作为其他plugin的ipam代理。

为了让CNI更轻便,而且让IPAM 和 cni (二进制)保持正交关系(低耦合)。

正交设计原则

消除重复
分离关注点
缩小依赖范围
向稳定的方向依赖

ipam 被设计为一个第三方的插件,为了保证ipam被代理调用,同时设计了一个协议,用于指导代理(ipam)功能的实现。
然而这不是容器运行时的职责,而是CNI plugin的职责: 在执行的过程中,适当的时间点,调用IPAM插件。
ipam 插件必须定义了ip,子网,网关,路由。 并且返回这些信息给“主” 插件去应用(把这些参数在调用cni二进制的时候给过去)。
ipam插件也有可能通过协议(dhcp)来获取这些信息。 或者数据存储在本地文件系统中。 或者网络配置的ipam部分。

被代理的插件协议(Delegated Plugin protocol)

和cni 插件一样,被代理的(ipam)插件也是通过调用一个可执行的(二进制)。 这些插件也在预先指定好的目录下,

# ls -l /opt/cni/bin/
total 177440
-rwxr-xr-x. 1 root root  4151672 Feb  5  2021 bandwidth # 带宽限制
-rwxr-xr-x. 1 root root  4536104 Feb  5  2021 bridge
-rwxr-xr-x. 1 root root 10270090 Feb  5  2021 dhcp # ipam
-rwxr-xr-x. 1 root root  4767801 Feb  5  2021 firewall # 防火墙限制
-rwxr-xr-x. 1 root root  3357992 Feb  5  2021 flannel
-rwxr-xr-x. 1 root root  4144106 Feb  5  2021 host-device
-rwxr-xr-x. 1 root root  3565330 Feb  5  2021 host-local # ipam
-rwxr-xr-x. 1 root root  4288339 Feb  5  2021 ipvlan
-rwxr-xr-x. 1 root root 65658880 Apr 10 03:51 kube-ovn
-rwxr-xr-x. 1 root root  3566204 Apr 10 03:51 loopback
-rwxr-xr-x. 1 root root  4497003 Apr 10 03:51 macvlan
-rwxr-xr-x. 1 root root 41849770 Apr 10 03:48 multus
-rwxr-xr-x. 1 root root  3979034 Apr 10 03:51 portmap # 端口映射
-rwxr-xr-x. 1 root root  4467317 Feb  5  2021 ptp
-rwxr-xr-x. 1 root root  3701138 Feb  5  2021 sbr
-rwxr-xr-x. 1 root root  3153330 Feb  5  2021 static
-rwxr-xr-x. 1 root root  3668289 Feb  5  2021 tuning
-rwxr-xr-x. 1 root root  4287972 Feb  5  2021 vlan
-rwxr-xr-x. 1 root root  3759977 Feb  5  2021 vrf

CNI_PATH: 用于指定CNI的路径

所有被代理的cni插件收到的环境变量都是一样的,就像cni插件一样,通过stdin收到网络配置,基于stdout传出配置。

被代理的插件(下层的)提供完整的网络配置给到(上层的)插件,(插件间按照主次顺序调用)

被代理的插件执行流程

当一个插件执行一个被代理的插件时,它应该:

在CNI_PATH(环境变量)对应目录下查找二进制对象
根据插件收到的配置和环境变量执行找到的二进制对象(执行时传给配置和环境变量)
确保 被代理的插件的stderr被传递给调用者的stderr对象

ADD 场景中,如果被代理的插件执行失败了,那么在返回错误前需要执行该插件的del操作。
同样的,DEL | check 上层插件检测失败,要将stderr返回给下层。

第五部分,返回值类型

只有3种类型

 Success (or Abbreviated Success) 
 Error
 _Version

5.1 Success

所有插件的请求配置内容中都会有一个 prevResult, prevResult 作为插件自身的输出。
任何插件都可能会对 prevResult 进行修改。 如果不修改,直接反射(传递)即可。

ADD 操作成功后,所有插件(链式执行后)必须返回一个json对象:
cniVersion:
interfaces: 网卡名, mac, ns
ips: attachment 配置好的ipcidr, 网关, 网卡index
routes: 路由 dst cidr,gw
dns: nameservers domain search options

ipam (被代理的插件)

被代理的插件可以忽略 相关的配置项。
ipam 必须返回一个缩略的 Success 成功对象,可能不会包含 interfaces 数组,以及ips 对象内部的interface。

错误对象暂时不了解

版本

插件必须返回cniVerison, 以及 supportedVersions


{
    "cniVersion": "1.0.0",
    "supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0" ]
}


示例

  1. CNI_COMMAND=ADD, 把如下参数传递给 bridge cni二级制 插件


{
    "cniVersion": "1.0.0", 
    "name": "dbnet", // 网络名
    "type": "bridge", // cni 二进制的名字
    "bridge": "cni0", // 参数 网桥名
    "keyA": ["some more", "plugin specific", "configuration"], // 额外参数
    "ipam": { // 被代理调用的ipam 插件
        "type": "host-local",
        "subnet": "10.1.0.0/16",
        "gateway": "10.1.0.1"
    },
    "dns": { // 被代理调用的 dns 插件 ?
        "nameservers": [ "10.1.0.1" ]
    }
}

接下来 主 cni (比如multus),会调用ipam(基于ipam内的参数,执行调用host-local二进制,将subnet gw属性基于stdin传递给该二进制)

host-local 会返回如下结果


{
    "ips": [
        {
          "address": "10.1.0.5/16",  // 分配出ip
          "gateway": "10.1.0.1"      // 返回网关
        }
    ],
    "routes": [
      {
        "dst": "0.0.0.0/0"
      }
    ],
    "dns": {
      "nameservers": [ "10.1.0.1" ]
    }
}

然后 bridge 二进制cni文件,基于ipam的返回结果,配置容器的网络,然后返回如下结果



{
    "ips": [
        {
          "address": "10.1.0.5/16",
          "gateway": "10.1.0.1",
          "interface": 2
        }
    ],
    "routes": [
      {
        "dst": "0.0.0.0/0"
      }
    ], // 以上是ipam的返回内容,保持不变
    "interfaces": [ //以下是cni添加的内容
        {
            "name": "cni0", // 网桥名
            "mac": "00:11:22:33:44:55"  // 网桥的mac
        },
        {
            "name": "veth3243", // veth-pair 接在网桥的一段
            "mac": "55:44:33:22:11:11" // 其mac
        },
        {
            "name": "eth0",  // 容器ns 内部的eth0 网卡,veth-pair的另一端
            "mac": "99:88:77:66:55:44", // mac
            "sandbox": "/var/run/netns/blue" // ns 路径
        }
    ],
    "dns": {
      "nameservers": [ "10.1.0.1" ]
    }
}



这个结果就是 bridge cni操作后的 prevResult。

第一个插件,一般是cni 二进制,它的prevResult是空的,但是它的(下一个|上层)插件会基于第一个插件的result作为prevResult。

  1. 基于 tuning (二进制)插件, 基于CNI_COMMAND=ADD, 调用 tuning的 add函数,基于 prevResult, 结合mac的capability (特性),stdin 收到的配置请求体如下

{
  "cniVersion": "1.0.0",
  "name": "dbnet",
  "type": "tuning", // 二进制插件
  "sysctl": {
    "net.core.somaxconn": "500"
  },
  "runtimeConfig": { // 特性, 将eth0的mac调整为该值
    "mac": "00:11:22:33:44:66"
  },
  "prevResult": { // 下层的返回
    "ips": [
        {
          "address": "10.1.0.5/16",
          "gateway": "10.1.0.1",
          "interface": 2
        }
    ],
    "routes": [
      {
        "dst": "0.0.0.0/0"
      }
    ],
    "interfaces": [
        {
            "name": "cni0",
            "mac": "00:11:22:33:44:55"
        },
        {
            "name": "veth3243",
            "mac": "55:44:33:22:11:11"
        },
        {
            "name": "eth0",
            "mac": "99:88:77:66:55:44",
            "sandbox": "/var/run/netns/blue"
        }
    ],
    "dns": {
      "nameservers": [ "10.1.0.1" ]
    }
  }
}


tuning 成功执行后的返回结果,可以看到eth0 mac已发生变化



{
    "ips": [
        {
          "address": "10.1.0.5/16",
          "gateway": "10.1.0.1",
          "interface": 2
        }
    ],
    "routes": [
      {
        "dst": "0.0.0.0/0"
      }
    ],
    "interfaces": [
        {
            "name": "cni0",
            "mac": "00:11:22:33:44:55"
        },
        {
            "name": "veth3243",
            "mac": "55:44:33:22:11:11"
        },
        {
            "name": "eth0",
            "mac": "00:11:22:33:44:66", // 已变化
            "sandbox": "/var/run/netns/blue"
        }
    ],
    "dns": {
      "nameservers": [ "10.1.0.1" ]
    }
}

tuning返回的prevResult

  1. 最后 调用 portmap 插件,CNI_COMMAND=ADD 对应 portmap add函数,使用的 prevResult如上,
    而 tuning 执行完毕后,如下

{
  "cniVersion": "1.0.0",
  "name": "dbnet",
  "type": "portmap", // 增加了该部分
  "runtimeConfig": {
    "portMappings" : [
      { "hostPort": 8080, "containerPort": 80, "protocol": "tcp" }
    ]
  },
  "prevResult": {// 下层的返回
    "ips": [
        {
          "address": "10.1.0.5/16",
          "gateway": "10.1.0.1",
          "interface": 2
        }
    ],
    "routes": [
      {
        "dst": "0.0.0.0/0"
      }
    ],
    "interfaces": [
        {
            "name": "cni0",
            "mac": "00:11:22:33:44:55"
        },
        {
            "name": "veth3243",
            "mac": "55:44:33:22:11:11"
        },
        {
            "name": "eth0",
            "mac": "00:11:22:33:44:66",
            "sandbox": "/var/run/netns/blue"
        }
    ],
    "dns": {
      "nameservers": [ "10.1.0.1" ]
    }
  }
}

DELETE正好是add 的逆操作,所以最上层的插件先执行,执行的参数就是上面ADD最后返回的结果。

相关文章

  • cni设计简析

    cni设计 参考: https://github.com/containernetworking/cni/blob...

  • CNI之flannel介绍

    CNI介绍 CNI 容器网络接口,设计用于任何的容器运行环境CNI插件生态有两种:overlay、underlay...

  • 读《为有暗香来》有感(其五)

    于红梅老师在《简析内容 确定目标 选择策略——语文教学设计之我见(其二)》中指出教材内容的简析是确定教学目标的基础...

  • Interaction Analysis - 1.3

    简书Settings页面交互设计简析 http://www.jianshu.com/settings 闲话少说,今...

  • 简析极简风格设计

    手机最开始出现只是为了通话,后来可以发短信,拍照片,听音乐,看视频。到现在手机完全成了我们身体的延伸,没有手机我们...

  • mybatis-spring解析

    1、概述 原生Mybatis源码简析(上)原生Mybatis源码简析(下)在介绍原生Mybatis源码简析文章中,...

  • 简析 Swift 的模块系统

    简析 Swift 的模块系统 简析 Swift 的模块系统

  • 简析设计规范

    写在前面,本文是通过阅读他人的教程经过整理和摘录而成,如有侵权行为出现请立即联系我,我马上进行处理,但一般都是开源...

  • [HandyJSON] 设计思路简析

    早早就说好要分享一下HandyJSON的实现思路了,今天总算有点时间,这里就记录一下吧。 背景 我所在iOS团队是...

  • 简析Swift和C的交互

    简析Swift和C的交互 简析Swift和C的交互

网友评论

      本文标题:cni设计简析

      本文链接:https://www.haomeiwen.com/subject/iyruurtx.html