Kubernetes(6)|Kubernetes(6) SIG-Node与CRI

1. 定义
1.1 SIG-Node 是要给Kubernetes的一个小组,负责kubelet和容器运行时管理(本来以为他是个组件或机制之类的。。。)。虽然这个小组相对比较沉寂不太发声,但它所负责的模块是Kuberetes整套体系中非常核心的部分。

【Kubernetes(6)|Kubernetes(6) SIG-Node与CRI】kubelet的工作原理

Kubernetes(6)|Kubernetes(6) SIG-Node与CRI
文章图片
image
SyncLoop本身要求这个控制循环是绝对不可以被阻塞的,(这家伙一阻塞,那么这个node上后续所有的pod等操作就直接跟着pending了),所以SyncLoop都会开启单独的goroutine(可以开始慢慢涉及代码了)来操作的。
1.2 CRI CRI(Container Runtime Interface),容器运行时接口,是gRPC接口,是kubelet调用下层容器运行时的时候调用的接口。是为了屏蔽Kubernetes项目对容器运行时的感知,因为容器运行时是多种多样的,Docker之外,还有加入Kubernetes其中的rkt,以及前隔离容器runV,如果不加这一层CRI的话,像1.6版本之前的Kubernetes一样,直接调用容器运行时的API来创建和管理容器的话,当对接多个容器运行时,则对kubelet的代码周期维护会造成极大的压力。
2016年,SIG-Node通过将kubelet对容器的操作,统一地抽象成一个接口,具体的接口实现,由各个容器运行时来实现。
而这一层容器操作的接口,就是CRI。
当CRI被提出后,Kubernetes及kubelet本身的架构,就可以用如下的接口图来描述了。

Kubernetes(6)|Kubernetes(6) SIG-Node与CRI
文章图片

当Kubernetes通过编排能力创建了一个Pod之后,调度器会为这个Pod选择一个具体的节点来运行。这个时候,kubelet当然就会通过SyncLoop来判断需要进行的操作,比如创建一个Pod。kubelet之后就会调用一个叫做GenericRuntime的通用组件来发起一个创建一个Pod的CRI grpc请求。如果使用的容器项目是Docker的话,会有一个叫做 dockershim的组件,接收到CRI请求并解析后,组装成一个Docker API请求发送给Docker Daemon。


但是更为普适的场景是,在每台宿主机上安装一个负责响应CRI请求的组件,这个组件(也可以叫做CRI shim),来扮演kubelet和各个容器项目之间的shim。具体来说就是实现CRI抽象接口的和具体容器运行时间的接口参数“翻译”和请求。
1.3 容器运行时
举例
  • rkt(CoreOS)
  • Docker
  • runV-基于虚拟化技术的强隔离容器
2. CRI设计与实现原理
正如1中描述,CRI定义了一系列kubelet操作各种容器运行时的接口,之后容器运行时各自实现接口,完成对容器运行时的相关操作。
除了Docker shim是内嵌在kubelet之中之外,其他的CRI shim都需要在node上各自实现和安装。
2.1 CRI的接口定义 下图是CRI里主要的待实现接口(好吧,我会去看源码的https://github.com/kubernetes/kubernetes/tree/release-1.13)

Kubernetes(6)|Kubernetes(6) SIG-Node与CRI
文章图片

CRI的接口类型可以分为两组
  • RunTimeService,提供和容器操作相关的interface。如创建和启动容器、删除容器,执行exec命令等
  • ImageService,提供容器镜像相关的操作,比如拉取和删除镜像。
2.2 RuntimeService
CRI设计的一个重要原则就是确保这个接口本身,只关注容器,不关注POD。
  • Pod是Kubernetes的编排概念,而不是容器运行时的概念。所以我们不能假设所有的下层容器项目,都能够暴露出可以直接映射为Pod的API。
  • 如果在CRI中引入的Pod的概念,那么接下来只要Pod API对象的字段发生变化,那么CRI很可能就需要变更。
那么,CRI存在既然是翻译kubelet对docker操作的,那就避免不了是通过Pod API对象的yaml文件进行解析,那么CRI怎么既不引Pod概念又能够把kubelet对docker的操作从yaml文件中翻译成docker指令的?比如通过kubetcl run去手动起一个包含两个容器的Pod foo时,CRI做了啥事情呢?
可以看到CRI的主要接口中,是有一个RunPodSandbox的interface的,这个Interface实际获取到的是Pod中一部分与容器运行时相关的字段,比如Hostname、DnsConfig、CgroupParent等字段。至于这些字段要如何使用,就得看各个容器的CRI shim是如何根据自身的特性来通过这些字段实现Kubernetes所希望的Pod的状态。
上面提到的kubectl run的例子,当请求通过编排之后,到达了某个node的kubelet之后,kubelet就会按照下图所示的顺序来调用CRI的接口。

Kubernetes(6)|Kubernetes(6) SIG-Node与CRI
文章图片

正如前面提到的,不同的容器的CRI shim的实现是不通的,
  • 如果是Docker容器,dockshim会创建出一个名叫foo的Infra容器(Docker模块的博文提过的)来hold住整个Pod的Network space
  • 如果是基于虚拟化技术的容器,比如Kata COntainers项目,他的CRI shim就会直接创建出一个轻量级的虚拟机来充当Pod,如上图的vm runtime。
除了上述提到的对容器生命周期的实现之外,CRI shim还要做另外一部分十分重要的事情,就是实现exec,logs等接口,这部分接口在调用gRPC期间,kubelet需要维护一个长连接来传输数据。想exec,logs这类API,被称为Streaming API。CRI shim中对Streaming API的实现,依赖于一套独立的Streaming Server机制。原理示意图如下:

Kubernetes(6)|Kubernetes(6) SIG-Node与CRI
文章图片
Streaming API
  • 请求先到APi Server
  • API Server调用kubelet的Exec API
  • kubelet调用CRI的Exec接口,CRI shim”响应“该接口
  • CRI shim并没有直接调用下层容器项目的接口,而是返回Url给kubelet,这个URL就是CRI shim对应的Streaming Server的地址和端口。
  • kubelet拿到这个URL之后,以redirect方式返回给API Server
  • API server通过URL发起真正的/exec请求,与他建立长连接
什么是Streaming Server?
2.3 ImageService 更新中

    推荐阅读