云原生(Cloud Native)最初是由 Pivotal公司的Matt Stine于2013年提出的。Pivotal公司先后开源了云原生的Java开发框架Spring Boot和Spring Cloud。随后,Google在2015年成立了CNCF(Cloud Native Computing Foundation),使得云原生受到越来越多的关注。
要想理解什么是云原生,我们需要先理解什么是云。
有人认为“云”的同义词是“可公开所有信息的互联网”,这个说法是不正确的。云一般指的是一个提供资源的平台,云计算的本质是按需分配资源和弹性计算。
顾名思义,云原生应用即专门为在云平台部署和运行而设计的应用。云原生应用并非完全颠覆传统的应用,采用云原生的设计模式可以优化和改进传统应用模式,使应用更加适合在云平台上运行。
在云计算越来越流行的今天,云原生成了一个必然的导向。云原生存在的意义是解放开发和运维,而不是让开发和运维工作变得更加复杂和繁重。
其实,大部分传统应用即便不做任何改动,也可以在基于 Linux 操作系统内核的云平台上部署和运行,但是仅以能够部署和运行为主要目的,将云主机当作物理机一样使用,是无法充分利用云平台的能力的。
让应用能够利用云平台实现资源的按需分配和弹性伸缩,是云原生应用被重点关注的地方。云原生还关注规模,分布式系统应该具备将节点扩展到成千上万个的能力,并且这些节点应具有多租户和自愈能力。
云原生使得应用本身具有“柔性”,即面对强大压力的缓解能力以及压力过后的恢复能力。正所谓“刚而易折,柔则长存”,对于一个单机处理能力很强的“刚性”系统而言,一旦崩溃,则很难恢复;而通过云原生实现的关注分布式与可水平伸缩的“柔性”系统,是不太容易全线覆灭的。
从本质上来说,云原生是一种设计模式,它要求云原生应用具备可用性和伸缩性,以及自动化部署和管理的能力,可随处运行,并且能够通过持续集成、持续交付工具提升研发、测试与发布的效率。
在云原生体系中,有下面两组词语用于形容应用。
无状态(stateless)、牲畜(cattle)、无名(nameless)、可丢弃(disposable):表示应用并未采用本地内存和磁盘存储状态和日志,因此可以将应用随意部署到另一个全新的环境中,在本书中我们将这类应用统称为无状态应用。
有状态(stateful)、宠物(pet)、有名(having name)、不可丢弃(non-disposable):表示应用状态将依赖于本地的运行环境,因此无法将应用随意部署至其他环境,应用是不能随意扩展的,在本书中我们将这类应用统称为有状态应用。
十二要素(The Twelve Factors)是由Heroku团队提出的云应用设计理念,它为构建流程标准化和高可移植的SaaS应用提供了完善的方法论。遵循十二要素设计的应用具备云原生应用的所有特征。十二要素适用于任何语言开发的后端应用服务,它提供的方法论和核心思想如下。
将流程自动化和标准化,降低新员工的学习成本。
划清与底层操作系统间的界限,以保证最大的可移植性。
适合部署在现代云平台上,避免对服务器与操作系统进行管理。
将开发环境与生产环境的差异降至最低,便于实施持续交付和敏捷开发。
应用可以在不改变现有工具、架构或开发流程的情况下,方便地进行水平伸缩。
十二要素重点关注应用程序的健康成长,开发者之间的有效代码协作,以及避免软件腐蚀。十二要素的内容如图1-8所示。
图1-8 十二要素
下面我们来逐条介绍一下十二要素。
1.基准代码(Codebase)
同一应用对应同一套基准代码,并能够多次部署。
部署到不同环境的同一个应用,其基准代码库应该相同,但每份部署可以包含各自环境中的不同配置。一次部署对应一个运行起来的应用程序,应用与部署的关系是一对多的,这体现了应用代码的可重用性。同一套基准代码可以重用到多次部署中去,共享的是代码,而不同的仅仅是配置。从另一个角度来说,非运行时的应用对应的是代码仓库,它的每一个运行时实例都对应一次部署,同一代码仓库可以保障应用的复原能力。
推荐使用 Git、SVN 等优秀的源代码管理工具作为基准代码库。基准代码与部署的关系如图1-9所示。
图1-9 基准代码与部署的关系
2.依赖(Dependencies)
显式声明第三方依赖。
随着技术的发展,应用程序的开发已不再是一个从零开始的过程,大量的第三方类库使得工程师们可以站在巨人的肩膀上进行增量式开发。应用程序不应隐式地依赖类库,而是应该通过依赖清单明确地声明其依赖项。
显式声明依赖简化了环境配置流程,开发工程师仅需要安装编程语言环境和它对应的依赖管理工具,并从代码库中检索出代码,即可通过一个命令来构建所有的依赖项,从而轻松地开始工作。
十二要素要求应用同样不应该隐式依赖某些系统工具,比如 curl。即使这些系统工具存在于所有的现代操作系统中,也无法保证未来的操作系统都能支持或兼容现有应用的使用方式。
现代编程语言都会提供依赖打包管理工具,如Java语言的 Maven、Gradle以及早期的Ant等。
3.配置(Config)
将配置存储至环境变量。
应用的配置在不同环境中部署时也会有所差异,若应用将配置以编码的方式写入程序的常量,则会造成代码与配置混淆。十二要素强调配置应该与代码分离,不应在源码中包含任何与环境相关的敏感信息。
虽然将配置提炼到属性文件可以实现将其与代码分离,但属性文件仍然可能会被不小心地提交至源码仓库。因此,十二要素推荐将配置存储于环境变量中,这样可以非常方便地在不同的部署环境间修改,而无须改动代码。
与配置文件相比,环境变量与语言和系统无关。将配置存储在环境变量中能够方便与Docker等基于容器的应用配合使用,也易于与Kubernetes的ConfigMap配合使用。将配置排除在代码之外的标准取决于,应用是否可以立刻开源且不必担心暴露任何系统的敏感信息。
十二要素并不赞同采用配置分组的方式管理配置。有些开发团队愿意将应用的配置按照特定的环境(如开发环境、测试环境和生产环境)进行分组。采用配置分组方式不利于扩展,当工程师添加他们自己的开发环境(例如 john-dev)时,将导致各种配置组合激增,给管理部署增加额外的不确定性。
十二要素要求环境变量的粒度足够小且相对独立,它们不应该作为环境组合使用,而是应该独立存在于每个部署之中。当应用程序拥有更多种类的配置项或进行环境部署时,采用这种配置管理方式更容易实现平滑过渡。
需要特别指出的是,这里所指的配置并不包括应用程序的内部配置。举例来说,Spring 容器中Bean的依赖注入配置,或者Servlet的映射配置文件web.xml等,它们更应该被认为是代码的一部分。
4.后端服务(Backing Services)
将后端服务作为松耦合的资源。
后端服务是指应用程序所依赖的通过网络调用的远程服务,如数据库、缓存、消息中间件以及文件系统等,不同后端服务之间的区别仅仅在于资源的URL不同。
十二要素要求应用程序不应该区别对待本地服务和远程服务,它们同样都属于附加资源。应用程序可以在不改动任何代码、仅修改资源地址的情况下,将出现硬件问题的数据库切换为备份数据库,或将本地数据库切换为云数据库。应用程序与这些附加资源应该保持松耦合的状态。图1-10展示了后端服务松耦合的状态。
图1-10 后端服务松耦合的状态
5.构建、发布、运行(Build、Release、Run)
严格分离构建阶段与运行阶段。
分离构建阶段与运行阶段的根本在于,要严格区分应用的非运行时状态和运行时状态。构建是将应用的源代码编译打包成可执行软件的过程,属于非运行时行为。将基准代码转化为一份部署一般要经过构建阶段、发布阶段和运行阶段。构建阶段是将源码从编译状态转化为可执行的二进制文件的过程;发布阶段是将构建结果与当前部署所需要的配置相结合,并分发至运行环境的过程;运行阶段是在执行环境中启动一系列发布完毕的应用程序进程的过程。
十二要素规定,禁止在运行阶段改动代码,这样做会导致基准代码失去同步。建议每个发布版本对应一个唯一的发布 ID,发布版本只能追加而不能修改,除了回滚,其他变动都应该产生新的发布版本。构建与发布的流程如图1-11所示。
图1-11 构建与发布的流程
6.进程(Processes)
将应用作为无状态的进程运行。
应用进程应该是无状态的。只有无状态的应用才能做到水平伸缩,从而利用云平台弹性伸缩的能力,而需要持久化的数据应该存储于后端服务中。
在有些遗留系统的设计中,Web应用通常会将用户会话中的数据缓存至内存,并保证将同一用户的后续请求路由到同一个进程,这种会话被称为黏性会话。十二要素并不推荐这种做法,会话数据应该保存至 Redis 这样的带有过期时间的缓存中,并作为后端服务提供服务。
7.端口绑定(Port Binding)
通过端口绑定对外发布服务。
应用本身对于发布服务的环境不应该有过多的要求,不需要依赖云平台提供应用运行容器,只要云平台分配某个端口对外发布服务即可。通过端口绑定访问服务也意味着任何应用都可以成为另一个应用的后端服务。
例如,可以利用Jetty这种内嵌的Web服务器或Spring Boot等快速开发框架来开发包含可发布HTTP服务的应用。
8.并发(Concurrency)
能够通过水平伸缩应用程序进程来实现并发。
云平台操作系统与UNIX操作系统类似,运行在系统之上的不同进程彼此独立并且共享操作系统管理的硬件资源。不同的应用彼此独立、互不干扰地运行在一个云平台上,可以充分利用云平台的整体计算能力。这样的进程模型对于系统扩容非常实用。
开发人员应该将不同类型的工作分配给不同的进程,例如,将HTTP 请求交给Web服务器的进程来处理,将常驻后台进程交给 worker 进程负责,将定时任务交给clock进程负责。这条原则与微服务的设计原则有异曲同工之妙,它希望应用开发者将应用的职责尽可能进行拆分。图1-12清晰地展示了如何通过增加不同类型的应用进程来实现系统的水平伸缩。
图1-12 通过增加不同类型的应用进程来实现系统的水平伸缩
9.已处理(Disposability)
可以快速启动和优雅关闭应用。
快速启动是为了充分利用云平台根据需要调度资源的能力,在需要的时候,以最小的延时扩展计算能力,提供服务。优雅关闭是为了保证应用逻辑的完整性,将该完成的任务正确完成并释放资源,将未能完成的任务重新交回系统由其他应用的运行实例来继续完成。随时可能有大量部署在云平台上的应用实例启动、运行和关闭,因此快速启动和优雅关闭应用对于维持系统的高性能和稳定性尤为重要。
10.开发环境与线上环境等价(Dev/Prod parity)
要保持开发环境与线上环境等价。
开发环境和线上环境之间存在着很多差异,主要包括代码差异和操作差异。开发人员正在编写的代码可能需要很长时间才会上线,这将导致开发环境和线上环境的代码差异很大。在开发环境中,代码一般由开发人员编写并调试,而在线上环境中,代码则由运维人员部署,不同的操作方式也可能带来环境的差异。
保持环境一致,可以提高功能测试和集成测试的有效性,避免出现开发环境测试正常但生产环境出现问题的情况。推荐使用Jenkins等持续集成工具来缩短生产代码和代码库中的代码不一致的时间,并采用自动化部署的方式避免操作差异。
11.日志(Logs)
使用事件流处理日志。
运行在云平台上的应用处在复杂的分布式基础设施之上,如果日志仍然写在硬盘的一个文件中,将给系统排错或通过日志挖掘信息带来很大的困难,并且当日志与应用绑定时,应用不能作为无状态进程,也就无法充分利用云平台的扩容能力了。
为了解决以上问题,我们应采取相应措施——应用将日志输出到标准输出(STDOUT),然后由云平台统一收集并处理。在线上环境中,进程的输出事件流由运行环境截获,运行环境会将所有输出事件流整合在一起,然后发送给一个或多个最终的处理程序,用于查看或长期存档。
推荐使用 Flume、Filebeat 或 fluentd 等日志收集工具。日志事件流最终可以被发送到Elasticsearch或 Splunk 这样的日志索引及分析系统中,用于排错查询和后期分析。
12.管理进程(Admin Processes)
将后台管理任务当作一次性进程运行。
与用来处理应用的常规业务进程不同,工程师经常希望执行一些用于管理或维护应用的一次性任务,这类任务被称为后台管理任务,例如检查和清理环境、迁移数据等。
这些后台管理任务应该作为一次性进程,与常驻进程使用同样的环境,基于同样的代码库和配置发布运行。总而言之,一次性进程同样应该遵循前面提到的十一个要素。
遵循十二要素的应用程序环境是一次性且可复制的。由于应用程序的状态均通过后端服务持有,因此无状态的应用有助于编排系统自动化扩展。扩容时,编排系统仅须将应用程序的运行时环境数量扩充到期望位并直接启动进程即可;缩容时,则需要停止应用进程并删除环境,无须进行环境状态备份。
要想了解有关十二要素的详细内容,请参见官方网站:https://www.12factor.net/。
在十二要素发布之后,就职于Pivotal公司的Kevin Hoffman出版了Beyond the Twelve-Factor App一书,书中不仅对原十二要素进行了更加详细的阐述,还增加了三个新要素,具体如下。
1.优先考虑API设计(API first)
在云原生应用中,系统之间的跨进程调用是不可避免的,因此对于开发者而言,设计出合理的、具有高兼容性的交互API是首要任务。
API如同契约,一旦生效,就应该尽可能少地被改动。若API在投入使用后再进行修改,其影响范围可能不易掌控,还会波及很多外部系统。相反,在API不改动的情况下,修改内部实现则相对容易。
无论使用RESTful API、WebService API,还是同构语言的接口级别API,都应该优先勾勒一个API的设计蓝图。
设计一个具有前瞻性的API并不容易,在很多情况下需要对现有的API进行改动。每次对API 进行改动都需要做到向后兼容,并为每次改动提供唯一的版本号。应尽量避免废止原有的API,以及修改原有API中已经存在的属性,而应该通过增量的方式增加新的功能。
2.通过遥测感知系统状态(Telemetry)
对于部署在云环境上的应用,其系统环境是封闭且隔离的,在出现状况时不应该登录有问题的物理服务器去观察和收集应用的状态,况且不同的云原生应用实例是无差别地被混合部署在物理机上的,如果采用微服务架构,应用的实例数量还会呈几何级增长,出现问题时通过登录物理服务器来解决是不现实的。
云原生应用的无状态特性使得解决故障变得非常简单。应用本身出现问题时可以直接关闭该应用进程,物理服务器出现问题则可以直接将该服务器移出集群。通过调度编排系统可以自动在其他服务器上启动相应个数的实例,整个过程基本不需要人为参与。因此云原生应用需要通过遥测来感知应用以及服务器本身的状况,暴露尽量多的监测信息为运维工程师或云调度系统提供判断和处理问题的依据,应用本身则需要提供包括 APM 信息、健康检查、系统日志在内的采集数据。
将云原生应用比喻为一个太空探测器比较贴切。它可以通过遥控的方式采集它所在星球的各类样本,但无须通过宇航员真正登陆那个星球来完成每个操作。
3.认证和授权(Authentication and Authorization)
虽然十二要素中并没有提到安全问题,但在云环境中,安全是至关重要的。开发者通常关注更多的是如何实现业务功能,但与此同时,开发者需要知道,云原生应用所运行的云环境中可能包含某些不为人知的其他应用。
将安全问题完全抛给云平台是很危险的,因此建议采用OAuth2认证和RBAC授权等比较完善的安全机制。
十二要素以及以上三个补充要素为设计云原生应用提供了思路,设计应用时不必完全生搬硬套,也不一定每一条都得满足,灵活运用即可,建议根据应用适合的场景进行裁剪和改良。
2015年,Google牵头创立了CNCF(Cloud Native Computing Foundation),同年,CNCF发布了其标志性作品——Kubernetes 1.0。由此,围绕CNCF又产生了许多很有价值的云原生项目。CNCF 独立维护了一个全景图项目,该项目发布非常频繁,截止到本书写作时,其最新版本是0.9.9。大家可以在GitHub上查看相关内容。
如图1-13所示,在CNCF全景图中,云原生的生态圈在横切面上被划分为五层,同时在纵切面上又规划出两部分作为共用层。五层分别是应用定义与开发层、编排与治理层、运行时层、供应保障层和云设施层。另外两部分是观察与分析、平台。将这些功能整合起来就是一个完善的云平台服务产品。
图1-13 CNCF全景图
图1-13中所含信息量巨大,无法看清细节,下面我们就层层递进地来仔细解读一下CNCF全景图。首先来看看横切面五层中每层涵盖的内容。
应用定义与开发层
如图1-14所示,应用定义与开发层中的内容对于有经验的技术人员来说是比较熟悉的。无论是否要开发基于云原生的应用,这些内容都是开发和运维的基础,与传统开发模式并无二致。
图1-14 应用定义与开发层
应用定义与开发层包括数据库与数据分析、流式处理、软件配置管理、应用定义以及持续集成/持续交付,下面分别来看。
1.数据库与数据分析
图1-15展示了CNCF应用定义与开发层中数据库与数据分析部分的内容。
图1-15 数据库与数据分析
这部分主要包括数据库与大数据分析工具,涉及关系型数据库、NoSQL、NewSQL、数据库中间层以及大数据处理方案。
基于SQL操作的关系型数据库主要包括Oracle、SQL Server、MySQL、PostgreSQL、DB2、MariaDB等。虽然各种新型数据库层出不穷,但关系型数据库的存储引擎毕竟经历了数十年的打磨,又有经典的ACID事务模型作为支撑,因此它作为核心数据存储选型的地位不可撼动。针对关键业务,大部分企业依然倾向于采用关系型数据库进行存储,如存储交易数据、订单信息等。数据库本身的稳定性、SQL的查询灵活度、开发工程师的熟悉程度以及数据库管理员的专业度等,共同形成了关系型数据库的强大生态圈,使得关系型数据库始终是格式化数据存储行业最优先的选择。
作为关系型数据库的有效补充,NoSQL数据库在数据存储领域也占据重要地位,常用的有MongoDB、Couchbase、Redis、Cassandra、HBase、Neo4j等。面向文档、面向列簇、面向Key-Value、面向图等的 NoSQL 数据库在数据的分布式处理方面表现得更加优秀,编程模型也更加贴近面向对象原有的方式,虽然查询的灵活度远不如SQL,但在特定场景中的性能、对面向对象的原生存储能力、无Schema模式等方面都做得很不错,因此在适合的业务场景下,NoSQL数据库也大有用武之地。比如,将Redis当作缓存,将Neo4j当作关系分析的数据库,将MongoDB当作存储Schema易变型数据的数据库,这些都是很常见的使用方式。
NewSQL数据库目前是新一代数据库的焦点,是颠覆关系型数据库统治地位的有力竞争者。它在兼容SQL的同时,更加擅长分布式处理。其中的优秀代表是PingCAP开源的TiDB。
TiDB采用Key-Value存储引擎,采用不同于ACID的强一致分布式事务,可动态平滑地进行数据迁移,自动水平伸缩,也可以在线修改Schema,进行索引变更,是完整的数据存储方案。但新的存储引擎的成熟度毕竟还需要时间来检验,而且“无须专业数据库管理员运维”的理念也需要时间来让更多的企业接受。因此,愿意尝试的公司仍然秉持“关系型数据库和NewSQL数据库共用”的较为谨慎的态度。随着时间的沉淀,相信NewSQL的前景会越来越光明。
大数据处理方案用在离线或准实时的计算大数据的技术栈中,如Hadoop、Spark、Druid等。这里的 Druid 不是指阿里巴巴开源的数据库连接池,而是一个用于大数据实时处理的分布式系统。
以Map/Reduce闻名的Hadoop体系,将计算任务抽象成Mapper和Reducer的编程模型,能够通过分布式手段在由成百上千台 PC 服务器组成的不完全可靠的集群中并发处理大量的数据集,并且将分布式和故障恢复等实现细节隐藏起来。它将数据处理过程分解为多个包含Mapper和Reducer的作业,将这些作业放入集群执行,并最终得出计算结果。但由于Map/Reduce任务将中间结果存入HDFS文件系统,因此延时较长,只适合高吞吐和大批量的数据处理场景,无法满足交互式数据处理的需要。
以RDD(Resilient Distributed Dataset)作为计算模型的Spark,提供了一个供集群使用的分布式内存。在Spark中,RDD转换操作后会生成新的RDD,而新的RDD数据仍然依赖于原来的RDD。因此,程序构造了一个由多个相互依赖的RDD组成的DAG(有向无环图),并将其作为一个作业交由Spark执行。由于每次迭代的数据并不需要写入磁盘,而可以保存在内存中,因此Spark的性能较Hadoop有很大提升。
2.流式处理
流式处理所包含的内容如图1-16所示。
流式处理包括消息中间件以及流式实时计算框架。
消息中间件用于异步化和解耦系统依赖,如RabbitMQ和Kafka,未上榜的还有老牌消息中间件 ActiveMQ,以及国产的优秀消息中间件 RocketMQ 等。由于存储引擎不同,ActiveMQ、RabbitMQ这种可以基于消息索引查询的消息中间件,功能虽然完善,但分布式能力和性能不尽如人意。Kafka 以及早期的 RocketMQ 采用日志追加的存储引擎,因此分布式能力和性能大幅提升,但自身对于消息的控制能力有限。新一代的 RocketMQ 以及阿里巴巴随之孵化的OpenMessaging,正在努力平衡和兼容两种流派的消息中间件。
图1-16 流式处理
流式实时计算框架包括 Strom、Flink 等,相较于 Hadoop 这样的离线计算体系,这类框架更加关注实时性。Storm与Flink都是将输入流进行转换和计算,并将结果作为输出流传输至下一个计算节点的。对于实时统计PV、UV等需求,采用流式实时计算框架来实现是不错的选择。
3.软件配置管理
软件配置管理即SCM(Software Configuration Management),它通过执行版本控制来保证所有代码和配置项变更的完整性和可跟踪性。在源代码版本控制工具领域,老牌的SVN已是明日黄花,将会逐渐退出历史舞台,取而代之的是Git,Git已是当前的行业标准。Git的出现使得源代码版本控制工具升级为分布式工具,进而愈加受到青睐。
GitLab使用Git作为其源代码版本控制工具,在管理源码的同时可以提供便捷的Web服务,在成为代码托管服务平台的同时还可以通过安装配置各种插件完成代码评审、代码质量检查等工作。企业一般都使用GitLab来搭建自己的软件配置管理系统。
对于个人开发者、开源项目负责人、企业付费用户来说,推荐采用GitHub来管理源代码,世界顶级公司大多将顶级开源项目源码托管在GitHub上。
由开源中国搭建的码云同样采用 Git 作为其源代码管理工具,它在国内的访问速度优于GitHub,更加符合国人的使用习惯,也是非常优秀的源码管理平台。
4.应用定义
对于熟悉Java技术栈的工程师来说,图1-14中可能没有特别熟悉的产品。其实,Java中的Maven就属于该范畴,它由于功能稳定、周边生态多元化,因此成为业界的主流,也是Java用于编译打包和依赖管理的首选。Maven使用项目对象模型(Project Object Model)声明和管理项目的生命周期和应用依赖,并且可以自定义插件开发方式。大量的第三方插件使得Maven的应用场景被无限扩大,比如,代码静态检查、代码风格评审、测试覆盖率计算等都会用到Maven。
5.持续集成/持续交付
持续集成是指自动且持续不断地构建和测试软件项目并监控其结果是否正确。有了持续集成工具的支持,项目可以频繁地将代码集成到主干位置,进而使得错误能够快速被发现。它的目的是让产品在快速迭代的同时还能保持高质量。持续集成并不能消除 Bug,但是它能让 Bug被快速发现并且容易被改正。
持续交付是指频繁地将应用的新迭代版本交付给测试团队或最终用户以供评审,如果评审通过,则自动部署至生产环境。持续交付的中心思想是,无论应用如何更新,它都可以随时随地交付并自动化部署。
常见的持续集成/持续交付工具有Jenkins、Bamboo、CircleCI、Travis等。
上面讲到的软件配置管理、应用定义、持续集成/持续交付三个部分的内容如图1-17所示。
图1-17 软件配置管理、应用定义、持续集成/持续交付
应用定义与开发层的变动非常频繁,之前CNCF将开发语言、开发框架等都纳入这一层,但从0.9.6版本起,新的全景图却将这些内容全部删除了。原因大概是 CNCF 认为这些内容属于业务开发范畴,云原生无须关注。因此,无论选择Java还是PHP进行开发,也不管使用Spring还是Play搭建架构,都可能开发出契合云原生的应用,也可能开发出不契合云原生的应用。
编排与治理层
将应用框架的分布式治理与云原生所需的调度、编排等功能抽象出来,形成独立的一层,即编排与治理层,如图1-18所示。这种划分方式使业务开发工程师对云原生的了解更准确,云原生中间件工程师也无须过多关注业务。相对来说,CNCF 的产品更多地集中在这一层,这是云原生产品比较容易发力的部分。
图1-18 编排与治理层
这一层包括调度与编排、分布式协调与服务发现、服务管理。下面我们具体来看一下每个部分涉及的内容。
1.调度与编排
调度与编排提供了面向应用的容器集群部署和管理功能,目的是解耦CPU、GPU、内存、网络以及存储等基础设施与应用程序间的依赖,使开发人员将重点完全放在核心业务应用的研发上,而不必操心资源管理的问题,同时也使运维人员将重点放在硬件基础设施以及操作系统和网络本身的维护上,而不必操心资源利用率最大化的问题。调度与编排负责按照预定的策略将承载着应用的容器调度到拥有相应运行资源的执行服务器中。除了 CNCF 的核心成员Kubernetes,Mesos、Swarm也属于此范畴。
虽然调度与编排同属一部分,但它们负责的内容并不相同,调度是将分布式系统中的闲置资源合理分配给需要运行的进程并采用容器进行封装的过程,编排则是对系统中的容器进行健康检查、自动扩缩容、自动重启、滚动发布等的过程。
Mesos采用两级调度架构,因此能将调度和编排分离得比较彻底。由Mesos自身负责资源的调度,由运行在Mesos系统中的Marathon进行容器的编排。
调度与编排是云原生中最基础的需求,其中包含的内容如图1-19所示。
图1-19 调度与编排
2.分布式协调与服务发现
分布式场景由于网络的延迟以及不确定性,因此复杂度非常高。分布式系统一般都是通过一个可靠性非常高的注册中心对分布式服务进行协调与发现的。注册中心需要确保数据在分布式场景下的一致性,并且能够将分布式集群的状态变化及时通知给每个分布式节点。
在很多分布式系统中,我们都可以看到分布式协调和服务发现的身影,如Hadoop、Kafka、Dubbo中的ZooKeeper,Spring Cloud中的Eureka,Swarm中的Consul,其他常见的产品还有CNCF的项目CoreDNS,以及负责Kubernetes元数据存储的etcd。虽然etcd并未在Kubernetes中扮演分布式协调和服务发现的角色,但它可以作为注册中心提供这方面的能力。分布式协调与服务发现中包含的内容如图1-20所示。
图1-20 分布式协调与服务发现
3.服务管理
如同编排与调度是云原生的基础需求一样,服务管理是云原生的另一个重要基础,也是CNCF的重点关注点之一。服务管理的产品主要集中在三个方面:远程通信、反向代理、服务治理。
服务间的远程通信需要依托高性能和跨语言的通信框架,gRPC、Thrift、Avro 等跨语言框架集序列化和通信功能于一身,是分布式系统的重要基石。
关于反向代理的产品目前已经非常成熟,有涉及硬件的 F5,涉及软件的 HAProxy、Nginx等。它们负责承载入口流量,并将请求按照规则配置分发给相关的系统。
在服务治理方面,也已经有很多成熟的框架可以使用,如Netfix OSS负责网关的Zuul、负责客户端负载均衡的Ribbon、负责熔断的Hystrix等,它们也是Spring Cloud开发套件中的重要组成部分。由Twitter开源的用Scala语言开发的Finagle,以及国内非常流行的Dubbo,也属于此范畴。
在服务治理方面,业界新兴的Service Mesh概念正在渐渐被更多人认同,它的中文翻译为服务网格。Linkerd 和 Istio 是这方面的代表,由于技术实在太新,目前还处于快速发展时期。其中Linkerd和Istio的通信底层组件Envoy都已加入CNCF。Istio是与英文sail对应的希腊文单词,中文则是“航行”的意思,它与Kubernetes一脉相承,Kubernetes同为希腊文,是“舵手”的意思。除了Envoy,Linkerd也实现了Istio的接口并提供了底层通信能力。因此,Kubernetes掌控编排,Istio掌控服务治理,云原生的未来秩序已逐渐清晰。
值得一提的是,Kubernetes也内置了服务管理的功能,它们并未从Kubernetes的核心中抽离出来。Kubernetes使用Service和Ingress处理服务发现和负载均衡。未来的Service Mesh是否能够从Kubernetes中将服务治理完全接管过来,这将是一个值得关注的重点。
服务管理中包含的内容如图1-21所示。
图1-21 服务管理
运行时层
云原生的应用并不直接运行在物理服务器或传统的虚拟机之上,通常为了运行云原生应用,会在物理服务器或传统的虚拟机之上再构建一层,用于轻量和灵活地运行更多的实例。
云原生应用的运行时环境与传统应用的运行时环境有很大不同,但是它们的抽象层级是类似的,与传统应用的文件系统、进程以及网络环境相对应的是云原生存储、容器和云原生网络。运行时层包含的内容如图1-22所示,下面我们具体来看一下每个部分涉及的内容。
图1-22 运行时层
1.云原生存储
云原生存储是指适合云服务的分布式文件存储系统,它能将运行在云平台上的应用所需的或产生的数据放入一个可以不依赖本地磁盘且可以平滑扩容的高可靠文件系统,典型的产品有HDFS、Ceph、ClusterFS等。
云原生存储与专门用于存储业务数据的数据库并不是一个概念,目前很少有将数据库安装在云原生存储上的成熟方案。云原生存储目前最广泛的应用途径是存放日志、图片、文档等文件。云原生存储中包含的内容如图1-23所示。
图1-23 云原生存储
2.容器
容器是过去几年的热门话题。云原生应用选择容器这种更加轻量级的方案作为运行应用的载体,将容器作为最小单元对应用进行资源隔离以保证云平台的隔离性,并且通过容器打包环境和应用来提供更易复制的部署方式。不过需要注意的是,Kubernetes采用Pod作为应用的最小单元,一个Pod内可以包含多个容器。Docker是目前使用最广泛的容器,业界也有rkt等替代方案。关于容器,其中包含的内容如图1-24所示。
图1-24 容器
3.云原生网络
云原生网络可以解决为每个容器(或Pod)分配独立IP地址的问题。若采用和宿主机一样的IP地址,运行在同一个宿主机中的容器IP地址则会发生冲突。虽然可以通过改造应用程序来屏蔽对IP地址的依赖,但为了使应用透明化,令每个容器都拥有一个独立的IP地址才是上策。为了实现这一点,使用软件定义网络(SDN)是最佳方案。
云原生网络比较复杂,各种网络方案也层出不穷,因此产生了 CNI(Container Network Interface)这样的容器网络接口标准,它的主要实现有Flannel、Calico、OVS、weave等。云原生网络包含的如容见图1-25所示。
图1-25 云原生网络
供应保障层
供应保障层包括宿主机管理工具、基础设施自动化工具、容器仓库、镜像安全和密钥管理,如图1-26所示,这一层用于为宿主机和容器本身提供保障,它的关注点更加偏向于运维。下面我们来具体看一下其中的每个部分。
图1-26 供应保障层
1.宿主机管理工具
虽然云原生应用运行在一个相互隔离的由调度系统掌控的容器环境中,但它们最终仍然是运行在真正的物理服务器或虚拟机之上的。因此,我们需要通过管理工具将Docker、Kubernetes、Mesos、etcd、ZooKeeper、Flannel等众多运行时所需环境和工具自动安装和配置在应用运行的宿主机上。
原有的自动化运维工具在这里仍然有用武之地,它们无须再安装复杂的软件应用环境,只搭建容器运行环境和编排调度平台即可,这类工具包括Ansible、Puppet、Chef等。自动化运维工具的目标不再是安装应用及其相关环境,而是安装云原生应用所需的环境。
2.基础设施自动化工具
对于云原生的系统来说,调度编排平台即基础设施。基础设施自动化工具的用途是为调度编排平台安装插件。常见的相关工具有Docker的包管理工具Infrakit,以及简化Kubernetes应用的部署工具Helm。Helm可以看作Kubernetes的apt-get或yum,它通过Helm Charts帮助使用者安装和更新复杂的 Kubernetes 应用,支持版本管理和控制,这在很大程度上降低了Kubernetes应用部署和管理的复杂性。
宿主机管理工具与基础设施自动化工具有类似之处,都属于系统软件安装的范畴,它们包含的内容如图1-27所示。
图1-27 宿主机管理工具和基础设施自动化工具
3.容器仓库
容器仓库负责容器镜像内容的存储与分发。Docker Registry是Docker 的核心组件之一,客户端的 docker pull 以及docker push 命令都与它交互。Harbor是一个比较知名的项目,它的目标是帮助用户迅速搭建一个企业级的Docker Registry服务。
4.镜像安全
安全漏洞一直存在于程序世界。升级为容器之后,此类安全威胁仍然存在,因此我们需要一个能够发现容器中可能存在的安全问题的工具,如Clair、Twistlock等工具均可以检查容器中应用的漏洞。
供应保障层中还包含一个密钥管理的部分,是新加入CNCF的部分,笔者并不是很熟悉,因此不做详细介绍。以上三个部分的内容如图1-28所示。
图1-28 容器仓库、镜像安全、密钥管理
云设施层
云设施层主要包括用于提供物理服务器的云厂商,如图1-29所示。
按照公有云和私有云来分,常见的公有云有 AWS、Azure、阿里云、腾讯云、华为云等,常见的私有云有OpenStack、VMware等。严格来讲,这一层其实并不在CNCF的范畴内,它们仅用于提供服务所需的资源。
图1-29 云设施层
下面再介绍一下另一个维度下的云原生应用。观察与分析是每一层都需要的功能,平台则是将每一层功能组合起来的整体解决方案。
观察与分析
观察与分析包括对系统指标的监控,对链路调用的追踪,以及对分布式日志的收集。除了收集相关数据,还能够通过对这些数据进行解读,将系统当前状态以易懂的可视化图形形式展现出来,以便运维工程师掌控整个系统。
1.监控
监控部分包括以下内容:对物理服务器指标进行采集与报警的工具,如Nagios、Zabbix等;对容器指标进行采集的工具,如 CAdvisor 等;存储海量采集信息的时间序列数据库,如Prometheus、InfluxDB等。监控部分的具体内容如图1-30所示。
监控往往通过“采集、存储、分析、报警(展现)”的流程自动将系统状态通知给系统责任人,令其处理或定期分析。一般可以采用Grafana等专门用于监控分析的图形工具来展示数据。
2.日志
由于云原生应用是无状态的,因此不应该将日志写入本地磁盘,而是应该写入日志中心。用于采集标准输出并将日志输入其他流的工具主要有Fluentd、Flume、FileBeat、Logstash等,然后这些工具会将日志通过各种缓冲的管道进行处理,写入日志中心,日志中心的存储介质可以是Elasticsearch、HBase等。Elastic公司提供的由搜索引擎Elasticsearch、日志收集工具Logstash和图形界面Kibana所组成的日志中心套件(简称ELK)是一站式的开源解决方案,也有如Splunk这样的一体化商业日志解决方案。
图1-30 监控
3.追踪
云原生应用运行实例多,应用调用复杂,因此一旦系统响应变慢,便会难以定位问题。因此需要提供一套梳理和分析服务之间调用链以及服务内部调用栈的解决方案。OpenTracing是调用链的一个标准协议,遵循该协议的开源解决方案主要有 ZipKin、JAEGER,以及国产的优秀开源项目SkyWalking等。也有一些开源解决方案并未遵循此协议,如PinPoint、Open-Falcon、CAT等。
日志与追踪两部分中包含的内容如图1-31所示。
图1-31 日志和追踪
平台
基于云的整合平台是目前各个云公司都着力打造的产品,比较知名的有Mesosphere公司围绕Mesos打造的DC/OS,以及Rancher公司打造的可以整合Mesos和Kubernetes的Rancher平台等。
上面提到的技术只是整个云原生技术的冰山一角,无论是开源产品还是商业产品,优秀的解决方案非常多。云原生可以有不同的实现方式,但它的本质在于弹性地利用云平台的资源。无论是通过“应用程序 + 分布式中间件 + 自动化运维”的方式,还是通过“应用程序 + 调度编排平台 + 容器 + 服务治理”的方式,都可以实现健壮的系统。