作者 | 李元戎
策划 | 王一鹏
在云原生场景下,容器和kube.NETes在开发、测试、生产中的应用越来越广泛,传统的操作系统往往会带来安全性、运维开销、OS 版本等方面的问题,容器操作系统即容器 OS 是针对云原生场景设计的一种轻量化操作系统。本次分享首先介绍容器 OS 的理念,然后分享在openEuler社区孵化的容器操作系统 KubeOS 的设计思路和解决的问题,最后深入介绍 KubeOS 的架构、功能和使用。
本文整理自华为操作系统开发工程师、KubeOS 开源项目负责人李元戎在DIVE全球基础软件创新大会 2022 的演讲分享,主题为“KubeOS : 面向云原生场景的容器操作系统”。分享主要分为三部分:1.云原⽣场景下 OS 管理问题与解决⽅法;2.KubeOS:⾯向云原⽣场景的容器操作系统;3.未来的工作。
云原⽣场景下 OS 管理问题与解决⽅法
现在说起容器,大家想到的基本上都是Docker。
Docker 在 2013 年开源以后就立即火爆了起来,可以说 Docker 将容器技术推向了巅峰。那么 Docker 技术到底解决了一个什么样的问题呢?Docker 自己宣传的口号是:一次构建到处运行。
它一举解决了开发、测试、生产中环境不一致这困扰业界多年的问题。从更高的维度来说,Docker 其实解决的是软件到底应该通过什么样的方式进行交付。当软件的交付方式变得清晰明确以后,那么我们做托管软件的平台也就变得非常简单明了了。
Docker 提供了三个概念:容器、镜像和镜像仓库,通过 Docker Client 可以以 Restful API 的方式去管理容器的全生命周期。那么 Docker 最核心的创新是什么呢?其实就是 Docker 镜像这个概念,通过 Docker 容器镜像,可以将一个应用软件运行依赖的全部环境都打包在一起,让这个程序通过 Docker 容器运行的时候可以与操作系统是无关的。这样也就基本上实现了它所宣传的 run anywhere。
Docker 镜像为了可以共享资源,在制作过程中引入了层的概念。也就是说如果想做一个新的容器镜像,不需要从头开始做,只需要找到一个有 Root FS 的 Base Image ,以后需要什么就可以一层一层的往上叠加。Docker 容器其实也是基于分层概念,容器运行的时候它就会在镜像上面增加一个可写层,也就是我们常说的容器层,然后容器层下面是镜像层,镜像层都是只读的。容器镜像的一个核心的特性就是 copy on right,可以把对于容器的修改全限制在容器层下,不会影响其他共享这个镜像的容器。Docker 在单机上的打包、发送、运行的性能是很优秀的,但只在单机上运行并不能发挥它最大的价值。业界更希望基于 Docker 技术可以形成云化的集群系统,然后进行业务容器的调度和编排。那我们就要说接下来的 Kubernetes 了。
Kubernetes 现在可以说已经真正成为了全球的主流技术。2017 年的时候,Kubernetes、Swarm 和 Mesos 三家容器编排系统的大战就基本上结束了,Kubernetes 成为了最后的赢家,成为了容器编排系统的事实标准。Kubernetes 的思想是将一切都视为资源,比如说 node、POD、deployment、service,这些日常的、内置的资源对于一般的系统部署升级和管理是够用的。但是在一些特定的场景下,当内置的资源不满足需求的时候,Kubernetes 又提供了一种扩展的机制,你可以把需要的新资源抽象成 Kubernetes 的 API 对象,然后注册到集群中,和其他资源一起来使用,即 CRD 机制。说起 CRD 就不得不提 operator。其实从 Kubernetes 的设计和定义来看,它其实似乎更适用于无状态的应用。
但是 CoreOS 公司它基于 Kubernetes 的声明式 API 机制提出的 Kubernetes operator 可以有效解决有状态的应用或者是分布式应用的状态描述,在 operator 当中的 API 对象不再是描述单状态应用的状态,而是去描述分布式应用集群的状态。也就是说它把一个完整的分布式应用的集群都算作 Kubernetes 它需要最终去维护的这种最终的状态。当你定义的分布式应用集群的状态发生改变以后,operator 会根据实现代码去执行相应的逻辑功能,然后达到向我们预期的状态不停演进的功能。
可以说容器和 Kubernetes 促进了云原生生态的发展,在基础设施纷纷云化的情况下,云原生场景下 OS 管理的问题也就随之而来了。
首先是 Kubernetes 并不能对集群节点的 OS 进行管理,所以云原生场景下 OS 管理的第一个问题,Kubernetes 和 OS 是分别独立进行管理的。Kubernetes 也需要进行更新维护,然后进行用户权限控制,这和 OS 的管理其实非常类似。所以当运维人员分别对 Kubernetes 和 OS 进行管理的时候,他往往需要进行很多冗余操作。按理来说是希望它们互相能够感知的,但是实际上这两套系统互相协调非常困难,甚至它们之间根本就没有协调。所以当 OS 升级影响到了节点可用性的时候,Kubernetes 无法进行感知。如果 OS 进行升级,又希望集群中业务不中断,运维人员首先需要锁定节点,让工作负载不再分配到这个节点,然后需要把这个节点上的 POD 调入到其他节点,然后才能去升级这个节点,最后再把这个节点解锁,恢复正常的应用,这无疑增加了运维的难度和开销。
第二个问题就是 OS 的版本管理问题。一个通用的 linux 操作系统,一般都会内置一个软件更新升级的包管理器,通过这个包管理器,每一个包独立进行安装、升级、删除,这对于操作系统来说非常灵活。
但是在云原生场景下,往往会带来版本分裂的问题。就像图中所示,一开始这个集群中两个节点的包版本都是一致的。但是随着使用,有的包升级了,有的包没升级,或者升级的版本不一致。时间久了集群中每一台节点都会有不同的软件包,不同的版本,这样造成的版本分裂问题是很严重的。
如果 OS 和业务耦合比较紧密的话,OS 进行大版本的升级也会比较困难。业界比较主流的思想是通过改造 OS 来解决以上问题。因为容器把应用运行所需要依赖的环境都打包到了容器镜像里面。它对一个操作系统所需要的功能越来越少,所以就有了轻量级的操作系统。为了容器运行而设计的这种轻量级操作系统,我们叫它为容器 OS,也就是 ContAIner OS,也可以叫做 Container specific OS。
那么对一个容器 OS 来说,都需要它有什么呢?首先肯定是有一个 Linux kernel,然后要有容器引擎,比如 Docker,然后还需要一些安全的机制,这些就够了。所以容器 OS 第一个特点就是极简化,它包含的软件包比较少,相应的攻击面和漏洞肯定就少,容器 OS 就更安全。第二个特点是不可变,只有在部署的时候可以修改,一旦部署就是固定的。第三个是原子更新,因为它不可变,所以只能整体进行更新。最后是应用以容器的形式运行。
KubeOS:⾯向云原⽣场景的容器操作系统
近几年容器 OS 又有了新的发展,Kubernetes OS 除了刚才讲的容器 OS 的特征以外,最显著的特征是集成了 Kubernetes 的某些社区版本。它会把 OS 的管理交由 Kubernetes 去控制,由 Kubernetes 来控制 OS 的更新。其实业界已经有一些主流的操作系统公司推出了这样的容器 OS,KubeOS 就是 openEuler 推出的这样一款容器 OS。
KubeOS 的镜像都是基于 openEuler Repo 源进行构造的。KubeOS 部署以后,用户可以在 master 节点上只通过命令行和 Yaml 文件就去管理集群所有 worker node 上面的 OS 版本。因为 KubeOS 将 OS 作为 Kubernetes 的一个组件接入到集群中,这样 OS 和其它的业务容器就位于同等地位,可以通过 Kubernetes 统一去管理容器和 OS,实现 OS 和业务容器的协同调度。并且我们还基于 openEuler 的版本进行了一些定制化的改造,让 KubeOS 可以进行原子化更新升级,避免版本分裂的问题。
下面对KubeOS进行一个详细的介绍。KubeOS 的第一个特性是将 OS 作为组件接入到 Kubernetes 中。我们利用 Kubernetes 的 API 扩展机制为 OS 设计了一个 CRD 的 API 对象,然后把它注册到集群中,并且依托于 Kubernetes 的 operator 扩展机制定义了一个 OS controller,去对之前注册那个 OS 对象进行管理和监控。这样就让 OS 和集群中其他的内置资源处于同等地位,都可以通过 kubernetes 进行管理。用户只需要修改 OS 的 CR,然后输入预期的 OS 版本和状态,其他操作都可以由 KubeOS 和 Kubernetes 完成。这样 OS 的管理就在云端进行了。
KubeOS 的第二个特性是 OS 是进行原子升级的,KubeOS 中不提供包管理器,软件包的变化即 OS 版本的变化,也就是说每一个 OS 的版本都会对应一个确定的 OS 镜像,或者说一组确定的 RPM 包的组合。如图所示,软件包的更新即为 OS 版本的更新,这样可以让任何时候集群中的 OS 的版本是确定的、一致的,有助于大规模应用的部署。并且我们 OS 是尽量轻量化的,只包含 Kubernetes 和容器运行所需要的组件,这样不仅减少攻击面,让 OS 更加安全,也可以进行快速的更新,快速的升级。
如图是 KubeOS 的架构设计,KubeOS 一共分成三个模块,第一个模块 OS operator 部署在 master 节点上的,它是全局的 OS 管理器,会监控集群中所有节点的 OS 的状态。当用户去更改集群中 OS 信息的时候,比如说指定了新的版本,Operator 会感知到,然后把升级任务下发到各个节点上。OSProxy 它就是部署在每一个节点上,它就是单节点的 OS 管理器,会监控当前节点的状态,当他接到 Operator 下发的升级任务时,会去做比如说封锁节点、迁移 Pod 这些操作,并且把需要升级的 OS 信息转发给 OS Agent,OS Agent 是真正的 OS 升级的执行单元,它接收来自于 OS Proxy 的相关信息,完成升级和重启操作。
如图是 KubeOS 的文件系统布局的设计,首先是 Root 分区,因为 KubeOS 我们采用了双分区升级的方式,每一个分区它会存放一个 OS 的版本,所以说分成了 RootA 和 RootB,每次升级的时候会下载 OS 镜像到另外一个分区,在下次启动的时候将启动目录切换到另外一个分区,就完成了双分区的升级,并且 KubeOS 文件系统是只读的,这也是为了安全性的考虑,但是我们还是提供了一个 persist 分区,用它存放持久性的用户数据,它其中有一个 Union Path,它采用 overlay 的形式,在镜像上增加叠加层,还有一个 Writable Path,它主要使用 bind mount 形式,直接在镜像上面增加了一个可写层,最后是 Boot 分区,存放的是 grub2 文件。
最后我们再介绍一下 KubeOS 的升级流程。首先第一步,用户通过修改 OS 的 Yaml 文件,指定要升级的 OS 信息,比如 OS 的版本、存放镜像的地址,以及一次升级的 OS 的数量。
当集群中 OS 的状态发生了变化以后,OS Operator 就会感知到这个变化,去查询集群中所有节点的状态,若发现和当前节点的状态不一致,就会把需要升级的节点标记为升级节点,相当于把任务下发到各个节点了。
OSProxy 监控发现当前的节点被标记为升级节点了,就开始执行升级操作,从集群中获取要升级到的 OS 的版本,它把当前节点的所有 Pod 进行驱逐,并把当前节点锁定,把 OS 的信息发送给 OS Agent,Os Agent 接收到相关的信息以后,从一开始用户指定的升级镜像存放的服务器下载镜像,然后设置启动分区,进行重启和升级。升级以后 OS Proxy 看当前节点的状态发现已经升级完成了,就把当前节点重新解锁,并且取消升级标记。
未来的工作
我们接下来要做什么?首先我们需要去不断丰富 KubeOS 的功能,比如提供系统配置下发的功能,提供更多的安全策略。第二点是要不断完善,比如更全面的支持,提供支持更多的架构,更多的容器引擎等等。还有就是让 KubeOS 的使用和部署变得更加方便,比如提供一键式的部署。