Kubernetes_核心组件_kubelet_kubelet服务全解析(二)

news2024/11/24 13:03:02

文章目录

  • 前言
  • kubelet 架构
  • kubelet 职责
    • Node管理(节点管理)
    • Pod管理
  • kubelet管理Pod
    • kubelet如何管理当前节点上所有Pod
      • kubelet三个端口
      • kubelet获取Pod清单
      • kubelet通过CRI接口管理Pod以及里面的容器
    • PodWorker的工作细节
      • PodWorker的工作细节
      • PLEG组件
      • PLEG报错
  • kubelet创建并启动Pod
    • Pod启动流程
    • 更加详细的Pod启动过程
  • kubelet CRI 容器运行时
    • CRI 容器运行时
    • CRI 容器运行时架构
      • kubele调动高层运行时通过 CRI 规范接口
      • 高层运行时调用低层运行时通过 OCI 规范接口
    • CRI 高层运行时和低层运行时
      • 高层运行时和低层运行时
      • 如何查看当前k8s集群的高层运行时和低层运行时
      • 高层运行时: 从docker到containerd
      • 三种高层运行时比较
      • Docker和Containerd的细节差异
      • Containerd和CRI-O的性能比较
    • 小结
  • 尾声

前言

kubelet 架构

controller-manager包含了一组控制器,它是整个集群的大脑,每个控制器会关注自己所关注的对象,这些控制器里面都会有一个固定的模式,每个控制器都会通过list watcher,去watch它所关注的对象,当这些对象发生变化以后,以事件通知的形式,告知controller,controller会注册这些事件的回调函数,并且将这些事件放在一个队列里面,同时会去启动一堆worker线程,来从队列里面取出这些对象,并且进行相关的处理。

也就是说,当读了任何一个controller manager的代码,可以将同样的思路套到其他的controller里面。在每个节点上面都会去运行一个组件叫做kubelet。

Kubelet架构

在这里插入图片描述

它是承担的很多很多职责的这样一个组件。

在这里插入图片描述

最上层,它会提供一个api层,这个api里面就会有蛮多的职责,比如做自己的探活,还有业务指标如何上报,这些都是通过api,你可以看到它工作在不同的端口上面都有监听器,承担不同的职责。

在这里插入图片描述

中间就是在kubelet里面运行了一个一个的manager,你可以理解为一个一个独立职责的小的管理器。

第一列(从左到右):

  1. probemanager:就是为这个节点上面的pod做探活的这样一个管理器,liveless readyless probe,这些探活由kubelet发起,kubelet的probemanager会去读取当前节点上面所有probe的定义,然后它会去看这些probe有没有定义探活,如果定义了探活的属性,那么probemanager就会按照你定义的interval去做监听。
  2. oomwatcher:kubelet是整个节点的守护神,它是一个代理,它负责所有应用的生命周期,同时它有包含节点当前继续正常工作的职责,所以它由oomwatcher来监听,比如节点出现oom问题,比如某些进程出现了oom的错误,oomwathcer就会通过oom这个事件来获取这些异常,并且上报给kubelet。
  3. GPU manager:就是用来管理GPU等等这些扩展的device,当节点上有GPU卡的时候,GPUmanager就是用来管理这张GPU卡的。
  4. cadvisor:是独立开源的软件,kubelet里面会内嵌cadvisor,它其实基于cgroup技术去获取节点上运行应用的资源状态,整个集群的监控都是由cadvisor收集上来的,并且通过kubelet上报的。

第二列(从左到右):
5. syncLoop: syncLoop使用控制器模式,它要去watch一堆对象,也就是当前节点的pod对象,syncloop做完之后,下面内嵌了一堆的worker,pod worker就是当我的 sync group 接收到pod的变更通知的时候,那么这个 pod worker 就会去干活,也就是去维护pod的生命周期。kubelet 中的 syncLoop,要去监听apiserver去获取当前节点的pod清单,获取pod清单之后,然后获取的pod清单,每个pod都是一个一个的通知事件,然后podworker就会处理这些事件。
6. PodWorker: PodWorker会去调用cri,然后获取当前节点pod对应的容器是不是在启动的状态。如果没有启动,那么他就会去启动这些容器进程,把这个应用拉起来,它就是通过cri的接口。cri接口传统都是通过dockershim去做的,但是随着kubernetes架构不断升级,它废弃了docker-shim这条线,它决定不再支持比较臃肿的dokcer shim,而是通过remote container interface,比如通过containerd,或者cri来支持整个运行时。

第三列(从左到右):
7. diskspacemanager:这个节点的磁盘空间大小,包括每个应用,你上去了临时空间,你是不是超了,由diskspacemanager来管理。
8. statusmanager:用来管理节点的状态的。
9. evictionmanager:kubelet是这个节点的守护神,它会去监听当前节点上所有pod所消耗的资源,比如说内存,内存是不可压缩资源,可压缩资源cpu,cpu是基于分时复用的,当你多个进程去抢cpu的时候,反正我们按照协商好的比例去分cpu时间片,当出现竞争的时候,顶多受损,不会异常退出。但是内存不一样,内存是不可压缩资源,针对内存,kubelet就会做比较激进的动作,eviction,evictionmanager就是承担这个职责的组件。它会去监听当前节点的资源使用情况,如果它发现内存已经达到了一个水位,比如10g内存使用了9g,如果不制止这种情况,那么可能整个节点都会受到影响。evictionmanager就会去监听这个水位,当这个内存达到一定水位的时候,它就会去做驱逐,它会去按照既定策略将低优先级的业务,而且占用内存又比较大的,超出了自己预设值的这些进程驱逐掉,也就是将pod从这个节点删除掉。通过这种极端的方式来保护整个节点。
10. volumemanager:pod启动要挂载磁盘,要挂载存储卷的,这个是由volumemanager去做的。

第四列(从左到右):
11. imagegc:如果我这个节点不断的启动应用,每个应用都需要拉取自己的image,如果我不做一些清理工作,就会导致这个节点的image会越来越多,把磁盘占满,所谓的imagegc就会去扫描一些不怎么活跃的镜像,把这些镜像清除掉。
12. containegc就是你的进程可能会退出,它会有一个exit的container,这个container其实它的文件层是没有清理掉的,所谓的container gc就是会清除这些退出的容器。
13. imagemanager:就是做镜像管理的。
14. certificatesmanager:kubelet有签证书的能力,当我们需要kubelet去管理证书的时候,certificatesmanager就可以起作用。

kubelet 职责

每个节点上都运行一个kubelet服务进程,默认监听10250端口,kubelet的职责包括:

  1. 管理Node(通过kubelet访问apiserver的方式): 每个kubelet进程会在APIServer上注册节点自身信息,定期向 master 节点汇报节点的资源使用情况,并通过 cAdvisor 监控节点和容器的资源。(pod健康状态通过pleg去做上报,节点的资源使用情况通过cadvisor上报)
  2. 管理Pod及Pod中的容器(通过kubelet watch apiserver的方式): 接收并执行master apiserver发来的指令(其实是用watch的机制去监听的,如果这些pod发生了变化,他就会去执行某些操作,具体执行什么样的操作就是管理pod当中的容器)

Node管理(节点管理)

kubelet本身有节点管理的功能,包括注册节点,更新节点状态,所以在启动kubelet的时候,如果加了register-node,那么就会自动的将当前节点注册到apiserver。

节点管理最主要的两个是节点自注册和节点状态更新∶

  1. kubelet 可以通过设置启动参数–register-node 来确定是否向API Server注册自己
  2. 如果 kubelet 没有选择自注册模式,则需要用户自己配置Node 资源信息,同时需要告知 kubelet集群上的APIServer的位置
  3. kubelet在启动时通过APIServer注册节点信息,并定时向APIServer发送节点新消息,APIServer在接收到新消息后,将信息写入etcd。

Pod管理

比节点管理相关更加重要的是pod的管理,因为节点的注册管理是一次性的,然后由kublet不断的上报状态,更重要的是应用的管理,也即是pod。

kubelet如何管理pod?它首先要知道节点上要启动哪些pod,这就涉及到如何获取pod清单,kubelet有几种方式去获取pod清单,第一种就是文件的方式,也就是static pod,扫描到目录下有pod清单文件,就将这些pod启动起来。

staticPodPath: /etc/kubernetes/manifests

第二种方式是通过http endpoint来加载pod,你可以给它一个启动参数叫做manifest-url,这个url是http-endpoint,你把所有pod清单放在那,他也会去读取那个url来加载pod清单。

第三种方式是通过apiserver了,当apiserver启动之后,它自己会去启动apiserver,kubelet连接到apiserver,会去监听apiserver上面所有的pod,当然是和这个节点相关的所有pod,经过调度scheduler之后的所有pod。(kubelet会去watch apiserver,apiserver那边有pod创建出来了,并且和这个节点相关,这个节点上的kubelet就会去工作)

获取Pod清单∶

(1) 文件∶启动参数–config指定的配置目录下的文件(默认/etc/Kubernetes/manifests/)。该文件每20秒重新检查一次(可配置)。
(2) HTTP endpoint(URL)∶启动参数–manifest-url设置。每20秒检查一次这个端点(可配置)。
(3) APIServer∶通过APIServer监听etcd目录,同步Pod清单。
(4) HTTP Server∶ kubelet 侦听 HTTP 请求,并响应简单的 API 以提交新的 Pod 清单。

kubelet管理Pod

在这里插入图片描述

从上到下分为三层,如下:
最上一层:kubelet三个端口
次一层:kubelet获取Pod清单
第三层:kubelet通过CRI接口管理Pod以及里面的容器

下面分层介绍。

kubelet如何管理当前节点上所有Pod

kubelet三个端口

kubelet api 包含三个端口
(1) 10250 端口: 业务api, 上面阐述的节点管理和Pod管理通过 10520 端口的api来实现的
(2) 10250 端口: 只读api
(3) 10248 端口: 健康检查api
在这里插入图片描述

查看 kubelet 三个端口

在这里插入图片描述
测试 健康检查接口 (apiserver和kubelet都有健康检查接口)

在这里插入图片描述

kubelet获取Pod清单

在这里插入图片描述

中间还有一个syncloop和PodWorkder

所谓的syncloop又是控制器模式了,它要去watch一堆对象,也就是当前节点的pod对象,sync loop做完之后,下面内嵌了一堆的worker,pod worker就是当我的sync loop接收到pod的变更通知的时候,那么这个pod worker就会去干活,也就是去维护pod的生命周期。

kubelet要去监听apiserver去获取当前节点的pod清单,获取pod清单之后,然后获取的pod清单,每个pod都是一个一个的通知事件,然后podworker就会处理这些事件。

kubelet通过CRI接口管理Pod以及里面的容器

在这里插入图片描述

kubelet (本质是kubelet中的podworker) 会去调用cri,然后获取当前节点pod对应的容器是不是在启动的状态。如果没有启动,那么他就会去启动这些容器进程,把这个应用拉起来,它就是通过cri的接口。

cri接口传统都是通过dockershim去做的,但是随着kubernetes架构不断升级,它废弃了docker-shim这条线,它决定不再支持比较臃肿的dokcer shim,而是通过remote container interface,比如通过containerd,或者cri来支持整个运行时。

PodWorker的工作细节

PodWorker的工作细节

上面将kubelet三层结构中,包括 kubelet 三个接口、kubelet 获取Pod清单、kubelet 通过 CRI 接口管理Pod以及里面的容器。这里详细讲解一下 kubelet 通过 syncLoop 获取 Pod 清单列表,这个 syncLoop 底层怎么工作的。
在这里插入图片描述

步骤1:kubelet中的synloop监听pod的状态变化
kubelet它本身的synloop,就是去监听pod的状态变化,这个pod的状态变化就是一个一个的pod的update,或者是add事件,接收到这些事件之后,它就会将事件存在updateoption里面,那么不同的worker,在kubelet里面有多个worker,每个worker都会从队列里面获取这些pod变更事件的清单。

事件就是Event,类似于mysqll中的 crud 语句,记录着数据操作,APIServer审计日志会记录所有的事件的

步骤2:针对每一个pod执行sync pod的操作
针对每一个pod,它就会去执行sync pod的操作,sync pod里面最重要的行为就是 compute pod actions,也就是针对这个pod采取什么样的行为,它会去比对当前节点上面的已经在运行的容器进程,然后去判断说,如果pod是新的我就create,如果pod是已经存在的,比如说是delete事件,那么我就应该去删除,如果是一个更新事件,那么我就要去判断运行的容器进程和你当前的pod是不是匹配的,比如它的哈希值变的话,那么我就要重建,这就是 compute pod actions所做的事情。它算完了之后就需要通过cri的接口,去create这些进程,或者kill这些进程。

步骤3:kubelet中的pleg将每一个pod生命周期上报给apiserver
右边还有个组件叫做pleg,它主要是pod lifecycle event的一个汇聚器,就是pod运行完之后,上面左边部分是用来管理pod的生命周期,管理完之后这些pod的状态如何上报,那么就是通过pleg组件去上报的。kubelet在pleg组件里面维护了一个pod cache,它本地的一个缓存,这个pleg会去定期的往container runtime里面去发一个list的操作,去获取当前节点上pod的清单,正在运行的pod清单,那么这里就会把当前所有在运行pod状态在pleg这里汇聚,由pleg 通过pod lifecycle event发回上报给apiserver,apiserver那端就知道这个node节点上面所有pod的状态了。

小结:apiserver将Pod 事件Event发送给kubelet,kubelet将 Pod 生命周期 lifecycle 发送给apiserver
这就是 apiserver 与 kubelet 相互的业务沟通,之前学习证书的时候,apiserver和kubelet需要相互调用,各自保留对方的客户端证书

PLEG组件

PLEG 是 kubelet 用来检查容器 runtime 的健康检查机制。这件事情本来可以由 kubelet 使用 polling 的方式来做。但是 polling 有其成本上的缺陷,所以PLEG 应用而生。PLEG 尝试以一种“中断”的形式,来实现对容器 runtime 的健康检查,虽然实际上,它同时用了 polling 和”中断”两种机制。

在这里插入图片描述

PLEG报错

如果contained runtime不响应了,那么relist就会失败,relist失败,那么这些状态就上报不了,那么最终这个状态无法上报,那么kubernets就会认为你整个节点有问题。所以runtime本身不响应,那么这个节点会变的不正常。

还有就是bug,退出容器的gc没有做好,就节点上出现了几千上万的exit container在那里,pleg再去跟runtime relist 这些container的时候,他就会去遍历所有的已经退出的容器,就会导致时间非常的长,它没有办法及时返回所有容器的清单,没有在它规定的时间内返回,那么整个pleg的操作就超时了,那么就会导致整个节点的状态发生变化。

当集群节点进入 NotReady 状态的时候,我们需要做的第一件事情,肯定是检查运行在节点上的 kubelet 是否正常。在这个问题出现的时候,使用 systemctl 命令查看 kubelet 状态,发现它作为 systemd 管理的一个 daemon,是运行正常的。当我们用 journalctl 查看 kubelet 日志的时候,发现下边的错误。

journalctl -u kubelet.service

journalctl命令是用于查看和管理systemd日志的命令行工具。systemd是Linux系统的系统管理和初始化系统,journalctl命令可以用来查看systemd的日志信息。

在这里插入图片描述

这个报错很清楚的告诉我们,容器 runtime 是不工作的 [container runtime is down],且 PLEG 是不健康的 [PLEG is not healthy]。这里容器 runtime 指的就是 docker daemon。Kubelet 通过直接操作 docker daemon 来控制容器的生命周期。而这里的 PLEG,指的是 pod lifecycle event generator。

基本上看到上边的报错,我们可以确认,容器 runtime 出了问题。在有问题的节点上,通过 docker 命令尝试运行新的容器,命令会没有响应。这说明上边的报错是准确的。​​​​​​​

kubelet创建并启动Pod

Pod启动流程

在这里插入图片描述

高层运行时有两种,一种docker,一种containerd,上图是以containerd来说明,更加轻量。

步骤1:用户去创建pod,那么pod的请求会发给apiserver,apiserver接收到请求之后,会把对象存储到etcd里面 [吻合: apiserver与etcd的关系是apiserver单向调用etcd],存储完成之后,调度器scheduler 就去watch到pod的创建时间,并且完成调度,绑定pod [scheduler完成了将这个新建的Pod放到哪个Node上]。

步骤2:绑定之后,也就是更新,继续去etcd里面做持久化,持久化完成之后,kubelet就watch到已经绑定的pod。[此时开始kubelet管理所在Node上的新建的这个Pod]

kubelet在启动pod的时候,它启动的不是一个容器进程,而是多个容器进程,不仅仅启动了应用进程,同时还启动了pause容器镜像的实例,容器进程启动了之后可以把它和某个网络namespace产生关联关系,把它放在某个网络namespace下面,在这个独立的网络namesapce下面,它可以有独立的网络配置,如果直接将容器进程放到网络namesapce里面会出现什么样的问题呢?

kubelet两个参数
–network-plugin=cni 表示Pod之间使用cni通信
–pod-infra-container-image=k8s.gcr.io/pause:3.4.1 表示Pod使用pause镜像启动一个pause容器,维持Pod生命周期,并且把这个pause容器放在一个network ns里面

你的容器进程可靠吗?会不会有错误,会不会异常退出,oom,空指针,等等,当出现这些问题的时候,容器进程就退出了,退出之后容器进程就和这个网络namespace之间的绑定关系就消失了,如果不做一些措施的话,那是不是每次容器进程退出都需要重新为这个容器进程配置网络,那么这样的效率不会很高,如果容器频繁重启,那么就会导致节点上面多了很多不必要的系统操作。

有什么办法让容器进程退出的时候,网络,存储这些东西都不发生变化,这就是为什么需要pause容器。在容器启动之前,网络就需要就绪,网络是要在容器之前就绪的,这就需要额外的容器进程先启动起来。这个也是pause的功能。

pause容器里面entrypoint其实就是pause,它永远sleep,它是一个不会退出,不消耗资源的一个稳定的进程,那么所有的网络就可以挂载在这个pause上面,当容器应用进程启动的时候,我只需要将应用进程的网络namespace挂载在pause上面就行了。

即使容器出现各种问题重启,没关系,因为网络是挂载在其下面的。所以它就提供了非常稳定的基座。

解释: pause容器里面entrypoint其实就是pause, pause容器永远运行
在Kubernetes中,Pause容器是一个特殊的容器,它被用作Pod中其他容器的父容器。Pause容器不执行任何操作,而只是保持运行状态,以保持Pod的生命周期。当Pod中的所有其他容器退出运行时,Pause容器仍然保持运行状态。
而entrypoint是容器在启动时运行的可执行程序或命令。对于Pause容器来说,entrypoint就是容器启动时运行的空命令(例如/pause),而这个空命令并不会做任何实际的工作。它只是保持容器的运行状态,以确保Pod在存在其他容器运行时保持处于活跃状态。
总之,Pause容器的entrypoint就是一个空命令,它的作用是保持容器的运行状态,以保持Pod的生命周期,并在其他容器退出运行时继续保持Pod的活跃状态。

更加详细的Pod启动过程

复述一下Pod启动:pod启动的时候最先启动pause容器,启动之后containerd就会去调用cni的插件去setup pod,其实它会将ADD的命令告诉cni插件帮我setup网络,cni这边setup网络之后会去将pod信息返回给runtime,然后这个信息会返回给kubelet,那么这个时候其实pod就有IP了,这个时候才会去进行下面的用户应用容器的启动。

可以看到在启动容器的时候,网络是已经就绪的,所以有网络需求就可以满足了,整个启动完之后就会将状态回写到Apiserver里面,那么整个的启动过程就完成了。
在这里插入图片描述

上面是更加详细的Pod启动流程框图,最左边是kubelet 中间是CRI 右边是CNI

kubelet 最重要的功能是 syncPod ,在这个 syncPod 操作之前,包括五个步骤

步骤1. Check Admit:kubelet在sync pod也就是做pod同步的时候,首先也会去做准入,比如它要去启动pod,如果节点资源不够,你非要将nodename设置为该节点,因为和节点产生绑定关系,那么就需要启动这个容器了,启动的时候我要去check一下,也需要去做准入的,要去看你的cpu的需求满足不满足,如果不满足就直接报错了,out of cpu的error,写回到这个pod状态里面。

步骤2. Check network plugin:然后会去监听当前节点上网络插件的情况,如果网络插件不就绪,那么pod是启动不起来的,所以这里也会直接报错。

步骤3. Update cgroup:namspace cgroup这些技术要去启动容器进程的时候,要将容器对应的cgroup文件配置好。

步骤4. Makepoddir:pod需要存储日志,容器的日志需要在主机上有个目录,它会去将数据目录创建起来。

步骤5. Wait For attach and mount:你的pod是需要一些存储,比如configmap轻量级存储只需要将文件下载下来,然后mount到容器当中就行了,如果是更高要求的存储,比如网络存储,那么需要去创建volume,然后和这个节点产生attach关系,然后再mount到容器里面,其实就是等待存储就绪。

问题:CRI CNI CSI,它们之间关系是怎么样的呢?在启动pod的时候谁先启动,谁后启动?
回答:CRI (Container Runtime Interface)、CNI (Container Network Interface) 和 CSI (Container Storage Interface) 是 Kubernetes 中的三个关键组件,分别负责容器运行时、容器网络和容器存储。它们之间的关系是:
CRI:负责与容器运行时(如Docker、Containerd等)进行通信,以创建和管理容器。
CNI:负责为容器提供网络连接,包括为容器分配IP地址、配置网络路由等。
CSI:负责为容器提供持久化存储功能,包括挂载存储卷、快照、克隆等。
在启动Pod时,先启动CRI来创建和管理容器,然后CNI为容器分配网络连接,最后CSI为容器提供持久化存储。因此,启动顺序是先启动CRI,然后启动CNI,最后启动CSI。这样确保容器能够正常运行并连接到网络和存储资源。

上面可以很清晰看到 CSI 容器存储 就在kubelet这部分运行,就是在pod后续加载还没有启动的时候,我就得先去将存储挂载好,并且mount进来。如果这一步不过的话,它会一直卡在这里,接下来才会去做syncpod。

syncpod里面就是来计算sandbox和容器变化,如果sandbox发生变化了,就是已经在运行的容器和我pod本身不匹配了,那就是重建了,这里面其实就是computer pod action的动作,如果这个pod已经不存在了,你的容器还在,那么就需要将容器kill掉。

然后你要启动一个新的容器,那么会去经历启动的动作。之前讲了第一步就是createpodsandbox,它会去生成这些sandbox的config,它有一些mainfest,要将这些配置文件生成出来。然后pod的日志目录要创建好,然后要去调用runtime的sandbox。

在这里插入图片描述

以containerd为例,它的cri本身又是一个grpc的服务,kubelet会去调用grpc服务,这个时候整个请求就转到containerd进程里面,kubelet就要暂时等待它的返回结果了。然后就是 create sandbox container,然后一步一步的往下走,最后由cri去调用cni的接口。

针对这种创建网络的请求,其实就是setup网络的过程,在cni里面是add network这种方法去实现的,上面就是整个pod清单的获取,一直到pod容器进程启动的这样一个过程。至于用户容器启动就不多说了。

kubelet CRI 容器运行时

CRI 容器运行时

kubelet在启动容器进程的时候,要真正的启动这些容器进程,随着版本的迭代,它逐渐将启动过程当中这些标准的行为,抽象为一个一个的接口。它的好处是kubelet就不需要和具体的runtime产生绑定关系了,kubelet自己是一些代码框架,它定义好这些接口,它只需要调用这些接口,由不同的容器运行时提供商来实现这些接口。(让运行时可以适配我的标准,这样可以使用你也可以不使用你,这就是 SPI 机制)

SPI 我定义,我使用,别人实现
API 我定义,我实现,别人使用
对于任何一个程序来说(也包括),最上层提供给别人使用的是自己的API接口,最下层依赖/使用别人的SPI机制

这样我们就可以选择是使用docker还是containerd呢还是kata其他类型的容器?这样就使得kubernetes就不和某一个运行时有具体的强绑定关系,防止被某个厂商锁死。

容器运行时(Container Runtime),运行于Kubernetes(K8s)集群的每个节点中,负责容器的整个生命周期。其中Docker是目前应用最广的。随着容器云的发展,越来越多的容器运行时涌现。为了解决这些容器运行时和Kubernetes的集成问题,在Kubernetes1.5版本中,社区推出了CRI(Container Runtime Interface,容器运行时接口)以支持更多的容器运行时。

在这里插入图片描述

如上图,CRI就是将容器运行时的这些实现标准的接口都抽象出来了,kubelet要去操作任何的容器进程都是通过CRI的接口,然后不同的容器运行时会实现这些CRI接口,最终去操作这些容器进程。

CRI 容器运行时架构

整体流程:kubele调动高层运行时通过 CRI 规范接口,高层运行时调用低层运行时通过 OCI 规范接口

在这里插入图片描述

kubelet是客户端,可以看到在kubelet里面有grpc client,它会去基于grpc框架去调用运行时服务,类似docker containerd这类的服务。它是运行在kubelet之外的,在安装kubernetes的时候,会先去安装docker containerd,安装完之后再去安装kubelet。

这样的话kubelet作为客户端来调用runtime的接口。这些runtime就要按照CRI的规范去实现那些接口。

容器运行时最终的目的是要去启动一个一个的容器进程,所以你可以理解为容器运行时它本身是一个中间层,它向上面对的是kubelet,向下面对的是这些容器进程。所以它分为high-level-runtime和low-level-runtime,high-level-runtime是对外提供的这些grpc服务。由客户端grpc client来调用这些服务,它接受到这些请求需要通过low-level的api去启动和操作这些容器。它(指高层运行时)有点像一层代理,它只是做命令的转发,接受来自客户端的请求,然后再去操作下面的容器进程。它所谓的low-level-runtime,容器的话就是runc。

kubele调动高层运行时通过 CRI 规范接口

Dockershim, containerd和CRI-O都是遵循CRI的容器运行时, 我们称他们为高层级运行时(High-level Runtime)。kubele调动高层运行时通过 CRI 规范接口。

CRI是Kubernetes定义的一组gRPC服务。kubelet作为客户端,基于gRPC框架,通过Socket和容器运行时通信。CRI 包括两类服务∶镜像服务(Image Service)和运行时服务(Runtime Service)。

(1) 镜像服务:提供下载、检查和删除镜像的远程程序调用。
(2) 运行时服务:包含用于管理容器生命周期,以及与容器交互的调用(exec/attach/port-forward)的远程程序调用。

imageservice提供了很多接口,主要是对image相关的操作。
runtimeservice同样通过了很多接口,可以看到对sandbox有很多操作,以及和对用户容器相关的。

在这里插入图片描述

高层运行时调用低层运行时通过 OCI 规范接口

容器运行时最终遵循了oci的一个标准,oci定义了容器相关的行业标准。这个行业标准主要分为了三大类:定义了镜像如何打包、如何解压的这样一个规范、通过镜像如何去运行容器进程这样的一个规范。

OCI(Open Container Initiative,开放容器计划)定义了创建容器的格式和运行时的开源行业标准,包括镜像规范(Image Specification)和运行时规范(Runtime Specification)。

镜像规范定义了OCI镜像的标准。高层级运行时将会下载一个OCI镜像,并把它解压成 OCI运行时文件系统包(filesystem bundle)(还有存储镜像文件到指定的目录下)。

运行时规范则描述了如何从OCI运行时文件系统包运行容器程序,并且定义它的配置、运行环境和生命周期。如何为新容器设置命名空间(namepsaces)和控制组(cgroups),以及挂载根文件系统等等操作,都是在这里定义的。它的一个参考实现是runC。我们称其为低层级运行时(Low-level R untime)。除runC以外,也有很多其他的运行时遵循OCI标准,例如kata-runtime。

CRI 高层运行时和低层运行时

高层运行时和低层运行时

容器运行时是真正管理容器的组件,分为高层运行时和低层运行时。高层运行时由 Docker、containerd或CRI-O 充当, 低层运行时由 runc、kata 或 gVisor 充当。低层运行时 kata 和 gVisor 都处于小规模落地或者实验阶段,其生态成熟度和使用案例都比较欠缺,所以除非有特殊的需求,否则 runc 几乎是必然的选择。因此对于容器运行时的选择上,主要聚焦与高层运行时的选择。

容器运行时包括高层运行时和低层运行时,流程是 kubelet 调用高层运行时,高层运行时调用低层运行时,低层运行时调用具体业务容器。在整个业务流程中,kubelet和具体业务流程是不可选择的,但是高层运行时和低层运行时是可选择的。
高层运行时有 Docker containerd 和 cri-o 三种可以选择,具体选择那种,下面会讨论这三种的优劣,一般是 containerd 和 cri-o 比较好。
低层运行时目前只有 runc 一种可用,那就那没得选,就是 runc。

如何查看当前k8s集群的高层运行时和低层运行时

kubectl get nodes -o jsonpath=‘{range .items[*]}{@.metadata.name}{“\n”}{@.status.nodeInfo.containerRuntimeVersion}{“\n”}{@.status.nodeInfo.containerRuntimeVersion}{end}’

该命令将返回一个包含每个节点名称和容器运行时版本的列表。通常,如果你安装了Docker,那么结果应该显示Docker的版本号。如果你使用其他容器运行时,你将看到相应的版本号。

kubectl get nodes 获取节点的信息。这将返回所有集群中的节点列表。
-o jsonpath 使用 JSON 路径格式化输出结果。 [-o 表示 output, 后面的 jsonpath 表示输出格式]
‘…’ JSON 路径的模板包裹在引号中。其中使用 {} 定义占位符来引用节点属性。
{range .items[*]} 是一个迭代器,用于遍历每个节点的列表。
@.metadata.name 引用节点的名称。
{“\n”} 是一个换行符,用于在每个节点信息之间添加换行。
@.status.nodeInfo.containerRuntimeVersion 引用节点的容器运行时版本。
最后,{end} 表示结束迭代。

在这里插入图片描述

{@.metadata.name}{“\n”} 输出节点名称,这里为 m
{@.status.nodeInfo.containerRuntimeVersion}{“\n”} 输出该节点的容器运行时版本
{@.status.nodeInfo.containerRuntimeVersion}{end} 输出该节点的容器运行时版本

直接使用 kubectl get nodes -o json 也可以看到

在这里插入图片描述

使用 kubectl describe nodes m 也可以看到
在这里插入图片描述

另外,你还可以通过逐个检查每个节点上运行的容器运行时进程来确定。连接到每个节点,并使用以下命令检查运行的容器运行时进程:

ps -ef | grep containerd

这将在节点上查找名为"containerd"的进程。如果存在,则使用Containerd作为高层运行时。同样,你还可以使用其他容器运行时的进程名称进行检查,如Docker(“docker”)或CRI-O(“crio”)等。

在这里插入图片描述
在这里插入图片描述

高层运行时: 从docker到containerd

Docker 内部关于容器化运行时功能的核心组件是 containerd ,后来 containerd 也可以直接和 kubelet 通过 CRI SPI 对接,独立在 k8s 中使用。(即 containerd 可以独立作为一个进程运行,不一定需要放在 docker 进程里面)

相对于 docker 而言,containerd 减少了 docker 所需的处理模板 dockerd 和 docker-shim ,并且对 docker 支持的存储驱动进行了优化,因此在容器的创建爱你、启动、停止、删除,已经对镜像的拉取上,都具有性能上的优势。

架构的简化同时也带来了维护的遍历。当然 docker 也具有很多 containerd 不具有的功能 (因为 containerd 是docker的简化),尽管如此,containerd 目前也基本能够满足容器的众多管理需求,所以将它作为运行时的也越来越多。

在这里插入图片描述

containerd是能够取代docker的,因为docker本身内嵌了containerd的,docker是一个独立的产品,我们可以通过docker的命令去管理自己的网络,管理自己的存储,所以docker大部分臃肿的逻辑都在docker daemon它自身的逻辑上面,这些逻辑处理完之后也是将请求发给自带内嵌的containerd里面去, 这样其实真正执行的逻辑是在containerd里面。

存储,网络是kubernetes和docker之间的竞争,在kubernetes里面用的都是kubernetes管理的这套体系,那么docker的那部分其实是冗余的。不再使用docker自身的daemon了,但是可以用你docker自身所带的containerd的。

三种高层运行时比较

上面说的,高层运行时包括三种,所以有选择的余地,低层运行时只有一种,就是runc,所以只能选它。

在这里插入图片描述
上面是三个常用的运行时,最上面的路径其实是最长的。

(1) 如果是docker运行时,那么是kubelet调用dockeshim,调用docker再去调用containerd,containerd再通过runc的接口去调用底层的容器进程。
(2) 如果是containerd那么就没有docker-shim和docker,直接由kubelet发起命令到containerd,然后containerd到runc。
(3) 如果是crio那么更加轻量级,kubelet调用crio,然后直接调用runc。

越来越简洁。

Docker和Containerd的细节差异

在这里插入图片描述

方式1(图片上半部分):如果是通过kubelet去调用docker的话,这个请求是在kubelet的cri接口调用docker-shim,docker-shim再连接到docker-daemon,docker-daemon里面红圈的这部分才是有用的,也就是和image相关的操作,以及正真去去调用containerd的这部分操作是有用的,上面存储网络,已经docker提供的cli这些都是不需要的,那么这些都是冗余的。

方式2(图片下半部分):如果将docker换掉的话,那么红线外的部分就没有了,那么复杂性就大大降低的,直接使用cri的接口调用containerd,移除了这些不必要的调用转发环节,这样整个系统架构更加简单。

上面就是理解去掉docker的意义,这是一个必要的动作。

Containerd和CRI-O的性能比较

在这里插入图片描述
在选型的时候一个是架构的简单程度,稳定性,还有它的性能。

在这里插入图片描述

小结

问题:kubelet是如何调用容器的?
回答:kubelet 通过基于 SPI 机制,通过 CRI 接口调用 高层运行时,然后高层运行时调用低层运行时,然后低层运行时调用 Container 容器。高层运行时和低层运行时都是逻辑上的概念,需要具体实现的中间件。当前的高层运行时只有 dockershim containerd cri-o 三种选择,低层运行时只有 runC 可以选择。

注意:kubelet是如何和docker打交道的 kubelet和docker的cgroupDriver必须相同

尾声

参考资料:kubelet 架构
kubelet 职责
kubelet 管理 Pod
kubelet 创建并启动 Pod
kubelet CRI 容器运行时

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/781286.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uni-app 使用安卓模拟器 mumu教程

第一步 去官网下载 这个直接下载就行 第二步 去uni-app 里面配置 在这里设置adb路径和模拟器端口 进去安装目录找到adb.exe 相关的就行我的是 D:/moniqi/MuMuPlayer-12.0/shell/adb.exe 端口设置的是 16385 但是不起作用 第三步 .\adb connect 127.0.0.1:7555 .\adb devi…

可证明安全初步(Provable Security Basics)

Speecher: Bingsheng Zhang 这一系列的课程,为了介绍一些基础,弥补一些上密码学课和看论文的Gap。 历史上的密码学是art,就像鲁班锁,看着很精妙,但是没有证明。 1970s以来,逐渐发展成Science。 定义和模…

【未来趋势】无屏幕笔记本

近日,来自以色列的科技公司Sightful推出了号称全球首台增强现实(AR)笔记本电脑——Spacetop。通过AR技术,用户可以拥有一块100英寸的虚拟屏幕,并且可以无视环境在任何地点办公,如咖啡店、公园、飞机上等等&…

深度理解 Spring IOC

Spring容器高层视图 Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。 Bean缓存池&…

PHP8知识详解:PHP的历史版本

PHP(全称:Hypertext Preprocessor)是一种广泛应用于web开发的脚本语言。它最初由Rasmus Lerdorf在1994年开发,并于1995年发布了第一个版本。以下是PHP的一些历史大版本及其介绍: PHP 1.0(1995年&#xff09…

因果图用例设计案例

大家在测试过程中经常用的等价类划分、边界值分析、场景法等,并不能覆盖所有的需求,下面就来讲一种不经常用到但又非常重要的测试用例编写方法-因果图法。 应用场景:页面上有多个控件(输入),控件&#xff…

c++--反向迭代器的实现

1.反向迭代器 迭代器有两种,一种是正向的,一种是反向的,方向迭代器的主要功能就是实现数据的反向访问,通过正向迭代器的适配生成的,适配器就是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的…

Linux学习之Shell中if-else

在Shell脚本中&#xff0c;if-else使用方法如下&#xff1a; if [测试条件] then 测试条件成立执行 else条件不成立执行 ficat << CCC >> testIfElse.sh表示只有遇到CCC才结束往testIfElse.sh写入文件。<< CCC表示后续的输入作为子命令或者子Shell的输入&am…

[JavaScript游戏开发] 跟随人物二维动态地图绘制、自动寻径、小地图显示(人物红点显示)

系列文章目录 第一章 2D二维地图绘制、人物移动、障碍检测 第二章 跟随人物二维动态地图绘制、自动寻径、小地图显示(人物红点显示) 文章目录 系列文章目录前言一、本章节效果图二、介绍2.1、左边区域2.2、右边区域 三、列计划3.1、目标3.1.1、完成跟随人物二维动态地图绘制(本…

MySQL知识2

十、数据库表的6种约束 建表同时添加约束&#xff1a; 主键约束和唯一&#xff1a; 默认值&#xff1a; 检查约束&#xff1a; 未能生效&#xff0c;原因&#xff1a;版本是5.7 在8.0之后&#xff1a;支持检查约束 主键自增可以写null&#xff0c;可以写default&#xff0c;可以…

音视频开发-ffmpeg介绍-系列一

目录 一.简介 FFmpeg框架的基本组成包含: 二. FFmpeg框架梳理音视频的流程​编辑 基本概念&#xff1a; 三.ffmpeg、ffplay、ffprobe区别 4.1 ffmpeg是用于转码的应用程序 4.2 fffplay是用于播放的应用程序 4.3 ffprobe是用于查看文件格式的应用程序 4.4 ffmpeg是用于转…

LocalDateTime、OffsetDateTime、ZonedDateTime之间的关系

什么是LocalDateTime&#xff1f; ISO-8601日历系统中不带时区的日期时间。 该类不存储时区&#xff0c;所以适合日期的描述&#xff0c;比如用于生日、deadline等等。 但是如果没有偏移量/时区等附加信息&#xff0c;一个时间是不能表示时间线上的某一时刻的。 什么是Offset…

【网络编程】网络套接字udp通用服务器和客户端

1.预备知识 认识端口号 端口号(port)是传输层协议的内容&#xff1a; 端口号是一个2字节16位的整数(uint16)端口号用来标识主机上的一个进程IP地址port能够标识网络上的某一台主机和某一个进程一个端口号只能被一个进程占用 认识TCP协议 此处我们先对TCP(Transmission Con…

day34-Animated Countdown(动画倒计时)

50 天学习 50 个项目 - HTMLCSS and JavaScript day34-Animated Countdown&#xff08;动画倒计时&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport&q…

自动驾驶感知系统-超声波雷达

超声波雷达&#xff0c;是通过发射并接收40kHz的超声波&#xff0c;根据时间差算出障碍物距离。其测距精度是1~3cm.常见的超声波雷达有两种&#xff1a;第一种是安装在汽车前后保险杠上的&#xff0c;用于测量汽车前后障碍物的驻车雷达或倒车雷达&#xff0c;称为超声波驻车辅助…

易班开放应用授权重定向,出现跨域的解决方案

问题描述 今天开发H5网站需要接入易班&#xff0c;经过易班授权然后重定向&#xff08;code: 302&#xff09;&#xff0c;使用axios发请求&#xff0c;但是前后端均配置跨域的情况下&#xff0c;不管怎么弄都是一直跨域 但是我们看network&#xff0c;network中对应请求的res…

nvm安装(win/linux)

win安装nvm Win安装nvm1、下载nvm2、直接安装nvm-setup.exe3、cmd运行查看安装情况 Linux安装nvm1、下载nvm安装包2、安装及配置环境变量3、查看安装情况 前端开发多个工程&#xff0c;node版本需要时不时的进行切换&#xff0c;如果重新下载安装nodejs会导致浪费很多无用的时间…

人群异常聚集检测告警算法 yolov5

人群异常聚集检测报警系统基于yolov5图像识别和数据分析技术&#xff0c;人群异常聚集检测告警算法通过在关键区域布设监控摄像头&#xff0c;实时监测人员的密集程度和行为动态,分析和判断人群密集程度是否超过预设阈值&#xff0c;一旦发现异常聚集&#xff0c;将自动发出信号…

Pytorch个人学习记录总结 03

目录 Transeforms的使用 常见的transforms Transeforms的使用 torchvision中的transeforms&#xff0c;主要是对图像进行变换&#xff08;预处理&#xff09;。from torchvision import transforms transeforms中常用的就是以下几种方法&#xff1a;&#xff08;Alt7可唤出…