《containerd 系列》一文读懂 containerd 中的 NRI 机制
本文内容节选自 《containerd 原理剖析与实战》
本文介绍 containerd 中的一种可插拔的扩展机制 NRI。
1. NRI 介绍
NRI(Node Resource Interface),即节点资源接口。是 containerd 中位于 CRI 插件中的一种扩展机制。NRI 可以提供容器不同生命周期事件的接口,用户在不修改容器运行时源代码的情况下添加自定义逻辑。NRI 在 containerd 中的定位如图所示。
<p align=center>图 containerd CRI Plugin 中的 NRI</p>
类似于 CNI 插件机制,NRI 插件机制允许将第三方自定义逻辑插入兼容 OCI 的运行时,例如,可以在容器生命周期时间点执行 OCI 规定范围之外的操作,分配和管理容器的设备和其它资源,以及修改原始 OCI 信息。
NRI 本身与任何容器运行时的内部实现细节无关,containerd 社区的目标是希望在常用的 OCI 运行时(containerd 和 cri-o)中实现对于 NRI 插件的支持。
2. NRI 插件的工作流程
下面介绍 NRI 的工作流程 。一个正常的 CRI 请求流程如下图所示。
<p align=center>图 容器信息在 CRI 调用中的转化过程</p>
如图所示,通过 k8s 创建 Pod 时:
kubelet 将 Pod 信息转化为 CRI 的请求信息调用 CRI 容器运行时,如 containerd 或者 cri-o。
CRI 容器运行时将 CRI 请求信息转化为 OCI 格式,调用低级容器运行时,如 runc,kata 等。
runc,kata 通过 OCI 格式将容器进程启动起来。 接下来介绍 NRI 在 CRI 的请求流程中是如何工作的,如下图所示。
图 NRI 在 CRI 的请求流程中的工作流程
如图所示,NRI 插件工作在通过 OCI 调用低级容器运行时操作容器之前:
CRI 容器运行时将 CRI 请求转化为 OCI Spec 之后通过 NRI adaptation 调用 NRI 插件,(NRI adaptation 的功能包括插件发现、启动和配置,将 NRI 插件与运行时 Pod 和容器的生命周期事件关联,NRI adaptation 也就是 NRI 插件的 client)。NRI adaptation 将 Container 和 Pod 的信息(OCI Spec 的子集)传递给 NRI 插件,同时接收 NRI 插件返回的信息来更新容 OCI Spec。
NRI adaptation 通过 ttrpc 调用 NRI Plugin,NRI adaptation 作为 NRI 的 client,NRI 协议同样是基于 protobuf 的 ttRPC 接口。其中 ttrpc 交互的 socket 为
/var/run/nri/nri.sock
。NRI Plugins 作为 ttrpc 的 Server,接受 NRI adaptation 的请求,处理相关的逻辑,并将相应返回给 NRI adaptation。
containerd 通过 NRI adaptation 得到修改后的 OCI Spec,通过修改后的 OCI Spec 启动对应的低级容器运行时,如 kata、runc 等。
3. NRI 插件协议介绍
NRI 插件协议主要是定义了一套容器运行时与 NRI 插件交互的 API。API 共有两部分:
TTRPC API: 是基于 protobuf 定义的容器运行时对接 NRI 插件的 API。containerd 基于 ttrpc 与 NRI 插件中的 stub 进行通信。stub 是 NRI 项目实现的代码框架,类似于 k8s scheduler framework 的扩展方式,基于该框架开发自己的 NRI 插件时,与 containerd 的交互将被 stub 组件接管。
Stub Interface: 该接口用于自定义 NRI 插件时实现的接口,该接口是 NRI Plugin 中的 stub 与自定义逻辑交互的接口。自定义 NRI Plugin 逻辑可以实现其中的一个或多个有关 Pod 以及 容器生命周期的接口。 NRI 插件协议如下图 所示。
图 NRI Plugin 协议 API
TTRPC API
TTRPC API 中主要包含两类 API: Runtime Service 和 Plugin Service:
Runtime Service: 顾名思义是在容器运行时中实现的 Service,NRI 项目抽象了 adaptation 的框架,该框架中封装集成了 Runtime Service 的 Server 实现,容器运行时中引用少量代码即可接入,当前 containerd 将 adaptation 框架集成在了 CRI Plugin 中。该接口的 Client 端在 NRI Plugin 中的 stub 中,Server 端位于 CRI Plugin 中的 adaptation 中。Runtime Service 是容器运行时暴露给 NRI Plugin 的公共接口,所有请求都是由插件发起的。该接口提供两个功能:插件注册、容器更新。
Plugin Service: 该接口是在 NRI Plugin 中实现的 Server。同样 NRI 项目抽象了 stub 框架,该框架封装了 Plugin Service 的 Server 实现。NRI stub 是用于开发自定义 NRI 插件的框架,用户开发自己的自定义 NRI 插件时,只需引用并集成 stub,实现自己的 Stub Interface 接口即可。Plugin Service 是 NRI 插件中暴露给容器运行时的接口。这个接口上的所有请求都是由 NRI adaptation 发起的。该接口提供以下功能:1. 配置插件。2. 获得已经存在的 pod 和容器的初始列表。3. 将插件 hook 到 pod/container 的生命周期事件中 4. 关闭插件。
❝
📢 注意 adaptation 是 NRI 项目提供给容器运行时的适配库,供容器运行时 (如 CRI Plugin)用来集成 NRI 并与 NRI 插件交互。它实现了基本的插件发现、启动和配置。它还提供了必要的功能: 将 NRI 插件与容器运行时的 Pod 和 容器 的生命周期事件挂钩。多个 NRI 插件可以同时处理任何一个 pod 或容器的生命周期事件,adaptation 按照一定的顺序调用插件,并将多个插件的响应合并为一个。在合并响应时,当检测到多个插件对单个容器所做的任何改变有冲突时,会将该事件对应的错误返回给容器运行时。
❝
stub 则是 NRI 项目提供给 NRI 插件开发者的另一个库,该库封装了许多实现 NRI 插件的底层细节。stub 负责通过和 adaptation 连接建立、插件注册、配置插件以及相关事件订阅。用户开发 NRI 插件,都是基于 stub 库来实现的。
Stub Interface
Stub interface 接口是用户自行实现 NRI 插件逻辑需要满足的接口,自定义 NRI 插件采用集成 stub 的方式扩展,stub 通过函数调用方式调用用户实现的 Stub Interface 接口,用户可以实现其中的一个或多个有关 Pod 以及 容器生命周期的接口。
Stub Interface 接口支持如表 7.11 所示。
自定义插件需要实现的 Stub Interface 接口
可以看到,自定义插件可以在 Pod 和 Container 的生命周期中插入自定义的逻辑,用于修改容器的 OCI Spec 配置信息。
4. NRI Pod 生命周期事件
NRI Pod 生命周期事件如下图所示, NRI 插件可以订阅如下 Pod 生命周期,同时可对 Pod 信息进行修改:
创建 Pod
停止 Pod
移除 Pod
图 NRI Pod 生命周期事件
5. NRI Container 生命周期事件
NRI Container 生命周期事件如下图所示, NRI 插件可以订阅如下 Container 生命周期,同时可对 Container 信息进行修改:
创建容器
创建容器后
开始容器
容器启动后
更新容器
更新容器后
停止容器
移除容器
图 NRI 订阅的 Container 生命周期事件
4. containerd 中启用 NRI 插件
可以在 containerd 配置文件中启用或禁用 NRI 的支持。默认情况下 NRI 插件是禁用的。可以通过编辑 containerd 配置文件(默认情况下是 /etc/containerd/config.toml
)中的 [plugins."io.containerd.nri.v1.nri"]
部分来启用,并将 disable = true
更改为 disable =false
。 启用后,containerd NRI 的配置文件如下。
配置文件的相关参数如下。
disable: 是否禁用 NRI 插件,默认值 true,禁用 NRI 插件。
disable_connections: 是否禁用外部插件的主动连接。默认值是 false, 即默认允许外部 NRI 插件的连接。启用外部连接后, containerd 会主动监听 socket_path 指定的 socket,允许外部插件进行插件注册。
plugin_config_path: 该路径是查找 NRI 插件配置文件的地方,默认为
/etc/nri/conf.d
。NRI 插件的名称应该与插件名称保持一致,如 NRI 插件名称为 01-logger,则插件对应的配置文件为01-logger.conf
或logger.conf
。注意 NRI 插件的配置文件不像 CNI ,NRI 插件没有特定的格式,是由 NRI 插件自行定义的格式。plugin_path: NRI 插件存放的路径,默认路径为
/opt/nri/plugins
。NRI 插件启用后,containerd 会在该路径查找 NRI 插件进行自动注册。注意插件的命名要符合一定的规则, 命名格式要按照idx-pluginname
的格式,其中 idx 为 两位字符,范围是 00-99 的数字对应的字符串,如01-logger
。plugin_registration_timeout: 插件注册的超时间,默认是 5s。
plugin_request_timeout: 插件请求的超时时间,默认是 2s。
socket_path: containerd 和 NRI 插件交互的 socket 地址,默认是
/var/r``un/nri/nri.sock
NRI 插件的启动有两种方式,一种是 containerd 自动启动,一种是 NRI 插件外部运行。
containerd 预注册启动: 当 containerd 的配置文件中启用 NRI 插件后,NRI Adaptation 实例化时,即会自动启动 NRI 插件。这种方式需要将 NRI 插件放在 plugin_path 中,默认路径为
/opt/nri/plugins
,并将 NRI 插件所需的配置文件放在 plugin_config_path 中,默认路径为/etc/nri/conf.d
。当 containerd 启动时就会自动加载并运行/opt/nri/plugins
路径下的 NRI 插件,并获取/etc/nri/conf.d
下插件对应的配置文件对插件进行配置。NRI 插件外部启动: 这种这种方式下 NRI 插件进程可以由 systemd 创建,或者运行在 Pod 中。只要保证 NRI 插件可以通过 NRI socket 和 containerd 进行通信即可,默认的 NRI socket 路径为
/var/run/nri/nri.sock
。NRI 插件启动后,会通过 NRI Socket 向 NRI Adaptation 中注册自己。使用 NRI 插件外部启动方式时,一定要确保 NRI 配置中的 disable_connections 为 false。
5. containerd 插件示例
containerd NRI 项目中提供了多个 NRI 插件 详细参见 github.com/containerd/nri/tree/main/plugins
,本文以 logger 示例进行演示。首先下下载并编译相关插件二进制,命令如下。
执行成功后会在 build/bin/
目录下生成对应的 NRI 插件二进制,如下。
接下来分别通过 NRI 插件外部启动和 containerd 预注册方式启动两种方式进行演示。
1. NRI 插件外部启动方式启动
首先以外部启动方式启动,外部 NRI 启动方式直接启动二进制文件即可,命令如下:
其中:
通过 -idx 指定注册给 adaptation 的 index,该项为插件的 index 值,adaptation,会按照 index 递增的顺序,依次调用多个插件。
通过 -log-file 指定日志输出的路径,若不指定,logger 插件将会打印到 stdout,此处打印到指定路径
/var/run/containerd/nri/logger.log
打开另一个 terminal 窗口, 执行命令如下。
可以看到 logger 插件已经能够正常打印日志了,如下图所示。
图 NRI logger 插件 日志打印结果
2. containerd 预注册方式启动
该方式与之前配置不同的是要将 NRI 插件和 NRI 插件的配置文件放在指定的路径下 。
❝
注意以下命令的操作是在 nri 插件编译完成之后进行的。
通过 systemctl 重启 containerd。
注意需要重启 containerd,因为 containerd 在初次启动时才会进行加载 NRI 插件。
查看 logger 日志,可以看到 NRI logger 插件已经成功注册。
6. NRI 插件的应用
NRI 插件的出现可以补充 Kubernetes 对于节点层面资源管理功能的不足,如 CPU 编排、内存分层、缓存管理、IO 管理等。使用 NRI 插件可以将 kubelet 的 Resource Manager 下沉到 CRI Runtime 层进行管理。kubelet 当前不适合处理多种需求的扩展,在 kubelet 层增加细粒度的资源分配会导致 kubelet 和 CRI 的界限越来越模糊。而 NRI 插件,则是在 CRI 生命周期间做调用与修改,更适合做资源绑定和节点的拓扑感知。并且在 CRI 内部做插件定义和修改,可以做到上层 Kubenetes 不感知的情况下做适配。
以上内容节选自新书 《containerd 原理剖析与实战》
最后,附上本书的购买链接,新书刚刚上架原价 109,限时优惠内购 69.9 元,感兴趣的朋友可以尽快入手。
本文使用 文章同步助手 同步
版权声明: 本文为 InfoQ 作者【公众号:云原生Serverless】的原创文章。
原文链接:【http://xie.infoq.cn/article/4896663228025031324d2fdb4】。文章转载请联系作者。
评论