Kubernetes 自开源至今已经走过六个年头了,云原生时代也已到来,我关注云原生领域也四年有余了,最近开始思考云原生的未来走向,特此撰写本文作为《云原生应用白皮书》的开篇,更多关于云原生应用的介绍请转到白皮书中浏览。
Kubernetes 已成为云原生应用的既定运行平台,本文以 Kubernetes 为默认平台展开,包括云原生应用的分层模型。
Kubernetes 从开源至今已经走过快六个年头(2014 年 6 月开源)了,可以说是 Kubernetes 的诞生开启了整个云原生的时代。我粗略的将云原生的发展划分为以下几个时期。
第一阶段:孵化期(2014 年)
2014 年,Google 开源 Kubernetes,在此之前的 2013 年,Docker 开源,DevOps、微服务已变得十分流行,云原生的概念已经初出茅庐。在开源了 Kubernetes 之后,Google 联合其他厂商发起成立了 CNCF,并将 Kubernetes 作为初创项目捐献给了 CNCF。CNCF 作为云原生的背后推手,开始推广 Kubernetes。
第二阶段:高速发展期(2015 年 - 2016 年)
这几年间,Kubernetes 保持着高速发展,并于 2017 年打败了 Docker Swarm、Mesos,确立了容器编排工具领导者的地位。CRD 和 Operator 模式的诞生,大大增强了 Kubernetes 的扩展性,促进了周边生态的繁荣。
第三阶段:野蛮生长期(2017 年 - 2018 年)
2016 年之后的云原生基本都默认运行在 Kubernetes 平台上,2017、2018 年 Google 主导的 Istio、Knative 相继开源,这些开源项目都大量利用了 Kubernetes 的 Operator 进行了扩展,Istio 刚发布时就有 50 多个 CRD 定义。Istio 号称是后 Kubernetes 时代的微服务,它的出现第一次使得云原生以服务(应用)为中心。Knative 是 Google 在基于 Kubernetes 之上开源的 Serverless 领域的一次尝试。2018 年 Kubernetes 正式从 CNCF 毕业,Prometheus、Envoy 也陆续从 CNCF 毕业。CNCF 也与 2018 年修改了 charter,对云原生进行了重定义,从原来的三要素:”应用容器化;面向微服务架构;应用支持容器的编排调度“,修改为”云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式 API“。这一年,我曾写过两篇 Kubernetes 及云原生发展的年终总结和展望,见 2017 年和 2018 年的预测和总结。
第四阶段:普及推广期(2019 年至今)
经过几年的发展,Kubernetes 已经得到的大规模的应用,云原生的概念开始深入人心,Kubernetes 号称是云原生的操作系统,基于 Operator 模式的生态大放异彩。整合 Kubernetes 和云基础设施,研发和运维关注点分离。Kubernetes 到 Service Mesh(后 Kubernetes 时代的微服务),基于 Kubernetes 的 Serverless 都在快速发展,OAM 诞生,旨在定义云原生应用标准。
Kubernetes 开源之初就继承了 Google 内部调度系统 Borg 的经验,屏蔽掉了底层物理机、虚拟机之间的差异,经过几年时间的发展成为了容器编排标准,进而统一了 PaaS 平台的基础设施层。
下图是 Kubernetes 原生内置的可以应用到一个 Pod 上的所有控制器、资源对象等。
图片来自图书 Kubernetes Patterns(O’Reilly)
Kubernetes 作为云原生基础设施设计之初遵循了以下原则:
其中声明式 API 可谓开创了云原生时代的基调,而调节器模式是 Kubernetes 区别于其他云部署形式的主要区别之一,这也为后来的 Operator 框架的诞生打下了基础。
根据声明式 API 可以做应用编排,定义组件间的依赖,通常使用人类易读的 YAML 文件来表示。但是,YAML 文件声明的字段真的就是最终的状态吗?有没有可能动态改变?
我们在创建 Deployment
时会指定 Pod 的副本数,但是其实际副本数并不一定是一成不变的。假如集群中还有定义 HPA,那么 Pod 的副本数就可能随着一些外界因素(比如内存、CPU 使用率或者自定义 metric)而改变,而且如果集群中还有运行自定义的控制器话,那么也有可能修改应用的实例数量。在有多个控制器同时控制某个资源对象时,如何确保控制器之间不会发生冲突,资源对象的状态可预期?可以使用动态准入控制来达到这一点。
我们都知道要想运行一个应用至少需要以下几点:
下图展示了基于 Kubernetes 原语及 PaaS 平台资源的 Kubernetes 原生应用的组成。
我们都知道 Kubernetes 提供了大量的原语,用户可以基于这些原语来编排服务,管理应用的生命周期。上图展示的是基于 Kubernetes 原生应用可以使用的 Kubernetes 原语、扩展及平台层资源,从内向外的对象跟应用程序(业务逻辑)的关联度依次降低,到最外层基本只剩下平台资源依赖,已经与 Kubernetes 几乎没有关系了。该图里仅展示了部分资源和对象(包含阿里巴巴开源的 OpenKruise、Istio),实际上 Operator 资源之丰富,也是 Kubernetes 生态如此繁荣的原因之一。
Kubernetes 本身的原语、资源对象、配置、常用的 CRD 扩展有几十、上百个之多。开发者需要了解这些复杂的概念吗?我只是想部署一个应用而已!不用所对于应用开发者,即使对于基础实施开发和运维人员也需要很陡峭的学习曲线才能完全掌握它。
我将 Kubernetes 原生应用所需要的定义和资源进行了分层:
而这些不同的层,完全可以将其职责分配给相应的人员,比如核心层是由应用程序开发者负责,将其职责分离,可以很大程度上降低开发和运维的复杂度。
云原生应用落实到 Kubernetes 平台之上,仅仅利用 Kubernetes 的对象原语已很难描述一个复杂的应用程序,所以诞生了各种各样的 Operator,但这也仅仅解决了单个应用的定义,对于应用的打包封装则无能为力。
同一个资源对象又有多种实现方式,比如 Ingress 就有 10 多种实现,PV 就更不用说,对于对于开发者究竟如何选择,平台如何管理,这都是让人很头疼的问题。而且有时候平台所提供的扩展能力还可能会有冲突,这些能力有的可能互不相干,有的可能会有正交,有的可能完全重合。且应用本身与运维特性之间存在太多耦合,不便于复用。
上图中不同颜色的方框代表不同的资源类别,红线框代表不能为一个资源同时应用该配置,否则会出现冲突,不同的颜色上面是一个动画,展示的是部分资源组合。图中仅包含了部分 Kubernetes 中的原语和 Istio 中的资源对象组合及自定义扩展,实际上用户可以根据应用的自身特点,基于 Kubernetes 原语和 CRD 创建出千变万化的组合。
为了管理这些应用诞生出了众多的 Operator。Kubernetes 1.7 版本以来就引入了自定义控制器的概念,该功能可以让开发人员扩展添加新功能,更新现有的功能,并且可以自动执行一些管理任务,这些自定义的控制器就像 Kubernetes 原生的组件一样,Operator 直接使用 Kubernetes API 进行开发,也就是说它们可以根据这些控制器内部编写的自定义规则来监控集群、更改 Pods/Services、对正在运行的应用进行扩缩容。
Operator 的本质是一种调节器模式(Reconciler Pattern)的应用,跟 Kubernetes 本身的实现模式是一样的,用于管理云原生应用,协调应用的实际状态达到预期状态。
调节器模式的四个原则:
利用声明式 API 及调节器模式,理论上可以在 Kubernetes 上部署任何可声明应用,但是在 Operator 出现之前,管理 Kubernetes 上的有状态应用一直是一个难题,随着 Operator 模式的确立,该难题已得以解决,并促进了 Kubernetes 生态的进一步发展。随着该生态的繁荣,有一种碎片化的特征正在显现。
云原生应用碎片化的体现
Reconcile
模式的代码,统一了有状态应用的管理模式,极大得扩展了 Kubernetes 应用生态。Kubernetes 对于无状态应用的管理很出色,但是对于有状态应用就不是那么回事了。虽然 StatefulSet 可以帮助管理有状态应用,但是这还远远不够,有状态应用往往有复杂的依赖。声明式的 API 里往往要加载着大量的配置和启动脚本,才能实现一个复杂应用的 Kubernetes 化。
例如在 2017 年初,Operator Framework 出现之前,需要使用大量的 ConfigMap
、复杂的启动脚本才能在 Kubernetes 上定义 Hadoop YARN 和运行 Spark。虽然 StatefulSet
号称可以解决有状态应用的部署问题,但是它主要是保证了 Pod 的在启动、伸缩时的顺序和使 Pod 具有稳定的标识。但是很多分布式应用来说并不仅依靠启动顺序就可以保证其状态,根据其在分布式应用中的角色不同(master/worker)而需要有大量的自定义配置,在没有 Operator 之前这些配置通常是通过一些自定义脚本来实现,这些脚本可能存在于应用镜像中,也可以通过 ConfigMap
挂在到容器运行时,但无论如何这些脚本都可能因为散落在各处,这些脚本还是面向过程的,跟在 Kubernetes 诞生之前的运维方式毫无二致,这极其不便于版本控制和运维管理。
Operator 大大增强了 Kubernetes 的可扩展性,丰富了以 Kubernetes 为基础的云原生生态,许多原先不是为 Kubernetes 而构建的应用纷纷通过构建自己的 Operator 迁移到 Kubernetes 上。还有一些直接基于 Kubernetes 构建的 Service Mesh、Serverless 框架,它们应用 Operator 模式(如 Istio、Knative),试图成为云原生应用的基础设施层,补齐 Kubernetes 在服务治理、无服务架构等方面的短板,随着大量的 CRD、Operator 控制器的出现,而 Kubernetes 却无法以应用的视角来管理这些能力及其背后零散的 CRD,这使得云原生应用碎片化。
Operator 百花齐放,在没有一个大一统的视图之前,各个控制器之间存在着这样的关系:
Service
、VirtualService
、DestinationRule
同属一类资源(可访问性与路由),就是可组合的(后两者是 Istio 中的 CRD,用于流量管理)。CronHorizontalPodAutoscaler
(CRD)、HorizontalPodAutoscaler
(Kubernetes 内置),同时使用可能导致无法意料的情况发生。正是以为这样复杂的关系,导致其无法做到开箱即用,还需要基础设施团队基于云原生社区和生态自己构建出来的,比如应用交付领域的系列开源项目。
Kubernetes 之上有很多能力缺失,比如应用构建、发布、管理和运维等,Helm 的出现主要补偿了应用打包和版本管理的缺陷。其中云原生应用的配置包括:
这些配置可以存在于 ConfigMap
、Deployment
、Service
、Ingress
等 Kubernetes 的多个资源文件中,如何保证应用程序的复用性?应用程序之间有依赖该如何解决?这是时候你可能自然的想到了 Helm。
云原生应用打包和发布管理
Release
概念的引入,使得云原生应用版本化管理进一步加强;Helm 主要关注的是 12 因素应用法则构建、发布、运行这一原则中的”发布”这一环节。下图是 Helm v3 的架构图。
Helm 可以安装本地或者远程的 chart,当 chart 安装到 Kubernetes 中后就会创建一个 release,每次更新该 chart 的配置并执行 helm upgrade
,release 的版本数就会加 1,开发者可以升级 chart 或回滚到历史版本。
Helm 和 chart 的主要作用是:
打包
Helm 采用 Chart 的格式来标准化描述应用,可以将目录打包成版本化的压缩包进行部署理论上一个 Chart 是可以嵌套若干个 Chart 并定义依赖关系,组织形式非常灵活。Helm chart 用于打包 Kubernetes 原生应用。
配置
应用配置参数,在 Chart 中由 values.yaml
和命令行参数组成。Chart 采用 Go Template 的特性和 values.yaml
对部署的模板文件进行参数渲染,也可以通过 helm
命令 --set key=value
的方式进行参数赋值。
发布
Release 代表 Chart 在集群中的运行实例,Helm 围绕 Release 对应用提供了强大的生命周期管理能力,包括 Release 的查询、安装、更新、删除、回滚等。
以上关注的点都是基于 Kubernetes 原语的实现,虽然基于 Kubernetes 构建的 PaaS 平台部分屏蔽了底层基础设施的差异,但是仍有很多云服务是无法通过 Kubernetes 创建,或者需要提前创建供 Kubernetes 原生应用使用的,这些应用通常不运行在 Kubernetes 集群中。因此创建和管理一个云原生应用程序需要考虑以下方面:
那么究竟如何来给云原生应用分层,化繁就简?近几年来,基于 Kubernetes 的应用呈爆炸式发展,光是在应用交付领域的开源项目就达几十个之多。下图展示我根据这些项目的特性而绘制的 App Delivery Landscape。
以上为我个人分类的云原生应用全景模型,仅限于 Kubernetes 之上的应用,对于其他非 Kubernetes 应用非本文的考虑范围。另外,CNCF SIG App Delivery 中也给出的云原生应用的分层模型,其模型将非 Kubernetes 应用场景也纳入了考虑,详见:The Dictionary of Cloud-Native App Delivery。
Platform/Kuberntes,Kubernetes 仅仅是屏蔽了平台的一些差异,但是对于最上层的应用来说,没有涉及,用户需要自己来基于各种开源组件来搭积木。
那么以上这么多应用有哪些共性,能不能再进一步抽象呢?
VirtualService
、DestinationRule
,二者同时使用;一个 ApplicationConfiguration
的 Runtime 的正常流程应该是:
Component
,在 Component
中描述要应用相关的信息,如应用名称、镜像配置、环境变量等,应用到 Kubernetes cluster 中;Component
和本次的运维策略,apply 到集群中,集群的 OAM operator watch 到一次 ApplicationConfiguration
的下发,生成 Component
对应的 Workload
和 Trait
,Trait
controller 将本次的 Trait
策略应用到本次要管理的 Workload
当中,最终到达终态,完成一次发布。OAM 是对 Kubernetes 友好的,一样采用声明式 API 的理念开发。如果你已经编写了现成的 CRD Operator,可以平滑的接入到 OAM 体系中。OAM 以应用为中心,高度可扩展,扩展点包括:
以前 CNCF 的主要关注群体大多是基础设施领域的技术人员,但是自 2019 年 9 月,CNCF 宣布成立 SIG App Delivery 后,CNCF 正在将应用开发者和运维人员更紧密的联系在一起。应用交付 SIG 的使命是:
目前 OAM 定义的云原生应用模型已有以下项目支持。
kubectl
配置和管理基础架构、服务和应用。对于 OAM 的支持详见运行应用程序。应用交付领域相关的开源项目还有很多,详见 Awesome Cloud Native。
基于 Kubernetes 的云原生生态发展至今已有 6 年时间,当前已步入了普及推广阶段。可以说谁云原生应用定义的制高点,就可以掌握云原生的未来。从前我们是新技术浪潮的追随者,现在我们抓住时代的基于,参与标准制定、引领云原生的浪潮!欢迎加入 OAM 社区,一起参与进来,把国人参与指定的标准推向世界。
最后更新于 2024/11/07