按语
Heroku 是云应用平台的先驱,从对外提供服务以来,他们已经有上百万应用的托管和运营经验。其创始人 Adam Wiggins 根据这些经验,提出了十二要素应用宣言 。The Twelve-Factor App 定义了一个优雅的互联网应用在设计过程中,需要遵循的一些基本原则。
不过, The Twelve-Factor 是在特定的时期,针对特定的平台实践所总结出来的。12 元素依然璀璨,很多原则依然具有普适性;但是在应用全面迁移到云端的今天,Cloud Native 时代的应用开发,需要有更多与时俱进的实践新原则。The Twelve-Factor 的哪些原则依然是适用的?哪些在实践需要与时俱进?这些都是众多Cloud Native 的实践者需要探求的答案。
新原则1:微服务应该以无状态的方式运行
The Twelve-Factor 指出一个应用在运行时,应该以一个或者多个无状态的进程来运行。进程之间需要做到无状态,并且无共享。任何需要持久化的数据,都应该写入到后端服务中,譬如数据库。这一原则,目的是避免进程和进程之间的耦合。让每一个进程,能够独立于其他实体单独构建和运行。
在很多在线系统中,会使用 session(会话)来传递当前的状态。当被调用方接收到 session 后,会将它保存下来,后续的请求,都会被路由到这个服务中。这时请求和请求之间,会有严格的调用顺序。Cloud Native 的实践原则是需要避免这种情况。
在 Cloud Native 理念中,每一个功能实体实际上是以一个微服务的形式运行的。微服务是一个单独的自治系统,它可以不依赖其他服务而单独存在。所有的微服务,都应该是以一种无状态的接口对外提供服务(譬如 Restful API)。微服务自身不应该保存任何的状态。如果微服务需要持续地保存数据,应该采用的做法是:调用服务化的存储,将数据保存在另一个存储服务里面。微服务之所以小块头能发挥大能量,正是源于无状态的设计从而保证可以同时由多个实例提供服务,让服务能力能够自由的伸缩。
当然,在实际的实践过程中,特别是针对大的系统,很难做到完全无状态的拆分。对一个完整的微服务架构,会分为事务性服务和存储型服务。通常说来,事务性服务需要做到无状态设计;所有的持久化数据,可以通过存储型服务来进行存储和读取。存储型服务尽量使用云端提供的集中式的服务化存储能力,譬如云端的对象存储。
新原则 2:应通过配置来唯一定义一个系统
The Twelve-Factor 的第五条原则提到了应用需要严格区分构建、发布、运行这三个步骤:譬如直接修改处于运行状态的代码是非常不可取的做法,因为这些修改很难再同步回构建步骤。The Twelve-Factor 的第五原则,很好的规范了一个应用从开发到运营的标准化的操作流程。但是第五原则,只是保证了应用源码的唯一性。很多时候,一个系统的故障,并不仅仅是由源码导致的,很大一部分故障,和配置以及环境的变化存在关系。所以 The Twelve-Factor 的第五原则在 Cloud Native 的理念下,需要与时俱进。
Cloud Native 在实践时,是通过 Docker+ 微服务的方式去实现的。Docker 实现了 Build、Ship、Run 的统一。任何系统,都是可以通过一些配置,唯一定义出来的。当这个系统定义出来以后,应该是能够重构出来,不随着时间、运行环境或依赖条件变化而变化。同时,这个系统不管处于开发阶段、测试阶段还是运营阶段,其状态都是唯一的。
譬如,在实践过程中,可以通过 Docker Compose 的 YAML 文件唯一的定义一个容器间互联的系统。这个系统的配置是唯一确定的,可以在任何的容器环境下去执行。会有一个唯一的仓库 (Docker Registry) 来存储镜像。所有的镜像,都是通过 Dockerfile 唯一定义的。当触发了源码的变更以后,这个系统的各个组件可以自动的去构建,然后更新到“仓库”,在通过 Docker Compose 组装起来。只要遵循了这套标准化的流程,系统不会区分开发环境,测试环境,运营环境。所有的东西,就只有一个环境;这个环境,是唯一的、可定义的。
在实践这条原则时,需要遵循以下几条规范:
• 需要有唯一的源码库,并保证任何的变更都是在源码托管库中进行
• 通过 Dockerfile 来构建镜像而非通过运行中的容器来打包镜像
• 通过配置来定义容器之间的连接关系而非手动的部署
新原则 3:应该使用服务化存储
The Twelve-Factor 有一条原则是“把后端服务 (backing services) 当作附加资源”,后端服务是指程序运行所需要的通过网络调用的各种服务,如数据库(MySQL,CouchDB)、消息 / 队列系统(RabbitMQ,Beanstalkd)、SMTP 邮件发送服务(Postfix)以及缓存系统(Memcached)。这些都是作为“资源”来对待,目的是为了保证模块之间的松耦合。
作为一个系统中最重要的角色,存储模块当然必不可少。存储有很多种类:块存储、对象存储、数据库存储、缓存。传统上,我们接触比较多的是块存储。在 Cloud Native 的理念下,一个系统中,所有的组成实体都可以服务化。有事务型的服务,也有存储型服务。微服务之间是一种松散耦合的关系。服务和服务之间,是没有任何耦合关系的,只有调用关系。当一个服务,需要存储数据怎么办?一种方案是把数据存在在服务内部,这样势必导致了服务存在状态,从而限制了服务的横向扩展的能力。前面一节,我们提到了一个重要原则是,事务型服务尽量保证是无状态的。所以当一个事务型服务需要存储数据时,应该是将数据存储到另外一个存储型服务中,通过存储型服务提供的标准接口和协议读取或者写入数据。在云上我们通常使用的对象存储,或者云端的数据库,就是这种服务化的存储方式。
使用服务化存储,可以带来三个方面的好处:
• 将存储和逻辑处理分离,让系统能够自动化的重构出来
• 统一存储,不需要单独考虑服务内部存储的容错、备份等机制
• 做到事务型服务无状态,便于横向扩展
总结
The Twelve-Factor APP 作为网络应用开发的最佳实践原则,的确具有强大的生命力。每一条原则都是应用开发的珠玑。但是技术总是不停的发展,在云的时代,所有的应用都会迁移到云端,完全基于云的架构设计和开发模式需要一套全新的理念去承载。这个时候,Cloud Native 的思想应运而生。在 Cloud Native 时代,The Twelve-Factor 的核心思想依然璀璨,但是应该随着这种新的理念去延伸、发展。文章提到的三条新原则,也正是 The Twelve-Factor 的延伸。未来,随着 Cloud Native 思想在开发中使用越来越广泛,一定会延伸出更多的实践的原则。
资料附:Cloud Native 时代 12 新要素
- 从一个代码库部署到多个环境——一个代码库,包括生产环境软件包,确保了单一的信任源,从而保证了更少的配置错误和更强的容错和复原能力。
- 依赖管理是声明式的——云平台根据这些声明管理依赖,确保云应用所需的库和服务。
- 配置信息保存在环境中——环境变量是一种清楚、容易理解和标准化的配置方法,特别适合用不同编程语言编写的无状态应用的使用。
- 将后台服务视为附加的资源——将每一种资源都视为一种远程的资源,应用因此具有容错和复原能力,因为它一方面要求编码时就要考虑资源不可用的情况,另外一方面也增强微服务方法的好处。
- 区分构建、发布和运行阶段——Cloud Native 应用的构建流程把大部分发布配置挪到开发阶段,包括实际的代码构建和运行应用所需的生产环境配置。
- 作为无状态进程运行——尽量保持应用栈每一层的轻量级,保证 Cloud Native 基础设施的速度和效率。
- 通过端口绑定对外暴露服务——Cloud Native 应用的服务接口优先选择 HTTP API 作为通用的集成框架。
- 通过添加无状态进程实现横向扩展——强调无状态、无共享的设计,这意味着依赖底层平台就能实现横向扩展,不需要技术难度高的多线程编码。
- 快速地启动,优雅地关停——假设任何进程随时都能启动和关停。
- 开发、预发布和生产环境运行同样的应用和依赖配置——由于强调自动化和在每个阶段使用同一个云平台,如果每个人用同样的服务器配置,那么“应用在我这里是可以的”就意味着在其他人或者环境那里也是可以的。
- 日志输出到标准输出,方便日志聚合和事件响应——当日志是由云平台而不是应用包含的库处理时,日志处理机制必须保持简单。
- 临时任务作为短时进程运行——在 Cloud Native 中,管理任务也是一个进程,而不是特别的工具;同样重要的是,管理任务的进程不应使用秘密的 API 或者内部机制。
作者简介
刘永峰,腾讯云高级产品经理,布道师,十余年的产品及研发管理经验。国内最早一批从事可信计算研究的探索者。曾就职于中兴通讯,负责流媒体 Server 后台架构设计,Linux 内核相关的研究。2011 年起加入腾讯,一直从事公有云的产品设计,云计算市场趋势分析和研究,云计算技术的推广与普及。在技术和产品领域均具有丰富的行业经验。目前主要关注 Docker,微服务,Cloud Native, 企业云化趋势等相关领域。
感谢木环对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论