CRI shim:kubelet怎么与容器运行时交互
微信公众号:运维开发故事,作者;夏老师
CRI shim是什么?
实现了 CRI 接口的容器运行时通常称为 CRI shim, 这是一个 gRPC Server,监听在本地的 unix socket 上;而 kubelet 作为 gRPC 的客户端来调用 CRI 接口,来进行 Pod 和容器、镜像的生命周期管理。另外,容器运行时需要自己负责管理容器的网络,推荐使用 CNI。
kubelet 调用下层容器运行时的执行过程,并不会直接调用Docker 的 API,而是通过一组叫作 CRI(Container Runtime Interface,容器运行时接口)的 gRPC 接口来间接执行的,意味着需要使用新的连接方式与 docker 通信,为了兼容以前的版本,k8s 提供了针对 docker 的 CRI 实现,也就是 kubelet 包下的dockershim包,dockershim是一个 grpc 服务,监听一个端口供 kubelet 连接,dockershim收到 kubelet 的请求后,将其转化为 REST API 请求,再发送给docker daemon。Kubernetes 项目之所以要在 kubelet 中引入这样一层单独的抽象,当然是为了对 Kubernetes 屏蔽下层容器运行时的差异。
![image.png](https://img-blog.csdnimg.cn/img_convert/1edc2d18b36868c9614a5d4b13f413ed.png#clientId=ubfa83de4-e122-4&from=paste&height=512&id=HKTqm&margin=[object Object]&name=image.png&originHeight=1024&originWidth=2436&originalType=binary&ratio=1&size=135204&status=done&style=none&taskId=u82f88702-9519-4448-a937-872aeaa02d8&width=1218)
解决思路再次体现了《代码大全2》里提到的那句经典名言:
any problem in computer science can be sloved by another layer of indirecition。计算机科学领域的任何问题都可以通过增加一个中间层来解决,我们的 CRI shim就是加了这样一层。
CRI shim server 接口图示
**CRI 接口包括 RuntimeService 和 ImageService 两个服务,这两个服务可以在一个 gRPC server 中实现,也可以分开成两个独立服务。**目前社区的很多运行时都是将其在一个 gRPC server 里面实现。
![image.png](https://img-blog.csdnimg.cn/img_convert/88f85851df0cd60dcc2f401951a363a7.png#clientId=u6f450f7d-9368-4&from=paste&height=606&id=fnxe3&margin=[object Object]&name=image.png&originHeight=1212&originWidth=2122&originalType=binary&ratio=1&size=181649&status=done&style=none&taskId=ud6e49e52-c0c8-4e34-95e3-f2415c38710&width=1061)
ImageServiceServer 提供了 5 个接口,用于管理容器镜像。
管理镜像的 ImageService 提供了 5 个接口:
- 查询镜像列表;
- 拉取镜像到本地;
- 查询镜像状态;
- 删除本地镜像;
- 查询镜像占用空间等。
关于容器镜像的操作比较简单,所以我们就暂且略过。接下来,我主要为你讲解一下 RuntimeService 部分。
RuntimeService 则提供了更多的接口,按照功能可以划分为四组:
- PodSandbox 的管理接口:CRI 设计的一个重要原则,就是确保这个接口本身,只关注容器,不关注 Pod。
- PodSandbox 是对 Kubernete Pod 的抽象,用来给容器提供一个隔离的环境(比如挂载到相同的 CGroup 下面),并提供网络等共享的命名空间。PodSandbox 通常对应到一个 Pause 容器或者一台虚拟机;
- Container 的管理接口:在指定的 PodSandbox 中创建、启动、停止和删除容器;
![image.png](https://img-blog.csdnimg.cn/img_convert/1da4627bd83d58049d645c75d9df443d.png#clientId=u7f106c04-82a1-4&from=paste&height=238&id=Zi9ok&margin=[object Object]&name=image.png&originHeight=475&originWidth=1080&originalType=binary&ratio=1&size=331757&status=done&style=none&taskId=ue02c5d23-ed18-4c3b-98b0-b3469381a40&width=540)
- Streaming API 接口:包括 Exec、Attach 和 PortForward 等三个和容器进行数据交互的接口,这三个接口返回的是运行时 Streaming Server 的 URL,而不是直接跟容器交互。kubelet 需要跟容器项目维护一个长连接来传输数据。这种 API,我们就称之为 Streaming API。
- 状态接口:包括查询 API 版本和查询运行时状态。
我们通过 kubectl 命令来运行一个 Pod,那么 Kubelet 就会通过 CRI 执行以下操作:
- 首先调用 RunPodSandbox 接口来创建一个 Pod 容器,Pod 容器是用来持有容器的相关资源的,比如说网络空间、PID空间、进程空间等资源;
- 然后调用 CreatContainer 接口在 Pod 容器的空间创建业务容器;
- 再调用 StartContainer 接口启动运行容器
- 最后调用停止,销毁容器的接口为 StopContainer 与 RemoveContainer。
就完成了整个Container的生命周期。
Streaming API
CRI shim 里对 Streaming API 的实现,依赖于一套独立的 Streaming Server 机制。Streaming API 用于客户端与容器进行交互,包括 Exec、PortForward 和 Attach 等三个接口。kubelet 内置的 Docker 通过 nsenter、socat 等方法来支持这些特性,但它们不一定适用于其他的运行时,也不支持 Linux 之外的其他平台。因而,CRI 也显式定义了这些 API,并且要求容器运行时返回一个 Streaming Server 的 URL 以便 kubelet 重定向 API Server 发送过来的流式请求。
![image.png](https://img-blog.csdnimg.cn/img_convert/a31402d825d4f98d9fee08efbd0688f4.png#clientId=u6abb2437-a6fd-4&from=paste&height=348&id=dRIkw&margin=[object Object]&name=image.png&originHeight=695&originWidth=1937&originalType=binary&ratio=1&size=147078&status=done&style=none&taskId=u2727d943-eecb-43a8-b6aa-a31ad63edcb&width=968.5)
因为所有容器的流式请求都会经过 kubelet,这可能会给节点的网络流量带来瓶颈,因而 CRI 要求容器运行时启动一
个对应请求的单独的流服务器,将地址返回给 kubelet。kubelet 将这个信息再返回给 Kubernetes API Server,会直接打开与运行时提供的服务器相连的流连接,并通过它与客户端连通。
这样一个完整的 Exec 流程就如上图所示,分为多个阶段:
- 客户端 kubectl exec -i -t …;
- kube-apiserver 向 kubelet 发送流式请求 /exec/;
- kubelet 通过 CRI 接口向 CRI Shim 请求 Exec 的 URL;
- CRI Shim 向 kubelet 返回 Exec URL;
- kubelet 向 kube-apiserver 返回重定向的响应;
- kube-apiserver 重定向流式请求到 Exec URL,然后将 CRI Shim 内部的 Streaming Server 跟 kube-apiserver 进行数据交互,完成 Exec 的请求和响应。
也就是说 apiserver 其实实际上是跟 streaming server 交互来获取我们的流式数据的。这样一来让我们的整个 CRI Server 接口更轻量、更可靠。
注意:当然,这个 Streaming Server 本身,是需要通过使用 SIG-Node 为你维护的 Streaming API 库来实现的。并且,Streaming Server 会在 CRI shim 启动时就一起启动。此外,Stream Server 这一部分具体怎么实现,完全可以由 CRI shim 的维护者自行决定。比如,对于 Docker 项目来说,dockershim 就是直接调用 Docker 的 Exec API 来作为实现的。
CRI-containerd架构解析与主要接口解析
![image.png](https://img-blog.csdnimg.cn/img_convert/5dd816ec1950eeb29da6638d74a35d19.png#clientId=u6b46695e-eb1c-4&from=paste&height=338&id=u2e9b7200&margin=[object Object]&name=image.png&originHeight=675&originWidth=1620&originalType=binary&ratio=1&size=218172&status=done&style=none&taskId=u18bd24b2-4b67-41dc-b8ac-8b4d6d3272b&width=810)
![image.png](https://img-blog.csdnimg.cn/img_convert/8344e052008131de4eb35099df361410.png#clientId=u7f106c04-82a1-4&from=paste&height=198&id=vwJx9&margin=[object Object]&name=image.png&originHeight=395&originWidth=1080&originalType=binary&ratio=1&size=225113&status=done&style=none&taskId=uf690d137-f035-4d9a-b4c9-da3ea33587c&width=540)
整个架构看起来非常直观。这里的 Meta services、Runtime service 与 Storage service 都是 containerd 提供的接口。它们是通用的容器相关的接口,包括镜像管理、容器运行时管理等。CRI 在这之上包装了一个 gRPC 的服务。右侧就是具体的容器的实现。比如说,创建容器时就要创建具体的 runtime 和它的containerd-shim。 Container 和 Pod Sandbox组成了一个Pod。
CRI-containerd 的一个好处是,containerd 还额外实现了更丰富的容器接口,所以它可以用 containerd 提供的 ctr 工具来调用这些丰富的容器运行时接口,而不只是 CRI 接口
CRI实现了两个GRPC协议的API,提供两种服务ImageService和RuntimeService。
// grpcServices are all the grpc services provided by cri containerd.
type grpcServices interface {runtime.RuntimeServiceServerruntime.ImageServiceServer
}
// CRIService is the interface implement CRI remote service server.
type CRIService interface {Run() error// io.Closer is used by containerd to gracefully stop cri service.io.Closerplugin.ServicegrpcServices
}
CRI的实现CRIService中包含了很多重要的组件:其中最重要的是cni.CNI,用于配置容器网络。还有containerd.Client,用于连接containerd来创建容器。
// criService implements CRIService.
type criService struct {// config contains all configurations.config criconfig.Config// imageFSPath is the path to image filesystem.imageFSPath string// os is an interface for all required os operations.os osinterface.OS// sandboxStore stores all resources associated with sandboxes.sandboxStore *sandboxstore.Store// sandboxNameIndex stores all sandbox names and make sure each name// is unique.sandboxNameIndex *registrar.Registrar// containerStore stores all resources associated with containers.containerStore *containerstore.Store// containerNameIndex stores all container names and make sure each// name is unique.containerNameIndex *registrar.Registrar// imageStore stores all resources associated with images.imageStore *imagestore.Store// snapshotStore stores information of all snapshots.snapshotStore *snapshotstore.Store// netPlugin is used to setup and teardown network when run/stop pod sandbox.netPlugin cni.CNI// client is an instance of the containerd clientclient *containerd.Client// streamServer is the streaming server serves container streaming request.streamServer streaming.Server// eventMonitor is the monitor monitors containerd events.eventMonitor *eventMonitor// initialized indicates whether the server is initialized. All GRPC services// should return error before the server is initialized.initialized atomic.Bool// cniNetConfMonitor is used to reload cni network conf if there is// any valid fs change events from cni network conf dir.cniNetConfMonitor *cniNetConfSyncer// baseOCISpecs contains cached OCI specs loaded via `Runtime.BaseRuntimeSpec`baseOCISpecs map[string]*oci.Spec
}
我们知道 Kubernetes 的一个运作的机制是面向终态的,在每一次调协的循环中,Kubelet 会向 apiserver 获取调度到本 Node 的 Pod 的数据,再做一个面向终态的处理,以达到我们预期的状态。
循环的第一步,首先通过 List 接口拿到容器的状态。确保有镜像,如果没有镜像则 pull 镜像再通过 Sandbox 和 Container 接口来创建容器。
需要注意的是,我们的 CNI(容器网络接口)也是在 CRI 进行操作的,因为我们在创建 Pod 的时候需要同时创建网络资源然后注入到 Pod 中(PS:CNI包含在创建Pod 这个动作里)。接下来就是我们的容器和镜像。我们通过具体的容器创建引擎来创建一个具体的容器。
执行流程为:
- Kubelet 通过 CRI runtime service API 调用 CRI plugin 创建 pod
- CRI 通过 CNI 创建 pod 的网络配置和 namespace
- CRI使用 containerd 创建并启动 pause container (sandbox container) 并且把这个 container 置于 pod 的 cgroups/namespace
- Kubelet 接着通过 CRI image service API 调用 CRI plugin, 获取容器镜像
- CRI 通过 containerd 获取容器镜像
- Kubelet 通过 CRI runtime service API 调用 CRI, 在 pod 的空间使用拉取的镜像启动容器
- CRI 通过 containerd 创建/启动 应用容器, 并且把 container 置于 pod 的 cgroups/namespace. Pod 完成启动。
ctr命令行工具初始用
打印服务端和客户端版本:
# ctr version
Client:
Version: v1.4.4
Revision: 05f951a3781f4f2c1911b05e61c160e9c30eaa8e
Go version: go1.15.8Server:
Version: v1.4.4
Revision: 05f951a3781f4f2c1911b05e61c160e9c30eaa8e
UUID: dee82270-b4b4-429c-befa-45df1421da7e
pull docker hub中的redis镜像:
# pull镜像
ctr images pull docker.io/library/redis:alpine3.13# 查看镜像
ctr i ls
pull私有仓库的镜像,需要使用-u :给定镜像仓库的用户名和密码:
ctr images pull -u user:password harbor.my.org/library/nginx:1.1
启动这个测试的redis容器:
ctr run -d docker.io/library/redis:alpine3.13 redis
查看容器(container和task):
ctr container ls
CONTAINER IMAGE RUNTIME
redis docker.io/library/redis:alpine3.13 io.containerd.runc.v2ctr task ls
TASK PID STATUS
redis 20808 RUNNING
注意: 在containerd中,container和task是分离的,container描述的是容器分配和附加资源的元数据对象,是静态内容,task是任务是系统上一个活动的、正在运行的进程。 task应该在每次运行后删除,而container可以被多次使用、更新和查询。这点和docker中container定义是不一样的。
进入到容器中执行redis命令:
ctr task exec -t --exec-id redis-sh redis sh
/data # redis-cli
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
查看一下系统中的进程信息:
ps -ef | grep runc | grep redis
/usr/local/containerd/bin/containerd-shim-runc-v2 -namespace default -id redis -address /run/containerd/containerd.sock
可以看出containerd中是存在namespace概念的,这样可以将不同业务和应用进行隔离,例如k8s使用containerd和直接使用ctr创建的容器可以隔离开。查看一下当前的namespace:
ctr ns ls
NAME LABELS
default
k8s.io
不同namespace下pull的镜像也是隔离显示的,可以使用-n指定具体的namespace:
ctr -n default i ls
ctr -n k8s.io i ls
crictl命令行工具配置和初始用
crictl是k8s cri-tools的一部分,它提供了类似于docker的命令行工具,不需要kubelet就可以通过CRI跟容器运行时通信。 crictl是专门为k8s设计的,提供了Pod、容器和镜像等资源的管理命令,可以帮助用户调试容器应用或者排查异常问题。 crictl 可以用于所有实现了CRI接口的容器运行时。可以排查常见的PLEG的问题。
尝试使用ctictl命令行工具查看一下镜像,给了一个警告信息:
crictl images
WARN[0000] image connect using default endpoints: [unix:///var/run/dockershim.sock unix:///run/containerd/containerd.sock unix:///run/crio/crio.sock]. As the default settings are now deprecated, you should set the endpoint instead.
ERRO[0002] connect endpoint 'unix:///var/run/dockershim.sock', make sure you are running as root and the endpoint has been started: context deadline exceeded
IMAGE TAG IMAGE ID SIZE
需要我们显示配置默认的endpoints:
crictl config runtime-endpoint unix:///run/containerd/containerd.sock
crictl config image-endpoint unix:///run/containerd/containerd.sock
上面的命令执行完成后将生成配置文件/etc/crictl.yaml:
runtime-endpoint: "unix:///run/containerd/containerd.sock"
image-endpoint: "unix:///run/containerd/containerd.sock"
timeout: 0
debug: false
pull-image-on-create: false
disable-pull-on-run: false
pull镜像:
crictl pull docker.io/library/redis:alpine3.13crictl images
IMAGE TAG IMAGE ID SIZE
docker.io/library/redis alpine3.13 554d20f203657 10.9MB
crictl pull的镜像实际上是在k8s.io namespace下,可以使用ctr -n k8s.io i ls查看。
crictl不能像ctr那样通过参数给定用户名和密码的方式从开启认证的私有仓库中pull镜像。需要对containerd进行配置。 containerd提供的各种功能在其内部都是通过插件实现的,可以使用ctr plugins ls查看containerd的插件:
ctr plugins lsTYPE ID PLATFORMS STATUS
io.containerd.content.v1 content - ok
io.containerd.snapshotter.v1 aufs linux/amd64 error
io.containerd.snapshotter.v1 btrfs linux/amd64 error
io.containerd.snapshotter.v1 devmapper linux/amd64 error
io.containerd.snapshotter.v1 native linux/amd64 ok
io.containerd.snapshotter.v1 overlayfs linux/amd64 ok
io.containerd.snapshotter.v1 zfs linux/amd64 error
io.containerd.metadata.v1 bolt - ok
io.containerd.differ.v1 walking linux/amd64 ok
io.containerd.gc.v1 scheduler - ok
io.containerd.service.v1 introspection-service - ok
io.containerd.service.v1 containers-service - ok
io.containerd.service.v1 content-service - ok
io.containerd.service.v1 diff-service - ok
io.containerd.service.v1 images-service - ok
io.containerd.service.v1 leases-service - ok
io.containerd.service.v1 namespaces-service - ok
io.containerd.service.v1 snapshots-service - ok
io.containerd.runtime.v1 linux linux/amd64 ok
io.containerd.runtime.v2 task linux/amd64 ok
io.containerd.monitor.v1 cgroups linux/amd64 ok
io.containerd.service.v1 tasks-service - ok
io.containerd.internal.v1 restart - ok
io.containerd.grpc.v1 containers - ok
io.containerd.grpc.v1 content - ok
io.containerd.grpc.v1 diff - ok
io.containerd.grpc.v1 events - ok
io.containerd.grpc.v1 healthcheck - ok
io.containerd.grpc.v1 images - ok
io.containerd.grpc.v1 leases - ok
io.containerd.grpc.v1 namespaces - ok
io.containerd.internal.v1 opt - ok
io.containerd.grpc.v1 snapshots - ok
io.containerd.grpc.v1 tasks - ok
io.containerd.grpc.v1 version - ok
io.containerd.grpc.v1 cri linux/amd64 ok
私有镜像仓库相关的配置在cri插件中,文档Configure Image Registry中包含了镜像仓库的配置。 关于私有仓库和认证信息配置示例如下,修改/etc/containerd/config.toml:
...
[plugins]
...
[plugins."io.containerd.grpc.v1.cri"]
...
[plugins."io.containerd.grpc.v1.cri".registry]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://registry-1.docker.io"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."harbor.my.org"]
endpoint = ["https://harbor.my.org"]
[plugins."io.containerd.grpc.v1.cri".registry.configs]
[plugins."io.containerd.grpc.v1.cri".registry.configs."harbor.my.org".tls]
insecure_skip_verify = true
[plugins."io.containerd.grpc.v1.cri".registry.configs."harbor.my.org".auth]
username = "username"
password = "passwd"
# auth = "base64(username:password)"
...
配置完成后重启containerd,就可以使用crictl pull配置的私有仓库的镜像了:
crictl pull harbor.my.org/library/nginx:1.1
reference
https://time.geekbang.org/column/article/71499?utm_campaign=guanwang&utm_source=baidu-ad&utm_medium=ppzq-pc&utm_content=title&utm_term=baidu-ad-ppzq-title
https://blog.frognew.com/2021/04/relearning-container-02.html
https://github.com/kubernetes-sigs/cri-tools/blob/master/docs/crictl.md
[
](https://time.geekbang.org/column/article/71499?utm_campaign=guanwang&utm_source=baidu-ad&utm_medium=ppzq-pc&utm_content=title&utm_term=baidu-ad-ppzq-title)
CRI shim:kubelet怎么与容器运行时交互相关推荐
- 【容器运行时】一文理解 OCI、runc、containerd、docker、shim进程、cri、kubelet 之间的关系
参考 docker,containerd,runc,docker-shim 之间的关系 Containerd shim 进程 PPID 之谜 内核大神教你从 Linux 进程的角度看 Docker R ...
- 课时 28:理解容器运行时接口 CRI(知谨)
CRI 是 Kubernetes 体系中跟容器打交道的一个非常重要的部分.本文将主要分享以下三方面的内容: CRI 介绍 CRI 实现 相关工具 CRI 介绍 在 CRI 出现之前(也就是 Kuber ...
- 从零开始入门 K8s | 理解容器运行时接口 CRI
作者 | 知谨 阿里云工程师 本文整理自<CNCF x Alibaba 云原生技术公开课>第 28 讲,点击直达课程页面. 关注"阿里巴巴云原生"公众号,回复关键词** ...
- 什么是Kubernetes的CRI - 容器运行时接口
我们都知道Kubernetes不会直接和容器打交道,Kubernetes的使用者能接触到的概念只有pod,而pod里包含了多个容器.当我们在Kubernetes里用kubectl执行各种命令时,Kub ...
- 课时 30:理解 RuntimeClass 与使用多容器运行时(贾之光)
本文将主要分享以下三方面的内容: RuntimeClass 需求来源 RuntimeClass 功能介绍 多容器运行时示例 RuntimeClass 需求来源 容器运行时的演进过程 我们首先了解一下容 ...
- 1.Containerd容器运行时初识与尝试
0x00 前言简述 1.基础介绍 2.专业术语 3.架构简述 0x01 安装配置 1.Ubuntu安装Containerd.io流程 0x02 简单使用 1.镜像拉取与运行 2.创建和使用网络 3.与 ...
- 3.Containerd容器运行时的配置浅析与知识扩充实践
公众号关注「WeiyiGeek」 设为「特别关注」,每天带你玩转网络安全运维.应用开发.物联网IOT学习! 本章目录: 0x00 Containerd 容器运行时配置指南 如何配置 Container ...
- 云原生钻石课程 | 第1课:容器运行时技术深度剖析
点击上方"程序猿技术大咖",关注并选择"设为星标" 回复"加群"获取入群讨论资格! 本篇文章来自<华为云云原生王者之路训练营>钻 ...
- 部署一个 Containerd 容器运行时的 Kubernetes 集群
前面我们介绍了 containerd 的基本使用,也了解了如何将现有 docker 容器运行时的 Kubernetes 集群切换成 containerd,接下来我们使用 kubeadm 从头搭建一个使 ...
- 关于容器和容器运行时的那些事
转载本文需注明出处:微信公众号EAWorld,违者必究. 前言: 容器,容器编排,微服务,云原生,这些无疑都是当下软件开发领域里面最热门的术语.容器技术的出现并迅速的广泛应用于软件开发的各个领域里,主 ...
最新文章
- android stadio svn 使用技巧
- TypeScript学习笔记3:运算符
- 《Web前端工程师修炼之道(原书第4版)》——我该从哪里开始呢
- python3.6.3安装过程_python3.6.3安装图文教程 TensorFlow安装配置方法
- (扫盲)RPC远程过程调用
- yii model层操作总结
- matlab 信息融合,MSDF,matlab,多传感器信息融合
- 如果一切需要重学,2014年应该学哪些技术?
- 【网络流24题】餐巾计划问题(费用流)
- 关于在EF中通用方法
- 关于SWAT模型的一些原理(二)
- python做语音识别
- 在html创建色块,浅谈网页制作中色块使用
- 【论文排版术】学习笔记1
- 项目实训--Unity多人游戏开发(十六、草丛隐身与道具隐身)
- edg击败we视频_2017LPL春季赛4月8日WE VS EDG视频:EDG 2:0 WE获胜
- wps通过vb宏来查看文档中使用的所有字体
- spring-session(一)揭秘
- 页面跳转传参,A 页面跳转到B页面,把A页面获取的值传到B页面
- Selenium教程(4)操作选择框
热门文章
- HTTP TFP状态解释
- visio的替代者yEd Graph Editor
- [网络规划] 拓扑图绘图工具yED Graph Editor使用(持续更新)
- 乘风领航、耀世创新——DEFI平台Lizard打造数字金融新世界
- ElasticSearch:简单介绍以及使用Docker部署ElasticSearch 和 Kibana
- CTF-8021-题目一
- ArcGis基础—shapefile矢量文件与lyr图层文件之间有何区别?
- 百度表格识别——原理解读
- stm32F407控制器在驱动电机等执行机构时,ADS1256采集模块出现死机现象,问题待解决
- specular高光贴图