浅析容器运行时

1、概述

首先要明确一个知识点就是容器本质上就是一个特殊的进程,通过 Namespace 实现资源(网络、文件系统等)隔离,通过 Cgroups 实现资源(CPU、内存)限制,让我们使用起来就感觉像在操作虚拟机一样,但其和虚拟机有本质上的区别,那就是容器和宿主机是共享同一个内核的。为了将我们的应用进程运行在容器中,当然就需要有一些方便的接口或者命令去调用 Linux 的系统功能来实现,而容器运行时就是用来运行和管理容器进程、镜像的工具。

2、容器运行时分类

截止当前最火的容器运行时非 Docker 莫属,Docker 凭借一己之力大大推进了容器技术的普及。当然随着容器生态的不断发展,业界也慢慢出现了其他的运行时,比如 containerd、rkt、kata container、cri-o 等等,这些运行时提供的功能不尽相同,有些只有运行容器的功能,有些除运行容器之外还提供了容器镜像的管理功能。根据容器运行时提供的功能,我们可以将容器运行时分为低层运行时高层运行时

低层运行时主要负责与宿主机操作系统打交道,根据指定的容器镜像在宿主机上运行容器进程,并对容器的整个生命周期进行管理,也就是负责设置容器 Namespace、Cgroups 等基础操作的组件。常见的低层运行时有:

  • runc :传统的运行时,基于 Linux Namespace 和 Cgroups 技术实现,负责实际的容器创建、隔离和执行操作。代表实现 Docker、Containerd。
  • runv :基于虚拟机管理程序的运行时,通过虚拟化 guest kernel,将容器和主机隔离开来,使得其边界更加清晰, 代表实现是 Kata Container 和 Firecracker。 (注意:虽然kata container更安全,但是其用在生产里的还是不多)
  • runsc :runc + safety,通过拦截应用程序的所有系统调用,提供安全隔离的轻量级容器运行时沙箱,代表实现是谷歌的 gVisor。

高层运行时主要负责容器的生命周期管理、镜像管理、网络管理和存储管理等工作,为容器的运行做准备。主流的高层运行时包括 Containerd、CRI-O。高层运行时与低层运行时各司其职,容器运行时一般先由高层运行时将容器镜像下载下来,并解压转换为容器运行需要的操作系统文件,再由低层运行时启动和管理容器。

  • containerd 是一个面向容器生命周期管理的高层容器运行时。它提供了容器的生命周期管理、镜像管理、存储管理和网络管理等功能。containerd 可以与容器编排系统(如 Kubernetes)紧密集成,为容器的创建、启动、停止和销毁等操作提供 API 接口。
  • CRI-O是一个轻量级容器运行时,专注于在Kubernetes环境中运行容器,符合Kubernetes CRI规范,使用runc作为底层执行引擎。

注意 1:runc 是 Docker 和 containerd 的一个组件(说明:containerd也是docker的一个组件),它是一个独立的容器运行时工具,被设计用于创建和管理容器的生命周期,Docker 和 containerd 都使用 runc 作为底层的容器执行引擎。

在 Docker 中,runc 负责实际执行容器的启动、停止、销毁等操作,Docker 使用 runc 来创建和管理容器,并提供了更高级别的用户界面和工具,以简化容器的管理和部署过程;在containerd中,containerd 使用 runc 作为底层的容器运行时,通过调用 runc 的接口来执行容器的实际操作。

因此,runc 可以被视为 Docker 和 containerd 的共同组件,它提供了容器的执行引擎,被上层的容器管理工具使用,以实现容器的创建、运行和管理。

注意 2:runc更加详细的介绍请参见《浅析开源容器标准——OCI 》这篇博文

3、容器高层运行时和底层运行时的交互方式和职责

容器高层运行时和低层运行时在容器的创建和管理过程中通过协同工作来实现功能的完成。它们之间的交互可以通过以下方式进行:

  1. 镜像管理:高层运行时负责容器镜像的管理和解析。它会从容器镜像仓库中下载镜像,并根据需要进行解压和转换,以生成容器运行所需的文件系统。一旦镜像准备就绪,高层运行时将镜像的元数据和位置信息传递给底层运行时。

  2. 容器创建和配置:高层运行时负责处理容器的配置信息,如容器的网络设置、存储卷挂载和环境变量等。它会将这些配置信息传递给底层运行时,以便在容器启动之前进行相应的准备工作。

  3. 容器启动和管理:底层运行时负责实际的容器启动和管理。它使用底层的容器运行时工具(如 runc)来创建容器的隔离环境,并执行容器的启动过程。底层运行时根据高层运行时提供的配置信息,如容器的资源限制和安全设置,来确保容器以指定的方式启动和运行。

  4. 生命周期管理:底层运行时负责容器的生命周期管理,包括容器的启动、停止和销毁。高层运行时可以通过与底层运行时的交互,向其发出相应的指令来管理容器的生命周期。

在这个交互过程中,高层运行时和底层运行时之间通常使用定义良好的接口进行通信,以确保它们之间的兼容性和互操作性。这些接口可以基于标准规范,如 OCI(Open Container Initiative)规范,来定义容器运行时的行为和交互方式。

总体而言,高层运行时和底层运行时通过交互和协同工作,实现了容器的创建、配置、启动和管理过程。高层运行时负责容器的上层逻辑和管理,底层运行时负责容器的底层执行和隔离。它们共同构成了容器运行时的架构,并为容器提供了一致的运行环境。

4、浅析典型容器运行时技术实现——Docker

Docker 是一个典型的容器运行时技术实现,它包含高层容器运行时 containerd 和低层容器运行时 runc。Docker 使用 containerd 作为其高层容器运行时,它提供了更高级别的容器管理功能,并与容器编排系统紧密集成。containerd 负责容器的生命周期管理、镜像管理和资源管理等任务,同时使用 runc 作为底层容器运行时来实际执行容器的创建和运行。这种分层的架构使得 Docker 具备了高度的可扩展性和模块化,容器编排系统可以与 containerd 进行交互,并利用其提供的功能进行容器的创建和管理。而 runc 则负责底层的容器执行和资源隔离,通过与 containerd 的协作,实现了完整的容器化平台。

4.1 Docker架构

从 Docker 1.11 版本开始,Docker 容器运行就不是简单通过 Docker Daemon 来启动了,而是通过集成 containerd、runc 等多个组件来完成的。虽然 Docker Daemon 守护进程模块在不停的重构,但是基本功能和定位没有太大的变化,一直都是 CS 架构,守护进程负责和 Docker Client 端交互,并管理 Docker 镜像和容器。

现在的架构中组件 containerd 就会负责集群节点上容器的生命周期管理,并向上为 Docker Daemon 提供 gRPC 接口。(说明:docker daemon相当就是一个代理了)

当我们要创建一个容器的时候,现在 Docker Daemon 并不能直接帮我们创建了,而是请求 containerd 来创建一个容器,containerd 收到请求后,也并不会直接去操作容器,而是创建一个叫做 containerd-shim 的进程,让这个进程去操作容器,我们指定容器进程是需要一个父进程来做状态收集、维持 stdin 等 fd 打开等工作的,假如这个父进程就是 containerd,那如果 containerd 挂掉的话,整个宿主机上所有的容器都得退出了,而引入 containerd-shim 这个垫片就可以来规避这个问题了。

  • containerd-shim:可以翻译成垫片或者中间件。
  • containerd-shim可以认为是托管我们容器父进程的一个工具;
  • 每一个容器起起来之后,都会有一个conatinerd-shim存在;
  • containerd-shim主要是来控制你的容器的;

然后创建容器需要做一些 namespaces 和 cgroups 的配置,以及挂载 root 文件系统等操作,这些操作其实已经有了标准的规范,那就是 OCI(开放容器标准),runc 就是它的一个参考实现(Docker 被逼无耐将 libcontainer 捐献出来改名为 runc 的),这个标准其实就是一个文档,主要规定了容器镜像的结构、以及容器需要接收哪些操作指令,比如 create、start、stop、delete 等这些命令。runc 就可以按照这个 OCI 文档来创建一个符合规范的容器,既然是标准肯定就有其他 OCI 实现,比如 Kata、gVisor 这些容器运行时介绍都是符合 OCI 标准的。(说明:runc就是一个二进制命令;)

所以真正启动容器是通过 containerd-shim 去调用 runc 来启动容器的,runc 启动完容器后本身会直接退出,containerd-shim 则会成为容器进程的父进程, 负责收集容器进程的状态, 上报给 containerd, 并在容器中 pid 为 1 的进程退出后接管容器中的子进程进行清理, 确保不会出现僵尸进程。

而 Docker 将容器操作都迁移到 containerd 中去是因为想做 Swarm,想要进军 PaaS 市场,因此做了这个架构切分,让 Docker Daemon 专门去负责上层的封装编排。当然后面的结果我们知道 Swarm 在 Kubernetes 面前是惨败,然后 Docker 公司就把 containerd 项目捐献给了 CNCF 基金会,这个也是现在的 Docker 架构。

5、总结

Linux提供了命名空间和控制组两大系统功能,它们是容器的基础。但是,要把进程运行在容器中,还需要有便捷的 SDK 或命令来调用 Linux 的系统功能,从而创建出容器。容器的运行时(runtime)就是容器进程运行和管理的工具。

容器运行时分为低层运行时和高层运行时,功能各有侧重。低层运行时主要负责运行容器,可在给定的容器文件系统上运行容器的进程;高层运行时则主要为容器准备必要的运行环境,如容器镜像下载和解压并转化为容器所需的文件系统、创建容器的网络等,然后调用低层运行时启动容器。

在Docker架构中,低层运行时由runc实现,高层运行时由containerd实现。containerd与Docker引擎交互,处理容器的创建、启动、停止和销毁等操作,同时通过与低层运行时的交互实现底层容器的隔离和资源管理。这种架构使得Docker具有灵活性和可扩展性,提供了完整的容器化解决方案。

参考:容器运行时与k8s概述

参考:CRI与OCI

posted @ 2023-06-20 19:03  人艰不拆_zmc  阅读(501)  评论(0编辑  收藏  举报