首页 > 其他分享 >25-稳定基石:带你剖析容器运行时以及 CRI 原理.md

25-稳定基石:带你剖析容器运行时以及 CRI 原理.md

时间:2024-01-11 13:57:34浏览次数:41  
标签:容器 container 25 rpc md Kubelet returns CRI

当一个 Pod 在 Kube-APIServer 中被创建出来以后,会被调度器调度,然后确定一个合适的节点,最终被这个节点上的 Kubelet 拉起,以容器状态运行。

那么 Kubelet 是如何跟容器打交道的呢,它是如何进行创建容器、获取容器状态等操作的呢?

今天我们就来了解一下。

容器运行时 (Container Runtime)

Kubelet 负责运行具体的 Pod,并维护其整个生命周期,为 Pod 提供存储、网络等必要的资源。但 Kubelet 本身并不负责真正的容器创建和逻辑管理,这些全部都是通过容器运行时(Container Runtime)完成的。大家平常熟知的 Docker 其实就是一种容器运行时,除此之外,还有containerdcri-okatagVisor 等等。

下图就是 Kubelet 跟容器运行时进行的交互图:

Drawing 0.png

图 1:Kubelet 跟容器运行的交互图

Kubelet 负责跟 kube-apiserver 进行数据同步,获取新的 Pod,并上报本机 Pod 的各个状态数据。Kubelet 通过调用容器运行时的接口完成容器的创建、容器状态的查询等工作。下图就是使用 Docker 作为容器的运行时。

Drawing 1.png
图 2:使用 Docker 作为容器的运行

Docker 作为 Kubelet 内置支持的主要容器运行时,也是目前使用最为官方的容器运行时之一。

除了 Docker,在 Kubernetes v1.5 之前,Kubelet 还内置了对 rkt 的支持。在这个阶段,如果我们想要自己去定义容器运行时,或者更改容器运行时的部分逻辑行为,是非常痛苦的,需要通过修改 Kubelet 的代码来实现。这些改动如果更新到上游社区,也会给社区造成很大的困扰,毕竟 Kubelet 自身的稳定性关乎着整个集群的稳定性。因此,这些改动在上游社区的合并通常都很谨慎,往往就需要开发者自己维护这些代码,维护成本非常高,也不方便升级。

介于这一点,很多开发者都希望 Kubernetes 可以支持更多的容器运行时。因此,从 v1.5 版本开始,社区引入了 CRI(Container Runtime Interface)来解决这个问题。

CRI 接口的引入带来了两个好处:一是它很好地将 Kubelet 与容器运行时进行了解耦,这样我们每次对容器运行时进行更新升级等操作时,都再不需要对 Kubelet 做任何的更改;二是解放了 Kubelet,减少了 Kubelet 的负担,能够保证 Kubernetes 的代码质量和整个系统的稳定性。

下面我们就来了解一下CRI。

CRI

CRI 接口可以分为两部分。

一个是容器运行时服务 RuntimeService,它主要负责管理 Pod 和容器的生命周期,比如创建容器、删除容器、查询容器状态等等。下面就是用Protocol Buffers定义的 RuntimeService 的接口:

// Runtime service defines the public APIs for remote container runtimes
service RuntimeService {
    // Version returns the runtime name, runtime version, and runtime API version.
    rpc Version(VersionRequest) returns (VersionResponse) {}
    // RunPodSandbox creates and starts a pod-level sandbox. Runtimes must ensure
    // the sandbox is in the ready state on success.
    rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {}
    // Start a sandbox pod which was forced to stop by external factors.
    // Network plugin returns same IPs when input same pod names and namespaces
    rpc StartPodSandbox(StartPodSandboxRequest) returns (StartPodSandboxResponse) {}
    // StopPodSandbox stops any running process that is part of the sandbox and
    // reclaims network resources (e.g., IP addresses) allocated to the sandbox.
    // If there are any running containers in the sandbox, they must be forcibly
    // terminated.
    // This call is idempotent, and must not return an error if all relevant
    // resources have already been reclaimed. kubelet will call StopPodSandbox
    // at least once before calling RemovePodSandbox. It will also attempt to
    // reclaim resources eagerly, as soon as a sandbox is not needed. Hence,
    // multiple StopPodSandbox calls are expected.
    rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {}
    // RemovePodSandbox removes the sandbox. If there are any running containers
    // in the sandbox, they must be forcibly terminated and removed.
    // This call is idempotent, and must not return an error if the sandbox has
    // already been removed.
    rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {}
    // PodSandboxStatus returns the status of the PodSandbox. If the PodSandbox is not
    // present, returns an error.
    rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse) {}
    // ListPodSandbox returns a list of PodSandboxes.
    rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse) {}
    // CreateContainer creates a new container in specified PodSandbox
    rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {}
    // StartContainer starts the container.
    rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {}
    // StopContainer stops a running container with a grace period (i.e., timeout).
    // This call is idempotent, and must not return an error if the container has
    // already been stopped.
    // TODO: what must the runtime do after the grace period is reached?
    rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {}
    // RemoveContainer removes the container. If the container is running, the
    // container must be forcibly removed.
    // This call is idempotent, and must not return an error if the container has
    // already been removed.
    rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {}
    // PauseContainer pauses the container.
    rpc PauseContainer(PauseContainerRequest) returns (PauseContainerResponse) {}
    // UnpauseContainer unpauses the container.
    rpc UnpauseContainer(UnpauseContainerRequest) returns (UnpauseContainerResponse) {}
    // ListContainers lists all containers by filters.
    rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {}
    // ContainerStatus returns status of the container. If the container is not
    // present, returns an error.
    rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {}
    // UpdateContainerResources updates ContainerConfig of the container.
    rpc UpdateContainerResources(UpdateContainerResourcesRequest) returns (UpdateContainerResourcesResponse) {}
    // ReopenContainerLog asks runtime to reopen the stdout/stderr log file
    // for the container. This is often called after the log file has been
    // rotated. If the container is not running, container runtime can choose
    // to either create a new log file and return nil, or return an error.
    // Once it returns error, new container log file MUST NOT be created.
    rpc ReopenContainerLog(ReopenContainerLogRequest) returns (ReopenContainerLogResponse) {}
    // ExecSync runs a command in a container synchronously.
    rpc ExecSync(ExecSyncRequest) returns (ExecSyncResponse) {}
    // Exec prepares a streaming endpoint to execute a command in the container.
    rpc Exec(ExecRequest) returns (ExecResponse) {}
    // Attach prepares a streaming endpoint to attach to a running container.
    rpc Attach(AttachRequest) returns (AttachResponse) {}
    // PortForward prepares a streaming endpoint to forward ports from a PodSandbox.
    rpc PortForward(PortForwardRequest) returns (PortForwardResponse) {}
    // ContainerStats returns stats of the container. If the container does not
    // exist, the call returns an error.
    rpc ContainerStats(ContainerStatsRequest) returns (ContainerStatsResponse) {}
    // ListContainerStats returns stats of all running containers.
    rpc ListContainerStats(ListContainerStatsRequest) returns (ListContainerStatsResponse) {}
    // UpdateRuntimeConfig updates the runtime configuration based on the given request.
    rpc UpdateRuntimeConfig(UpdateRuntimeConfigRequest) returns (UpdateRuntimeConfigResponse) {}
    // Status returns the status of the runtime.
    rpc Status(StatusRequest) returns (StatusResponse) {}
}

另一个部分是镜像服务 ImageService,主要负责容器镜像的生命周期管理,比如拉取镜像、删除镜像、查询镜像等等,如下所示:

// ImageService defines the public APIs for managing images.
service ImageService {
    // ListImages lists existing images.
    rpc ListImages(ListImagesRequest) returns (ListImagesResponse) {}
    // ImageStatus returns the status of the image. If the image is not
    // present, returns a response with ImageStatusResponse.Image set to
    // nil.
    rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse) {}
    // PullImage pulls an image with authentication config.
    rpc PullImage(PullImageRequest) returns (PullImageResponse) {}
    // RemoveImage removes the image.
    // This call is idempotent, and must not return an error if the image has
    // already been removed.
    rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse) {}
    // ImageFSInfo returns information of the filesystem that is used to store images.
    rpc ImageFsInfo(ImageFsInfoRequest) returns (ImageFsInfoResponse) {}
}

每一个容器运行时都需要自己实现一个 CRI shim,即完成对 CRI 这个抽象接口的具体实现。这样容器运行时就可以接收来自 Kubelet 的请求。

我们现在就来看看有了 CRI 接口以后,Kubelet 是如何和容器运行时进行交互的,见下图:

Drawing 2.png

图 3:Kubelet 与容器运行时的交互)

从上图可以看出,新增的 CRI shim 是 Kubelet 和容器运行时之间的交互纽带,Kubelet 只需要跟 CRI shim 进行交互。Kubelet 调用 CRI shim 的接口,CRI shim 响应请求后会调用底层的运行容器时,完成对容器的相关操作。

这里我们需要将 Kubelet、CRI shim 以及容器运行时都部署在同一个节点上。一般来说,大多数的容器运行时都默认实现了 CRI 的接口,比如containerd

目前 Kubelet 内部内置了对 Docker 的 CRI shim 的实现,见下图:

Drawing 4.png
图 4:Kubelet 内置对 CRI shim 的实现

而对于其他的容器运行时,比如containerd,我们就需要配置 kubelet 的 --container-runtime 参数为 remote,并设置 --container-runtime-endpoint 为对应的容器运行时的监听地址。

Kubernetes 自 v1.10 版本已经完成了和 containerd 1.1版本 的 GA 集成,你可以直接按照这份文档来部署 containerd 作为你的容器运行时。

Drawing 5.png

图 5:部署 containerd

containerd 1.1 版本已经内置了对 CRI 的实现,比直接使用 Docker 的性能要高很多。

写在最后

Kubernetes 作为容器编排调度领域的事实标准,其优秀的架构设计还体现在其可扩展接口上。比如 CRI 提供了简单易用的扩展接口,方便各个容器运行时跟 Kubelet 进行交互接入,极大地方便了用户进行定制化。

通过 CRI 对容器运行时进行抽象,我们无须修改 Kubelet 就可以天然地支持多种容器运行时,这极大地方便了开发者的对接,也减少了升级和维护成本。CRI 的出现也促进了容器运行时的繁荣,也为强隔离、多租户等复杂的场景带来了更多的选择。

除了 CRI 以外,在 Kubernetes 中还可以为不同的 Pod 设置不同的容器运行时(Container Runtime),以提供性能与安全性之间的平衡。从 1.12 版本开始,Kuberentes 就提供了 RuntimeClass 来实现这个功能。你可以阅读这份官方文档,来学习如何使用 RuntimeClass。

如果你对本节课有什么想法或者疑问,欢迎你在留言区留言,我们一起讨论。


标签:容器,container,25,rpc,md,Kubelet,returns,CRI
From: https://www.cnblogs.com/huangjiale/p/17958427

相关文章

  • 大话 JavaScript(Speaking JavaScript):第三十一章到第三十三章
    第三十一章:模块系统和包管理器原文:31.ModuleSystemsandPackageManagers译者:飞龙协议:CCBY-NC-SA4.0JavaScript没有内置模块支持,但社区已经创建了令人印象深刻的解决方法。要管理模块,可以使用所谓的包管理器,它们处理发现、安装、依赖管理等。模块系统JavaScript模......
  • 大话 JavaScript(Speaking JavaScript):第二十一章到第二十五章
    第二十一章:数学原文:21.Math译者:飞龙协议:CCBY-NC-SA4.0Math对象用作多个数学函数的命名空间。本章提供了一个概述。数学属性Math的属性如下:Math.E欧拉常数(e)Math.LN22的自然对数Math.LN1010的自然对数Math.LOG2Ee的底数2对数Math.LOG10Ee的十进制对数Ma......
  • 大话 JavaScript(Speaking JavaScript):第二十六章到第三十章
    第四部分:提示,工具和库原文:IV.Tips,Tools,andLibraries译者:飞龙协议:CCBY-NC-SA4.0本部分提供了使用JavaScript的技巧(最佳实践,高级技术和学习资源),并描述了一些重要的工具和库。第二十六章:元代码风格指南原文:26.AMetaCodeStyleGuide译者:飞龙协议:CCBY-NC-SA......
  • 大话 JavaScript(Speaking JavaScript):第十一章到第十五章
    第十一章:数字原文:11.Numbers译者:飞龙协议:CCBY-NC-SA4.0JavaScript对所有数字都使用单一类型:它将它们全部视为浮点数。但是,如果小数点后没有数字,则不显示小数点:>5.0005在内部,大多数JavaScript引擎都会优化并区分浮点数和整数(详情请参见JavaScript中的整数)。但这......
  • 大话 JavaScript(Speaking JavaScript):第十六章到第二十章
    第十六章:变量:作用域、环境和闭包原文:16.Variables:Scopes,Environments,andClosures译者:飞龙协议:CCBY-NC-SA4.0本章首先解释了如何使用变量,然后详细介绍了它们的工作方式(环境、闭包等)。声明变量在JavaScript中,您在使用变量之前通过var语句声明变量:varfoo;foo......
  • 并行 sha256sum 命令
    之前为文件夹里的文件生成SHA-256摘要时,我使用的是sha256sum*.mp4*.xml*.jpg>sha256sums.txt这个命令是逐个生成哈希值的,在计算完成1.mp4之前并不会开始计算2.mp4,不能很好得利用多核性能。解决办法也很简单,利用“百闻不如一见”的xargs即可:echo*.mp4*.xml*.jp......
  • 大话 JavaScript(Speaking JavaScript):第一章到第五章
    第一部分:JavaScript快速入门原文:I.JavaScriptQuickStart译者:飞龙协议:CCBY-NC-SA4.0这部分是JavaScript的一个独立快速介绍。你可以在不阅读本书中的其他内容的情况下理解它,本书的其他部分也不依赖于它的内容。然而,阅读本书的提示在阅读本书的提示中适用。第一章:基......
  • CloseMaxScript6 快速开始使用
    设置参数如果不使用硬件绑定则不需要勾选点击加密查看结果带有Register结尾的文件名是硬件绑定的注册机文件(请勿外传)......
  • MURD1060-ASEMI快恢复TO-252封装二极管MURD1060
    编辑:llMURD1060-ASEMI快恢复TO-252封装二极管MURD1060型号:MURD1060品牌:ASEMI封装:TO-252平均正向整流电流(Id):10A最大反向击穿电压(VRM):600V产品引线数量:3产品内部芯片个数:2产品内部芯片尺寸:72MIL峰值正向漏电流:<10ua恢复时间:>2000ns正向浪涌电流:550A正向压降:1.7V恢复时间:35ns最大正向电......
  • 绕过安全限制,通过cmd执行PowerShell脚本
    我的初衷是写一个自动激活系统的cmd脚本给身边的朋友使用,它需要判断当前运行的系统版本,需要记住大量的与具体的版本对应的key。开始动手后我发现没法往下写。cmd本质上不支持“结构化编程”,这意味着它没有“函数”这个东西。它只能定义label,用label来模拟函数。过去,当看到Pas......