云原生应用
云原生应用的基本概念
云原生应用,是指原生为在云平台上部署运行而设计开发的应用。公平的说,大多数传统的应用,不做任何改动,都是可以在云平台运行起来的,只要云平台支持这个传统应用所运行的计算机架构和操作系统。只不过这种运行模式,仅仅是把虚拟机当物理机一样使用,不能够真正利用起来云平台的能力。
CNCF 将云原生定义为:
云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式API。
这些技术能够构建容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统作出频繁和可预测的重大变更。
云原生计算基金会(CNCF)致力于培育和维护一个厂商中立的开源生态系统,来推广云原生技术。我们通过将最前沿的模式民主化,让这些创新为大众所用。
云原生应用与相关技术理念的关系
云原生应用与云平台的关系
云平台是用来部署、管理和运行 SaaS 云应用的。SaaS 是云计算的三种服务模型之一,即跟业务相关的应用即服务。云计算最根本的特性是提供按需分配资源和弹性计算的能力,而云原生应用的设计理念就是让部署到云平台的应用能够利用到云平台的能力,实现按需使用计算资源和弹性伸缩,成为一个合格的 SaaS 应用。
云原生应用与 12 要素的关系
12 要素是 PaaS 平台 Haroku 团队提出的应用设计理念,是有关 SaaS 应用设计理念的红宝书;可以说,12 要素应用就是云原生应用的同义词。
云原生应用与 Stateless 和 Share Nothing 架构的关系
为了实现水平伸缩的能力,云原生应用应该是 Stateless 和 Share Nothing 的。
云原生应用与微服务架构的关系
微服务架构是实现企业分布式系统的一种架构模式,即将一个复杂的单体应用按照业务的限定上下文,分解成多个独立部署的组件。这些独立部署的组件,就称为微服务。而在谈论云原生应用与微服务架构关系的时候,根据上下文不同可能是有两种不同的含义。一种含义是宏观的云原生应用,即将整个分布式系统看作一个应用,这种语境下,微服务架构是实现云原生应用的一种架构模式;另一种含义是微观的云原生应用,即每个微服务是一个应用,这种语境下,每个微服务要按照云原生应用的设计理念去设计,才能真正实现微服务架构所要达到的目的,即让分布式系统具备按需使用计算资源和弹性伸缩的能力,这里 “应用” 和 “服务” 变成了同义词。
云原生应用与宠物和牲畜的关系
云原生应用的设计理念是希望把应用当作牲畜来养,而不是当作宠物来养。部署一个云原生应用的集群,就好像圈养了一大群奶牛,目的主要是为了产奶,对待每头牛就像对待机器一样没有什么感情,死了一头就再养一头,而不会像对待宠物那样细心呵护。而传统应用,因为往往因为对运行环境依赖严重,运维人员需要细心照顾、维护,万一出现宕机,一般要在原来的服务器上修复问题再恢复运行;如果恢复不了,整个应用系统就瘫痪了,因此会令运维人员像 “宠物死了” 一样伤心。
云原生应用的设计理念——12 要素
一个应用对应一套代码多次部署
这一理念主要是强调应用应该清晰明确地区分什么是应用,什么是部署。一个应用对应的就是一个代码仓库,一个软件产品;一次部署对应的是一个运行起来的应用;因此应用与部署的关系是一对多。这种一对多的关系也体现了应用代码的可重用性,一套代码可以重用到多次的部署中去;不同部署之间的区分是配置,而代码是共享的。对应用架构来说,最基本的是要区分运行时行为和非运行时行为,一个应用的非运行时的代表就是一个代码仓库,它可能有多个运行时实例,每个实例就是一次部署。
明确地声明并隔离依赖的程序库
不管用什么语言开发应用,编程语言一定都有管理程序库的机制。这一理念强调所有依赖库一定要明确的声明出来,因为只有这样,在运行应用的时候,才能保证所有运行所需要的程序库都正确部署到了云环境中。
将配置存储到部署环境中
正像前面所说,一个应用的不同部署之间是共享一套代码的,不同之处是配置。代码是存储到代码仓库中的,那自然配置不应该是存到代码仓库中。每次部署都有自己独立的部署环境,每次部署所对应的配置要存到这次部署所对应的部署环境中去,因此配置的另一个同义词就是环境变量。这里的部署,不包括应用内部的配置,例如 Java 的 Properties 文件或者是 Servlet 的映射配置文件 web.xml 等,这些算作是代码而不是配置。这是一个容易令人混淆的地方,那到底什么算代码,什么算配置?判断的标准很简单,就是变化的频率。变动导致产品版本更新的,就是代码;每次部署都可能变更,而每次变动不导致产品版本更新的,就是配置,就是环境变量。
将后端支撑服务作为挂载资源来使用
这一理念强调应用使用后台支撑服务的方式。不同的服务之间的区别就只是资源的 URL 不同,也就是设定这个资源的相关环境变量不同。不管是本地资源还是远程资源,应用程序都可以正常使用,区别只是环境变量的值不同,而应用本身并不会因为环境变量不同而有所区别。最常用的后台支撑服务就是数据库、缓存、消息队列等服务。这一理念可以保证应用在任何环境都可以正常运行,不会因为后台支撑服务的变化而导致应用无法运行。
严格区分构建阶段和运行阶段
这一理念跟区分应用和部署类似,本质上也是要严格区分应用的非运行时行为和运行时行为。构建是将应用的代码仓库编译打包成可运行的软件的过程,是非运行时行为。因此说,这一理念另一方面也说明要防止在运行阶段改代码的行为,这样才能够保证运行中应用的稳定性。
将应用作为无状态的进程来运行
这一理念要求所有的用户数据都要通过后端支撑服务来存储,而应用本身是无状态的,因为只有这样,应用才能做到水平伸缩,从而利用云平台弹性伸缩的能力。
仅需要绑定一个端口就可以对外发布一个服务
这一理念强调应用本身对于发布服务的环境不应该有过多的要求,而应该是完全自包含的,也就是说不需要依赖云平台提供应用运行容器,而只需要云平台分配某个端口对外发布服务。这一理念保证应用可以使用云平台中任意分配的端口发布服务。
可以像 UNIX 进程一样水平扩展
在 UNIX 操作系统上,不同的进程彼此独立地运行着,共享这整个操作系统管理的计算机资源。云原生应用在云平台上的运行模式也是类似的,云平台就是分布式操作系统,不同的云原生应用彼此独立互补干扰的运行在一个云平台上,可以充分利用云平台的整体计算能力。
可以快速启动和优雅地关闭
快速启动是为了能充分利用云平台根据需要调度资源的能力,能够在需要的时候,以最小的延时扩展计算能力提供服务。优雅地关闭,一方面是为了释放资源,将不再使用的计算资源归还云平台;另一方面也是为了保证应用逻辑的完整性,将该完成的任务正确完成,未能完成的任务重新交回到系统由其它应用的运行实例来继续完成。要假设云原生应用的目标工作环境中随时有大量同样的应用实例在运行、启动和关闭,因此快速启动和优雅关闭对高性能和稳定的系统非常重要。
保持开发环境、预发布环境和生产环境尽量一致
保持环境一致,是为了提高开发单元测试、功能测试和集成测试的有效性,避免出现开发测试中正常而在生产环境中出现问题的情况。
将日志作为事件流来处理
云原生应用运行在复杂的分布式基础设施之上,如果日志不通过简单统一的模式来管理,将给系统排错或通过日志挖掘信息带来很大困难。同时,如果应用将日志输出到系统的文件中,也会给系统的存储空间造成压力,增加系统运维的复杂性。因此这一理念推荐应用将日志输出到标准输出,然后由云平台统一收集处理。
将应用管理任务当作一次性进程来运行
将应用的管理任务与应用的业务请求以相似的方式运行,以同样的方式进行调度、日志和监控,将有利于系统的稳定性和分析系统的整体行为。
云原生应用的挑战
处理分布式系统的网络通信问题
云原生应用必须要针对分布式系统中网络通信的复杂性进行设计。对于分布式系统,如果还像单一进程应用那样考虑问题,就会进入所谓的 “分布式系统的认识误区”,包括武断地认为:网络是可靠的;网络的延时为零;网络带宽是无限大的;网络是安全的;网络拓扑是不变的;系统中只有一个管理员和网络环境都是统一一致的。也许现在很少会有人幼稚到真的认为分布式环境中的交互处理和运行在单一进程中的函数调用是一样的;但开发的复杂度、功能上线的压力,经常会使开发人员把这些复杂问题暂时放在一边,不断积累起越来越多的 “技术负债”。
处理分布式系统的状态一致性问题
分布式系统的 CAP 理论认为,在分布式系统中,系统的一致性、可用性和分区容忍性,三者不可能同时兼顾。当然,实际在分布式系统中,由于网络通信固有的不稳定,分区容忍性是必须要存在的,因此在设计应用的时候,就要在一致性和可用性之间权衡选择。
最终一致性
很多情况下,在一致性和可用性之间,云原生应用比传统应用更加偏向可用性,而采用最终一致性代替传统用事务交易保证的 ACID 一致性。传统的 ACID 一致性编程模型与业务无关,开发人员对它经验丰富,而最终一致性的交互模式与业务相关,必须通过业务的合理性来校验阶段不一致的合理性,这使得最终一致性比 ACID 一致性复杂得多。
服务发现和负载均衡
云原生应用的运行实例随时可能关闭和启动,因此需要机制使得访问应用服务的客户端随时都能找到健康运行的实例,放弃对宕机实例的访问,这就是服务发现的问题。与服务发现同时存在的,是在多个健康实例中选择一个实例真正为某个客户请求提供服务的过程,这就是负载均衡。
任务分解和数据分片
大的任务要分解成很多小任务,分配到各个运行实例上去执行,然后再将执行结果汇总,这就是任务分解。数据分布到各个实例上做处理和存储,这个就是数据分片。这些都需要适应云计算环境的机制去支持。
主控角色选举
不管是任务分解还是数据分片,每个应用实例上负责的子任务和数据分片虽然是不同的,但如何分解、谁负责谁这种分配映射表一定是完全相同的;因此在这种情况下,需要负责计算分配映射表的主控角色;而因为云计算环境下没有实例是永远保证健康运行的,主控角色不可能是永远固定的;这就需要主控角色选举的机制,能够在主控角色空白或出现故障宕机的情况下,自选举出新的主控角色。
像设计模式解决面向对象设计中的复杂问题一样,面对云原生应用的复杂应用场景,我们也需要一些典型的设计模式能够可重用地解决一些特定场景的问题。这些我们将在本系列文章的后面结合应用案例予以介绍。