Container Runtimes(四): Kubernetes Container Runtimes & CRI

引言

这是关于容器运行时系列文章的第四篇,也是最后一篇。 从第1篇开始已经有一段时间了,在那篇文章中,我概述了容器运行时,并讨论了低级和高级运行时之间的区别。 在第2篇文章中,我详细介绍了低级容器运行时,并构建了一个简单的低级运行时。 在第3篇文章中,我升级了技术栈,介绍了高级容器运行时。linux

Kubernetes运行时支持容器运行时接口(CRI)的高级容器运行时。 CRI在Kubernetes 1.5中引入,并充当kubelet和容器运行时之间的桥梁,指望与Kubernetes集成的高级容器运行时实现CRI。 预计运行时将处理镜像管理并支持Kubernetes Pod,并管理各个容器,所以根据咱们在第3篇文章中的定义,Kubernetes运行时必须是高级运行时。低级运行时缺乏一些必要的特性。 因为第3篇文章介绍了有关高级容器运行时的全部内容,所以在本文中,我将重点介绍CRI,并介绍一些支持CRI的运行时。nginx

为了更多地了解CRI,值得看一下Kubernetes架构。 Kubelet是一个位于Kubernetes集群中每一个工做节点上的代理。 Kubelet负责管理其节点的容器工做负载,当涉及到实际运行工做负载时,kubelet使用CRI与在同一节点上运行的容器运行时进行通讯。 这样,CRI仅仅是一个抽象层或API,它使您能够将容器运行时的实现单独拆分出来,而没必要将其内置到kubelet中。
image.pnggit

CRI运行时示例

这里列出一些可与Kubernetes一块儿使用的CRI运行时。github

containerd

containerd是我在第3篇文章中提到的高级运行时。containerd多是当前最流行的CRI运行时,它将CRI实现做为默认状况下启用的插件。 默认状况下,它在unix socket上开启监听,所以经过以下配置链接到容器:json

cat <<EOF | sudo tee /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
EOF

这是一个有趣的高级运行时,由于它在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来安装。socket

cri-o

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

cat <<EOF | sudo tee /etc/crictl.yaml
runtime-endpoint: unix:///var/run/crio/crio.sock
EOF

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.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
EOF

或者能够在每次命令行执行时指定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}
}