首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Containerd深度剖析-CRI篇

撰文 | 段全锋

编辑 | zouyee

段全锋: 软件工程师,熟悉K8s架构、精通Runtime底层技术细节等。

目前我司现网的K8s集群的运行时已经完成从docker到Containerd的切换,有小伙伴对K8s与Containerd调用链涉及的组件不了解,其中Containerd和RunC是什么关系,docker和containerd又有什么区别,以及K8s调用Containerd创建容器又是怎样的流程,最终RunC又是如何创建容器的,诸如此类的疑问。本文就针对K8s使用Containerd作为运行时的整个调用链进行介绍和源码级别的分析。

其中关于kubelet与运行时的分层架构图可以参看下图

“运行时简介”

容器运行时意思就是能够管理容器运行的整个生命周期,具体一点就是如何制作容器的镜像、容器镜像格式是什么样子的、管理容器的镜像、容器镜像的分发、如何运行一个容器以及管理创建的容器实例等等。

容器运行时有一个行业标准叫做OCI规范,这个规范分成两部分:

a.容器运行时规范:描述了如何通过一个bundle运行容器,bundle就是一个目录,里面包括一个容器的规格文件,文件叫 config.json 和一个rootfs,rootfs中包含了一个容器运行时所需操作系统的文件。

b. 容器镜像规范:定义了容器的镜像如何打包如何将镜像转换成一个bundle。

目前流行将运行时分成low-level运行时和high-level运行时,low-level运行时专注于如何创建一个容器例如runc和kata,high-level包含了更多上层功能,比如镜像管理,以docker和containerd为代表。

K8s的kubelet是调用容器运行时创建容器的,但是容器运行时这么多不可能逐个兼容,K8s在对接容器运行时定义了CRI接口,容器运行时只需实现该接口就能被使用。下图分别是k8s使用docker和containerd的调用链,使用containerd时CRI接口是在containerd代码中实现的;使用docker时的CRI接口是在k8s的代码中实现的,叫做docker-shim(kubernetes/pkg/kubelet/dockershim/docker_service.go),这部分代码在k8s代码中是历史原因,当时docker是容器方面行业事实上的标准,但随着越来越多运行时实现了CRI支持,docker-shim的维护日益变成社区负担,在最新的K8s版本中,该部分代码目前已经移出,暂时由mirantis进行维护,下图是插件的发展历程。

“Containerd CRI简介”

Containerd是一个行业标准的容器运行时,它是一个daemon进程,可以管理主机上容器的全部生命周期和它的文件系统,包括:镜像的分发和存储、容器的运行和监控,底层的存储和网络。

Containerd有多种客户端,比如K8s、docker等,为了不同客户端的容器或者镜像能隔离开,Containerd中有namespace概念,默认情况下docker的namespace是moby,K8s的是k8s.io。

container在Containerd中代表的是一个容器的元数据,containerd中的Task用于获取容器对象并将它转换成在操作系统中可运行的进程,它代表的就是容器中可运行的对象。

Containerd内部的cri模块实现K8s的CRI接口,所以K8s的kubelet可以直接使用containerd。CRI的接口包括:RuntimeServiceImageService

kubelet调用CRI接口创建一个包含A和B两个业务container的Pod流程如下所示:

为Pod创建sandbox

创建container A

启动container A

创建container B

启动container B

“Containerd CRI实现”

RunPodSandbox

RunPodSandbox的流程如下:

拉取sandbox的镜像,在containerd中配置

获取创建pod要使用的runtime,可以在创建pod的yaml中指定,如果没指定使用containerd中默认的(runtime在containerd中配置)

如果pod不是hostNetwork那么添加创建新net namespace,并使用cni插件设置网络(criService在初始化时会加载containerd中cri指定的插件信息)

调用containerd客户端创建一个container

在rootDir/io.containerd.grpc.v1.cri/sandboxes下为当前pod以pod Id为名创建一个目录(pkg/cri/cri.go)

根据选择的runtime为sandbox容器创建task

启动sandbox容器的task,将sandbox添加到数据库中

代码在containerd/pkg/cri/server/sanbox_run.go 中

CreateContainer

CreateContainer在指定的PodSandbox中创建一个新的container元数据,流程如下:

获取容器的sandbox信息

为容器要用的镜像初始化镜像handler

从sandbox中获取所使用的runtime

为容器创建containerSpec

使用containerd客户端创建container

保存container的信息

代码见 containerd/pkg/cri/server/container_create.go 下面是省略过的代码。

StartContainer

StartContainer用于启动一个容器,流程如下:

读取保存的container元数据

读取关联的sandbox信息

为容器创建task

启动task

代码见 containerd/pkg/cri/server/container_start.go,下面是该部分省略过后的代码:

创建task的代码如下,调用了containerd的客户端的TasksClient,向服务器端发送创建task的请求

task启动的代码如下,调用了containerd的客户端的TasksClient,向服务器端发送启动task的请求。

“Task Service”

Task Service创建task流程

下面是tasks-service处理创建task请求的代码,根据容器运行时创建容器。

runtime创建容器代码如下,启动了shim并向shim发送创建请求。

startShim调用shim可执行文件启动了一个service,代码如下:

Task Service启动task流程

下面是tasks-service启动一个task的流程:

启动容器的进程通过向shim的server端发送请求完成。

“Containerd-shim启动流程”

containerd/runtime/v2/shim/shim.go 中

是containerd-shim-runc-v2 start 的代码入口:

containerd-shim-runc-v2 start进程会再次创建一个containerd-shim-runc-v2 -namespace xxxx -id xxxx - address xxxx 的进程用于启动shim server。

shim server是个ttrpc服务,提供如下接口:

创建task是执行了runc create --bundle xxxx xxxx 命令,参考代码:

启动task是执行了runc start xxxx 命令,参考代码:

小结

kubelet创建sandbox流程总结如下:

containerd的cri模块创建sandbox元数据并保存

containerd的cri模块创建sandbox容器并保存

containerd的cri模块通过grpc调用tasks-service创建task

tasks-service模块创建containerd-shim-xxxx-xxxx start 进程

containerd-shim-xxxx-xxxx start 进程创建containerd-shim- xxxx-xxxx 进程并退出

containerd-shim-xxxx-xxxx 进程启动shim server,提供ttrpc服务

tasks-service模块调用shim server的Create接口,创建task,shim server 执行runc create 命令

containerd的cri模块通过grpc调用tasks-service启动task

 tasks-service模块调用shimserver的Start接口,启动task,shim server 执行runc start命令

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20230110A01GA900?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券