引言

这是对于容器运行时系列文章的第四篇,也是最初一篇。 从第1篇开始曾经有一段时间了,在那篇文章中,我概述了容器运行时,并探讨了低级和高级运行时之间的区别。 在第2篇文章中,我具体介绍了低级容器运行时,并构建了一个简略的低级运行时。 在第3篇文章中,我降级了技术栈,介绍了高级容器运行时。

Kubernetes运行时反对容器运行时接口(CRI)的高级容器运行时。 CRI在Kubernetes 1.5中引入,并充当kubelet和容器运行时之间的桥梁,冀望与Kubernetes集成的高级容器运行时实现CRI。 预计运行时将解决镜像治理并反对Kubernetes Pod,并治理各个容器,因而依据咱们在第3篇文章中的定义,Kubernetes运行时必须是高级运行时。低级运行时短少一些必要的个性。 因为第3篇文章介绍了无关高级容器运行时的所有内容,因而在本文中,我将重点介绍CRI,并介绍一些反对CRI的运行时。

为了更多地理解CRI,值得看一下Kubernetes架构。 Kubelet是一个位于Kubernetes集群中每个工作节点上的代理。 Kubelet负责管理其节点的容器工作负载,当波及到理论运行工作负载时,kubelet应用CRI与在同一节点上运行的容器运行时进行通信。 这样,CRI仅仅是一个形象层或API,它使您能够将容器运行时的实现独自拆分进去,而不用将其内置到kubelet中。

CRI运行时示例

这里列出一些可与Kubernetes一起应用的CRI运行时。

containerd

containerd是我在第3篇文章中提到的高级运行时。containerd可能是以后最风行的CRI运行时,它将CRI实现作为默认状况下启用的插件。 默认状况下,它在unix socket上开启监听,因而通过如下配置连贯到容器:

cat <<EOF | sudo tee /etc/crictl.yamlruntime-endpoint: unix:///run/containerd/containerd.sockEOF

这是一个乏味的高级运行时,因为它在1.2版开始通过称为“runtime handler”的货色反对多个低级运行时。 runtime handler是通过CRI中的一个字段传递的,基于该运行时处理程序的容器将运行一个名为shim的应用程序以启动容器。 它能够用于应用除runc之外的低级运行时来运行容器,例如gVisorKata ContainersNabla Containersruntime handler在k8s 1.12 alpha版本的RuntimeClass object中正式被提交,这里有更多对于containerd's shim的概念介绍。

Docker

Docker runtime第一个实现了对CRI的反对,并且作为kubeletDocker之间的一个shim而实现。 从那以后,Docker已将其许多性能合成为容器,当初通过容器反对CRI。 装置最新版本的Docker时,将同时装置containerdCRI间接与containerd通信。 因而,Docker自身并不需要反对CRI。 因而,依据你的理论状况,能够间接装置容器或者通过Docker来装置。

cri-o

cri-o是一个轻量级的CRI运行时,它是Kubernetes特定的高级运行时。 它反对OCI兼容镜像的治理,并从任何OCI兼容镜像注册表中提取。 它反对runcClear Containers作为低级运行时,在实践上反对其余OCI兼容的低级运行时,但依赖于与runc OCI命令行界面的兼容性,因而在实践中它不如容器的shim API灵便。
cri-oendpoints默认状况下位于/var/run/crio/crio.sock,因而能够通过如下形式配置crictl:

cat <<EOF | sudo tee /etc/crictl.yamlruntime-endpoint: unix:///var/run/crio/crio.sockEOF

CRI标准

CRI是一个protocol buffersgRPC API。 该标准是在kubelet下的Kubernetes镜像仓库中的protobuf文件中定义的。 CRI定义了几种近程过程调用(RPCs)和音讯类型。 RPCs用于“镜像”(ImageService.PullImage),“创立容器”(RuntimeService.RunPodSandbox),“创立容器”(RuntimeService.CreateContainer),“启动容器”(RuntimeService.StartContainer),“进行容器”等操作 (RuntimeService.StopContainer)等

例如,通过CRI启动一个新的Kubernetes Pod的典型交互看起来相似于以下内容(以我本人的伪gRPC模式,每个RPC都会失去一个更大的申请对象,为简便起见,我对其进行了简化)。 RunPodSandboxCreateContainer RPC在其响应中返回ID,这些ID在后续申请中应用:

ImageService.PullImage({image: "image1"})ImageService.PullImage({image: "image2"})podID = RuntimeService.RunPodSandbox({name: "mypod"})id1 = RuntimeService.CreateContainer({    pod: podID,    name: "container1",    image: "image1",})id2 = RuntimeService.CreateContainer({    pod: podID,    name: "container2",    image: "image2",})RuntimeService.StartContainer({id: id1})RuntimeService.StartContainer({id: id2})

应用crictl工具能够间接与CRI运行时进行交互,能够间接从命令即将gRPC消息发送到CRI运行时并用它来调试和测试CRI实现,而无需启动kubelet或Kubernetes集群,能够从GitHub上cri-tools版本页面下载crictl二进制文件来获取相干文件。
能够通过在/etc/crictl.yaml下创立配置文件来配置crictl。 在这里,你应该将运行时的gRPC端点指定为Unix socket文件(unix:///path/to/file)或TCP端点(tcp://<host>:<port>)。 在本例中将应用containerd

cat <<EOF | sudo tee /etc/crictl.yamlruntime-endpoint: unix:///run/containerd/containerd.sockEOF

或者能够在每次命令行执行时指定runtime endpoint

crictl --runtime-endpoint unix:///run/containerd/containerd.sock …

应用crictl运行一个单容器pod, 首先,通知运行时pull所需的nginx镜像,因为没有本地存储的镜像就无奈启动容器。

sudo crictl pull nginx

接下来创立一个Pod的创立申请,能够应用JSON文件进行操作。

cat <<EOF | tee sandbox.json{    "metadata": {        "name": "nginx-sandbox",        "namespace": "default",        "attempt": 1,        "uid": "hdishd83djaidwnduwk28bcsb"    },    "linux": {    },    "log_directory": "/tmp"}EOF

而后创立pod sandbox,将sandbox的ID存储为SANDBOX_ID

SANDBOX_ID=$(sudo crictl runp --runtime runsc sandbox.json)

接下来,在JSON文件中创立容器的创立申请。

cat <<EOF | tee container.json{  "metadata": {      "name": "nginx"    },  "image":{      "image": "nginx"    },  "log_path":"nginx.0.log",  "linux": {  }}EOF

而后,在后面创立的Pod中创立并启动容器。

{  CONTAINER_ID=$(sudo crictl create ${SANDBOX_ID} container.json sandbox.json)  sudo crictl start ${CONTAINER_ID}}

查看正在运行的pod以及正在运行的容器:

sudo crictl inspectp ${SANDBOX_ID}sudo crictl inspect ${CONTAINER_ID}

通过进行并删除容器进行清理:

{  sudo crictl stop ${CONTAINER_ID}  sudo crictl rm ${CONTAINER_ID}}

而后进行并删除Pod

{  sudo crictl stopp ${SANDBOX_ID}  sudo crictl rmp ${SANDBOX_ID}}