本文是关于深入理解Kubernetes网络原理系列第3章
  • 深入理解Kubernetes Pod网络原理 - 网络名称空间
  • 深入理解Kubernetes Pod网络原理 - Linux虚拟网络技术
  • 深入理解Kubernetes Pod网络原理 - CNI
  • 深入理解Kubernetes Pod网络原理 - 跟随 flannel 学习CNI原理
  • 深入理解Kubernetes Pod网络原理 - 跟随 flannel + multus 剖析 Chained Plugins
  • 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 1 (Shell)
  • 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 2 (libcni)
  • 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 1
  • 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 2
  • 深入理解Kubernetes Pod网络原理 - Pod网络排错思路

概述

这是我在 kubernetes 网络之旅中的中的第4部分,主要学习 CNI 规范(5要素,旧版本是4要素)。要深深记住这些,在后面旅程中常常被用到。

Notes

本文讲解均按照 1.1.0 规范进行,但目前流行的 CNI 可能并不使用该版本,例如 2014 年维护的 k8s 1.29 等版本,安装的最新 cilium 使用的规范也是 0.3.1,这些在后续 cni 联系部分会提到。

本文中 SPEC 是 CNI 规范的意思,Plugins/插件 为 CNI Plugin 意思

CNI

在 k8s blogs [1] 中有提到 cri-containerd (是 containerd 为 kubernetes CRI plugin 的实现,现被合并到 containerd 项目中),这里有一张图很详细的说明了当在创建一个 Pod cri-containerd 是如何工作的。

image-20250208212209394

图1:how CRI talks to CNI plugin to attach a network interface to the newly created pod.
Source:https://blog.devops.dev/simplifying-multi-network-connectivity-in-openshift-with-multus-cni-dabe4da209f8

图1 表示了 CNI 在 kubernetes 云原生环境中的概略图,左上角我们有一个Pod (Kubernetes中称为一组容器), 我们将把 kubernetes 网络附加到 Pod中。CNI 是 一个通用的接口,它能够用于任何容器运行时和任何网络,Kubernetes 与 CNI 之间没有很强的联系。runtime 会调用 CNI,图中有一个动词 “ADD” ,还有其他的动词,例如 “DEL” 。“ADD” 是向容器添加网络接口,将想要完成的操作放置在 “JSON” 文本中,该文件是提供给 CNI Plugins 调用时使用。例如告知 Plugin 网络 provider (OVS? Linux bridge? or BPF)。

CNI Plugin 的行为就像 “胶水” 在 kubernetes 网络中,只需要一些 “胶水” 操作网络插件,就可以将你的网络和容器进行连接

在这里不再详细的阐述 CRI-Containerd 中 Pod 启动的原理,仅仅了解 CNI 如何适配即可。

什么是CNI

CNI 包含两部分,“规范” (Specification) 和 “插件” (Plugins)

  • 规范文档:和编程语言无关 (The CNI spec is language agnostic.),定义了配置的格式,用于如何提供 CNI 插件,例如 CNI 插件应该做什么,使用哪个 CNI 的操作详情,和 CNI 返回 k8s runtime 的结果。
    • libcni,CNI 运行时的实现
    • CNI skel
  • 插件:网络插件是包含一组示例插件,他们不依赖于特定的云系统 (Cloud sytem, 这里指的是容器编排环境,k8s, nomad),实际使用 cli 在命令行运行。
    • 接口插件 (Interface plugins):ptp, bridge, macvlan…
    • 链式插件 (Chained plugins):portmap, bandwidth, tuning。
tip
CNI 规范部分适用于许多不同符合 CNI SPEC 标准的 CNI 插件。

CNI规范

CNI规范 (CNI Specification) 是一个和编程语言无关的,供应商中立的 (vendor-neutral)。不仅仅是用于 Kubernetes 的标准,也被用于 nomad, mesos 等 orch。他定义了网络配置格式(包含 runtime 和 cni 之间传递的参数,版本控制,返回结果等),执行流和网络操作,正如 overview 中提到的 “动词” 。

CNI五要素

定义了 Kubernetes 网络插件 (plugins) 的标准,就是 runtime 与 Plugin 之间的接口。主要分为“五要素”。

  • 网络配置格式 (Network configuration format),通过这部分,可以让管理员能够使用“什么”类型的配置来定义网络。
  • 执行协议 (Execution Protocol),约定了容器运行时与网络插件之间通信的协议。
  • 网络配置的执行 (Execution of Network Configurations),根据配置的定义来执行相应的网络插件。
  • 插件委派 (Plugin Delegation);允许插件链式调用其他插件,以实现更复杂的网络功能。通过这个约束,可以将本插件的功能委托给另外一个插件执行。
  • 结果类型 (Result Types)。规范了插件返回给容器运行时的结果格式。

Section 1 网络配置格式

网络配置格式是对配置的概述,通常实现了下面的功能。

  1. 基于 JSON 的配置,在插件执行时,此配置格式由 runtime 解析。
  2. 标准 KEY(CNI SPEC规范定义的),也有特定的 KEY(传递给 CNI 使用的参数的 KEY)。
  3. 每一次操作使用 stdin 将配置传递给 Plugin。
  4. 网络配置存储在磁盘,或 rumtime。

下面是一个示例配置文件,定义了一个 mynet 配置,使用了 3 个插件 (bridge 和 ipam) ,这里 plugins 部分还包含了一个 “dns” KEY,这是 SPEC 中的 KEY,用于定义 dns server 相关配置。

yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
1
{
2
  "cniVersion": "1.1.0",
3
  "cniVersions": ["0.3.1", "0.4.0", "1.0.0", "1.1.0"],
4
  "name": "dbnet",
5
  "plugins": [
6
    {
7
      "type": "bridge",
8
      // plugin specific parameters
9
      "bridge": "cni0",
10
      "keyA": ["some more", "plugin specific", "configuration"],
11

12
      "ipam": {
13
        "type": "host-local",
14
        // ipam specific
15
        "subnet": "10.1.0.0/16",
16
        "gateway": "10.1.0.1",
17
        "routes": [
18
            {"dst": "0.0.0.0/0"}
19
        ]
20
      },
21
      "dns": {
22
        "nameservers": [ "10.1.0.1" ]
23
      }
24
    },
25
    {
26
      "type": "tuning",
27
      "capabilities": {
28
        "mac": true
29
      },
30
      "sysctl": {
31
        "net.core.somaxconn": "500"
32
      }
33
    },
34
    {
35
        "type": "portmap",
36
        "capabilities": {"portMappings": true}
37
    }
38
  ]
39
}

Section 2 执行流

执行协议也成为“执行流” (execution flow),在这部分中

  • 基本操作 (Verb),ADD, DEL, CHECK, GC (1.1.0), VERSION。
  • 网络插件是可以执行的文件。在 kubernetes 环境中作为单独程序执行。
  • 网络操作由运行时启动或创建。
  • 通过 stdin 将“配置文件” (JSON) 和 “环境变量” 传递给 plugins。
  • 通过配置文件可以传递指定参数给 plugin (via stdin)、
  • 通过 stdout 报告执行结果。
  • runtime执行插件时通常在 “runtime” 的 network namespace。(通常指的是root)

参数 (Parameters)

  • CNI_COMMAND:指示期望的操作 (Verb); 例如 ADD, DEL, CHECK, GC, or VERSION.
  • CNI_CONTAINERID: Container ID. 由runtime分配的container的唯一标识符 (Plaintext);该参数必须不为空,字母数字开头,可以包含 点(.), 连字符 (-), 下划线 (_)。
  • CNI_NETNS: A reference to the container’s “isolation domain”. If using network namespaces, then a path to the network namespace (e.g. /run/netns/[nsname])
  • CNI_IFNAME: 容器内创建的网络接口的名称,如果不能使用这个名称创建网络接口,则必须返回错误
  • CNI_ARGS: 传递给 CNI Plugin 的额外参数。用分号 (;) 分割的 key value对, “FOO=BAR;ABC=123”
  • CNI_PATH: CNI Plugins 可执行文件的路径列表,根据不同的操作系统使用不同的路径分隔符; 例如 Linux 是 “:”, Windows 是 “;”。

错误 (Error)

当 CNI 执行成功时退出码为 “0”,不为“0” 时 为错误,并且 CNI Plugin 应该输出 result 结构的“错误” (Result部分见 section 5)。

Verb (CNI 操作)

CNI 1.1.0 定义了 5 个动词,版本 1.1.0 为 4 个 (without GC),0.4.0之前的版本为 3 个 (without CHECK), 包含 ADD, DEL, CHECK, GC, 和 VERSION. 通过环境变量 CNI_COMMAND 传递。

ADD: 为容器添加网络,或者应用网络配置的变更

当收到 ADD 命令后,至少要执行下面两个操作之一:

  • 使用环境变量 CNI_IFNAMECNI_NETNS创建网络接口。
  • 或者根据环境变量 CNI_IFNAMECNI_NETNS 来调整网络配置。
tip

这里需要注意的是,

  • runtime不应该对相同的 (CNI_CONTAINERID, CNI_IFNAME) 调用两次 ADD。
  • 插件执行成功,必须通过 stdout (standard out) 输出标准的 result 结构。
  • 如果插件提供了 prevResult 作为 input 部分,那么必须处理 prevResult
  • 如果“网络接口”在容器内已经存在,网络插件必须返回 result error 结构

ADD 的输入是通过标准输入 (STDIN) 输入 JSON 序列化后的配置文件,输出是标准输出 (STDOUT) 的标准 result 结构。

输入

参数说明
CNI_COMMAND运行的 verb
CNI_CONTAINERID容器的唯一ID
CNI_IFNAME要创建的网卡接口名称
CNI_NETNS容器的网络名称空间
CNI_ARGS(可选)传递给 CNI 插件的额外参数
CNI_PATH(可选)CNI 插件路径
DEL: 删除容器的网络

容器收到 DEL 命令后,至少要执行下面两个操作之一:

  • 删除 CNI_NETNS 定义网络名称空间内的,CNI_IFNAME 定义的容器内的网络接口。
  • 撤掉 ADD 函数内应用的任何修改。

DEL 的输入是通过标准输入 (STDIN) 输入 JSON 序列化后的配置文件,输出是标准输出 (STDOUT) 的标准 result 结构。

tip
这里需要注意的是,网络插件必须支持针对相同的网络对 (CNI_CONTAINERID, CNI_IFNAME) 的多次 DEL 调用,并且在缺少相关网络接口或任意添加的更改不存在时返回 success

输入

参数说明
CNI_COMMAND运行的 verb
CNI_CONTAINERID容器的唯一ID
CNI_IFNAME要创建的网卡接口名称
CNI_NETNS(可选)容器的网络名称空间
CNI_ARGS(可选)传递给 CNI 插件的额外参数
CNI_PATH(可选)CNI 插件路径
CHECK: 检查容器网络是否符合预期

CHEK是 0.4.0 版本新增的功能,在 CNCF 演讲中也有提到,CNI 开发者意识到在 ADD 后可能会出现错误(因为有时网络是异步创建的;有时 runtime 会发生改变),CHECK的目的是让 runtime 定期调用,检查网络是否按照预期方式进行设置。

Plugin 考虑事项:

  • 插件必须咨询 prevResult(上一次 ADD 命令的结果)来了解预期的网络接口和地址。
  • 插件必须允许后面链式插件可修改后的网络资源。例如在 ADD 操作后添加路由。
  • 以下情况插件必须返回错误:
    • 如果 CNI Result 中包含资源 (interface, address or route) 是由插件创建的,并且在 prevResult 中有列出,但是丢失或无效的状态。
    • 如果 CNI Result 类型中没有记录的其他资源(例如防火墙规则 (Firewall rules)、流量控制 (Traffic shaping controls)、IP 保留 (IP reservations)、外部依赖 (External dependencies) 等)缺失或无效。
    • 如果插件意识到容器不可达时。
  • 插件必须能处理在 ADD 后立即调用的 CHECK,并且需要考虑到一些资源可能是异步加载的,因此需要给出合理的延迟时间来完成。
  • 插件应该调用任何委派插件(例如 IPAM 插件)的 CHECK,并将错误传递给调用者。

runtime 考虑事项:

  • runtime 掉要 CHECK 必须遵循:
    • 没有执行 ADD 操作的不能调用 CHECK。
    • 已经在 ADD 之后执行过 DEL 操作的不能调用 CHECK。
  • 在配置文件中配置了 disableCheck=true 的不能调用 CHECK。
  • runtime 需要在网络配置中包含 prevResult 字段,该字段存储容器上一次 ADD 操作的结果。runtime 可以通过使用 libcni 提供的缓存功能来实现。
  • runtime 可以选择在某个插件返回错误时停止执行后续的 CHECK 操作。
  • runtime 可以在成功执行 ADD 后,直到容器被删除之前,执行 CHECK
  • 运行时可以假定错误的/失败的 CHECK ,容器处于永久性配置错误的状态。
tip
这里需要注意的是,所有的参数除了 CNI_PATH,必须和这个容器的 ADD 操作时参数一致。

输入

参数说明
CNI_COMMAND运行的 verb
CNI_CONTAINERID容器的唯一ID
CNI_NETNS容器的网络名称空间
CNI_IFNAME要创建的网卡接口名称
CNI_ARGS(可选)传递给 CNI 插件的额外参数
CNI_PATH(可选)CNI 插件路径

STATUS: 检查插件状态

tip
STATUS 是绝定网络插件就绪的一种方式,是 CNI 1.1.0 中新增的规范,可能其他 CNI 解决方案并未支持。

如果“插件”准备就绪去完成 ADD 请求,那么插件的退出码必须为0,反之如果“插件”知道无法完成 ADD 请求,那么它的退出码必须为“非0”并且通过标准输出 (STDOUT) 输出标准 result 结构(详见 Section 5)。

下列错误码在 STATUS 的上下文中定义:

  • 50: The plugin is not available (i.e. cannot service ADD requests)
  • 51: The plugin is not available, and existing containers in the network may have limited connectivity.

插件需考虑的内容:

  • Status is purely informational. A plugin MUST NOT rely on STATUS being called.
  • 插件必须不能依赖 STATUS 调用,即 Status 仅仅提供信息参考
  • 当 STATUS 返回了一个错误,例如 ADD, DEL 操作仍然被允许调用, Status 不会作为一个前置条件,不能避免 Runtime 的其他请求。
  • 如果插件依赖另外的委托插件 (delegated plugin), 去完成 ADD 请求,在他自己收到了一个 STATUS 请求,也必须对此执行 STATUS 请求。如果委托插件返回一个错误 result,那么执行插件 (通常为当前插件) 也应该返回一个错误 result。

输入

参数说明
CNI_PATH(可选)CNI 插件路径
VERSION : 探测插件版本的支持

该插件应通过标准输出 (STDOUT) JSON 序列化后的对象。包含 cniVersion:使用协议的版本

输入参数说明
CNI_COMMAND执行的 Verb
GC: 清理任意的旧资源
tip

GC 操作允许 runtime 指定 “期望” 的网络附件集合 “插件”,其后网络插件可以删除与此网络附加集合中不存在的相关的任何资源。例如,预留的IPAM;防火墙规则。

GC 是 CNI 1.1.0 中新增的规范,可能其他 CNI 解决方案并未支持。

这个操作的目标和遵守如下所述几点:

  1. 插件应该尽可能清理无效资源。
  2. 插件可以假设隔离域(通常为网络命名空间)已被删除。
  3. GC 过程中插件应该尽可能完成清理,不应因错误中断。
  4. 插件必须转发 GC 请求到它依赖的插件。
  5. runtime 不能用 GC 代替 DEL。

输入

参数说明
CNI_COMMAND执行的 Verb
CNI_PATHCNI 插件路径

Section 3 网络配置的执行

这一部分是 section 2 执行流的一个补充,主要阐述了了 “runtime 如何解析网络配置并调用 CNI 插件”,这个操作被称为 “attachment”。通过 (CNI_CONTAINERID, CNI_IFNAME) 来识别唯一的 attachment。

生命周期和顺序 (Lifecycle & Ordering)

  1. 容器运行时在调用任何插件前,必须为容器创建一个网络名称空间。
  2. 容器运行时不能为同一个容器进行并行操作,但是允许调用并行操作对不同的容器,这包含多个 attachment
    1. 异常:容器运行时独占执行 “gc” 或 “ADD,” “DEL”,容器运行时必须确保在执行 “GC” 没有 “ADD,” 或 “DEL” 在处理中(或执行中)。并且在执行新的 “ADD,” 或 “DEL” 前必须等待 “GC” 完成。
  3. 网络插件在执行时,当处理在不同容器上同时执行的场景。如有必要,必须对共享资源上实施锁定(例如操作 IPAM数据库)。
  4. 容器运行时必须确保每次执行 “add” 操作后,最终会跟随一个对应的 “delete” 操作;(唯一的例外是在发生灾难性故障(例如节点丢失)时。即使 “add” 操作失败,也必须执行 “delete” 操作。)
  5. “delete” 操作后可以跟随其他的 “deletes” 操作。
  6. 在执行 “add” 和 “delete” 操作之间,网络配置不应发生变化。
  7. 在 “attachment” 之间,网络配置不应发生变化。
  8. 容器运行时负责清理容器的网络命名空间。

Attachment 参数

对于配置,在 “attachment” 之间是不应发生变化的,但是容器运行时为每个 “attachment” 提供了特定的参数。

参数说明
Container ID容器的唯一明文标识符,由“容器运行时”分配。不能为空。必须以字母或数字字符开头,后面可以跟着字母、数字字符、下划线(_)、点(.)或连字符(-)。在执行过程中,设置为 CNI_CONTAINERID 参数。
Namespace容器的“隔离域”引用。如果使用网络命名空间,则是网络命名空间的路径(例如 /run/netns/[nsname])。在执行过程中,始终设置为 CNI_NETNS 参数。
Container interface name要在容器内创建的接口名称。在执行过程中,应始终设置为 CNI_IFNAME 参数。
Generic Arguments特定 attachment 相关的附加参数,以 key, value 对的形式提供。在执行过程中,始终设置为 CNI_ARGS 参数。
**Capability Arguments **这些也是 key, value 对。key 是字符串,而 value 是任何可以 JSON 序列化的类型。key 和 value 由协定 (Conventions) 定义。

另外容器运行时必须提供 CNI Plugin 的路径列表,在执行过程中通过环境变量 “CNI_PATH” 提供给网络插件。

添加一个Attachment

对于每个网络配置插件 key 中定义的每一个配置

  1. 查找在 type 字段中指定的可执行文件。如果该文件不存在,则为错误。
  2. 从插件配置中派生请求配置,具有以下参数:
    1. 如果这是列表中的第一个插件,则没有提供先前一个 result;
    2. 对于所有后续插件,先前的 result 是前一个插件的执行 result。
  3. 执行二进制网络插件文件,并将 Verb 设置为 CNI_COMMAND=ADD。将上述定义的参数作为环境变量提供。通过标准输入 (STDIN) 提供派生的配置。
  4. 如果插件返回错误,停止执行并将错误返回给调用者。
由于执行 checkdelete 时需要,容器运行时必须永久存储最终插件执行返回的结果,

删除一个Attachment

删除动作与添加相似,但有一些不同之处:

  1. 网络配置中的插件顺序是以相反的顺序执行,例如,清除防火墙规则,删除预留 IPAM,删除网络接口。
  2. 提供的前一个 result 始终是 add 操作的最终 result。

对于网络配置中 plugins 字段定义的每个插件,按相反顺序执行:

  1. 查找在 type 字段中指定的可执行文件。如果该文件不存在,则为错误。
  2. 从插件配置中派生请求配置,并使用初始 add 操作的先前 result。
  3. 执行二进制插件文件,并将 Verb 设置为 CNI_COMMAND=DEL。将上述定义的参数作为环境变量提供。通过标准输入 (STDIN) 提供派生的配置。
  4. 如果插件返回错误,停止执行并将错误返回给调用者。

如果所有插件都执行成功,返回成功给调用者(通常为 rumtime)

垃圾回收网络

容器运行时还可以请求网络配置中的每个插件通过 GC 命令清理任何过期的资源。该步骤没有 Attachment 参数,但对于网络配置文件中 plugins 部分定义的每个插件的执行协定 (convention [2] , 这是 CNI 的另一个部分),有下面要求

  1. 查找在 type 字段中指定的可执行文件。如果该文件不存在,则为错误。
  2. 从插件配置中派生请求配置。
  3. 执行二进制插件文件,并将 Verb 设置为 CNI_COMMAND=GC。通过标准输入 (STDIN) 提供派生的配置。
  4. 如果插件返回错误,继续执行,并将所有错误返回给调用者。

如果所有插件都执行成功,返回成功给调用者(通常为 rumtime)

从插件配置中派生请求配置

网络配置格式(即要执行的插件配置列表)必须转换为插件可以理解的格式(即对单个执行的插件配置)。派生配置 (Deriving request configuration) 就是转换过程。

由于在执行单个插件调用的请求配置也是 JSON 格式。它由插件配置组成,除了指定的 ADD 和 DEL 外,其他部分通常不变。

运行时必须始终插入以下字段到请求配置中:

  • cniVersion:运行时选择的协议版本 - 例如,字符串 “1.1.0”
  • name:来自网络配置中的 name 字段,例如,flannel, cilium

对于特定的 Attachment (ADD, DEL, CHECK),还需要额外的字段:

  • runtimeConfig:运行时必须插入一个对象,包含插件提供的功能和容器运行时请求的功能的并集。
  • prevResult:运行时必须插入包含 “前一个” 插件返回的 result 类型。 “前一个” 的含义由具体的操作 (ADD, DEL, CHECK) 来定义 。在链中的第一个添加操作中此字段不用配置。
  • capabilities:不能配置。
Deriving 配置说明

虽然 CNI_ARGS 会提供给所有插件,并且没有指示它们是否会被使用,但 capabilities 参数需要在配置中明确声明。因此,运行时可以确定指定的网络配置是否支持特定的功能。capabilities不是由 SPEC 定义的,而是由 CNI 协定 [2] 文档进行说明。

下面是一个 capabilities 参数的示例,他标注了该插件有端口映射的能力。

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
1
{
2
  "type": "myPlugin",
3
  "capabilities": {
4
    "portMappings": true
5
  }
6
}

runtimeConfig 参数是从网络配置中的 capabilities 和运行时生成的 capabilities 参数中派生出来的。具体来说,任何由插件配置支持并由运行时提供的 capabilities 都应插入到 runtimeConfig 配置中。

因此,上述示例中的说明配置可能导致以下内容作为执行配置的一部分传递给插件:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
1
{
2
  "type": "myPlugin",
3
  "runtimeConfig": {
4
    "portMappings": [ { "hostPort": 8080, "containerPort": 80, "protocol": "tcp" } ]
5
  }
6
  ...
7
}

Section 4: 插件委托 (Plugin Delegation)

在执行操作时,CNI 插件可能希望将一些功能委托给其他插件。例如 IPAM。

作为其操作的一部分,CNI 插件需要为接口分配并维护 IP 地址,并创建与该网络接口相关的必要路由。因此 CNI 插件可以选择将 IP 管理委托给另一个插件。

但在使用中,为了避免在许多 CNI 插件使用时可能需要相同的代码来支持用户可能需要的几种 IP 管理的方案。

需要注意的是,是CNI 插件调用 IPAM 插件 ,而不是运行时。CNI 插件必须在适当的时刻调用 IPAM 插件。IPAM 插件必须确定接口的 IP, Mask, Gateway, Routers,并将这些信息返回给 “主” 插件以便应用。IPAM 插件也可以通过协议 (DHCP), 本地文件系统上的存储数, 网络配置文件中的 “ipam” 部分定义等方式获取这些信息。

委托插件的执行过程如下:

  1. 在环境变量 CNI_PATH 中提供的插件路径列表中找到插件的二进制文件。
  2. 使用收到的环境变量和配置执行插件。
  3. 确保委托插件的 STDERR 输出是输出到调用插件的 STDERR。

如果执行操作为 CNI_COMMAND=CHECK, DEL, 也可以执行任意的委托插件,当任意委托插件返回错误,错误也将返回给前一个插件。

如果执行操作为 CNI_COMMAND=ADD,如果委托插件执行错误,则上个插件应在返回错误之前执行 DEL。

Section 5: Result 类型

Result 类型是插件在执行后输出的一个结果信息的标准,他是一个 JSON 结构的内容。通过标准输出传递给运行时。

ADD 成功时应输出的类型的 schema

当执行 ADD 时,输出的结果必须为下面的结构

KEY说明
cniVersion和输入时相同的版本,字符串类型,例如 “1.1.0”
interfacesattachment 创建所有成功的接口,数组类型,包含主机级别 (host-level) 的接口
name (string): 接口的名称.
mac (string): 接口的mac地址 (if applicable).
mtu: (uint) 接口的 MTU (maximum transmission unit) 值 (if applicable).
sandbox (string): 接口引用的隔离域,如果是 host 网络则为空。对于容器内创建接口通过环境变量 CNI_NETNS 传递.
socketPath (string, optional): 该接口对应的 socket 文件的绝对路径 (if applicable).
pciID (string, optional): 如果适用,表示与该接口对应的 PCI 设备的特定平台标识符
ips这个 attachment 分配的IP,
address (string): CIDR标识的IP地址 (eg “192.168.1.3/24”).
gateway (string): 如果存在为这个子网的默认网关
interface (uint): CNI 插件 Result 接口列表中的索引,用于指定这个个接口应该被应用这个 IP 配置。
routes这个 attachment 创建的路由
dst:CIDR 表示的路由的目标地址。
gw:下一跳地址。如果未设置,可以使用 ips 数组中 gateway 的值。
mtu (uint):最大传输单元(MTU)。
priority (uint):路由的优先级,数值越小优先级越高。
table (uint):路由添加到的路由表。
scope (uint):路由前缀所覆盖的目标的范围 (global (0), link (253), host (254))。
dnsnameservers (list of strings): 网络中 nameserver 的优先级列表,包含字符串 IPV4 和 IPV6
domain (string): 本地域
search (list of strings): 搜索域列表,优先于 domain
options (list of strings): 传递给 resolver 的列表
插件在其请求配置中提供了prevResult key,必须将其作为结果输出,并包括该插件可能做出的任何修改。如果插件没有做出任何在 Success 结果类型中反映的更改,则必须输出一个与提供的 prevResult 等效的结果。

用于 Delegated plugins (IPAM) 返回类型

Delegated plugins 必须返回一个缺少 interfaces, ips 的简版 Success 对象。

VERSION Success

VERSION 操作返回的 schema

  • cniVersion: 输入中指定的cniVersion 的值
  • supportedVersions: 支持的规格版本的列表
json
1
2
3
4
5
6
7
8
1
{
2
    "cniVersion": "1.0.0",
3
    "supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0" ]
4
}

Error

  • cniVersion: 使用的版本协议
  • code: 数字错误码,错误代码 [3] 0-99 用于保留。 100+ 的值可自由用于分配插件特定错误。
  • msg: 描述错误的短说明,例如异常类型
  • details: 错误详细描述
json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
1
{
2
  "cniVersion": "1.1.0",
3
  "code": 7,
4
  "msg": "Invalid Configuration",
5
  "details": "Network 192.168.0.0/31 too small to allocate from."
6
}

通过实例理解 CNI SPEC

本章节内容采用的官方给出的示例来说明 CNI 配置一些示例配置文件

ADD

容器运行时将执行下面的步骤来执行 ADD 操作。

  1. 使用下面 JSON 格式的配置 调用 bridge plugin,执行 ADD 操作 (CNI_COMMAND=ADD )
json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1
{
2
    "cniVersion": "1.1.0",
3
    "name": "dbnet",
4
    "type": "bridge",
5
    "bridge": "cni0",
6
    "keyA": ["some more", "plugin specific", "configuration"],
7
    "ipam": {
8
        "type": "host-local",
9
        "subnet": "10.1.0.0/16",
10
        "gateway": "10.1.0.1"
11
    },
12
    "dns": {
13
        "nameservers": [ "10.1.0.1" ]
14
    }
15
}

接下来 ipam 作为 bridge 的代理,执行本地的二进制文件 host-local,输入参数为 CNI_COMMAND=ADD.

host-local 将返回下面内容

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
1
{
2
    "ips": [
3
        {
4
          "address": "10.1.0.5/16",
5
          "gateway": "10.1.0.1"
6
        }
7
    ],
8
    "routes": [
9
      {
10
        "dst": "0.0.0.0/0"
11
      }
12
    ],
13
    "dns": {
14
      "nameservers": [ "10.1.0.1" ]
15
    }
16
}

bridge 根据委托插件 ipam 来配置接口,并返回下面信息

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
1
{
2
    "ips": [
3
        {
4
          "address": "10.1.0.5/16",
5
          "gateway": "10.1.0.1",
6
          "interface": 2
7
        }
8
    ],
9
    "routes": [
10
      {
11
        "dst": "0.0.0.0/0"
12
      }
13
    ],
14
    "interfaces": [
15
        {
16
            "name": "cni0",
17
            "mac": "00:11:22:33:44:55"
18
        },
19
        {
20
            "name": "veth3243",
21
            "mac": "55:44:33:22:11:11"
22
        },
23
        {
24
            "name": "eth0",
25
            "mac": "99:88:77:66:55:44",
26
            "sandbox": "/var/run/netns/blue"
27
        }
28
    ],
29
    "dns": {
30
      "nameservers": [ "10.1.0.1" ]
31
    }
32
}
  1. 接下来 tuning 插件使用环境变量 CNI_COMMAND=ADD 指定操作 verb。需要注意的是,这里提供了 prevResult 以及 MAC 配置相关参数。返回的结果如下
json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
  "cniVersion": "1.1.0",
  "name": "dbnet",
  "type": "tuning",
  "sysctl": {
    "net.core.somaxconn": "500"
  },
  "runtimeConfig": {
    "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" ]
    }
  }
}

插件返回的结果如下,MAC 的配置将被改变

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
    "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" ]
    }
}
  1. 最终调用 portmap 插件,使用 CNI_COMMAND=ADD,需要注意的是,prevResult 匹配通过之前的 tuning 插件返回的 result。
json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
  "cniVersion": "1.1.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" ]
    }
  }
}

PortMap 插件输出的结果与 Bridge 插件返回的结果完全相同,因为该插件没有修改任何会更改 result。

这个示例是作为 section 1 中示例配置ide说明,他的完整网络配置如下:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
  "cniVersion": "1.1.0",
  "cniVersions": ["0.3.1", "0.4.0", "1.0.0", "1.1.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}
    }
  ]
}

Delete

对于同一网络配置JSON列表,容器运行时将对删除操作执行下面步骤。需要注意的一点是,插件对比 ADD 和 CHECK 操作是以相反顺序执行的。

  1. 首先, portmap 使用下列配置文件 调用 DEL 操作 (CNI_COMMAND=DEL)
json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
  "cniVersion": "1.1.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" ]
    }
  }
}
  1. 其次,tuning 使用下面配置执行 DEL 请求 (CNI_COMMAND=DEL)
json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
  "cniVersion": "1.1.0",
  "name": "dbnet",
  "type": "tuning",
  "sysctl": {
    "net.core.somaxconn": "500"
  },
  "runtimeConfig": {
    "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": "00:11:22:33:44:66",
            "sandbox": "/var/run/netns/blue"
        }
    ],
    "dns": {
      "nameservers": [ "10.1.0.1" ]
    }
  }
}
  1. 最后调用 bridge 插件
json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
{
  "cniVersion": "1.1.0",
  "name": "dbnet",
  "type": "bridge",
  "bridge": "cni0",
  "keyA": ["some more", "plugin specific", "configuration"],
  "ipam": {
    "type": "host-local",
    "subnet": "10.1.0.0/16",
    "gateway": "10.1.0.1"
  },
  "dns": {
    "nameservers": [ "10.1.0.1" ]
  },
  "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" ]
    }
  }
}

bridge 插件返回前执行委托插件 host-local 操作 DEL ( CNI_COMMAND=DEL) 。

Reference

[1] Containerd Brings More Container Runtime Options for Kubernetes[2] CNI Conventions[3] CNI result error code