亚马逊AWS官方博客

Bottlerocket:一套专用型容器操作系统

Original URL: https://aws.amazon.com/cn/blogs/containers/bottlerocket-a-special-purpose-container-operating-system/

 

2020年3月10日,我们正式推出Bottlerocket,一套用于托管Linux容器的新型专用操作系统。在本文中,我们将共同了解Bottlerocket项目的开发初衷、一路走来做出的工程选择,以及我们对于这套操作系统未来发展方向的规划与愿景。

2014年,我们推出了Amazon Elastic Container Service(ECS),一项Linux容器的编排服务。与之对应,我们还同步推出了用于托管容器的预配置、即用型操作系统:针对Amazon ECS优化的AMI。此AMI通过两方面对Amazon ECS做出优化。首先,它安装有运行ECS所必需的一切软件,能够在启动之后立即开始运行Docker容器。其次,它基于经过简化的Amazon Linux AMI版本,旨在减少非必要软件带来的维护负担并节约磁盘存储空间。但从本质上看,该AMI仍然是一套为运行传统软件应用设计的通用型操作系统,而不是为容器设计的操作系统。

2017年,我们又推出了Amazon Elastic Kubernetes Service(EKS),而且同样提供与之配套的、针对Amazon EKS做出优化的AMI,作为托管Kubernetes Pod的预配置、即用型操作系统。与针对Amazon ECS优化的AMI一样,与Amazon EKS配套的AMI同样是一套为运行传统软件应用设计的通用操作系统,而不是为容器量身定制。

下面请出我们的主角,Bottlerocket项目!它的诞生,源自我们长期以来在Amazon大规模生产运行服务当中总结出的经验教训,同时也结合了过去六年以来我们对于容器技术的提炼与沉淀。除了内部经验与Amazon工程师的直接反馈以外,我们还认真听取客户关于ECS优化型AMI、EKS优化型AMI以及其他以容器为中心型操作系统的优劣势意见。有一些中肯、有价值的意见,如:增强安全性、保证集群内各实例的统一性,容易运维等。这些意见帮助我们逐步开发出后来的Bottlerocket项目。我们坚信,Bottlerocket项目能够改善上述各类使用场景,我们也将不断努力推动Bottlerocket更上一层楼!

同样需要强调的是,Bottlerocket当然不是第一套在这些元素之间做出抉择取舍的操作系统。与各类新兴软件项目一样,Bottlerocket也站在众多原有开发成果的肩膀之上。在设计及构建Bottlerocket时,我们受到传统通用型Linux发行版以及各类以容器为中心的操作系统(例如CoreOS Container Linux、Rancher OS以及Atomic项目)的启发。我们做出的很多工程选择也与这些操作系统相似,同时努力将各类行之有效的方法与探索性尝试引入自己的设计体系当中。

在深入探讨技术细节之前,我们不妨先来聊聊如何使用容器,以及为什么用户往往会在使用过程中提出共通的反馈意见。无论以哪种环境为基础,计算机的启动总需要一段时间。但比启动更麻烦的,在于如何将随机应用程序部署到计算机之上,并保证其始终可靠运行。容器技术的出现让这项目标变得更加轻松可行。容器映像提供了一种可靠且可重复的机制,能够将应用程序运行所需本地依赖(包括动态链接库、需要调用的其他程序及资源)进行整体打包。而支持容器技术的Linux内核原语(包括cgroups与命名空间)也能够提供一定资源与明确的隔离机制。更重要的是,容器的启动速度要远远快于整机启动。上述属性的存在,保证了每个应用程序都能够在容器内作为“唯一正在运行的应用”存在。以此为基础,我们得以将一台计算机拆分为多个小部分,保证多个应用程序能够并行运行且彼此不会发生冲突,这就让一台计算机运行多款应用程序、甚至以单机集群运行应用程序的多个副本成为可能。

容器编排领域的整体生态系统也在持续为软件系统的部署与运行提供助力。容器编排工具能够帮助用户在同一组计算机之上,轻松管理多款不同应用程序及其对应的多个应用程序副本。编排工具还包含多种其他功能机制,例如服务发现、网络策略管理、负载均衡、应用程序跟踪等等。所有这一切的实现,又离不开当前风头一时无两的微服务架构的加持。当然,要想要众多计算机上广泛运行容器系统,至少需要保证各计算机之间具备可靠的一致性、可预测性与安全性。这也正是我们开发Bottlerocket项目的意义所在——建立起高质量的底层操作系统,为容器的批量托管建立起良好基础。

容器生态系统的发展与繁荣,无疑要归功于其背后庞大的开源社区。目前,用于容器开发、运行及操作的众多核心组件都属于开源成果,范围涵盖Docker、容器化、Kubernetes乃至Linux自身。我们希望Bottlerocket项目也能很好地适应容器生态系统,并以开源项目的形式长期存续下去。感兴趣的朋友请关注文章结尾部分,了解如何参与到Bottlerocket项目当中。

设计Bottlerocket

我们首先来看看Bottlerocket项目在安全性、一致性以及可操作性方面需要达成的实际目标,再以此为基础讨论工程选择问题。我们做出的大部分选择都能够支持多项目标,因此单纯按目标进行选择分类可能不够清晰明确。但在本文中,我将尽量以主要目标为核心对选择进行大致排序。

安全性

首先从安全性说起。安全性的主要概念在于减少攻击面,验证软件质量并强制划分权限边界。

Bottlerocket中包含的软件更少,甚至直接剔除了某些大家可能需要的组件——没错,Bottlerocket中没有SSH、没有解释器(例如Python)、也没有Shell。我们希望Bottlerocket在大多数情况下都能轻装上阵,我们也坚信剔除此类组件之后,攻击者将更难在系统当中寻找驻留点。(当然,Bottlerocket还是拥有故障排查与调试机制的,具体细节将在后文中说明。)除了减少所包含的软件之外,Bottlerocket还采用多种软件强化技术来控制操作系统的攻击面,具体包括构建不受位置限制的可执行文件(PIE)、使用重定位只读文件(RELRO)链接,并使用Rust及Go等内存安全语言构建全部第一方软件等等。

Bottlerocket还通过密码技术进行自我验证。该操作系统由磁盘镜像组成,系统会在启动镜像时使用dm-verity进行自我验证;对于磁盘镜像的任何意外变更,都将导致操作系统无法启动。Bottlerocket还拥有自己的软件更新程序,而非直接使用更常见的Linux包管理器。Bottlerocket的所有更新都来自一套遵循The Updata Framework(TUF)规范的代码库。TUF能够有效缓解针对传统程序包管理器系统中各类软件代码库的常见攻击方法。

Bottlerocket还会在强制模式下使用SELinux限制自身受到的修改,甚至能够拒绝高权限容器下达的操作请求。SELinux属于一套对Linux内核实施强制访问控制(MAC)的实现版本,其限制了进程所能采取的具体操作类别。如今,Bottlerocket的SELinux限制策略已经非常明确,能够限制各类容器对操作系统做出意外更改。展望未来,我们希望进一步扩展这些策略,从而对应更多不同类型的持续威胁活动。

一致性

我们希望Bottlerocket能够帮助用户增强环境一致性;在通过计算机集群运行容器时,用户应该能够在其中任何一个节点之上运行相同的工作负载。Bottlerocket主要通过三种方式增强一致性:基于镜像的更新、只读root文件系统,外加API驱动型配置。

目前,各类常见的通用型Linux发行版总会内置用于软件安装及更新操作的集成软件包管理系统。这虽然能够提升发行版的灵活性,保证其可以轻松适应不同类型的工作负载,但在管理大量主机时,这种灵活性往往又成了缺点:每一台主机之上可能安装有不同的软件包及其不同版本,并导致一致性受到破坏。软件包管理器中的可用软件包种类越多,由此带来的挑战也就越严峻。我们安装的软件包组合可能从未进行过匹配测试。但与之对应,Bottlerocket的情况完全不同——其中并不提供能够安装多种软件的软件包管理器;相反,Bottlerocket直接使用预先构建的镜像,其中包含操作系统必要软件,以及诊断及可观察性工具等其他软件方案。当存在可用更新时,Bottlerocket会下载新的完整磁盘镜像,并可通过简单重启实现应用更新。这种基于镜像的部署方法能够严格保障一致性水平:fleet中的所有Bottlerocket主机都能够运行完全相同的软件,并保证Bottlerocket镜像中的每一种组件及其特定版本都经过严格的组合测试。

Bottlerocket专门用于运行容器,而且凭借基于映像的部署机制保证一致性。但我们也意识到,运行中容器内的每一个用例都是不同的,并不存在能够广泛适用的普适性软件与配置。与之相冲突的是,我们又希望Bottlerocket能够在不同的位置(包括Raspberry Pi设备上)配合不同的编排工具(例如Amazon ECS)正常运行。为了解决问题,Bottlerocket通过“变体(variant)”系统解决种种需求差异,并针对不同的用例提供不同的镜像。目前AWS面向Kubernetes 1.15发布的变体名为aws-k8s-1.15。我们还计划在未来针对Amazon EKS以及Amazon ECS为Kubernetes的其他版本发布更多变体选项。Bottlerocket中也包含对应工具,允许大家结合自身需求构建属于自己的变体版本。

与传统Linux发行版不同,Bottlerocket操作系统配置有只读的root文件系统。这样的设计将进一步提升一致性水平并减少变量漂移。应用程序无法修改磁盘镜像,同时也无法将当前变更引入另一台主机。在Bottlerocket下载完更新并准备安装时,该更新将被写入至辅助分区。在重新启动之后,Bottlerocket的引导加载程序将根据该分区进行引导,更改主分区并在辅助分区内继续保留旧版本镜像。一旦发生更新问题,我们可以利用这套机制实现快速回滚。Bottlerocket还在文件系统当中配备一个独立的可写入部分,专门用于存放持久性用户数据,例如容器镜像与存储卷等。

目前比较常见的作法,是在Linux上的/etc目录当中存储软件配置。Bottlerocket同样兼容/etc目录,但会将其以基于内存的临时文件系统的形式进行公开,此文件系统在每次引导时都会经历重建。除了这部分配置外加可能允许应用程序更改Bottlerocket本体的少量配置之外,其余所有配置都将通过Bottlerocket开放的API实现。该API拥有丰富的语义支持范围,涵盖结构化设置、事务设置与自动迁移等等。用户可以通过AWS Systems Manager经由Bottlerocket中的“control”容器访问该API,借此实现交互式变更;此外,大家也可以直接以编程形式完成变更。如果大家将Bottlerocket运行在EC2实例之上,亦可通过TOML格式的用户数据进行配置设定。

Bottlerocket还具备自行设定一部分配置选项的能力。在引导过程初期,Bottlerocket会自动生成主机名称与网络配置等以完成自我设置。在使用Bottlerocket的asws-k8s-1.15变体时,系统将运行帮助程序以配置Kubernetes中的特定设置,例如集群DNS设置与暂停容器镜像的名称等。大家可以使用API覆盖掉这些初始设置,也可以在配合EC2实例使用时通过TOML格式的用户数据完成设置。

可操作性

本文要探讨的最后一个目标是可操作性。虽然与其他基于Linux的容器操作系统有所区别,但Bottlerocket仍然具备多种常规操作(例如软件更新与故障排查)功能。Bottlerocket以明确定义的方式持续运行,用户也可以通过设置调整其具体行为模式。Bottlerocket内置有自动软件更新机制,还能够与Kubernetes相集成,通过监控警报及工作负载转移等机制减少服务中断造成的影响。另外,Bottlerocket还提供用于执行常规管理任务(例如变更设置以及手动安装软件更新)的工具。当然,请大家保证只在必要时才使用这些用于紧急情况的工具。

Bottlerocket的更新功能由多种不同组件共同实现。首先是一套基于TUF的代码库,其中包含更新的镜像与签名,用于证明镜像完整性以及代码库自身的完整性。其次,Bottlerocket中的托管工具updog用于同代码库进行交互并检索更新。Updog能够查询更新,并将更新内容立即应用于Bottlerocket。但需要注意的是,updog默认使用基于“wave”原则的更新策略——wave机制允许更新内容在不同时段内有次序地交付至集群内的不同主机,而非同一时间向全部主机公开更新。这一机制能够避免所有主机同时尝试进行更新,进而导致容器内工作负载出现服务中断;而一旦发现主机出现问题,也可以立即停止更新。每个主机都会在引导过程中被分配予一个随机wave,当然用户也可以根据需求对为各主机指定特定的wave。

Bottlerocket的更新功能还能够与容器编排工具相集成。作为预览版本中的一部分,Bottlerocket提供了Kubernetes operator,我们可以将其部署到集群中以使用updog执行更新。该operator将确保每次仅更新集群中的一台主机,并在应用更新之前妥善处理监控警报与负载转移等工作。目前这款更新程序仍处于早期开发阶段,我们欢迎大家就其功能扩展方向提出建议与意见。

由于Bottlerocket中没有安装SSH,因此我们需要一种不同的机制实现操作系统控制、与API进行交互,并在紧急状态下切换为管理模式。Bottlerocket在这方面提供两款工具:其一是用于典型计划内维护任务(例如变更设置)的“control”容器,其二为用于紧急用途的“admin”容器。其中control容器将在容器启动时开始运行,其中包含Amazon SSM代理;大家可以利用AWS Systems Manager API与之交互。此Control容器中还包含名为apiclientenable-admin-container的程序,前者用于实现与Bottlerocket API之间的交互,后者是一款小型帮助程序,能够自动发出API调用以启动应急admin容器。

Admin容器专门负责处理各类紧急状况。它以全权限形式启动,而且除了已应用的SELinux配置文件之外,不再受任何其他限制的约束。Admin容器以Amazon Linux 2容器镜像为基础,其中提供我们在通用Linux发行版中需要的一切工具。Admin容器中安装并运行有SSH,用户可以在实例启动时使用指定的SSH密钥通过Bottlerocket主网络接口完成接入。此外,Admin容器还提供名为sheltie的工具,能够将当前工作负载的上下文(Linux命名空间)转换为主机上下文,帮助大家在admin容器之内对主机进行操作。Admin容器在默认情况下不会启用,我们建议大家在Bottlerocket的生产部署中将其禁用。

Bottlerocket所运行的容器,一部分由编排工具负责管理,也有一部分以本地方式运营——我们将后一类称为“主机容器”。这些主机容器中也包括之前提到的control与admin容器。Bottlerocket使用两种相互独立的容器运行时——相当于两种不同的容器副本——运行这些容器。这样的处理方式原因有三:其一,根据SELinux配置要求,编排容器与主机容器可能拥有彼此不同的独立安全要求;其二,我们可以通过不同于主机容器的运行时(例如Docker或者CRI-O)启动编排容器;第三,编排容器与主机容器可以借此拥有互不干扰的故障域,保证在对某一容器运行时进行配置变更或发生故障时,另一过去时不会受到影响。Bottlerocket默认包含control容器,但admin容器只能在必要时手动添加。当然,大家也可以使用主机容器系统在Bottlerocket上运行自己的诊断、操作与管理工具。

下阶段发展目标

Bottlerocket目前尚处于预览阶段,在我们发布其正式版本之前,项目本身还需要做出诸多改进。我们已经发布了项目的公开路线图,下面聊聊其中的部分具体细节。

在真正将Bottlerocket推向全面普及之前,安全性仍是个需要解决的重要问题。我们对Bottlerocket目前的表现基本满意,但其中必然还有不少改进空间。在推出Bottlerocket的通用版本之前,我们将进一步完善SELinux管理策略。我们正积极探索减少对常规编排容器内文件系统的访问操作的方法,包括考虑将负责运行编排工具的容器副本部署在独立的命名空间中等。我们还在研究其他容器化工作负载运行方法,包括在带有Firecracker的microVM内运行部分需要高度隔离的用例。

Bottlerocket目前已经支持Kubernetes,但这并不代表Bottlerocket是一套单纯面向Kubernetes的操作系统。我们的发展路线图是在Bottlerocket上进一步添加对Amazon ECS的支持,并逐步将其他非破坏性更新集成到Amazon ECS集群当中。如果大家还希望在Bottlerocket中看到其他编排工具的身影,请让我们听到您的声音!

参与项目

Bottlerocket是一套完全开源的操作系统,由现有开源组件(例如Linux内核)、约50个软件包以及专为Bottlerocket编写的新组件(主要以Rust及Go语言编写而成)共同组成。Bottlerocket中使用的所有开源组件皆符合其原始许可,而专为Bottlerocket开发的组件也遵循类似的许可协议,您可以根据需求选择Apache 2.0许可或MIT许可。

Bottlerocket中的原创组件及其路线图皆为开源成果。我们的目标是将Bottlerocket打造成一个社区协作项目,大家可以直接为其做出贡献并制作自己的定制化版本。我们将为受支持的集成方案(例如Amazon EKS以及未来的Amazon ECS)制作一组官方镜像及更新。当然,官方镜像肯定无法支持用户的所有预期及需求,因此我们欢迎大家使用相同的工具组合构建起符合自身要求的镜像与更新。

欢迎您加入Bottlerocket大家庭!您可以查看我们的GitHub repo,通过问题参与讨论,并通过pull请求为项目做出贡献。我们还在AWS Developer Slack当中设有非官方交流频道#bottlerocket,感兴趣的朋友可以点击此处注册。

总结

Bottlerocket是一套与传统通用型Linux发行版完全不同的操作系统,我们坚信其中承载的种种变化将给用户的安全性与运营带来长期改善。我们也希望Bottlerocket中内置的各类工具(包括紧急状态下专用的admin容器等机制)能够帮助各位简化工作负载迁移流程。

欢迎大家马上开始试用Bottlerocket预览版本,我们期待听到您的反馈意见!

 

本篇作者

Samuel Karp

Samuel Karp是一位专门研究Bottlerocket OS、containered以及Firecracker等容器基础设施方案的高级软件开发工程师。