3.3 容器运行时接口
正如我们在之前的章节中所讨论的,Kubernetes提供了许多扩展点,允许你建立一个定制的应用平台。其中最关键的扩展点之一是容器运行时接口(CRI)。CRI是在Kubernetes v1.5中引入的,目的是让不断增长的容器运行时生态系统发挥作用,其中包括CoreOS的rkt和基于管理程序的运行时(如Intel的Clear Containers,后来成为Kata Containers)。
在CRI之前,增加对新的容器运行时的支持需要一个新的Kubernetes版本和对Kubernetes代码库的深入了解。一旦CRI建立,容器运行时的开发者可以简单地遵守接口规范,以确保运行时与Kubernetes的兼容性。
总的来说,CRI的目标是将容器运行时的实现细节从Kubernetes(特别是kubelet)中抽象出来。这是一个典型的依赖反转原则的例子。kubelet从散落在各处的容器运行时特定代码和if语句发展到依赖接口的更精简的实现。因此,CRI降低了kubelet实现的复杂性,同时也使其更具可扩展性和可测试性。这些都是精心设计的软件的重要品质。
CRI是使用gRPC和协议缓冲区实现的。该接口定义了两个服务:RuntimeService和ImageService。kubelet利用这些服务来与容器运行时进行交互。RuntimeService负责所有与Pod相关的操作,包括创建Pod、启动和停止容器、删除Pod等。ImageService关注的是容器镜像操作,包括列出、拉出和从节点上删除容器镜像。
虽然我们可以在本章中详细介绍RuntimeService和ImageService的API,但了解Kubernetes中最重要的操作流程可能更有用:在节点上启动Pod。因此,让我们在下一节中探讨kubelet和容器运行时之间通过CRI的互动。
3.3.1 启动Pod
下面的描述基于Kubernetes v1.18.2和containerd v1.3.4。这些组件使用CRI的v1alpha2版本。
一旦Pod被安排到一个节点上,kubelet就会与容器运行时一起工作并且启动Pod。如上所述,kubelet通过CRI与容器运行时间进行交互。在这里,我们将探讨kubelet和containerd CRI插件之间的交互。
containerd CRI插件启动了一个在UNIX套接字(socket)上监听的gRPC服务器。默认情况下,这个套接字位于/run/containerd/containerd.sock。kubelet可以通过这个套接字与containerd进行交互,并使用container-runtime和container-runtime-endpoint命令行标志。
为了启动Pod,kubelet首先使用RuntimeService的RunPodSandbox方法创建一个Pod沙箱。因为一个Pod是由一个或多个容器组成的,所以必须首先创建沙箱,以便为所有容器共享Linux网络命名空间(以及其他内容)。当调用这个方法时,kubelet将元数据和配置发送给containerd,包括Pod的名称、唯一的ID、Kubernetes命名空间、DNS配置等。一旦容器运行时创建了沙箱,运行时就会响应一个Pod沙箱ID,kubelet用这个ID在沙箱中创建容器。
一旦沙箱可用,kubelet就会使用ImageService的ImageStatus方法检查容器镜像是否存在于节点上。ImageStatus方法返回关于镜像的信息。当镜像不存在时,该方法将返回null,kubelet则继续拉取镜像。kubelet在必要时使用ImageService的PullImage方法来拉取镜像。一旦运行时获得了镜像,它就会响应镜像的SHA256摘要,然后Kubelet用它来创建容器。
在创建沙箱和拉取镜像后,kubelet使用RuntimeService的CreateContainer方法在沙箱中创建容器。kubelet将提供沙箱ID和容器配置给容器运行时。容器配置包括你可能期望的所有信息,包括容器镜像摘要、命令和参数、环境变量、挂载卷等。在创建过程中,容器运行时生成一个容器ID,然后把它传回给kubelet。这个ID就是你在容器状态下Pod的状态字段中看到的那个ID。
然后,kubelet继续使用RuntimeService的StartContainer方法来启动容器。当调用这个方法时,它需要使用从容器运行时收到的容器ID。
在这一节中,我们已经了解了kubelet如何使用CRI与容器运行时进行交互。我们还特别研究了启动Pod时调用的gRPC方法,包括ImageService和RuntimeService上的方法。kubelet使用这两个CRI服务提供的额外方法来完成其他任务。除了Pod和容器管理(即CRUD)方法,CRI还定义了在容器内执行命令(Exec和ExecSync)、附加到容器(Attach)、转发特定容器端口(PortForward)等方法。