参考文献:
A Brief History of Containers: From the 1970s Till Now
The differences between Docker, containerd, CRI-O and runc
在 1979 年 Unix V7 的开发过程中,引入了 chroot 系统调用
将进程及其子进程的根目录更改为文件系统中的新位置
这一进步是进程隔离的开始:为每个进程隔离文件访问
Chroot 于 1982 年被添加到 BSD
将近 20 年后的 2000 年,当时一家小型共享环境托管供应商提出了名为 FreeBSD Jails 的技术
用来实现其服务与其客户服务之间的明确分离,以确保安全性和易于管理
FreeBSD Jails 允许管理员将 FreeBSD 计算机系统划分为几个独立的、较小的系统,被称为 “ 监狱(jails) ”
能够为每个系统与配置分配一个 IP 地址
与 FreeBSD Jails 一样,Linux VServer 是一种可以对计算机系统上的资源(文件系统、网络地址、内存)进行分区的监狱机制
这种操作系统虚拟化于 2001 年推出,通过给 Linux 内核打补丁来实现
这些实验性的补丁现在仍然可以获得,最后一个稳定版的补丁发布于 2006 年
2004 年,Solaris Containers 的第一个公开测试版发布
它结合了系统资源控制和 zones 提供的边界分离,能够利用 ZFS 的快照、克隆之类的特性
这是一种 Linux 上的 “ 操作系统级别的 ” 虚拟化技术
通过给 Linux 内核打补丁,可以实现虚拟化、隔离、资源管理与检查点功能
这些代码并没有作为官方 Linux 内核的一部分发布
Google 于 2006 年推出了 Process Containers
目的是限制、统计和隔离进程集合的资源使用(CPU、内存、磁盘 I/O、网络)
一年后,它更名为 “ Control Groups (cgroups) ”,并最终合并到 Linux 内核 2.6.24
LXC(LinuX Containers)是第一个也是最完整的 Linux 容器管理器实现
它是在 2008 年使用 cgroups 和 Linux 命名空间实现的
它可以在单个 Linux 内核上运行,不需要任何补丁
CloudFoundry 于 2011 年启动 Warden,在早期阶段使用了 LXC,后来用自己的实现替换了它
Warden 可以隔离任何操作系统上的环境,作为守护进程运行并提供用于容器管理的 API
它开发了一个客户端-服务器模型来管理跨多个主机的容器集合,Warden 包括一个管理 cgroup、命名空间和进程生命周期的服务
Let Me Contain That For You (LMCTFY) 于 2013 年作为 Google 容器栈的开源版本启动,提供 Linux 应用程序容器
应用程序可以 “ 感知容器 ”,创建和管理它们自己的子容器
在 Google 开始将核心 LMCTFY 概念贡献给 libcontainer(现在是 Open Container Foundation 的一部分)之后
于 2015 年停止了 LMCTFY 的主动部署
当 Docker 于 2013 年出现时,容器技术开始大受欢迎
Docker 的增长和容器的使用齐头并进,并非巧合
和 Warden 一样,Docker 最初也使用了 LXC,后来用自己的库 libcontainer 替换了这个容器管理器
但是毫无疑问,Docker 通过为容器管理提供完整的生态系统而将自己与其他产品区分开来
随着基于容器的应用程序被广泛采用,系统变得越来越复杂,风险也随之增加,这使得人们开始重视容器的安全问题
像 dirty COW 这样的漏洞进一步地加深了对于容器安全性的思考
这促使软件开发生命周期中的安全性得到了更进一步的思考,使安全成为了容器应用开发中每个阶段的关键部分
这个模式也被称为 DevSecOps,其目标就是在不影响上市时间的前提下,从最开始构建安全的容器
人们已经开发出了数百种工具来让容器管理变得更简单
虽然这些工具已经存在了许多年,但直到 2017 年,其中的许多工具才算获得了真正的认可
例如 Kubernetes,在 2016 年被 Cloud Native Computing Foundation (CNCF) 采用后
VMWare、Azure 、AWS 甚至 Docker 都宣布会在它们各自的基础设施之上对 Kubernetes 提供支持
虽然市场仍在增长,但一些工具已经开始对容器社区中的某些功能作出定义
Ceph 和 REX-Ray 为 容器存储 设定了标准,而 Flannel 将组成数据中心的数百万个容器连接起来
在 CI/CD 中,Jenkins 正在彻底改变我们构建和部署应用程序的方式
社区的努力与开源项目的承诺为容器的生态提供了动力
Docker 在 2017 年将 Containerd 项目捐赠给 CNCF 就是这种理念的一个象征性事件
而 CNCF 在差不多的时间采用了 rkt (发音为 “ rocket ”)容器运行时
这导致了项目之间更加广泛的协作,使用户有了更多的选择,并且形成了一个以改进容器生态为核心的社区
2017 年,开源项目在成为更成熟的技术方面取得了长足的进步
Kubernetes 可以 支持 越来越复杂的应用程序类型 —— 使企业能够过渡到混合云与微服务
在哥本哈根的 DockerCon 上,Docker 宣布他们将支持 Kubernetes 的容器编排器
Azure 和 AWS 也开始支持 Kubernetes
Azure 推出了 AKS(Azure Kubernetes Service)
AWS 推出了 EKS (一种与专有 ECS 竞争的 Kubernetes 服务)
它也是 CNCF 采用的第一个项目,控制了越来越多的第三方系统集成服务供应商
2018 年,容器化成为现代软件基础设施的基础,Kubernetes 被用于大多数企业级容器项目
2018 年,GitHub 上的 Kubernetes 项目有超过 1500 名贡献者,拥有最重要的开源社区之一,拥有超过 27000 颗星
Kubernetes 的大规模采用推动了像 AWS、Google 的 GKE(Google Kubernetes Engine)
、Azure 和 Oracle 的 Container Engine for Kubernetes 这样的云供应商提供托管 Kubernetes 服务
此外,VMWare、RedHat 和 Rancher 等领先的软件供应商也开始提供基于 Kubernetes 的管理平台
基础设施供应商 VMware 在 2018 年底宣布收购 Heptio
这是一家帮助企业部署和管理 Kubernetes 的咨询公司,VMware 从此开始采用 Kubernetes
我们还目睹了新的混合技术的兴起,这些技术将类 VM 隔离与容器技术的速度结合起来
像 Kata containers 、gVisor 和 Nabla 这样的开源项目,试图通过轻量级虚拟机提供安全的容器运行时
这些虚拟机的执行方式与容器相同,但提供了更强的工作负载隔离
这一年容器技术的景观发生了重大变化
新的运行时引擎开始取代 Docker 运行时引擎,最引人注目的就是 containerd,一个开源的容器运行时引擎
另外还有 CRI-O,一个轻量级的 Kubernetes 运行时引擎
在 2019 年,容器领域发生了结构性的转变
当时 Docker Enterprise 被收购并拆分,直接导致了 Docker Swarm 的两年生命终止期
与此同时,rkt 容器引擎的流行度开始下降,虽然官方上它还是 CNCF 稳定版的一部分
VMware 则是首先收购了 Heptio,随后又收购了 Pivotal Software(同时拥有 PAS 和 PKS),加倍兑现了对 Kubernetes 的承诺
这么做是为了让企业能够在它们自己管理的环境中利用云原生部署的类云(cloud-like)能力
这一年,我们还看到了 serverless 技术应用的进展,例如使用像 Knative 这样的平台
Knative 是一个基于 Kubernetes 的 serverless 工作负载管理平台,获得了许多组织的青睐
2019 年还推出了基于 Kubernetes 的混合云(hybrid-cloud)解决方案
如 IBM Cloud Paks,Google Anthos,Aws Outposts,和 Azure Arc
这些云平台模糊了云和本地环境之间的传统界限,让企业可以同时管理本地的和单一供应商的云集群
我们相信,这些新能力的出现代表了 Kubernetes 演进的下一个阶段
像 Anthos、Arc 和 Outposts 这样的新的云能力
都指向了一个高度抽象的未来,计算资源将从管理层中解耦,就像 Kubernetes 所做的那样
在今天的 Kubernetes 中,主节点与工作节点位于同一个物理集群中
通过高度抽象,管理平面管理的工作负载可以位于分布在多个计算基础设施上的节点中,用户不用关心这些负载在物理上运行在哪里
从 KubeCon 圣地亚哥站创纪录的 12,000 多名与会者来看
我们相信 Kubernetes 在未来会成为容器、虚拟机和其他云原生工作负载的标准管理平台
Docker 不是容器领域唯一的竞争者,现在存在着许多各种各样的容器工具,docker
就是其中一个工具
Docker 公司支持着这些工具中的一部分,但不是全部
容器生态系统由许多令人兴奋的技术、大量行话和相互竞争的大公司组成
幸运的是,这些公司偶尔会在脆弱的休战中聚在一起以达成一些标准
这些标准有助于使生态系统在不同平台和操作系统之间更具互操作性,并减少对单个公司或项目的依赖
需要注意的主要标准有:
下图展示了 Docker、Kubernetes、CRI、OCI、containerd、runc 在生态系统中是如何组装在一起的:
我们会从 Docker 开始讲起,因为它是最出名的容器开发者工具
对于很多人来说,“ Docker ” 就是 “ 容器 ” 的同义词
是 Docker 引发了之后的整场变革,Docker 创造了一个非常易于使用的容器工具:docker
下图展示了使用 Docker 运行容器所涉及到的项目:
将 docker
安装在工作站或服务器上,并附带一系列工具,使开发人员或 DevOps 人员可以轻松构建和运行容器
docker
命令行工具可以用来构建容器镜像,从注册中心拉取镜像,以及创建、启动和管理容器
为了实现这一切,docker
由下边这些项目组成(还有其它项目,但这些是主要项目):
docker-cli
这是在使用 docker ...
命令时用到的命令行工具
containerd
这是一个用来管理和运行容器的守护进程
它能推送与拉取镜像、管理存储与网络,并且能够监视运行中的容器
runc
这是一个低层的容器运行时(真正完成创建和运行容器工作的运行时)
它包含了 libcontainer,一个用于创建容器的基于 go 语言的原生实现
当使用 docker
运行一个容器时
实际上需要先经过 Docker 守护进程、containerd,最后再通过 runc 才能将其运行起来
Kubernetes 包含一个叫做 dockershim 的组件,Kubernetes 靠它来直接支持 Docker
Kubernetes 更倾向于通过支持它的容器运行时接口(Container Runtime Interface (CRI))的容器运行时来运行容器
但由于 Docker 出现在 Kubernetes 之前,所以 Docker 没有实现 CRI,这就是 dockershim 存在的原因
它的作用基本上就是将 Docker 固定到了 Kubernetes 上面,或者说是将 Kubernetes 放到了 Docker 上面
shim 原本是一种锥形的或楔形的薄片材料,用于填充两个物体之间的空隙
目的是为了让两个物体能够更好地结合在一起,或者是为了提供一个平面
也可能被用作垫片,在两个易磨损的部分之间填充空隙
.
在计算机编程领域,shim 表示一个类库
用来透明地解释 API 调用,通常会改变其传递的参数,自己处理相关操作或者将其重定向到别的地方
.
之所以用这个单词,是因为 shim 的目的是为了衔接两个部分,而自己又比较轻量级
通常 shim 用来支持在新环境中调用旧的 API,或在旧环境中调用新的 API
shim 还可以用来支持在与开发目标平台不同的软件平台上运行程序
.
shim 会被用来在新环境中调用旧 API
通常是因为 API 的变化,导致那些依赖旧功能的老应用程序出现了兼容性问题
在这种情况下,可以通过在新代码上增加一层薄的兼容层来支持老 API
.
shim 用来在旧环境中调用新 API 是指
一个类库为一个旧环境带来了新的 API,但这些 API 都只能通过调用旧环境的能力来实现
例如一个用 Docker 搭建的容器集群,想要使用 Kubernetes 的 API,此时就可以用 dockershim 来适配
未来,Kubernetes 将会取消这种直接对接 Docker 的形式
取而代之的是,只使用实现了它的容器运行时接口的容器运行时
例如使用 containerd 或 CRI-O
但这并不意味着 Kubernetes 不再运行 Docker 格式的容器
containerd 与 CRI-O 都可以运行 Docker 格式(准确的说是 OCI 格式)的镜像
只是不再使用 docker
命令或 Docker 守护进程了
Dockershim 现已弃用,详见:Dockershim Deprecation FAQ
大多数人所说的 Docker 镜像,实际上是按照开放容器计划(Open Container Initiative (OCI))格式打包的镜像
因此,如果你从 Docker Hub(或其它注册中心)拉取了一个镜像
你应该可以将其与 docker
命令、Kubernetes 集群、podman
实用程序
或其它任何支持 OCI 镜像格式规范的工具一起使用
CRI 是 Kubernetes 用来控制不同容器运行时的 API,这些容器运行时用来创建和管理容器
CRI 使 Kubernetes 可以更轻松地使用不同的容器运行时
CRI 描述了 Kubernetes 将如何与容器运行时交互,这样 Kubernetes 项目就不用手动添加对每个运行时的支持
如何实际管理容器由容器运行时决定
如果你是终端用户,使用哪个具体的实现大多数时候并不重要
这些 CRI 实现都是可插拔的,并且可以无缝替换
如果你想要付费从供应商那里获得支持(如安全性、Bug 修复等),那么选择什么运行时可能就变得很重要了
例如 Red Hat 的 OpenShift 使用 CRI-O 并为其提供支持,而 Docker 则为自己的 containerd 提供支持
在 Kubernetes 中如何查看运行时?
在 Kubernetes 架构 中,kubelet(每个节点上运行的代理)负责向容器运行时发送指令来开始和运行容器
你可以通过查看每个节点上的 kubelet 参数来检查正在使用哪个容器运行时
选项 --container-runtime
与 --container-runtime-endpoint
用来配置使用的运行时
containerd 是一个来自 Docker 的高层容器运行时,实现了 CRI 规范
它会从注册中心拉取镜像、管理镜像,并将实际创建运行容器进程的任务交给低层运行时
containerd 项目与 Docker 项目是分开的,这可以让 Docker 更加模块化
因此 Docker 自己也会在内部使用 containerd
当你安装 Docker 时,也会安装 containerd
containerd 通过 cri 插件来实现 Kubernetes Container Runtime Interface (CRI)
CRI-O 是另一个高层容器运行时,实现了 Container Runtime Interface (CRI)
它是一个 containerd 的替代品,可以从注册中心拉取镜像、管理镜像,并将实际运行容器进程的任务交给低层运行时
CRI-O 由 Red Hat、IBM、Intel、SUSE 等开发,从最开始就是专门为了作为 Kubernetes 的容器运行时开发的
和 containerd 一样,它也提供了开始、停止、重启容器的能力
OCI 是一组技术公司,它们维护着容器镜像格式和容器应该如何运行的规范
OCI 的思想就是,允许在符合规范的不同运行时之间进行选择,这些运行时都有不同的低层实现
例如,可能有一个适用于 Linux 的 OCI 运行时,以及一个适用于 Windows 的 OCI 运行时
这就是同一个标准可以被许多不同的项目实现的好处
从蓝牙设备到 Java API,这种 “ 一个标准,多种实现 ” 的方式无处不在
runc 是一个实现了 OCI 规范的容器运行时,它可以运行容器进程
runc 被称为 OCI 的 参考实现
参考实现是一个已经实现了规范或标准的所有要求的软件,它通常是根据规范开发的第一个软件
runc 提供了 OCI 兼容运行时所期望的所有特性,尽管任何人都可以根据需要实现自己的 OCI 运行时
runc 为容器提供了所有的低层功能,与现有的低层 Linux 特性交互,如命名空间、控制组
它使用这些特性来创建和运行容器进程
runc 的几个替代方案:
runsc
的运行时中实现了 OCIrunc 是一个在 Linux 上运行容器的工具,它可以运行在 Linux、裸机或虚拟机上
在 Windows 上有一点不同,与 runc 等价的是微软的主机计算服务(Host Compute Service (HCS))
它包含一个被称为 runhcs 的工具,其本身也是一个 runc 的分支,同样实现了 Open Container Initiative 规范
参考文献:
A Brief History of Containers: From the 1970s Till Now
The differences between Docker, containerd, CRI-O and runc