专栏首页CNCF容器化到容器编排之旅

容器化到容器编排之旅

作者:Ivan Velichko 译者:马若飞 审校:罗广明 原文链接:https://www.servicemesher.com/blog/journey-from-containerization-to-orchestration-and-beyond/

编者按

本文是一篇介绍容器运行时和管理工具的文章。文中对主要的容器管理项目和技术做了较为详细的介绍和横向对比,并给出了项目的代码库供读者参考。

前言

容器带来了更高级的服务端架构和更复杂的部署技术。目前已经有一堆类似标准的规范(1, 2, 3, 4, ……)描述了容器领域的方方面面。当然,它的底层是Linux的基本单元,如namespace和cgroups。容器化软件已经变得非常的庞大,如果没有它自己关注的分离层,几乎是不可能实现的。在这个持续努力的过程中,我尝试引导自己从最底层到最高层尽可能多的实践(代码、安装、配置、集成等等),当然还有尽可能多的获得乐趣。本篇内容会随着时间的推移而改变,并反映出我对这一主题的理解。

容器运行时

我想从最底层的非内核原语说起——容器运行时。在容器服务里,运行时这个词是有歧义的。每个项目、公司或社区对术语容器运行时都有自己的、通常是基于上下文的特定理解。大多数情况下,运行时的特征是由一组职责定义的,从最基本的职责(创建namespace、启动init进程)到复杂的容器管理,包括(但不限于)镜像操作。这篇文章对运行时有一个很好的概述。

本节专门讨论低阶容器运行时。在OCI运行时规范中,组成Open Container Initiative的一些重要参与者对底层运行时进行了标准化。长话短说,低阶容器运行时是一个软件,作为一个包含rootfs和配置的目录输入,来描述容器参数(如资源限制、挂载点、流程开始等),并作为运行时启动一个独立进程,即容器。

到2019年,使用最广泛的容器运行时是runc。这个项目最初是Docker的一部分(因此它是用Go编写的),但最终被提取并转换为一个独立的CLI工具。很难高估这个组件的重要性——基本上runc是OCI运行时规范的一个参考实现。在我们的实践中将大量使用runc,下面是一篇介绍性文章(编者注:页面暂无内容)。

一个更值得注意的OCI运行时实现是crun。它用C语言编写,既可以作为可执行文件,也可以作为库使用。

容器管理

在命令行中可以使用runc启动任意数量的容器。但是如果我们需要让这个过程自动化呢?假设我们需要启动数十个容器来跟踪它们的状态,其中一些在失败时需要重启,在终止时需要释放资源,必须从注册中心提取镜像,需要配置容器间网络等等。这是一个稍微高级的任务,并且是“容器管理器”的职责。老实说,我不知道这个词是否常用,但我发现用它来描述很合适。我将以下项目归类为“容器管理器”:containerd, cri-o, dockerd 和 podman.

containerd

与runc一样,我们可以再次看到Docker的遗产——containerd曾经是Docker项目的一部分,现在它是一个独立的软件,自称为容器运行时。但显然,它与运行时runc不是同一种类型的运行时。不仅它们的职责不同,其组织形式也不同。runc只是一个命令行工具,containerd是一个长活的守护进程。一个runc实例不能比底层容器进程活得更久。通常,它在create调用时启动,然后在start时从容器的rootfs中exec特定文件。另一方面,containerd可以运行的比成千上万个容器更长久。它更像是一个服务器,侦听传入的请求来启动、停止或报告容器的状态。在幕后,containerd使用runc。它不仅仅是一个容器生命周期管理器,还负责镜像管理(从注册表中拉取和提交镜像,本地存储镜像等等),跨容器联网管理和其他一些功能。

cri-o

另一个容器管理器是cri-o。containerd是Docker重构后的结果,但cri-o却源于Kubernetes领域。在过去,Kubernetes使用Docker管理容器。然而,随着rkt的崛起,一些人增加了在Kubernetes中可互换容器运行时的支持,允许Docker和/或rkt完成容器管理。这种变化导致Kubernetes中有大量的条件判断,没有人喜欢代码中有太多的“if”。因此,容器运行时接口(CRI)被引入到Kubernetes中,这使得任何兼容CRI的高阶运行时(例如容器管理器)都可以在Kubernetes中使用,而无需任何代码的更改。cri-o是RedHat实现的兼容CRI的运行时,与containerd一样,它也是一个守护进程,通过开放一个gRPC服务接口来创建、启动、停止(以及许多其他操作)容器。在底层,cri-o可以使用任何符合OCI标准的低阶运行时和容器工作,默认的运行时仍然是runc。cri-o的主要目标是作为Kubernetes的容器运行时,版本控制也与K8S一致,项目的范围界定的很好,其代码库也比期望的更小(截止2019年7月大约是20个CLOC,近似于containerd的5倍)。

cri-o 架构 (图像来自 cri-o.io)

规范的好处是所有符合规范的技术都可以互换使用。一旦CRI被引入,一个containerd的插件就可以在它的功能之上实现CRI的gRPC服务。这个想法是可行的,所以后来containerd获得了原生的CRI支持。因此,Kubernetes可以同时使用cri-o和containerd作为运行时。

dockerd

还有一个守护进程是dockerd,它是个多面手。一方面,它为Docker命令行 开放了一个API,为我们提供了所有这些常用的Docker命令(Docker pullDocker pushDocker runDocker stats等)。但是我们已经知道这部分功能被提取到了containerd中,所以在底层dockerd依赖于containerd就不足为奇了。但这基本上意味着dockerd只是一个前端适配器,它将containerd的API转换为广泛使用的docker引擎的API。

dockerd(编者注:原文链接是moby项目)也提供了composeswarm功能,试图解决容器编排问题,包括容器的多机器集群。正如我们在Kubernetes上看到的,这个问题相当难解决。对于一个单dockerd守护进程来说,同时承担两大职责并不好。

dockerd 是 containerd 前端的一部分(图片来源于Docker Blog)

podman

守护进程中一个有趣的例外是podman。这是另一个Red Hat项目,目的是提供一个名为libpod的库(而不是守护进程)来管理镜像、容器生命周期和pod(容器组)。podman是一个构建在这个库之上的命令行管理工具。作为一个低阶的容器运行时,这个项目也使用runc。从代码角度来看,podman和cri-o (都是Red Hat项目)有很多共同点。例如,它们都在内部使用storage和image库。另一项正在进行的工作是在cri-o中直接使用libpod而不是runc。podman的另一个有趣的特性是用drop-in替换一些(最流行的?)日常工作流程中的docker命令。该项目声称兼容(在一定程度上)docker CLI API。

既然我们已经有了dockerd、containerd和cri-o,为什么还要开发这样的项目呢?守护进程作为容器管理器的问题是,它们大多数时候必须使用root权限运行。尽管由于守护进程的整体性,系统中没有root权限也可以完成其90%的功能,但是剩下的10%需要以root启动守护进程。使用podman,最终有可能使Linux用户的namespace拥有无根(rootless)容器。这可能是一个大问题,特别是在广泛的CI或多租户环境中,因为即使是没有权限的Docker容器实际上也只是系统上的一个没有root访问权限的内核错误。

关于这个有趣项目的更多信息可以在 这里 和 这里找到。

conman

这是我正在做的项目,目的是实现一个微型容器管理器。它主要用于教学目的,但是最终的目标是使它兼容CRI,并作为容器运行时运行在Kubernetes集群上。

运行时垫片(runtime shims)

如果你自己尝试一下就会很快发现,以编程方式从容器管理器使用runc是一项相当棘手的任务。以下是需要解决的困难清单。

在容器管理器重启时保证容器存活

容器可以长时间运行,而容器管理器可能由于崩溃或更新(或无法预见的原因)而需要重新启动。这意味着我们需要使每个容器实例独立于启动它的容器管理器进程。幸运的是,runc提供了一种方式通过命令runc run --detach从正在运行的容器中分离。我们也可能需要能够附加到一个正在运行的容器上。为此,runc可以运行一个由Linux伪终端控制的容器。通过Unix套接字传递PTY主文件描述符,可以将PTY的master端回传到启动进程(请参阅runc create --console-socket选项)。这意味着,只要底层容器实例存在,我们就可以保持启动进程的活动状态,以保存PTY文件描述符。如果我们决定在容器管理器进程中存储主PTY文件描述符,则重新启动该管理器将导致文件描述符的丢失,从而失去重新附着到正在运行的容器的能力。这意味着我们需要一个专用的(轻量级的)包装进程来负责转化和保持运行容器的附属状态。

同步容器管理器和包装的runc实例

由于我们通过添加包装器进程对runc进行了转化,所以需要一个side-channel(也可能是Unix套接字)来将容器的启动传回容器管理器。

持续追踪容器退出码(exit code)

分离容器会导致缺少容器状态更新。我们需要有一种方式将状态反馈给管理器。出于这个目的,文件系统听起来也是一个不错的选择。我们可以让包装器进程等待子runc进程终止,然后将它的退出码写到磁盘上预定义的位置。

为了解决所有这些问题(可能还有其他一些问题),通常使用所谓的runtime shims。shim是一个轻量级守护进程,控制一个正在运行的容器。shims的实现有conmon和containerd的 runtime shim。我花了一些时间实现了自己的shim作为conman项目的一部分,可以在文章“实现容器运行时shim”中找到(编者注:此文章还未撰写)。

容器网络接口 (CNI)

我们有多个责任重叠的容器运行时(或管理器),很明显需要提取网络相关的代码到一个专门的项目来复用它,或者每个运行时都应该有自己的方式来配置NIC设备,IP路由,防火墙和网络的其他方面。例如,cri-o和containerd都必须创建Linux网络名称空间,并设置Linux bridgeveth设备来为Kubernetes pods创建沙箱。为了解决这个问题,引入了容器网络接口项目。

CNI项目提供了一个定义CNI插件的容器网络接口规范。插件是一个可执行的sic,容器运行时(或管理器)会调用它来安装(或释放)网络资源。插件可以用来创建网络接口,管理IP地址分配,或者对系统进行一些自定义配置。CNI项目与语言无关,由于插件被定义为可执行的,它可以用于任何编程语言实现的运行时管理系统。CNI项目还为作为一个名为plugins的用于存放最流行的用例的独立的代码库提供了一组参考插件实现。例如bridge、loopback、flannel等。

一些第三方项目将其网络相关的功能实现为CNI插件。一些最著名的项目如Project Calico和Weave。

编排

容器的编排是一个非常大的主题。实际上,Kubernetes代码中最大的部分就是解决编排问题,而不是容器化问题。因此,编排应该有自己单独的文章(或几篇)而不在本文描述。希望他们能很快跟进。

值得注意的项目

buildah

Buildah是一个和OCI容器镜像一起使用的命令行工具。它是RedHat发起的一组项目(podman、skopeo、buildah)的一部分,目的是重新设计Docker处理容器的方法(主要是将单体和基于守护进程的方法转换为更细粒度的方法)。

cni

CNI项目定义了一个容器网络接口插件规范以及一些Go工具。有关更深入的解释,请参见这篇文章相应的部分。

cni-plugins

一个最流行的CNI插件(如网桥、主机设备、环回、dhcp、防火墙等)的主库。有关更深入的解释,请参见文章的相应部分。

containerd

高级容器运行时(或容器管理器)作为Docker的一部分启动,并提取到了独立的项目中。有关更深入的解释,请参见相应的部分。

conmon

一个用C语言编写的小型OCI运行时shim,主要由crio使用。它提供了父进程(crio)与启动容器之间的同步、容器启动、退出码追踪、PTY转发和其他一些功能。有关更深入的解释,请参见相应的部分。

cri-o

专注于Kubernetes容器管理器,遵循Kubernetes容器运行时接口(CRI)规范。版本控制与k8s版本控制相同。有关更深入的解释,请参见相应的部分。

crun

另一个OCI运行时规范实现。它声称是”快速和低内存占用的OCI容器运行时,完全用C编写“。但最重要的是,它可以用作任何C/C++代码(或提供绑定其他语言)的库。它允许避免一些由它的守护进程特性引起的特定的“runc”缺陷。有关更多信息,请参见Runtime Shims一节。

image

一个被低估的(主观评价)Go工具库,为criopodmanskopeo等知名项目提供了支持。通过它的名字就很容易猜到——其目的是用各种方式来处理容器镜像和镜像注册表。

lxc

一个由C编写的可替换的低级容器运行时。

lxd

一个由Go编写的高级容器运行时(或容器管理器)。底层使用lxc作为低级运行时。

moby

高级容器运行时(或容器管理器),以前称为docker/docker。提供一个著名的基于containerd功能的Docker引擎API。有关更深入的解释,请参见相应的部分。

OCI distribution spec

一个容器镜像发布规范(开发中)。

OCI image spec

一个容器镜像规范。

OCI runtime spec

一个低阶容器运行时规范。

podman

一个无守护进程的Docker替代品。Docker重新设计的frontman项目。更多信息参见 RedHat developers blog。

rkt

另一个容器管理系统。它提供了一个低阶运行时和一个高阶管理接口。它宣称是Pod原生的。向Kubernetes添加rkt支持的想法催生了CRI规范。该项目由CoreOS团队于5年前启动,在被RedHat收购后却停滞不前。截止到2019年8月,该项目的最后一次提交已经是大约两个月前了。更新:8月16日,CNCF宣布技术监督委员会(TOC)投票决定将rkt项目存档。

runc

一个低阶容器运行时和OCI运行时规范的参考实现。一开始作为Docker的一部分现在提取到了一个独立的项目中,普及度很高。有关更深入的解释,请参见相应的部分。

skopeo

Skopeo是一个命令行工具集,对容器镜像和镜像库执行各种操作。这是RedHat重新设计Docker(参见podman和buildah)工作的一部分,它将自己的职责抽取为专用的和独立的工具。

storage

一个被低估的Go类库,为crio、podman和skopeo等知名项目提供了支持。其目的是为存储文件系统层、容器镜像和容器(磁盘上的)提供方法。它还管理bundle的加载。

文章转载自ServiceMesher。点击这里阅读原文了解更多

本文分享自微信公众号 - CNCF(lf_cncf)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 007.Kubernetes二进制部署Flannel

    kubernetes 要求集群内各节点(包括 master 节点)能通过 Pod 网段互联互通。flannel 使用 vxlan 技术为各节点创建一个可以互通的...

    木二
  • Docker 容器真的安全吗?

    本文基于我今年在DockerCon上的演讲。它将讨论 Docker 容器安全性,我们当前的位置以及未来的发展方向。

    Hyman.W
  • 实惨!Docker Enterprise 卖了,Docker Swarm 或被逐步淘汰

    根据外媒报道,融资的消息是在 Mirantis 宣布收购 Docker Enterprise 后的同一天发布的。在这起融资消息之前,Docker 公司已先后融资...

    奋斗蒙
  • 运维猫-面试题总结-55道

    ps -A -o stat,ppid,pid,cmd | grep -e '^[Zz]' | awk '{print $2}' | xargs kill -9

    胡齐
  • [docker](一)docker-namespaces资源隔离和cgroups资源限制

    Docker容器本质上是宿主机上的进程。Docker通过namespaces实现了资源隔离,通过cgroups实现了资源限制,通过写时复制机制(copy-on-...

    baron
  • Docker 下载镜像

    要运行容器,就必须保证本地存在对应的镜像。所以,第一步是需要下载镜像。当尝试下载镜像时,Docker 会尝试先从默认的镜像仓库(默认使用 Docker Hub ...

    happyJared
  • 012.Kubernetes二进制部署worker节点Flannel

    kubernetes 要求集群内各节点(包括 master 节点)能通过 Pod 网段互联互通。flannel 使用 vxlan 技术为各节点创建一个可以互通的...

    木二
  • Docker 镜像详情

    借助 docker inspect 命令,我们可以获取镜像的详细信息,包括镜像 Id、创建时间、State、HostConfig、Config 等,如查看 re...

    happyJared
  • 快速搭建Jenkins集群

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    程序员欣宸
  • 如何用10分钟生成多平台docker镜像?

    ? 工作中需要在一台x86服务器从写好的golang程序源码生成linux/amd64、linux/arm64 docker镜像,查阅了下资料,这里记录一下操...

    腾讯云TStack

扫码关注云+社区

领取腾讯云代金券