95992828九五至尊2

深刻明白CNI

三月 19th, 2019  |  882828九五至尊手机版

一 、为啥会有CNI?

Overview

CNI是Container Network
Interface的缩写,简单地说,正是一个正经的,通用的接口。已知我们明日有丰硕多采的容器平台:docker,kubernetes,mesos,大家也有各样各种的器皿网络消除方案:flannel,calico,weave,并且还有各样新的化解方案在不断涌现。假使每出现二个新的解决方案,大家都亟待对双边举办适配,那么通过拉动的工作量必然是惊天动地的,而且也是双重和不须求的。事实上,我们即便提供3个规范的接口,更标准的说是一种协议,就能圆满地缓解上述难题。一旦有新的网络方案出现,只要它能满意那几个标准的说道,那么它就能为同样满意该协议的具有容器平台提供网络作用,而CNI正是这么的3个标准接口协议。

正文提议了二个通用的依照插件的Linux容器互联网化解方案,容器网络接口,CNI。它脱胎于意在满足当先1/4rtk网络陈设的rtk
Networking Proposal。

 

先是,大家对如下八个名词举行实际的定义:

2、什么是CNI?

  • container能够认为是与Linux network namespace是千篇一律的。而3个network
    namespace具体对应怎么样,则与具体的器皿运维时有关:例如,在rtk中,各种pod运转在3个独自的network
    namespace中。可是在docker中,network
    namespace存在于种种独立的Docker容器中
  • network代表了一组能够独自寻址并且能够并行交互的实业。这几个实体既能够是八个独门的器皿(如上所述),一台机械,可能别的什么网络设施(例如,一台路由器)。container能够进入三个或八个network,也足以从3个或多个network中移除。

通俗地讲,CNI是2个接口协议,用于连接容器管理种类和互连网插件。前者提供3个容器所在的network
namespace(从互连网的角度来看,network
namespace和容器是全然等价的),后者负责将network interface插入该network
namespace中(比如veth的一端),并且在宿主机做一些必不可少的配置(例如将veth的另一端加入bridge中),最后对namespace中的interface实行IP和路由的布置。那么CNI的干活实际上根本是从容器管理种类处获得运营时音讯,包罗network
namespace的不二法门,容器ID以及network interface
name,再从容器网络的计划文件中加载互联网陈设新闻,再将那一个音信传送给相应的插件,由插件进行实际的网络铺排工作,并将配备的结果再回到到容器管理种类中。

正文意在认证容器运维时和插件之间的接口。同时,大概还某个家喻户晓的字段,runtime也想传递给底层的插件,但是那一个内容并不在本文中展开描述。

最终,需求专注的是,在前边的CNI版本中,互联网计划文件只可以描述四个network,那也就标明了一个容器只可以进入八个容器互连网。然而在新生的CNI版本中,大家能够在铺排文件中定义叁个所谓的NetworkList,事实上就是概念2个network类别,CNI会依次调用各类network的插件对容器进行对应的配置,从而允许1个器皿能够进入七个容器互连网。

 

 

General consideration

3、怎么用CNI?

先是容器运转时需求为container新建1个network
namespace。之后,它供给控制该container属于怎么network,对于各种network还索要规定相应执行怎么样插件。network
configuration是以JSON格式存在的,很简单被积存在文件中。配置中供给包罗部分无法不的字段,例如,”name”,“type”以及对应的插件必须的字段。同时,network
configuration允许当中的字段在区别的调用间产生转移。由此,存在1个可选的”args”字段用于存放异变的新闻。最后,容器运转时经过各种地调用相应的插件来创设相应的network。当container的生命周期结束时,运维时再以相反的顺序调用插件,将它们从networks中移除。

在进一步追究CNI在此以前,小编觉着首先来探望CNI是怎么利用的,先对CNI有三个直观的认识,是很有必不可少的,那对我们现在的明亮也将尤其有救助。今后,大家将次第执行如下步骤来演示如何行使CNI,并对每一步的操作进行须要的辨证。

 

(1)、编写翻译安装CNI的合法插件

CNI Plugin

先天法定提供了三种档次的插件:main,meta和ipam。在那之中main类型的插件重要提供某种网络成效,比如我们在演示少校使用的brdige,以及loopback,ipvlan,macvlan等等。meta类型的插件无法看做独立的插件使用,它平常供给调用其余插件,例如flannel,可能同盟其余插件使用,例如portmap。最后ipam类型的插件其实是对具备CNI插件共有的IP管理有些的望梅止渴,从而收缩插件编写进度中的重复工作,官方提供的有dhcp和host-local两体系型。

Overview

接着执行如下命令,实现插件的下载,编写翻译,安装工作:

各种CNI插件都以以三个能被容器管理种类(比如,rkt可能Docker)调用的可执行文件的花样存在的。

$ mkdir -p $GOPATH/src/github.com/containernetworking/plugins

$ git clone https://github.com/containernetworking/plugins.git  $GOPATH/src/github.com/containernetworking/plugins

$ cd $GOPATH/src/github.com/containernetworking/plugins

$ ./build.sh

CNI插件负责将二个network interface插入container network
namespace(比如,veth
pair的内部一只)并且在宿主机中做一些必需的陈设(例如将veth的另一端参加bridge中)。接着通过调用适当的IPAM插件,将IP赋给interface并且安装路由。

  

Parameters

最后具备的插件都将以可执行文件的款型存在在目录$GOPATH/src/github.com/containernetworking/plugins/bin之下。

CNI插件援助如下三种操作:

 

  • 将container加入network(Add):
    • Parameters:
      • Version. 调用者使用的CNI 配置的版本音信
      • Container ID.
        那么些字段是可选的,不过建议采纳,在容器活着的时候须求该字段全局唯一的。比如,存在IPAM的条件恐怕会要求各种container都分配一个独立的ID,那样每1个IP的分配都能和一个一定的容器相关联。例如,在appc
        implementations中,container ID其实正是pod ID
      • Network namespace path. 那个字段表示要投入的network
        namespace的途径。例如,/proc/[pid]/ns/net可能对于该目录的bind-mount/link。
      • Network configuration.
        这是三个JSON文件用于描述container能够进入的network,具体内容在下文中讲述
      • Extra arguments.
        该字段提供了可选的建制,从而允许基于每种容器进行CNI插件的差不离布置
      • Name of the interface inside the container.
        该字段提供了在container (network
        namespace)中的interface的名字;由此,它也无法不符合Linux对于互连网命名的界定
    • Result:
      • Interface list. 依照插件的不比,这几个字段能够包含sandbox
        (container or hypervisor) interface的name,以及host
        interface的name,每一个interface的hardware
        address,以及interface所在的sandbox(假若存在的话)的音信。
      • IP configuration assigned to each interface.
        IPv4和/可能IPv6地址,gateways以及为sandbox或host
        interfaces中加上的路由
      • DNS inormation. 包含nameservers,domains,search
        domains和options的DNS information的字典
  •  将container从network中删除(Delete):
    • Parameter:
      • Version. 调用者使用的CNI 配置的版本消息
      • ContainerID. 定义同上
      • Network namespace path. 定义同上
      • Network configuration. 定义同上
      • Extra argument. 定义同上
      • Name of the interface inside the container. 定义同上
  • 版本新闻

    • Parameter: 无
    • Result: 再次回到插件协助的拥有CNI版本  

    {
    “cniVersion”: “0.3.1”, // the version of the CNI spec in use for this output
    “supportedVersions”: [ “0.1.0”, “0.2.0”, “0.3.0”, “0.3.1” ] // the list of CNI spec versions that this plugin supports
    }

(2)、创造布局文件,对所创制的互连网进行描述

  

工作目录”/etc/cni/net.d”是CNI暗中认可的网络铺排文件目录,当没有专门内定时,CNI就会私下认可对该目录举行搜寻,从中加载配置文件进行容器互联网的始建。至于对安顿文件相继字段的详细描述,笔者将在连续章节进行验证。

最后executable command-line
API会以network的type作为名字去调用相应的插件。它首先会在一多级预先定义好的目录中追寻该可执行文件。一旦找到,它就会用以下的环境变量作为参数去调用该可执行文件:

当今大家只须要执行如下命令,描述3个大家想要创立的容器网络”mynet”即可。为了简单起见,大家的NetworkList中可是唯有”mynet”那一个network。

  • CNI_COMMAND: 表示举行的操作;ADD, DEL或然VE宝马7系SION
  • CNI_CONTAINERID: Container ID
  • CNI_NETNS: network namespace文件的途径
  • CNI_IFNAME:
    创设的interface的名字,插件必须运用这么些名字,不然重回错误
  • CNI_A奥迪Q5GS:
    在调用时用户传入的附加的参数,由以分行分割的,字母数字键值对构成,例如,”FOO=BA途乐;ABC=123″
  • CNI_PATH:用于查找CNI插件的可执行文件的路线列表,在Linux中,路径之间由”:”分割,在Windows中用”;”分割
$ mkdir -p /etc/cni/net.d

$ cat >/etc/cni/net.d/10-mynet.conflist <<EOF
{
        "cniVersion": "0.3.0",
        "name": "mynet",
        "plugins": [
          {
                "type": "bridge",
                "bridge": "cni0",
                "isGateway": true,
                "ipMasq": true,
                "ipam": {
                        "type": "host-local",
                        "subnet": "10.22.0.0/16",
                        "routes": [
                                { "dst": "0.0.0.0/0" }
                        ]
                }
          }
        ]
}
EOF

$ cat >/etc/cni/net.d/99-loopback.conf <<EOF
{
    "cniVersion": "0.3.0",
    "type": "loopback"
}
EOF

以JSON方式存在的network
configuration将以stdin的不二法门进入插件。那意味着它并不和磁盘上有些特定的文件绑定,因而它所包蕴的新闻也能在历次调用之后发出变更

  

 

(3)、模拟CNI的实施进程,创立network
namespace,参加上文中描述的器皿网络”mynet”

Result

先是大家从github上下载、编写翻译CNI的源码,最后将在bin目录下生成3个名为”cnitool”的可执行文件。事实上,能够认为cnitool是3个模拟程序,大家先创建二个名为ns的network
namespace,用来模拟一个新成立的容器,再调用cnitool对该network
namespace举办互联网安插,从而模拟一个新建的容器参预3个容器互连网的历程。

亟需留意的是IPAM插件重返的是二个简洁的Result结构,对它的描述放在IP
Allocation中

从cnitool的实践结果来看,它会回到一个分包了interface,IP,路由等等各类音讯的json串,事实上它就是CNI对容器实行互联网布局后变化的结果新闻,对此大家将在继承章节进行详尽的描述。

当执行的是ADD命令时,若是重返值是0,并且有如下的JSON输出到stdout,那么注明履行成功了。在IPAM插件重临的结果中一致须求对ips和dns字段进行适量的填写,然则interface字段除外,因为IPAM插件并不应有发现到interface的留存

最后,我们能够看来network
namespace内新建的网卡eth0的IP地址为10.22.0.5/16,正好蕴涵在容器互联网”mynet”的子网范围10.22.0.0/16之内,由此大家得以认为容器已经打响进入了容器网络之中,演示成功。

{
  "cniVersion": "0.3.1",
  "interfaces": [                                            (this key omitted by IPAM plugins)
      {
          "name": "<name>",
          "mac": "<MAC address>",                            (required if L2 addresses are meaningful)
          "sandbox": "<netns path or hypervisor identifier>" (required for container/hypervisor interfaces, empty/omitted for host interfaces)
      }
  ],
  "ips": [
      {
          "version": "<4-or-6>",
          "address": "<ip-and-prefix-in-CIDR>",
          "gateway": "<ip-address-of-the-gateway>",          (optional)
          "interface": <numeric index into 'interfaces' list>
      },
      ...
  ],
  "routes": [                                                (optional)
      {
          "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-additional-search-domains>            (optional)
    "options": <list-of-options>                             (optional)
  }
}
$ git clone https://github.com/containernetworking/cni.git $GOPATH/src/github.com/containernetworking/cni

$ cd $GOPATH/src/github.com/containernetworking/cni

$ ./build.sh

$ cd $GOPATH/src/github.com/containernetworking/cni/bin

$ export CNI_PATH=$GOPATH/src/github.com/containernetworking/plugins/bin

$ ip netns add ns

$ ./cnitool add mynet /var/run/netns/ns 
{
    "cniVersion": "0.3.0",
    "interfaces": [
        {
            "name": "cni0",
            "mac": "0a:58:0a:16:00:01"
        },
        {
            "name": "vetha418f787",
            "mac": "c6:e3:e9:1c:2f:20"
        },
        {
            "name": "eth0",
            "mac": "0a:58:0a:16:00:05",
            "sandbox": "/var/run/netns/ns"
        }
    ],
    "ips": [
        {
            "version": "4",
            "interface": 2,
            "address": "10.22.0.5/16",
            "gateway": "10.22.0.1"
        }
    ],
    "routes": [
        {
            "dst": "0.0.0.0/0"
        }
    ],
    "dns": {}
}

$ ip netns exec ns ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.22.0.5  netmask 255.255.0.0  broadcast 0.0.0.0
        inet6 fe80::646e:89ff:fea6:f9b5  prefixlen 64  scopeid 0x20<link>
        ether 0a:58:0a:16:00:05  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 8  bytes 648 (648.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

cniVersion以Semantic Version
2.0的格式钦命了插件使用的CNI版本。interfaces描述了插件创造的network
interfaces。如若钦命了CNI_IFNAME,那么插件必须用该名字对sandbox/hypervisor
interface进行命名,不然再次回到错误

  

  • mac (string):interface的hardware
    address。假使对于插件来说L2地址是从未意思的,那二个该字段是可选的
  • sandbox (string):container/namespace-based
    environment须要回到sandbox所在的network
    namespace的共同体路径。Hypervisor/VM-based插件须要重临二个唯一的ID,代表新建的interface所在的virtualized
    sandbox。

4、怎么配CNI?

ips字段包罗了一文山会海的IP配置消息,详情参见IP well-known
structure。dns字段包括了叁个由通用的DNS音信整合的字典。详情参见DNS
well-known
structure。specification中并没有这几个消息毕竟应该被怎样行使。例如发生三个/etc/resolv.conf文件插入容器文件系统中,可能在宿主机械运输转三个DNS
forwarder。

从上文中大家能够明白,CNI只协理三种操作:ADD,
DEL,VERSION,而二种操作所须要配置的参数和结果如下:

比方遇到错误将获取2个非零的重返值以及如下情势的JSON输出:

  • 将container加入network(Add):
    • Parameters:
      • Version:CNI版本新闻
      • Container ID:
        那个字段是可选的,然而提议利用,在容器活着的时候须求该字段全局唯一的。比如,存在IPAM的条件也许会需要各样container都分配二个独自的ID,那样每二个IP的分配都能和贰个一定的容器相关联。在appc
        implementations中,container ID其实正是pod ID
      • Network namespace path:那些字段表示要参加的network
        namespace的路子。例如,/proc/[pid]/ns/net也许对于该目录的bind-mount/link。
      • Network configuration:
        那是贰个JSON文件用于描述container能够参预的network,具体内容在下文中描述
      • Extra
        arguments:该字段提供了一种可选机制,从而允许基于每种容器举办CNI插件的归纳安插
      • Name of the interface inside the
        container:该字段提供了在container (network
        namespace)中的interface的名字;因而,它也非得符合Linux对于互联网命名的范围
    • Result:
      • Interface list:依据插件的不比,这一个字段能够回顾sandbox
        (container or hypervisor) interface的name,以及host
        interface的name,每种interface的hardware
        address,以及interface所在的sandbox(假使存在的话)的音信。
      • IP configuration assigned to each
        interface:IPv4和/或许IPv6地址,gateways以及为sandbox或host
        interfaces中丰裕的路由
      • DNS inormation:包含nameservers,domains,search
        domains和options的DNS information的字典
  •  将container从network中删除(Delete):
    • Parameter:
      • Version:CNI版本新闻
      • ContainerID:定义同上
      • Network namespace path:定义同上
      • Network configuration:定义同上
      • Extra argument:定义同上
      • Name of the interface inside the container:定义同上
  • 版本新闻
    • Parameter:无
    • Result:再次来到插件补助的持有CNI版本  
{
  "cniVersion": "0.3.1",
  "code": <numeric-error-code>,
  "msg": <short-error-message>,
  "details": <long-error-message> (optional)
}

 

cniVersion以Semantic Version 2.0的格式钦赐了插件使用的CNI版本。Error
codes的0-99用以一些显著的荒谬(详情参见Well-known Error
Codes)。超越100的值可以用来插件特定的一无所长。

在上文的讲述中大家简要了对Network
configuration的讲述。事实上,它的剧情和上文演示实例中的”/etc/cni/net.d/10-mynet.conf”互联网安顿文件是一模一样的,用于描述容器了容器必要出席的互连网,下面是对中间有的关键字段的讲述:

其余,stderr能够用来出口一些unstructured output,例如logs。

  • cniVersion(string):cniVersion以Semantic Version
    2.0的格式钦点了插件使用的CNI版本
  • name (string):Network name。那应该在漫天管理域中都以绝无仅有的
  • type (string):插件类型,也表示了CNI插件可执行文件的文件名
  • args
    (dictionary):由容器运维时提供的可选的参数。比如,能够将二个由label组成的dictionary传递给CNI插件,通过在args下增添三个labels字段来贯彻
  • ipMasqs
    (boolean):可挑选(假如插件扶助的话)。为network在宿主机创造IP
    masquerade。借使需求将宿主机作为网关,为了能够路由到容器分配的IP,这么些字段是必须的
  • ipam:由特定的IPAM值组成的dictionary
    • type
      (string):IPAM插件的种类,也意味IPAM插件的可执行文件的公文名
  • dns:由特定的DNS值组成的dictionary
    • nameservers (list of
      strings):一星罗棋布对network可知的,以优先级顺序排列的DNS
      nameserver列表。列表中的每一项都富含了二个IPv4要么七个IPv6地址
    • domain (string):用于查找short hostname的本地方
    • search (list of strings):以优先级顺序排列的用来查找short
      domain的查找域。对于大多数resolver,它的预先级比domain更高
    • options(list of strings):一连串能够被传输给resolver的可选项

 

插件大概会定义它们本人能接受的额外的字段,不过际遇1个不明不白的字段只怕会时有产生错误。例外的是args字段,它能够被用于传输一些卓殊的字段,但也大概会被插件忽略

Network Configuration

 

network
configuration以JSON格式举办描述。configuration能够被积存在磁盘中要么通过容器运行时以任何措施发生。接下来是局地相比根本的字段:

⑤ 、CNI具体是怎么落到实处的?

  • cniVersion(string):cniVersion以塞马ntic Version
    2.0的格式钦定了插件使用的CNI版本
  • name (string):Network name。那应该在全体管理域中都以绝无仅有的
  • type (string):代表了CNI插件可执行文件的文件名
  • args
    (dictionary):由容器运营时提供的可选的参数。比如,能够将3个由label组成的dictionary传递给CNI插件,通过在args下扩大一个labels字段
  • ipMasqs
    (boolean):可挑选(假使插件扶助的话)。为network在宿主机创造IP
    masquerade。那些字段是必须的,要是需求将宿主机作为网关,从而能够路由到容器分配的IP
  • ipam:由特定的IPAM值组成的dictionary
    • type (string):表示IPAM插件的可执行文件的文本名
  • dns:由特定的DNS值组成的dictionary
    • nameservers (list of
      strings):一体系对network可知的,以先行级顺序排列的DNS
      nameserver列表。列表中的每一项都富含了三个IPv4要么二个IPv6地址
    • domain (string):用于查找short hostname的本地点
    • search (list of strings):以优先级顺序排列的用于查找short
      domain的查找域。对于超越五成resolver,它的事先级比domain更高
    • options(list of strings):一多级能够被传输给resolver的可选项

到近期甘休,大家对CNI的运用、配置和法则都早已有了基本的认识,所以也是时候基于源码来对CNI做二个淋漓尽致的知晓了。上面,我们将以上文中的示范实例作为线索,以模拟程序cnitool作为切入口,来对任何CNI的实践进程实行详细的解析。

插件也许会定义它们自个儿能收到的额外的字段,可是遭受3个未知的字段也许会生出错误。例外的是args字段,它能够被用于传输一些外加的字段,但只怕会被插件忽略

(1)、加载容器互连网计划音讯

 

首先大家来看一下器皿网络陈设的数据结构表示:

Example configurations

type NetworkConfigList struct {
    Name       string
    CNIVersion string
    Plugins    []*NetworkConfig
    Bytes      []byte
}

type NetworkConfig struct {
    Network *types.NetConf
    Bytes   []byte
}

// NetConf describes a network.
type NetConf struct {
    CNIVersion string `json:"cniVersion,omitempty"`

    Name         string          `json:"name,omitempty"`
    Type         string          `json:"type,omitempty"`
    Capabilities map[string]bool `json:"capabilities,omitempty"`
    IPAM         struct {
        Type string `json:"type,omitempty"`
    } `json:"ipam,omitempty"`
    DNS DNS `json:"dns"`
}
{
  "cniVersion": "0.3.1",
  "name": "dbnet",
  "type": "bridge",
  // type (plugin) specific
  "bridge": "cni0",
  "ipam": {
    "type": "host-local",
    // ipam specific
    "subnet": "10.1.0.0/16",
    "gateway": "10.1.0.1"
  },
  "dns": {
    "nameservers": [ "10.1.0.1" ]
  }
}

  

  

通过不难的分析今后,大家得以窥见,数据结构表示的内容和示范实例中的json配置文件主旨是如出一辙的。因而,这一步的源码完毕极粗略,基本流程如下:

{
  "cniVersion": "0.3.1",
  "name": "pci",
  "type": "ovs",
  // type (plugin) specific
  "bridge": "ovs0",
  "vxlanID": 42,
  "ipam": {
    "type": "dhcp",
    "routes": [ { "dst": "10.3.0.0/16" }, { "dst": "10.4.0.0/16" } ]
  }
  // args may be ignored by plugins
  "args": {
    "labels" : {
        "appVersion" : "1.0"
    }
  }
}
  • 先是确虞诩顿文件所在的目录netdir,假若没有越发钦赐,则暗许为”/etc/cni/net.d”
  • 调用netconf, err := libcni.LoadConfList(netdir,
    os.Args[2]),当中参数os.Args[2]为用户内定的想要参与的network的名字,在演示示例中即为”mynet”。该函数首先会寻找netdir中是或不是有以”.conflist”作为后缀的布局文件,要是有,且布局音讯中的”Name”和参数os.Args[2]如出一辙,则一向用配备音讯填写并重返NetConfigList即可。不然,查找是或不是存在以”.conf”或”.json”作为后缀的安插文件。同样,要是存在”Name”一致的铺排,则加载该配置文件。由于”.conf”或”.json”中都以单个的网络计划,因而必要将其包装成仅有三个NetConfig的NetworkConfigList再回到。到此结束,容器网络布置加载成功。

  

 

{
  "cniVersion": "0.3.1",
  "name": "wan",
  "type": "macvlan",
  // ipam specific
  "ipam": {
    "type": "dhcp",
    "routes": [ { "dst": "10.0.0.0/8", "gw": "10.0.0.1" } ]
  },
  "dns": {
    "nameservers": [ "10.0.0.1" ]
  }
}

(2)、配置容器运维时消息

  

同样,大家先来看一下容器运维时音讯的数据结构:

Network Configuration Lists

type RuntimeConf struct {
    ContainerID string
    NetNS       string
    IfName      string
    Args        [][2]string
    // A dictionary of capability-specific data passed by the runtime
    // to plugins as top-level keys in the 'runtimeConfig' dictionary
    // of the plugin's stdin data.  libcni will ensure that only keys
    // in this map which match the capabilities of the plugin are passed
    // to the plugin
    CapabilityArgs map[string]interface{}
}

Network configuration
lists能够以内定顺序允许三个CNI插件,并且将各类插件的同意结果传递给下2个插件。列表中含有了部分眼看的字段以及由三个或四个正式的CNI
network configuration组成的列表(如上所示)。

  

列表以JSON格式描述,可以储存在磁盘中,也能够由容器运转时以其它措施发生。接下来的这一个字段是引人侧目标同时有对应的含义:

其间最要害的字段无疑是”NetNS”,它钦赐了特殊必要投入容器网络的network
namespace路径。而Args字段和CapabilityArgs字段都以可选的,用于传递额外的布局音信。具体的剧情参见上文中的布局表达。在上文的言传身教实例中,大家并没有对Args和CapabilityArgs进行其余的配置,为了简单起见,大家能够直接认为它们为空。由此,cnitool对RuntimeConf的安顿也就颇为简略了,只须要将参数钦点的netns赋值给NetNS字段,而ContainerID和IfName字段随意赋值即可,暗中同意将它们分别赋值为”cni”和”eth0″,具体代码如下:

  • cniVersion(string):以Semantic Version
    2.0描述的CNI版本,对此整个configuration
    list以及各样独立的configuraion必须遵守
  • name (string):Network name。那应该在总体管理域中都是绝无仅有的
  • plugins (lists):一多元春式的CNI network configuration dictionary
    (如上所示)
rt := &libcni.RuntimeConf{
    ContainerID:    "cni",
    NetNS:          netns,
    IfName:         "eth0",
    Args:           cniArgs,
    CapabilityArgs: capabilityArgs,
}

当执行插件列表时,运转时必须用列表的name和cniVersion字段替代每一种network
configuraion的name和cniVersion字段。那确定保证了列表中插件的name和CNI版本都以同等的,从而防止插件之间发生版本冲突。假若插件通过network
configuration的capability字段表明它帮忙某种specific
capability,那么运维时务必将capability-based
keys以map的方式插入插件的config
JSON的runtimeConfig字段中。同时,传给runtimeConfig的key必须和network
configuration的capabilities key的名字如出一辙。

  

对于ADD操作,运维时必须添加二个prevResult字段到下五个插件的configuration
JSON中,并且它的剧情正是上1个插件的以JSON格式描述的结果。并且每一种插件都无法不将preResult的始末输出到stdout从而让持续的插件可能运转时能够获取该结果,除非,它们想要修改或限制从前的结果。插件是同意修改或限制全体可能部分的prevResult内容的。然则对于协理包涵prevResult的CNI版本的插件,它必须显式地由此,修改也许限制prevResult,然而忽略该字段是不容许的。

(3)、参加容器网络

而且,运营时必须在同一环境下实施列表中的每种插件

 根据加载的器皿网络安顿音信和容器运行时音讯,执行出席容器互连网的操作,并将推行的结果打字与印刷输出

对此DEL操作,运维时务必以相反的逐一执行插件列表

switch os.Args[1] {
case CmdAdd:
    result, err := cninet.AddNetworkList(netconf, rt)
    if result != nil {
        _ = result.Print()
    }
    exit(err)
    ......
}

 

  

Network Configuration List Error
Handling

  

当在实施插件列表时发生了不当,那么运转时务必下马实施。若是ADD操作实施破产了,当运维时要处理错误时,它须求以和ADD相反的相继对列表中的插件执行DEL操作,固然当中一些插件在ADD操作中还为被调用。

接下去大家进入AddNetworkList函数中

插件必须完全地推行DEL操作并不报错,即便稍微能源缺点和失误了。比如,对于IPAM插件,尽管container
network namespace 已经不设有了,它还是会自由IP
allocation并且成功重返,除非network
namespace对于IPAM尤其主要性。就算DHCP会向container network
interface发送2个’release’ message,可是因为DHCP
leases都以有生命周期的,因而release操作并不曾那么重庆大学,也就不应该回到错误。其余,对于bridge插件即便container
network namespace和/大概container network
interface已经不设有了,它也要调用IPAM插件的DEL操作并且删除相应的能源(借使有个别话)。

// AddNetworkList executes a sequence of plugins with the ADD command
func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
    var prevResult types.Result
    for _, net := range list.Plugins {
        pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
                .....
        newConf, err := buildOneConfig(list, net, prevResult, rt)
                ......
        prevResult, err = invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args("ADD", rt))
                ......
    }

    return prevResult, nil
}

 

  

Example network configuration
lists

从函数上方的笺注我们就足以精通到,该函数的效劳就是按顺序对NetworkList中的种种network执行ADD操作。该函数的履行进度也尤其清楚,利用二个循环遍历NetworkList中的各样network,并对每种network举行如下三步操作:

{
  "cniVersion": "0.3.1",
  "name": "dbnet",
  "plugins": [
    {
      "type": "bridge",
      // type (plugin) specific
      "bridge": "cni0",
      // args may be ignored by plugins
      "args": {
        "labels" : {
            "appVersion" : "1.0"
        }
      },
      "ipam": {
        "type": "host-local",
        // ipam specific
        "subnet": "10.1.0.0/16",
        "gateway": "10.1.0.1"
      },
      "dns": {
        "nameservers": [ "10.1.0.1" ]
      }
    },
    {
      "type": "tuning",
      "sysctl": {
        "net.core.somaxconn": "500"
      }
    }
  ]
}
  • 先是,调用FindInPath函数,依照newtork的品类,在插件的寄放路径,也正是上文中的CNI_PATH中找找是还是不是存在对应插件的可执行文件。若存在则赶回其绝对路径pluginPath
  • 紧接着,调用buildOneConfig函数,从NetworkList中领到分离出最近进行ADD操作的network的NetworkConfig结构。那Ritter别需求小心的是preResult参数,它是上二个network的操作结果,也将被编码进NetworkConfig中。须要注意的是,当我们在推行NetworkList时,必须将前一个network的施行结果作为参数字传送递给当下正在开始展览实践的network。并且在buildOneConfig函数创设每一个NetworkConfig时会暗中认可将在那之中的”name”和”cniVersion”和NetworkList中的配置保持一致,从而防止争辩。
  • 最后,调用invoke.ExecPluginWithResult(pluginPath, netConf.Bytes,
    c.args(“ADD”,
    rt))真正实施network的ADD操作。那里我们要求注意的是netConf.Bytes和c.args(“ADD”,
    rt)那多个参数。个中netConf.Bytes用于存放NetworkConfig中的NetConf结构以及诸如上文中的prevResult实行json编码形成的字节流。而c.args()函数用于营造1个Args类型的实例,在那之中重要囤积容器运维时音讯,以及履行的CNI操作的音信,例如”ADD”或”DEL”,和插件的仓库储存路径。

  

事实上ExecPluginWithResult仅仅是1个打包函数,它仅仅只是调用了函数defaultPluginExec.WithResult(pluginPath,
netconf, args)之后,就径直再次来到了。

Network configuration list runtime
examples

func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) {
    stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
        .....
    // Plugin must return result in same version as specified in netconf
    versionDecoder := &version.ConfigDecoder{}
    confVersion, err := versionDecoder.Decode(netconf)
        ....
    return version.NewResult(confVersion, stdoutBytes)
}

依照上述的network configuraion
list,容器运营时索要实施以下步骤来成功ADD操作。供给小心的是,运行时会将configuration
list中的cniVersion和name字段添加到各样插件的configuration中,从而保障列表中享有插件的本子和名字一样。

  

壹 、首先以如下格式调用bridge插件

可以看得出WithResult函数的执行流也是非常明显的,同样也足以分成以下三步执行:

{
  "cniVersion": "0.3.1",
  "name": "dbnet",
  "type": "bridge",
  "bridge": "cni0",
  "args": {
    "labels" : {
        "appVersion" : "1.0"
    }
  },
  "ipam": {
    "type": "host-local",
    // ipam specific
    "subnet": "10.1.0.0/16",
    "gateway": "10.1.0.1"
  },
  "dns": {
    "nameservers": [ "10.1.0.1" ]
  }
}
  • 率先调用e.RawExec.ExecPlugin(pluginPath, netconf,
    args.AsEnv())函数执行实际的CNI操作,对于它的具体内容,大家将在下文举办分析。此处须求注意的是它的第陆个参数args.AsEnv(),该函数做的干活实际上正是取得已部分环境变量,并且将args内的消息,例如CNI操作命令,以环境变量的样式保存起来,以例如”CNI_COMMAND=ADD”的样式传输给插件。由此大家可以掌握,容器运行时音信、CNI操作命令以及插件存储路径都是以环境变量的花样传递给插件的
  • 继之调用versionDecoder.Decode(netconf)从network配置中分析出CNI版本音信
  • 最后,调用version.NewResult(confVersion,
    stdoutBytes),依据CNI版本,营造相应的归来结果

  

说到底,我们来探望e.RawExecPlugin函数是怎么样操作的,代码如下所示:

二 、接着以如下的JSON调用tuning插件,当中的prevResult字段包蕴了bridge插件重返的结果

func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
    stdout := &bytes.Buffer{}

    c := exec.Cmd{
        Env:    environ,
        Path:   pluginPath,
        Args:   []string{pluginPath},
        Stdin:  bytes.NewBuffer(stdinData),
        Stdout: stdout,
        Stderr: e.Stderr,
    }
    if err := c.Run(); err != nil {
        return nil, pluginErr(err, stdout.Bytes())
    }

    return stdout.Bytes(), nil
}
{
  "cniVersion": "0.3.1",
  "name": "dbnet",
  "type": "tuning",
  "sysctl": {
    "net.core.somaxconn": "500"
  },
  "prevResult": {
    "ips": [
        {
          "version": "4",
          "address": "10.0.0.5/32",
          "interface": 0
        }
    ],
    "dns": {
      "nameservers": [ "10.1.0.1" ]
    }
  }
}

  

  

在看完代码之后,我们恐怕有微微的失望。因为这一个理论上卓殊主题的函数却出人意表的简易,它所做的干活仅仅只是exec了插件的可执行文件。话虽如此,大家如故有以下几点须要留意:

加以同样的network configuraion
list,容器运营时会以如下步骤达成DEL操作。要求小心的是,并不供给prevResult字段,因为DEL操作并不回去任何result。其余,插件的施行顺序和ADD是相反的。

  • 容器运转时消息以及CNI操作命令等都以以环境变量的花样传递给插件的,那一点在上文中已经持有提及
  • 容器网络的配备音讯是经过正式输入的格局传递给插件的
  • 插件的周转结果是以正规化输出的花样再次回到给CNI的

一 、首先以如下JSON调用tuning插件

到此甘休,整个CNI的执行流已经丰硕了然了。不难地说,三个CNI插件正是三个可执行文件,我们从安插文件中获取network配置音信,从容器管理体系处拿到运维时消息,再将前者以专业输入的花样,后者以环境变量的款式传递传递给插件,最后以布署文件中定义的逐一依次调用各样插件,并且将前一个插件的履行结果包蕴在network配置消息中传递给下四个推行的插件,整个经过就是那般。鉴于篇幅所限,本文仅仅只分析了CNI的ADD操作,可是相信有了上文的底蕴之后,精通DEL操作也不会太难。

{
  "cniVersion": "0.3.1",
  "name": "dbnet",
  "type": "tuning",
  "sysctl": {
    "net.core.somaxconn": "500"
  }
}

  

  

参照链接:

二 、接着以如下JSON调用bridge插件

[1] CNI源码:https://github.com/containernetworking/cni

{
  "cniVersion": "0.3.1",
  "name": "dbnet",
  "type": "bridge",
  "bridge": "cni0",
  "args": {
    "labels" : {
        "appVersion" : "1.0"
    }
  },
  "ipam": {
    "type": "host-local",
    // ipam specific
    "subnet": "10.1.0.0/16",
    "gateway": "10.1.0.1"
  },
  "dns": {
    "nameservers": [ "10.1.0.1" ]
  }
}

[2] CNI plugin源码:https://github.com/containernetworking/plugins

  

[3]
《Kubernetes指南》:https://feisky.gitbooks.io/kubernetes/network/cni/

IP Allocation

[4]
《CNI:容器网络接口》:http://cizixs.com/2017/05/23/container-network-cni

作为一切操作的一片段,CNI插件要求给interface分配并维护贰个IP地址,并且还要设置一些和该interface有关的必需的路由。那给了CNI插件十分的大的八面玲珑同时也给它导致了非常大的承受。许多插件需求再次编写多种用户想要的IP管理框架(例如,dhcp,
host-local)。为了减轻各类插件的承负,并且将IP管理的效应独立出来,我们定义了第二种插件类型
— IP Address Management
插件(IPAM插件)。此时,别的插件的义务正是在适龄的进行进度中调用相应的IPAM插件。IPAM插件用于明确interface的IP/子网,网关,路由并且将这一个新闻再次来到”main”
plugin去执行。IPAM插件能够从3个磋商(如dhcp)中,大概从当地文件系统存款和储蓄的多少中,可能network
configuration file中的”ipam”字段,大概上述那几个情势的构成人中学获取信息。

 

IP Address Management (IPAM) Interface

和CNI插件类似,IPAM插件也是以运转可执行文件的方法被调用的。可执行文件将会在一部分先行定义的门道列表中摸索,通过CNI_PATH钦定。IPAM插件将赢得全体传输给CNI插件的环境变量,并且和CNI插件一样,IPAM通过stdin获取network
configuration

对此ADD命令,固然重返值为0,并且stdout中有如下的JSON格式,表明履行成功

{
  "cniVersion": "0.3.1",
  "ips": [
      {
          "version": "<4-or-6>",
          "address": "<ip-and-prefix-in-CIDR>",
          "gateway": "<ip-address-of-the-gateway>"  (optional)
      },
      ...
  ],
  "routes": [                                       (optional)
      {
          "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插件分歧的是,IPAM插件重回的是简化的Result结构,在那之中不包含interfaces字段,因为IPAM插件不应当关注它们的父插件配置的interfaces,那多少个有特殊供给的IPAM插件除外(例如,dhcp
IPAM插件)。

ips字段蕴含了一文山会海的IP配置音讯,详情参见IP well-known structure

dns字段包涵了2个由通用的DNS音信整合的字典。详情参见DNS well-known
structure

再次来到的Errors和logs的和CNI插件相同。详情参见CNI Plugin Result

IPAM插件的例证如下:

  • host-local:在1个一定的限量内选拔一个未被其余container使用的IP
  • dhcp:使用DHCP协议获得并且尊敬3个IP的租用。DHCP
    request会通过刚创造的container
    interface发送出来,因此相关的network必须协理广播

Notes:

  • 路由应该被布署为0 metric
  • 暗许路由相应安排为”0.0.0.0/0″。因为任何的network也许早已配备了默许路由,CNI插件必须能够跳过已部分默许配置

Well-known Structures

IPs

  "ips": [
      {
          "version": "<4-or-6>",
          "address": "<ip-and-prefix-in-CIDR>",
          "gateway": "<ip-address-of-the-gateway>",      (optional)
          "interface": <numeric index into 'interfaces' list> (not required for IPAM plugins)
      },
      ...
  ]

  

ips字段是贰个由插件决定的IP配置列表。每种条目都以都以一个dictionary,描述了1个network
interface的IP配置。多个network
interface的IP配置和单个interface上的八个IP配置都将以ips列表中的区别条目重临。插件已知的保有性格都要提供,就算并不是严谨必须的:

  • version
    (string):”4″只怕”6″,代表了条目中IP地址的本子。全部的IP地址和网关地址都要符合相应的版本
  • address (string):CIDR形式的IP地址(例如,”192.168.1.3/24″)
  • gateway
    (string):对应子网的暗中同意网关,假设存在的话。并不供给CNI插件添加别的与网关相关的路由。路由是单身通过routes字段钦定的。叁个使用该字段的例子是,CNI
    bridge插件将该地址添加到Linux bridge中,将其看做网关。
  • interface
    (uint):该值表示,此IP配置必要功力的interface,在回去的结果JSON中的interfaces字段中对应的下标。IPAM插件不可能回到那一个值,因为它们没有其他关于network
    interface的音讯

Routes

  "routes": [
      {
          "dst": "<ip-and-prefix-in-cidr>",
          "gw": "<ip-of-next-hop>"               (optional)
      },
      ...
  ]

  

每一种routes字段由以下内容组成。全部的IP地址在routes中必然要有雷同的IP版本,4要么6

  • dst (string):以CIDHaval描述的靶子子网
  • gw
    (string):网关的IP地址。借使该字段不设有,则只要为私下认可网关(由CNI插件决定)

DNS

  "dns": {
    "nameservers": <list-of-nameservers>                 (optional)
    "domain": <name-of-local-domain>                     (optional)
    "search": <list-of-additional-search-domains>        (optional)
    "options": <list-of-options>                         (optional)
  }

  

dns字段包括了由局部通用的DNS音讯整合的dictionary

  • nameservers (list of
    strings):一密密麻麻对network可知的,以先行级顺序排列的DNS
    nameserver列表。列表中的每一项都带有了叁个IPv4要么贰个IPv6地址
  • domain (string):用于查找short hostname的本地点
  • search (list of strings):以先行级顺序排列的用于查找short
    domain的查找域。对于超过一半resolver,它的优先级比domain更高
  • options (list of strings):一名目繁多可以被传输给resolver的可选项

 

Well-known Error Codes

  • 1 – CNI版本不般配
  • 2 – network
    configuration存在不辅助的字段。错误音信中必须包括不援助字段的key和value

 

相关文章

Your Comments

近期评论

    功能


    网站地图xml地图