Py学习  »  docker

Kubernetes 弃用 Docker,一文看懂 Container 到底怎么用

高效运维 • 1 年前 • 315 次点击  

01 需求简介

注: Container runtime 统称为容器运行时

在 Docker 时代,关于容器运行时术语的定义是非常明确的,其为运行和管理容器的软件。但随着 Docker 涵盖的内容日益增多,以及多种容器编排工具的引入,该定义变得日益模糊了。

当你运行一个 Docker 容器时,一般的步骤是:

  • 下载镜像

  • 将镜像解压成一个 bundle,即将各层文件平铺到一个单一的文件系统中。

  • 运行容器

最初的规范规定,只有运行容器的部分定义为容器运行时,但一般用户,将上述三个步骤都默认为容器运行时所必须的能力,从而让容器运行时的定义成为一个令人困惑的话题。

当人们想到容器运行时,可能会想到一连串的相关概念;runc、runv、lxc、lmctfy、Docker(containerd)、rkt、cri-o。每一个都是基于不同的场景而实现的,均实现了不同的功能。如containerd和cri-o,实际均可使用runc来运行容器,但其实现了如镜像管理、容器API等功能,可以将这些看作是比runc具备的更高级的功能。

可以发现,容器运行时是相当复杂的。每个运行时都涵盖了从低级到高级的不同部分,如下图所示。

根据功能范围划分,将其分为低级容器运行时 (Low level Container Runtime)和高级容器运行时 (High level Container Runtime),其中只关注容器的本身运行通常称为低级容器运行时(Low level Container Runtime)。支持更多高级功能的运行时,如镜像管理及一些gRPC/Web APIs,通常被称为 高级容器运行时 (High level Container Runtime)。需要注意的是,低级运行时和高级运行时有本质区别,各自解决的问题也不同。

02 低级容器运行时

低级运行时的功能有限,通常执行运行容器的低级任务。大多数开发者日常工作中不会使用到。其一般指按照 OCI 规范、能够接收可运行 roofs 文件系统和配置文件并运行隔离进程的实现。这种运行时只负责将进程运行在相对隔离的资源空间里,不提供存储实现和网络实现。但是其他实现可以在系统中预设好相关资源,低级容器运行时可通过 config.json 声明加载对应资源。低级运行时的特点是底层、轻量,限制也很一目了然:
  • 只认识 rootfs 和 config.json,没有其他镜像能力

  • 不提供网络实现

  • 不提供持久实现

  • 无法跨平台等

低级运行时 demo

通过以 root 方式使用 Linux cgcreate、cgset、cgexec、chroot 和 unshare 命令来实现简单容器。
首先,以 busybox 容器镜像作为基础,设置一个根文件系统。然后,创建一个临时目录,并将 busybox 解压到该目录中。
$ CID=$(docker create busybox)$ ROOTFS=$(mktemp -d)$ docker export $CID | tar -xf - -C $ROOTFS
紧接着创建 uuid,并对内存和 CPU 设置限制。内存限制是以字节为单位设置的。在这里,将内存限制设置为 100MB。
$ UUID=$(uuidgen)$ cgcreate -g cpu,memory:$UUID$ cgset -r memory.limit_in_bytes=100000000 $UUID$ cgset -r cpu.shares=512 $UUID
例如,如果我们想把我们的容器限制在两个 cpu core 上,可以设定一秒钟的周期和两秒钟的配额(1s=1,000,000us),这将允许进程在一秒钟的时间内使用两个cpu core。
$ cgset -r cpu.cfs_period_us=1000000 $UUID$ cgset -r cpu.cfs_quota_us=2000000 $UUID

接下来在容器中执行命令。

$ cgexec -g cpu,memory:$UUID \>     unshare -uinpUrf --mount-proc \>     sh -c "/bin/hostname $UUID && chroot $ROOTFS /bin/sh"/ # echo "Hello from in a container"Hello from in a container/ # exit

最后,删除前面创建的 cgroup 和临时目录。

$ cgdelete -r -g cpu,memory:$UUID$ rm -r $ROOTFS

低级运行时demo

为了更好地理解低级容器运行时,以下列举了几个低级运行时代表,各自实现了不同的功能。

runC

runC是目前使用最广泛的容器运行时。它最初是集成在Docker的内部,后来作为一个单独的工具,并以公共库的方式提取出来。

在2015 年,在 Linux 基金会的支持下有了 Open Container Initiative (OCI)(就是负责制定容器标准的组织),Docker 将自己容器格式和运行时 runC 捐给了 OCI。OCI 在此基础上制定了 2 个标准:运行时标准 Runtime Specification (runtime-spec) 和 镜像标准 Image Specification (image-spec) ,下面通过示例,简要介绍一下 runC。

首先创建根文件系统。这里我们将再次使用 busybox。

$ mkdir rootfs$ docker export $(docker create busybox) | tar -xf - -C rootfs

接下来创建一个 config.json 文件

$ runc spec

这个命令为容器创建一个模板config.json。

$ cat config.json{        "ociVersion": "1.0.2",        "process": {                "terminal": true,                "user": {                        "uid": 0,                        "gid": 0                },                "args": [                        "sh"                ],                "env": [                        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",                        "TERM=xterm"                ],                "cwd": "/",                "capabilities": {...

默认情况下,它在根文件系统位于 ./rootfs 的目录下运行命令。

$ sudo runc run mycontainerid/ # echo "Hello from in a container"Hello from in a container

rkt(已废弃)

rkt是一个同时具有低级和高级功能的运行时。例如,很像Docker,rkt允许你构建容器镜像,获取和管理本地存储库中的容器镜像,并通过一个命令运行它们。

runV

runv 是 OCF 基于管理程序的(Hypervisor-based )运行时 Runtime.runV 兼容 OCF。作为虚拟容器运行时引擎的runV已被淘汰。runV团队与英特尔一起在OpenInfra Foundation中创建了Kata Containers项目

youki

Rust是时下最流行的编程语言,而容器开发也是一个时兴的应用领域。将两者结合使用Rust来做容器开发是一个值得尝鲜的体验。youki是使用Rust的实现OCI运行时规范,类似于runc。

03高级容器运行时

高级运行时负责容器镜像的传输和管理,解压镜像,并传递给低级运行时来运行容器。通常情况下,高级运行时提供一个守护程序和一个API,远程应用程序可以使用它来运行容器并监控它们,它们位于低层运行时或其他高级运行时之上。
高层运行时也会提供一些看似很低级的功能。例如,管理网络命名空间,并允许容器加入另一个容器的网络命名空间。
这里有一个类似逻辑分层图,可以帮助理解这些组件是如何结合在一起工作的。

高级运行时代表

Docker

Docker 是最早的开源容器运行时之一。它是由平台即服务的公司 dotCloud 开发的,用于在容器中运行用户的应用。
Docker 是一个容器运行时,包含了构建、打包、共享和运行容器。Docker 基于 C/S 架构实现,最初是由一个守护程序 dockerd 和 docker 客户端应用程序组成。守护程序提供了构建容器、管理镜像和运行容器的大部分逻辑,以及一些API。命令行客户端可以用来发送命令和从守护进程中获取信息。
它是第一个流行开来的运行时间,毫不过分的说,Docker 对容器的推广做出了巨大的贡献。
Docker 最初实现了高级和低级的运行时功能,但这些功能后来被分解成单独的项目,如 runc 和 containerd,以前 Docker 的架构如下图所示,现有架构中,docker-containerd 变成了 containerd,docker-runc 变成了 runc。

dockerd 提供了诸如构建镜像的功能,而 dockerd 使用 containerd 来提供诸如镜像管理和运行容器的功能。例如,Docker 的构建步骤实际上只是一些逻辑,它解释 Docker文件,使用 containerd 在容器中运行必要的命令,并将产生的容器文件系统保存为一个镜像。

Containerd

containerd 是从 Docker 中分离出来的高级运行时。containerd 实现了下载镜像、管理镜像和运行镜像中的容器。当需要运行一个容器时,它会将镜像解压到一个 OCI 运行时 bundle 中,并向 runc 发送 init 以运行它。
Containerd 还提供了 API,可以用来与它交互。containerd 的命令行客户端是 ctr 和 nerdctl。
可以通过 ctr 拉取一个容器镜像。
$ sudo ctr images pull docker.io/library/redis:latest

列出所有的镜像:

$ sudo ctr images list

运行容器:

$ sudo ctr container create docker.io/library/redis:latest redis

列出运行容器:




    
$ sudo ctr container list

停止容器:

$ sudo ctr container delete redis

这些命令类似于用户与 Docker 的互动方式。

rkt(已废弃)

rkt是一个同时具有低级和高级功能的运行时。例如,很像Docker,rkt允许你构建容器镜像,获取和管理本地存储库中的容器镜像,并通过一个命令运行它们。

Kubernetes CRI

CRI 在 Kubernetes 1.5 中引入,作为 kubelet 和容器运行时之间的桥梁。社区希望Kubernetes 集成的高级容器运行时实现 CRI。该运行时处理镜像的管理,支持Kubernetes pods,并管理容器,因此根据高级运行时的定义,支持 CRI 的运行时必须是一个高级运行时。低级别的运行时并不具备上述功能。
为了进一步了解 CRI,可以看看整个 Kubernetes 架构。kubelet 代表工作节点,位于Kubernetes集群的每个节点上,kubelet负责管理其节点的工作负载。当需要运行工作负载时,kubelet通过CRI与运行时进行通信。由此可以看出,CRI只是一个抽象层,允许切换不同的容器运行时。

CRI规范

CRI定义了gRPC API,该规范定义在 Kubernetes 仓库中 cri-api 目录中。CRI定义了几个远程程序调用(RPC)和消息类型。这些RPC用于管理工作负载等内容,如 “拉取镜像”(ImageService.PullImage)、”创建pod”(RuntimeService.RunPodSandbox)、”创建容器”(RuntimeService.CreateContainer)、”启动容器”(RuntimeService.StartContainer)、”停止容器”(RuntimeService.StopContainer)等操作。

例如,通过CRI启动一个新的Pod(篇幅有限,进行了一些简化工作)。RunPodSandbox和CreateContainer RPCs在其响应中返回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 运行时交互,可以用它来调试和测试CRI的相关实现。

cat <runtime-endpoint: unix:///run/containerd/containerd.sockEOF

或者通过命令行指定:

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

关于 crictl 的使用参见官网。

支持 CRI 的运行时

Containerd

containerd应该是目前最流行的CRI运行时。它以插件的方式实现CRI,默认是启用的。它默认在unix套接字上监听消息。
从1.2版本开始,它通过 runtime handler 来支持多种低级运行时。运行时处理程序是通过 CRI 中的字段传递,根据该运行时处理程序,containerd 运行 shim 的应用程序来启动容器。这可以用来运行 runc及其他的低级运行时的容器,如 gVisor、Kata Containers等。在 Kubernetes API 中通过 RuntimeClass 进行运行时配置。
下图是 Containerd 的发展史。

Docker

docker-shim 是 K8s 社区第一个被开发的,作为kubelet 和 Docker 之间的 shim。随着Docker 将其许多功能分解到 containerd 中,现在通过 containerd 支持 CRI。

当现代版本的 Docker 被安装时,containerd 也一起被安装,CRI 直接与 containerd 对话,随着docker-shim正式废弃,是时候考虑相关迁移的工作了,K8s在这方面做了大量的工作,具体可参看官方文档。

CRI-O

cri-o 是一个轻量级的 CRI 运行时,它支持 OCI,并提供镜像的管理、容器进程管理、监控日志及资源隔离等工作。

cri-o 的通信地址默认是在 /var/run/crio/crio.sock。

下图为CRI插件的演变史。

由于笔者时间、视野、认知有限,本文难免出现错误、疏漏等问题,期待各位读者朋友、业界专家指正交流。

参考文献

1.https://blog.mobyproject.org/where-are-containerds-graph-drivers-145fc9b7255
2.https://insujang.github.io/2019-10-31/container-runtime/
3.https://github.com/cri-o/cri-o

来源:本文转自公众号 DCOS,点击查看原文

如果你运维转型处于迷茫时,8月19-20日,GOPS 2022 · 深圳站,值得你参加~

通信行业数字化转型,AIOps、MLOps、云原生、SRE等相关精彩内容,2022年最值得参加的运维大会,就在这里啦!

近期好文:

运维必知必会的 Kubectl 命令总结,收藏好了~

运维专属音乐,724运维好声音作品大赏~

“高效运维”公众号诚邀广大技术人员投稿,

投稿邮箱:jiachen@greatops.net,或添加联系人微信:greatops1118.
点个“在看”,一年不宕机
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/137974
 
315 次点击