《CNI specification》翻译

Overview

本文提出了一个通用的基于插件的Linux容器网络解决方案,容器网络接口,CNI。它脱胎于旨在满足大多数rtk网络设计的rtk Networking Proposal。

首先,我们对如下两个名词进行具体的定义:

  • container可以认为是与Linux network namespace是同义的。而一个network namespace具体对应什么,则与具体的容器运行时有关:例如,在rtk中,每个pod运行在一个单独的network namespace中。但是在docker中,network namespace存在于每个独立的Docker容器中
  • network代表了一组可以独立寻址并且可以互相交互的实体。这些实体既可以是一个单独的容器(如上所述),一台机器,或者其他什么网络设备(例如,一台路由器)。container可以加入一个或多个network,也可以从一个或多个network中移除。

本文旨在说明容器运行时和插件之间的接口。同时,可能还有些众所周知的字段,runtime也想传递给底层的插件,不过这些内容并不在本文中进行描述。

 

General consideration

首先容器运行时需要为container新建一个network namespace。之后,它需要决定该container属于哪些network,对于每个network还需要确定对应执行哪些插件。network configuration是以JSON格式存在的,很容易被存储在文件中。配置中需要包含一些必须的字段,例如,"name",“type”以及相应的插件必须的字段。同时,network configuration允许其中的字段在不同的调用间发生改变。因此,存在一个可选的"args"字段用于存放异变的信息。最后,容器运行时通过顺序地调用相应的插件来创建相应的network。当container的生命周期结束时,运行时再以相反的顺序调用插件,将它们从networks中移除。

 

CNI Plugin

Overview

每个CNI插件都是以一个能被容器管理系统(比如,rkt或者Docker)调用的可执行文件的形式存在的。

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

Parameters

CNI插件支持如下三种操作:

  • 将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", // 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
}

  

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

  • CNI_COMMAND: 表示进行的操作;ADD, DEL或者VERSION
  • CNI_CONTAINERID: Container ID
  • CNI_NETNS: network namespace文件的路径
  • CNI_IFNAME: 创建的interface的名字,插件必须使用这个名字,否则返回错误
  • CNI_ARGS: 在调用时用户传入的额外的参数,由以分号分割的,字母数字键值对组成,例如,"FOO=BAR;ABC=123"
  • CNI_PATH:用于查找CNI插件的可执行文件的路径列表,在Linux中,路径之间由":"分割,在Windows中用";"分割

以JSON形式存在的network configuration将以stdin的方式进入插件。这意味着它并不和磁盘上某个特定的文件绑定,因此它所包含的信息也能在每次调用之后发生改变

 

Result

需要注意的是IPAM插件返回的是一个精简的Result结构,对它的描述放在IP Allocation中

当执行的是ADD命令时,如果返回值是0,并且有如下的JSON输出到stdout,那么说明执行成功了。在IPAM插件返回的结果中同样需要对ips和dns字段进行适当的填充,但是interface字段除外,因为IPAM插件并不应该意识到interface的存在

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

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。

ips字段包含了一系列的IP配置信息,详情参见IP well-known structure。dns字段包含了一个由通用的DNS信息组成的字典。详情参见DNS well-known structure。specification中并没有这些信息到底应该被如何使用。例如产生一个/etc/resolv.conf文件插入容器文件系统中,或者在宿主机运行一个DNS forwarder。

如果遇到错误将得到一个非零的返回值以及如下形式的JSON输出:

{
  "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的值可以用于插件特定的错误。

另外,stderr可以用于输出一些unstructured output,例如logs。

 

Network Configuration

network configuration以JSON格式进行描述。configuration可以被存储在磁盘中或者通过容器运行时以其他方式产生。接下来是一些比较重要的字段:

  • 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插件的可执行文件的文件名
  • 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的可选项

插件可能会定义它们自己能接收的额外的字段,但是遇到一个未知的字段可能会产生错误。例外的是args字段,它可以被用于传输一些额外的字段,但可能会被插件忽略

 

Example configurations

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

  

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

  

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

  

Network Configuration Lists

Network configuration lists能够以指定顺序允许多个CNI插件,并且将每个插件的允许结果传递给下一个插件。列表中包含了一些众所周知的字段以及由一个或多个标准的CNI network configuration组成的列表(如上所示)。

列表以JSON格式描述,可以储存在磁盘中,也可以由容器运行时以其他方式产生。接下来的这些字段是众所周知的并且有对应的含义:

  • cniVersion(string):以Semantic Version 2.0描述的CNI版本,对此整个configuration list以及每个单独的configuraion必须遵从
  • name (string):Network name。这应该在整个管理域中都是唯一的
  • plugins (lists):一系列标准的CNI network configuration dictionary (如上所示)

当执行插件列表时,运行时必须用列表的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中,并且它的内容就是上一个插件的以JSON格式描述的结果。并且每个插件都必须将preResult的内容输出到stdout从而让后续的插件或者运行时可以获取该结果,除非,它们想要修改或限制之前的结果。插件是允许修改或限制全部或者部分的prevResult内容的。然而对于支持包含prevResult的CNI版本的插件,它必须显式地通过,修改或者限制prevResult,但是忽略该字段是不允许的。

同时,运行时必须在同一环境下执行列表中的每个插件

对于DEL操作,运行时必须以相反的顺序执行插件列表

 

Network Configuration List Error Handling

当在执行插件列表时发生了错误,那么运行时必须停止执行。如果ADD操作执行失败了,当运行时要处理错误时,它需要以和ADD相反的顺序对列表中的插件执行DEL操作,即使其中某些插件在ADD操作中还为被调用。

插件必须完整地执行DEL操作并不报错,即使有些资源缺失了。比如,对于IPAM插件,即使container network namespace 已经不存在了,它仍然会释放IP allocation并且成功返回,除非network namespace对于IPAM特别重要。尽管DHCP会向container network interface发送一个'release' message,但是因为DHCP leases都是有生命周期的,因此release操作并没有那么重要,也就不应该返回错误。另外,对于bridge插件即使container network namespace和/或者container network interface已经不存在了,它也要调用IPAM插件的DEL操作并且删除相应的资源(如果有的话)。

 

Example network configuration lists

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

  

Network configuration list runtime examples

基于上述的network configuraion list,容器运行时需要执行以下步骤来完成ADD操作。需要注意的是,运行时会将configuration list中的cniVersion和name字段添加到每个插件的configuration中,从而保证列表中所有插件的版本和名字一致。

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

{
  "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、接着以如下的JSON调用tuning插件,其中的prevResult字段包含了bridge插件返回的结果

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

  

给定同样的network configuraion list,容器运行时会以如下步骤完成DEL操作。需要注意的是,并不需要prevResult字段,因为DEL操作并不返回任何result。另外,插件的执行顺序和ADD是相反的。

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

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

  

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

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

  

IP Allocation

作为整个操作的一部分,CNI插件需要给interface分配并维护一个IP地址,并且还要安装一些和该interface有关的必要的路由。这给了CNI插件很大的灵活性同时也给它造成了很大的负担。许多插件需要重复编写多种用户想要的IP管理框架(例如,dhcp, host-local)。为了减轻各个插件的负担,并且将IP管理的功能独立出来,我们定义了第二种插件类型 -- IP Address Management 插件(IPAM插件)。此时,其他插件的任务就是在适当的执行过程中调用相应的IPAM插件。IPAM插件用于确定interface的IP/子网,网关,路由并且将这些信息返回"main" plugin去执行。IPAM插件可以从一个协议(如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字段包含了一个由通用的DNS信息组成的字典。详情参见DNS well-known structure

返回的Errors和logs的和CNI插件相同。详情参见CNI Plugin Result

IPAM插件的例子如下:

  • host-local:在一个特定的范围内选择一个未被其他container使用的IP
  • dhcp:使用DHCP协议获取并且维护一个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,描述了一个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):以CIDR描述的目标子网
  • 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

 

posted on 2017-08-21 17:38  姚灯灯!  阅读(1140)  评论(0编辑  收藏  举报

导航