kubernetes CRI 前世今生

秒速五厘米 2023-06-04 14:58 74阅读 0赞

  在学习kubernetes的过程中,我们会遇到CRI、CNI、CSI、OCI 等术语,本文试图先通过分析k8s目前默认的一种容器运行时架构,来帮助我们更好理解k8s 运行时背后设计逻辑。进而引出CRI、OCI的提出背景。

一、k8s 架构

  我们在构建k8s集群的时候首先需要搭建master节点、其次需要创建node节点并将node节点加入到k8s集群中。当我们构建好k8s集群后,我们可以通过kubectl create -f nginx.yml 命令的方式来创建应用对应的pod。当我们执行命令

后,命令会提交给API server,它会解析yml文件,并将其以API对象的形式存到 etcd里。这时master组件中的Controller Manager会通过控制循环的方式来做编排工作,创建应用所需要的Pod。Scheduler 会 watch etcd中新Pod 的变化。如

果他发现有一个新的Pod 出现,Scheduler会运行调度算法,通过调度算法最终选择出最佳的Node节点,并将这个Node节点的名字写到pod对象的NodeName字段上面,这一步就是所谓的Bind Pod to Node(下图的标注),然后把bind的结果写回到etcd。

其次,当我们在构建k8s集群的时候,默认每个节点上都会初始化创建一个kubelet进程,kubelet进程的会watch etcd中的pod的变化,当kubelet进程watch到pod的bind的更新操作,并且bind的节点是本节点时,它会接管接下来的

所做的事情,如镜像下载,容器创建等。

595328-20190924154453278-2062939099.jpg

二、k8s 默认容器运行时架构

  接下来将通过k8s默认集成的容器运行时架构来看kublete如何创建一个容器(如下图所示)。

  1. kubelet 通过 CRI(Container Runtime Interface) 接口(gRPC) 调用 dockershim, 请求创建一个容器, 这一步中, Kubelet 可以视作一个简单的 CRI Client, 而 dockershim 就是接收请求的 Server.

  2. dockershim 收到请求后, 通过适配的方式,适配成 Docker Daemon 的请求格式, 发到 Docker Daemon 上请求创建一个容器。在docker 1.12后版本中,docker daemon被拆分成dockerd和containerd,containerd负责操作容器。

  3. dockerd收到请求后, 调用containerd进程去创建一个容器。

  4. containerd 收到请求后, 并不会自己直接去操作容器, 而是创建一个叫做 containerd-shim 的进程, 让 containerd-shim 去操作容器. 创建containered-shim的目的主要有:

1)让containerd-shim做诸如收集状态, 维持 stdin 等 fd 打开等工作.

2)允许容器运行时(runC)启动容器后退出,不必为每个容器一直运行一个容器运行时runC。

3)即使在 containerd 和 dockerd 都挂掉的情况下,容器的标准 IO 和其它的文件描述符也都是可用的。

4)向 containerd 报告容器的退出状态

5)在不中断容器运行的情况下升级或重启 dockerd

  1. 而containerd-shim 在这一步需要调用 runC 这个命令行工具, 来启动容器,runC是OCI(Open Container Initiative, 开放容器标准) 的一个参考实现。主要用来设置 namespaces 和 cgroups, 挂载 root filesystem等操作。

6.runC启动完容器后本身会直接退出, containerd-shim 则会成为容器进程的父进程, 负责收集容器进程的状态, 上报给 containerd, 并在容器中 pid 为 1 的进程退出后接管容器中的子进程进行清理, 确保不会出现僵尸进程。

595328-20190924162334102-1814702716.png

三、容器与容器编排背景简述

从k8s的容器运行时可以看出,kubelet启动容器的过程经过了很长的一段调用链路。这个是由于在容器及编排领域各大厂商与docker之间的竞争以及docker公司为了抢占paas领域市场,对架构做出的一系列调整。其实 k8s 最开始

的运行时架构远没这么复杂: kubelet 想要创建容器直接通过 docker api 调用 Docker Daemon,Docker Daemon 调 libcontainer 这个库把容器跑起来, 整个过程就搞完了。为了防止Docker垄断以及受控

docker运行时, 各大厂商于是就联合起来搞了开放容器标准OCI(Open Containers Initiative).大家可以基于这个标准开发自己的容器运行时。Docker公司则把 libcontainer 封装了一下, 变成 runC 捐献出来作为 OCI 的参考实现.

接下来就是 Docker 要搞 Swarm 进军 PaaS 市场, 于是做了个架构切分, 把容器操作都移动到一个单独的 Daemon 进程 containerd 中去, 让 Docker Daemon 专门负责上层的封装编排. 可惜 Swarm 在 k8s 面前实在是不够打, 惨败之后

Docker 公司就把 containerd 捐给 CNCF ,专注于搞 Docker 企业版了.

与此同时,容器领域,core os公司搞出了个rkt容器运行时。为了能从 docker 那边分一杯羹, 希望 k8s 原生支持 rkt 作为运行时, 由于core os与google的关系,最终rkt运行时的支持在2016年也被合并进kubelet主干代码里. 这样做后不但

没有给rkt项目带来更多用户,反而给k8s中负责维护 kubelet 的小组 SIG-Node带来了更大的负担,每一次kubelet的更新都要维护docker和rkt两部分代码。与此同时,随着虚拟化技术强隔离容器技术runV(Kata Containers前身,后与intel

clear container 合并)的逐渐成熟。k8s上游对虚拟化容器的支持很快被提上了日程。为了从集成每一种运行时都要维护一份代码中解放出来,k8s SIG-Node工作组决定对容器的操作统一地抽象成一个接口,这样kubelet只需要跟这个接口

打交道,而具体地容器运行时,他们只需要实现该接口,并对kubelet暴露gRPC服务即可。这个统一地抽象地接口就是k8s中俗称的 CRI。

四、CRI(容器运行时接口)

CRI 基于 gRPC 定义了 RuntimeService 和 ImageService 等两个 gRPC 服务,分别用于容器运行时和镜像的管理。如下所示:

  1. // Runtime service defines the public APIs for remote container runtimes
  2. service RuntimeService {
  3. // Version returns the runtime name, runtime version, and runtime API version.
  4. rpc Version(VersionRequest) returns (VersionResponse) {}
  5. // RunPodSandbox creates and starts a pod-level sandbox. Runtimes must ensure
  6. // the sandbox is in the ready state on success.
  7. rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {}
  8. // StopPodSandbox stops any running process that is part of the sandbox and
  9. // reclaims network resources (e.g., IP addresses) allocated to the sandbox.
  10. // If there are any running containers in the sandbox, they must be forcibly
  11. // terminated.
  12. // This call is idempotent, and must not return an error if all relevant
  13. // resources have already been reclaimed. kubelet will call StopPodSandbox
  14. // at least once before calling RemovePodSandbox. It will also attempt to
  15. // reclaim resources eagerly, as soon as a sandbox is not needed. Hence,
  16. // multiple StopPodSandbox calls are expected.
  17. rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {}
  18. // RemovePodSandbox removes the sandbox. If there are any running containers
  19. // in the sandbox, they must be forcibly terminated and removed.
  20. // This call is idempotent, and must not return an error if the sandbox has
  21. // already been removed.
  22. rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {}
  23. // PodSandboxStatus returns the status of the PodSandbox. If the PodSandbox is not
  24. // present, returns an error.
  25. rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse) {}
  26. // ListPodSandbox returns a list of PodSandboxes.
  27. rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse) {}
  28. // CreateContainer creates a new container in specified PodSandbox
  29. rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {}
  30. // StartContainer starts the container.
  31. rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {}
  32. // StopContainer stops a running container with a grace period (i.e., timeout).
  33. // This call is idempotent, and must not return an error if the container has
  34. // already been stopped.
  35. // TODO: what must the runtime do after the grace period is reached?
  36. rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {}
  37. // RemoveContainer removes the container. If the container is running, the
  38. // container must be forcibly removed.
  39. // This call is idempotent, and must not return an error if the container has
  40. // already been removed.
  41. rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {}
  42. // ListContainers lists all containers by filters.
  43. rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {}
  44. // ContainerStatus returns status of the container. If the container is not
  45. // present, returns an error.
  46. rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {}
  47. // UpdateContainerResources updates ContainerConfig of the container.
  48. rpc UpdateContainerResources(UpdateContainerResourcesRequest) returns (UpdateContainerResourcesResponse) {}
  49. // ReopenContainerLog asks runtime to reopen the stdout/stderr log file
  50. // for the container. This is often called after the log file has been
  51. // rotated. If the container is not running, container runtime can choose
  52. // to either create a new log file and return nil, or return an error.
  53. // Once it returns error, new container log file MUST NOT be created.
  54. rpc ReopenContainerLog(ReopenContainerLogRequest) returns (ReopenContainerLogResponse) {}
  55. // ExecSync runs a command in a container synchronously.
  56. rpc ExecSync(ExecSyncRequest) returns (ExecSyncResponse) {}
  57. // Exec prepares a streaming endpoint to execute a command in the container.
  58. rpc Exec(ExecRequest) returns (ExecResponse) {}
  59. // Attach prepares a streaming endpoint to attach to a running container.
  60. rpc Attach(AttachRequest) returns (AttachResponse) {}
  61. // PortForward prepares a streaming endpoint to forward ports from a PodSandbox.
  62. rpc PortForward(PortForwardRequest) returns (PortForwardResponse) {}
  63. // ContainerStats returns stats of the container. If the container does not
  64. // exist, the call returns an error.
  65. rpc ContainerStats(ContainerStatsRequest) returns (ContainerStatsResponse) {}
  66. // ListContainerStats returns stats of all running containers.
  67. rpc ListContainerStats(ListContainerStatsRequest) returns (ListContainerStatsResponse) {}
  68. // UpdateRuntimeConfig updates the runtime configuration based on the given request.
  69. rpc UpdateRuntimeConfig(UpdateRuntimeConfigRequest) returns (UpdateRuntimeConfigResponse) {}
  70. // Status returns the status of the runtime.
  71. rpc Status(StatusRequest) returns (StatusResponse) {}
  72. }
  73. // ImageService defines the public APIs for managing images.
  74. service ImageService {
  75. // ListImages lists existing images.
  76. rpc ListImages(ListImagesRequest) returns (ListImagesResponse) {}
  77. // ImageStatus returns the status of the image. If the image is not
  78. // present, returns a response with ImageStatusResponse.Image set to
  79. // nil.
  80. rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse) {}
  81. // PullImage pulls an image with authentication config.
  82. rpc PullImage(PullImageRequest) returns (PullImageResponse) {}
  83. // RemoveImage removes the image.
  84. // This call is idempotent, and must not return an error if the image has
  85. // already been removed.
  86. rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse) {}
  87. // ImageFSInfo returns information of the filesystem that is used to store images.
  88. rpc ImageFsInfo(ImageFsInfoRequest) returns (ImageFsInfoResponse) {}
  89. }

具体容器运行时则需要实现 CRI 定义的接口(即 gRPC server,通常称为 CRI shim)。容器运行时在启动 gRPC server 时需要监听在本地的 Unix Socket (Windows 使用 tcp 格式)。

五、 容器运行时实现

  除了上面介绍的默认的容器运行时的实现,目前容器运行时主要有:

  • cri-o同时兼容OCI和CRI的容器运行时
  • cri-containerd:基于Containerd的Kubernetes CRI 实现
  • rkt:由CoreOS主推的用来跟docker抗衡的容器运行时
  • frakti:基于hypervisor的CRI
  • Clear Containers:由Intel推出的同时兼容OCI和CRI的容器运行时
  • Kata Containers:符合OCI规范同时兼容CRI
  • gVisor:由谷歌推出的容器运行时沙箱(Experimental)

参考文档:

https://github.com/kubernetes/cri-api/blob/master/pkg/apis/runtime/v1alpha2/api.proto

https://feisky.gitbooks.io/kubernetes/plugins/CRI.html

https://aleiwu.com/post/cncf-runtime-landscape/

https://www.infoq.cn/article/r*ikOvovTHhADAWw1Hb1

转载于:https://www.cnblogs.com/justinli/p/11578951.html

发表评论

表情:
评论列表 (有 0 条评论,74人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Hadoop的前世今生

    随着数据的快速增长,数据的存储和分析都变的越来越困难。例如存储容量、读写速度、计算效率等都无法满足用户的需求。为了解决这些问题,Google提出了三个处理大数据的技术手段,分别

    相关 JDBC的前世今生

    > “请必须要有自信,你就是一道风景,没必要在别人风景里面仰视。”你好,我是梦阳辰,快来和我一起学习吧! 文章目录 01.JDBC是什么? 02.JDBC快

    相关 ssh的前世今生

    1.先说动态网页: 动态网页指那些由网站那边的服务器根据用户的请求动态生成的网页,静态网页与之相反其内容固定不变的。动态网页开发最根本的就是服务器端接收到浏览器提交的请求,通

    相关 Dubbo的前世今生

    SOA与服务治理 SOA(面向服务的体系结构)概念由来已久,在10多年前便开始进入到我们广大软件开发者的视线中。SOA是一种粗粒度、松耦合服务架构,服务之间通过简单、精确

    相关 websocke前世今生

    注:下面内容来自网上,本人经过加工整理。 1、问什么要用websocke? Browser已经支持http协议,为什么还要开发一种新的WebSocket协议呢?我们知道ht