1

容器运行时分析 - JonPan

 1 year ago
source link: https://www.cnblogs.com/panlq/p/16438769.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

什么是RunC#

Docker、Google、CoreOS 和其他供应商创建了OCI 开放容器计划。目前有两个标准文档:

  • 容器运行时标准(runtime spec)
  • 容器镜像标准(image spec)

OCI 对容器runtime的标准主要是指定容器的运行状态,如runtime需要提供的命令。下图是容器状态转换图:

  • init 状态:这个状态并不再标准中,仅表示没有容器存在的初始状态
  • creating:使用create 命令创建容器,状态为创建中
  • created:容器创建出来,但是还没运行,表示镜像和配置没有错误,容器可以运行在当前平台
  • running: 容器的运行状态,里面的进程处理up状态
  • stopped:容器运行完成、出错或者stop命令之后,容器处于暂停状态。

RunC就是一个命令行工具,可以操作宿主机的内核来管理Namespace和CGroups。使用runc,我们可以创建,启动,停止,删除容器。

Runc的由来#

runc是从docker的libcontianer中迁移出来的。实现了容器启动,停止,资源隔离等功能。Docker讲runc捐赠给OCI作为OCI容器运行时标准的参考实现。当我们用Docker运行一个容器时,经历了如下步骤:

图片来源:《Kubernetes In Action》 2.1

  1. Docker会检查busybox:latest镜像是否己经存在于本机。如果没有, Docker会从http://docker.io的Docker镜像中心拉取镜像。
  2. 将镜像下载到本地并解压为符合OCI标准的bundle文件, 将一个文件系统拆分成多层(overlay)
  3. Docker基于这个镜像bundle文件创建一个容器并在容器中运行命令。echo命令打印文字到标准输出流, 然后进程终止,容器停止运行。

RunC标准化的仅仅是第三步,也是Docker 贡献出来的部分。

怎么使用Runc#

 create the bundle
$ mkdir -p /mycontainer/rootfs

# [ab]use Docker to copy a root fs into the bundle
$ docker export $(docker create busybox) | tar -C /mycontainer/rootfs -xvf -

# create the specification, by default sh will be the entrypoint of the container
$ cd /mycontainer
$ runc spec

# launch the container
$ sudo -i
$ cd /mycontainer
$ runc run mycontainerid

# list containers
$ runc list

# stop the container
$ runc kill mycontainerid

# cleanup
$ runc delete mycontainerid

想象一下,我们用runc启动一个容器后,我们要怎么去跟踪他们的状态。我们要启动其他几个容器来跟踪他们的状态,其中一些需要在失败时重新启动,需要在终止时释放资源,且要从远程仓库中拉去镜像,并配置容器的网络和内存资源。如果我们要自动话这个过程,我们就需要一个容器管理器。如何用实现一个简单的runc "Building a container from scratch in Go"

Low-level & High-level#

当我们讨论容器运行时的时候,我们可能会想到:runc、lxc、lmctfy、docker、rkt、cri-o。这些中的每一个都是为不同的情况构建的,实现了不同的特性功能。如 contianerd,cri-o,实际上时使用runc来运行容器,在High-level实现了镜像管理和API层。如,镜像推送,镜像拉去,镜像管理,镜像解包和API。这些被视为High-level的功能。每一个High-level的实现都囊括了low-level。

从实际出发,通常只关注于正在运行的容器的runtime通常称为“low-level”容器运行时,支持更多高级功能如(镜像管理和gRPC/REST API)的运行时通常称为“High-leve”容器运行时。二者在根本上是解决不同的问题。

Low-level容器运行时#

容器是通过Linux namespace和Cgroups实现的。Namespace能让你为每个容器提供虚拟化系统资源,如文件系统,网络;Cgroups提供了限制每个容器所能使用的资源的方法,如CPU和内存。在低级别容器运行时中,其主要负责为容器建立ns和cgroups,然后在其中运行命令。一个健壮的低级容器运行时会做更多的事情,比如允许在 cgroup 上设置资源限制,设置根文件系统,以及将容器的进程 chroot 到根文件系统。
以下为几种Low-level容器运行时实现

  • runc: 最常见且被广泛使用容器运行时,代表实现就是Docker runc
  • runv: runv是一个基于虚拟机管理程序的运行时,它通过虚拟化guest kernel,将容器和主机隔离开,使其边界更加清晰,这种方式很容器就能帮助加强主机和容器的安全性,代表实是kataFirecracker
  • runsc: runc+safety 典型就是google的gvisor,通过拦截应用程序的所有系统调用,提供安全隔离的轻量级容器运行时沙箱。截止目前,貌似没有生产环境使用案例
  • wasm: Wasm的沙箱机制带来的隔离型和安全性都比Docker做的更好。但是wasm处于草案阶段。

High-level容器运行时#

通常情况下,开发人员想要运行一个容器不仅仅需要Low-Level容器运行时提供的这些特性,同时也需要与镜像格式、镜像管理和共享镜像相关的API接口和特性,而这些特性一般由High-Level容器运行时提供。就日常使用来说,Low-Level容器运行时提供的这些特性可能满足不了日常所需,因为这个缘故,唯一会使用Low-Level容器运行时的人是那些实现High-Level容器运行时以及容器工具的开发人员。那些实现Low-Level容器运行时的开发者会说High-Level容器运行时比如containerd和cri-o不像真正的容器运行时,因为从他们的角度来看,他们将容器运行的实现外包给了runc。但是从用户的角度来看,它们只是提供容器功能的单个组件,可以被另一个的实现替换,因此从这个角度将其称为runtime仍然是有意义的。即使containerd和cri-o都使用runc,但是它们是截然不同的项目,支持的特性也是非常不同的。dockershim, containerd 和cri-o都是遵循CRI的容器运行时,我们称他们为高层级运行时(High-level Runtime)。
以下为几种High-level容器运行时实现

dockerd#

Docker 是一个容器运行时,它包含生成、打包、共享和运行容器。Docker 是C/S架构,dockerd为服务端是一个守护进程,docker client客户端负责接收命令发送给dockerd。守护程序提供了构建容器、管理映像和运行容器的大部分逻辑,以及 API。可以运行命令行客户端来发送命令并从守护程序获取信息。
现在创建一个docker容器的时候,Docker Daemon 并不能直接帮我们创建了,而是请求 containerd 来创建一个容器。当containerd 收到请求后,也不会直接去操作容器,而是创建一个叫做 containerd-shim 的进程。让这个进程去操作容器,我们指定容器进程是需要一个父进程来做状态收集、维持 stdin 等 fd 打开等工作的,假如这个父进程就是 containerd,那如果 containerd 挂掉的话,整个宿主机上所有的容器都得退出了,而引入 containerd-shim 这个垫片就可以来规避这个问题了,就是提供的live-restore的功能。这里需要注意systemd的 MountFlags=slave。
然后创建容器需要做一些 namespaces 和 cgroups 的配置,以及挂载 root 文件系统等操作。runc 就可以按照这个 OCI 文档来创建一个符合规范的容器。

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

containerd#

containerd也是从docker中分离出来的项目。与docker-runc不同的是,containerd是一个常驻守护进程,负责管理由runc创建的容器。监听上层请求,来启动、停止或者上报容器的状态。负责管理容器的生命周期。除此之外,还负责镜像的推拉和镜像的本地存储,跨容器网络管理等其他功能。containerd的底层Low-level是runc,但并不局限于runc。可以用其他Low-level实现来替代runc。contianerd是一个工业级标准容器运行时,强调简单性、健壮性和可移植性。主要负责如下事情:

  • 管理容器的生命周期
  • 拉去和推送容器镜像
  • 调用runc运行容器及其他交互
  • 管理容器网络接口和请求

contianerd与docker的区别是contianerd专注容器的管理,而docker关注于用户端的使用。支持编译构建镜像并定义了镜像的格式。
从k8s的角度看,选择containerd作为运行时组建,它的调用链更短,组建更少,更稳定,占用节点资源更少。

参考#


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK