描述:目前Docker是Kubernetes默认的容器运行时(ContainerRuntime),由于k8s在2020年宣布1.20版本之后将弃用dockershim(其中也有kubernetes与Docker爱恨情仇)时,才把containerd拉回大众的视野之中,所以本章主要讲解containerd基础入门。
Q:什么是Containerd
答:Containerd是从Docker中分类出的容器运行时与runc一样被分解为Docke的高级运行时部分,它支持OCI的镜像标准、可以实现拉取和推送镜像、管理操作镜像负责容器的整个生命周期。例如当它需要运行一个容器时,它会将映像解压到一个OCI运行时包中,并将其发送给runc来运行它,Containerd还提供了一个API和客户端应用程序可以用来与之交互,containerd命令行客户端是ctr命令。官方介绍:Anindustry-standardcontainerruntimewithanemphasisonsimplicity,robustnessandportability-业界标准的容器运行时,强调简单性、健壮性和可移植性。
Q:Containerd有何特点
发展历史:(Containerd和Docker的前世今生以及爱恨情仇)
在几年之前Docker公司在容器技术领域强势崛起一家独大,Google、RedHat这样的巨头们都产生了很大的危机感,因此他们想与Docker公司一起联合研发推进一个开源的容器运行时作为Docker技术的核心依赖。然鹅Docker公司很高冷的表示:我不干!巨头们听到这个反馈就很不爽啊,因此通过一些手段对Docker公司软硬兼施,使其将libcontainer捐给了开源社区,也就是现在的runc,一个lowlevel的容器运行时。此外巨头们又成立了CNCF去对抗Docker公司的一家独大,CNCF成立的思路很明确:在容器领域干不过Docker,那就搞容器上层的建设——容器编排,从此K8s诞生了。虽然Docker公司也尝试使用Swarm去对抗K8s但最终也失败了。
自此K8s慢慢成为云原生领域的标配,其生态也越做越大、越做越完善。Docker公司为了融入生态,开源了Docker的核心依赖containerd。2019年2月28日containerd正式成为云原生计算基金会(CloudNativeComputingFoundation-CNCF)的一个毕业项目,紧随Kubernetes、Prometheus、Envoy和CoreDNS之后,在从containerd1.5开始,Kubernetes容器运行时接口(CRI)的containerd插件已经合并到containerd中。。
此外K8s为了对接下一层的容器,也因为其中立性搞了一个运行时接口,也就是CRI(ContainerRuntimeInterface),runc、containerd等运行时都去支持此接口。由于当时确实没有啥highlevel的runtime,oci-o虽然支持CRI接口,但其功能太弱;containerd也尚未成熟,而且其当时的定位是嵌入到系统中,并非给终端用户使用;rkt有自己的一套体系后来这个项目也失败了。只能暂时为了适配Docker搞了个shim,将CRI转换为DockerAPI。K8s和Docker进入了冷静期,双方都在各自优化自己,互不干扰。然而平静之下仍是暗潮汹涌,CNCF社区一直在不断完善containerd,其定位也发生了改变,由原来的系统嵌入组件,变成了今天的"工业标准容器运行时",并提供给终端用户使用。直到2020年12月K8s宣布废弃使用Docker,而改用Containerd。
总结:其实kubernetes宣布废弃dockershim,一方面是因为商业因素,而另一方面K8s已经提供了标准接口对接底层容器运行时,不再想单独维护一个类似于Dockershim的适配层去迎合不同的运行时。
Q:那什么是dockershim
正因为这样,只要Kubernetes的任何变动或者Docker的任何变动,都必须维护dockershim,这样才能保证足够的支持,但是通过dockershim操作Docker,其本质还是操作Docker的底层运行时Containerd,而且Containerd自身也是支持CRI(ContainerRuntimeInterface)交互,所以正如前面所说只是kubernetes不想单独维护一个类似于Dockershim的适配层去迎合不同的运行时。
Tips:在github存储库中的containerd基本项目级信息:
OCI
Docker公司与CoreOS和Google共同创建了OCI(OpenContainerInitial)并提供了两种规范:
Tips:文件系统束(filesystembundle):定义了一种将容器编码为文件系统束的格式,即以某种方式组织的一组文件,并包含所有符合要求的运行时对其执行所有标准操作的必要数据和元数据,即config.json与根文件系统。
Tips:CRI(ContainerRuntimeInterface,容器运行时接口):它是为了解决这些容器运行时和Kubernetes的集成问题在Kubernetes1.5版本中推出。
Docker、Google等开源了用于运行容器的工具和库runC作为OCI的一种实现参考,随后各种运行时和库也慢慢出现例如rkt、containerd(今天的主角)、cri-o,然而这些工具所拥有的功能却不尽相同,有的只有运行容器(runc、lxc),而有的除此之外也可以对镜像进行管理(containerd、cri-o),按照前面容器运行时进行分为两类,其不同容器运行时工具分类关系图如下。
Runtime
CRI
描述:CRI是Kubernetes定义的一组gRPC服务。Kubelet作为客户端,基于gRPC框架,通过Socket和容器运行时通信。它包括两类服务:镜像服务(ImageService)和运行时服务(RuntimeService)。镜像服务提供下载、检查和删除镜像的远程程序调用。运行时服务包含用于管理容器生命周期,以及与容器交互的调用(exec/attach/port-forward)的远程程序调用。
客户端工具
描述:containerd是Linux和Windows的守护程序。它管理其主机系统的完整容器生命周期,从图像传输和存储到容器执行和监督,再到低级存储到网络附件等等。
Containerd的设计是一个大的插件系统,下图中每一个虚线框其实就对应一个插件。
我们可以将上面的架构图简化如下,简化后的Containerd分为三大块Storage管理镜像文件的存储;Metadata管理镜像和容器的元数据;由Task触发运行时,并对外提供GRPC方式的API以及Metrics接口。
containerd-shim
描述:主要是用于剥离containerd守护进程与容器进程。目前已有shimv1和shimv2两个版本;它是containerd中的一个组件,其通过shim调用runc的包函数来启动容器。直白的说引入shim是允许runc在创建和运行容器之后退出,并将shim作为容器的父进程,而不是containerd作为父进程。
#-当我们执行pstree命令时可以看到如下的进程关系containerd-shim是独立于containerd进程运行的。pstreesystemd─┬─VGAuthService├─containerd───15*[{containerd}]├─containerd-shim─┬─sh│└─13*[{containerd-shim}]├─2*[containerd-shim─┬─sh]│└─12*[{containerd-shim}]]此种方式的目的是当containerd进程挂掉时保证容器不受影响,此外shim也可以收集和报告容器的退出状态,不需要containerd来wait容器进程。
Tips:我们有需求去替换runc运行时工具库时,例如替换为安全容器katacontainer或Google研发的gViser,则需要增加对应的shim(kata-shim)以上两者均有自己实现的shim。
CRI&OCI
如下图所示dockershim,containerd和cri-o都是遵循CRI的容器运行时,我们称他们为高层级运行时(High-levelRuntime)。
ContainerdvsCri-o
描述:前面我们说到过kubernetes为啥会替换掉Docker呢主要原因就是其复杂性,由于Docker的多层封装和调用,导致其在可维护性上略逊一筹,增加了线上问题的定位难度(貌似除了重启Docker,我们就毫无他法了);
-
Containerd
CRI-O
备注
性能
更优
优
功能
CRI与OCI兼容
稳定性
稳定
未知
DeviceMappervs.Overlayfs
描述:容器运行时使用存储驱动程序(storagedriver)来管理镜像和容器的数据。目前我们生产环境选用的是DeviceMapper。然而目前DeviceMapper在新版本的Docker中已经被弃用,containerd也放弃对DeviceMapper的支持。
当初选用DeviceMapper,也是有历史原因的。我们大概是在2014年开始Kubernetes这个项目的。那时候Overlayfs都还没合进kernel。当时我们评估了Docker支持的存储驱动程序,发现DeviceMapper是最稳定的。所以我们选用了DeviceMapper。但是实际使用下来,由DeviceMapper引起的Docker问题也不少。所以我们也借这个契机把DeviceMapper给换掉,换成现在containerd和Docker都默认的Overlayfs。
从下图的测试结果来看,Overlayfs的IO性能比DeviceMapper好很多。Overlayfs的IOPS大体上能比DeviceMapper高20%,和直接操作主机路径差不多。最终,我们选用了containerd,并以Overlayfs作为存储后端的文件系统,替换了原有的Docker加DeviceMapper的搭配。
我们在同一个节点上同时起10,30,50和80的Pod,然后再同时删除,去比较迁移前后创建和删除的用时。从下图可知,containerd用时明显优于Docker。
安装环境:已进行安全基线加固以及系统优化。
Tips:containerd默认的三个名称空间(Namespace)是default,docker.io,k8s.io
项目说明:
操作流程:
描述:在Docker我们常常需要将配置文件或者各类数据映射进入到docker容器之中,便于容器内部程序使用或者数据的持久化。
下面分别简单演示与宿主机或者其它容器共享目录;
ctr-nk8s.ioccreatedocker.io/library/busybox:latestbusybox-2--mounttype=bind,src=/tmp,dst=/host,options=rbind:rwctr-nk8s.iotstart-dbusybox-2shctr-nk8s.iotexec-t--exec-id$RANDOMbusybox-2sh#/#echo"WeiyiGeek">/host/name#/#root@containerd:~#cat/tmp/nameWeiyiGeek首先我们对docker的ns共享进行实验:
[root@dockerscripts]#dockerrun--rm-it-dbusyboxsh687c80243ee15e0a2171027260e249400feeeee2607f88d1f029cc270402cdd1[root@dockerscripts]#dockerrun--rm-it-d--pid="container:687c80243ee15e0a2171027260e249400feeeee2607f88d1f029cc270402cdd1"busyboxcatfa2.....d2[root@dockerscripts]#dockerps-aCONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMESfa2c09bd9c04busybox"cat"2secondsagoUp1secondpedantic_goodall687c80243ee1busybox"sh"22secondsagoUp21secondshopeful_franklin[root@dockerscripts]#dockerexec-it687c80243ee1sh/#psauxPIDUSERTIMECOMMAND1root0:00sh8root0:00cat15root0:00sh然后基于containerd的方式实现pidns共享:
$ctr-nk8s.iotls#TASKPIDSTATUS#busybox43908RUNNING#busybox-148850RUNNING#这里的48850即为已有task运行时的pid号#busybox-250314RUNNING$ctr-nk8s.ioccreate--with-ns"pid:/proc/48850/ns/pid"v4ehxdz8.mirror.aliyuncs.com/library/python:3.6-slimpython#将容器中运行的进程共享注入到48850pid之中$ctr-nk8s.iotstart-dpythonpython#启动了一个python的命令$ctr-nk8s.iotexec-t--exec-id$RANDOMbusybox-1sh#至此可以在busybox-1容器中看见刚执行的python命令/#psauxPIDUSERTIMECOMMAND1root0:00sh34root0:00python34.Docker与Containerd并用配置描述:为了更方便我们学习可以将Docker与Containerd联合使用。
功能说明
ContainerdCLI
DockerCLI
CrictlCLI
镜像列举
ctrimagels/list
dockerimagesls
crictlimagesls
导出镜像
ctrimageexportapp.tarweiyigeek.top/app:1.2.0
dockersave-oapp.tarapp:1.2.0
导入镜像
ctrimageimportapp.tar
dockerload-iapp.tar
拉取镜像
ctr-nk8s.ioimagespulldocker.io/library/redis:latest
dockerpullredis:latest
crictlpullredis:latest
上传镜像
ctr-nk8s.ioimagespushdocker.io/library/redis:latest
dockerpush
crictlpush
更改标记
ctr-nk8s.ioimagestagdocker.io/library/redis:latestweiyigeek.top/redis:latest
dockertagredis:latestweiyigeek.top/redis:latest
删除镜像
ctr-nk8s.ioimagesrmdocker.io/library/redis:latest
dockerrmi
crictlrmi
创建容器
ctr-nk8s.iocontainercreatedocker.io/library/redis:latestredis
dockercreate
crictlcreate
创建并运行容器
ctrrun-d--envname=WeiyiGeekapplicationweiyigeek.top/app:1.2.0
dockerrun
查看容器
ctr-nk8s.iocontainerlist
dockerps
crictlps
启动容器
ctr-nk8s.iotaskstart
dockerstart
crictlstart
停止容器
ctr-nk8s.iotaskpause
dockerstop
crictlstop
删除容器
ctr-nk8s.iocontainerrm
dockerrm
crictlrm
容器详情
ctr-nk8s.iocinfo39d36ef08456
dockerinspect39d36ef08456
crictlinspect39d36ef08456
容器连接
ctr-nk8s.iotaskattach
dockerattach
crictlattach
进入容器
ctr-nk8s.iotaskexec
dockerexec
crictlexec
stats(状态)
ctr-nk8s.iotaskmetric39d36ef08456
dockertop
crictlstats
日志查看
ctr-nk8s.ioevent
dockerlogs--tail508db74c2bf7595
crictllogs--tail508db74c2bf7595
描述:它是一个不受支持的调试和管理客户端,用于与容器守护进程进行交互。因为它不受支持,所以命令、选项和操作不能保证向后兼容或稳定从集装箱项目的发布到发布。
语法参数:
Tips:当导入本地镜像时ctr不支持压缩。
Tips:通过ctrcontainerscreate创建容器后,只是一个静态的容器,容器中的用户进程并没有启动,所以还需要通过ctrtaskstart来启动容器进程。当然也可以用ctrrun的命令直接创建并运行容器。在进入容器操作时与docker不同的是,必须在ctrtaskexec命令后指定--exec-id参数,这个id可以随便写只要唯一就行。
Tips:ctr没有stop容器的功能,只能暂停(ctrtaskpause)或者杀死(ctrtaskkill)容器
实际案例:
Tips:注意需要停止容器时需要先停止容器内的Task再删除容器。
描述:crictl是CRI兼容的容器运行时命令行对接客户端,你可以使用它来检查和调试Kubernetes节点上的容器运行时和应用程序。由于该命令是为k8s通过CRI使用containerd而开发的(主要是调试工具),其他非k8s的创建的crictl是无法看到和调试的,简单的说用ctrrun运行的容器无法使用crictl看到。
Tips:crictl默认使用命名空间k8s.io.
Tips:criplugin区别对待pod和container
基础配置
描述:在k8s1.19.x之前crictl默认连接到unix:///var/run/dockershim.sock,而在1.20.x起默认采用/run/containerd/containerd.sock运行时,当然除此之外还是支持cri-o运行时。
#crictlbydefaultconnectsonUnixto:unix:///var/run/dockershim.sockorunix:///run/containerd/containerd.sockorunix:///run/crio/crio.sock#oronWindowsto:npipe:////./pipe/dockershimornpipe:////./pipe/containerdornpipe:////./pipe/crio#Forotherruntimes,use:frakti:unix:///var/run/frakti.sock查看或编辑/etc/crictl.yaml的内容
$cat/etc/crictl.yamlruntime-endpoint:/run/containerd/containerd.sockimage-endpoint:"/run/containerd/containerd.sock"timeout:0debug:falsepull-image-on-create:falseTips:除了上面的设置端点外,我们还可以利用其它方式进行临时设置或者指定配置文件。
#1.通过在配置文件中设置端点并通过--config参数指定配置文件crictl--config=/etc/crictl-demo.yaml#2.通过设置参数--runtime-endpoint和--image-endpointcrictl--runtime-endpoint="/run/containerd/containerd.sock"--image-endpoint="/run/containerd/containerd.sock"基础语法:
Tips:用crictl创建容器对容器运行时排错很有帮助。在运行的Kubernetes集群中,沙盒会随机的被kubelet停止和删除,下面通过实例进行演示crictl使用。