十二要素应用: 云原生应用最佳实践

文摘   科技   2024-07-13 08:42   上海  

本文介绍了开发部署云原生应用的一套最佳实践,通过这套最佳实践,可以最大限度利用云原生的能力,创建灵活、健壮、易管理的现代云原生应用程序。原文: The Twelve-Factor App: Best Practices for Cloud-Native Applications[1]

导言

软件如今通常以服务的形式交付,被称为网络应用(web apps)软件即服务(software-as-a-service),而十二要素应用(The twelve-factor app)就是构建软件即服务应用的一种方法。

十二要素应用是Heroku联合创始人亚当·威金斯(Adam Wiggins)在2012年的一篇题为"十二要素应用:云原生应用的最佳实践(The Twelve-Factor App: Best Practices for Cloud-Native Applications)"的开创性论文中提出的,该方法源自Heroku部署及运营基于云的应用程序所获得的经验和见解。

十二要素应用的主要目标是提供一种标准化的方法,用于开发可在云原生环境中运行的应用程序,同时保持灵活性、健壮性和易管理性。

十二要素应用方法论为我们提供了一套最佳实践,用于构建符合如下要求的现代云原生应用程序:

代码库(Codebase)

  • 在版本控制系统中跟踪代码库。
  • 多个部署共享同一个代码库。

依赖(Dependencies)

  • 显式声明并隔离依赖。
  • 避免依赖全局范围的包。

配置(Config)

  • 将配置存储在环境中。
  • 将配置与代码分离。

后端服务(Backing Services)

  • 将后端服务(数据库、缓存)视为附加资源。
  • 通过环境变量访问。

构建、发布、运行(Build, Release, Run)

  • 严格隔离构建、发布和运行阶段。
  • 保持构建和发布的不可变性。

进程(Processes)

  • 以无状态进程的方式执行应用程序。
  • 在数据库或外部服务中存储共享状态。

端口绑定(Port Binding)

  • 通过端口绑定导出服务。
  • 保持应用程序的独立。

并发性(Concurrency)

  • 通过进程模型进行扩展。
  • 启用水平缩放。

可处置性(Disposability)

  • 通过快速启动和优雅关机最大限度提高鲁棒性。
  • 目标是快速启动并优雅处理流程故障。

研发/生产环境一致(Dev/Prod Parity)

  • 尽可能保持开发、预发布和生产环境一致。
  • 最小化差异以减少bug的产生。

日志(Logs)

  • 将日志作为事件流进行处理。
  • 将日志流传输到集中位置进行分析。

管理进程(Admin Processes)

  • 将管理任务作为一次性进程运行。
  • 避免将管理进程与主应用程序代码混在一起。

为什么选择十二要素应用程序?

要知道我们为什么需要十二要素应用,首先要了解创建应用的传统流程。

以前,你必须满怀希望、克服重重困难才能将想法传播到全世界,你需要花费数天、数周甚至数月的时间来订购并收到服务器来托管应用程序。最终,一旦将应用托管到服务器上,今后都将依赖该服务器。

你编写的代码只能在该服务器上运行,不能在其他服务器上运行,如果需要扩容,必须扩展该服务器上的资源。

如果用户正在做某件事情,而服务器崩溃了,用户在此之前所做的一切都会丢失,从而不得不重新做一遍。

但是,如今我们生活在高增长的世界里,快速创业公司的用户数量在短短几个月内可以从零增长到数百万

如果你有一个想法,只需要花几天时间开发代码,配置和托管可以在几小时内完成。如今大多数云平台都可以在几分钟内提供服务器和其他资源,并且云平台预计在 99.99% 的时间内都能正常工作,这意味着不需要停机并关闭应用程序

对于这种情况,应用程序需要从底层基础设施中解脱出来。通过这种方式,你可以将应用托管在任何想要的地方,无论是本地、GCP、AWS还是AZURE,这就是所谓的可移植性。

可移植性意味着无需更改应用代码,就能在不同环境中运行相同的应用程序。

这意味着你的应用首先需要具有可移植性。

说到扩容,在过去,你不得不关闭应用程序来增加服务器资源,这就是所谓的垂直扩容

如今,你需要提供和运行更多服务器,并运行更多应用程序实例,这就是所谓的水平扩容

总结一下

现代应用程序需要具备可移植性,因此不能与底层基础设施紧密耦合。

开发、测试和生产环境之间进行部署时,应尽量减少分歧,以实现持续部署。

而且必须能够通过同时启动多个实例来轻松扩容,并适配现代云平台。

为了实现这一目标,在开发应用程序时必须牢记某些原则。

大约十年前,Heroku 的工程技术人员提出了构建现代应用程序时需要考虑的十二个因素,被称为"十二因素应用"。

让我们深入探讨一下"十二要素应用"的每一项原则。

1.代码库

在代码版本控制系统中跟踪代码库,进行多次部署。

第一条规则是为应用程序建立单一的代码库。

假设应用程序支持多种服务,如订单或付款,不要将所有服务都放在一个代码库中,每个服务应该有单独的代码库来实现分布式系统。

每个代码库都必须部署在不同的环境中,如研发预发布生产环境,维护单个和多个代码库的工具就是 git

Git 是一种底层技术,它能对代码库进行版本控制,并在团队成员之间提供良好的协作。

而 GitHub 是一个网络平台,为代码版本控制提供托管服务。

2.依赖

明确声明并隔离依赖关系。

大多数编程语言都提供包管理系统,用于安装或分发支持的库,例如 Python 的 pip 或 Perl 的 CPAN。

库安装在包管理系统中,可以全局安装,也可以安装在包含应用程序的目录中(称为绑定)。

全局安装意味着该计算机上的所有项目和应用程序都可以使用。

而安装范围仅限于应用目录,则意味着在项目自身的目录结构中专门为项目安装库。

十二要素应用从不依赖隐含的全局系统软件包。

因此,通过依赖声明清单文件(如 Python 中的 requirments.txt)来完整、准确的声明所有依赖关系,该文件可由 pip 包管理系统安装。此外,在执行过程中使用依赖隔离工具,以确保没有隐式依赖关系从周围系统"泄漏"进来。例如,Python 有虚拟环境(venv) 的概念。

因此,有了依赖关系声明清单文件和依赖关系隔离工具(如 Python 中的 venv),就能定义需求和隔离依赖关系。

然而,如果应用所依赖的工具与编程语言无关,怎么办?

比如,curl命令和其他系统依赖工具。请记住,curl必须安装在主机操作系统上,才能在像 Python 这样的虚拟隔离环境中与应用程序一起工作。venv 本身不会提供 curl 二进制文件,而是依赖于主机系统的 curl。

这就需要一种能在所有不同编程语言中使用的更通用的方法,如 Docker 容器

Docker 允许我们在一个与主机系统隔离的独立环境中运行应用程序,它是一种更高效、更可靠的管理依赖关系的方式。

通过使用 docker,我们可以完全遵循第二条规则 "明确声明并隔离依赖关系"。

3.配置

在环境中存储配置

配置是指应用程序在开发、测试、预发和生产等不同环境中运行所需的配置设置。

配置设置,如数据库连接字符串、应用程序接口密钥和其他参数,这些参数会因部署环境而异。

因此,十二要素应用并不将配置设置存储在传统的配置文件或代码中,而是将其配置存储在环境变量(env vars)中

环境变量是一种在运行时向应用程序传递信息而无需更改代码本身的方法。环境变量易于在不同部署(开发、测试、预发和生产)之间更改,而无需更改任何代码。

因此,在十二要素应用中,配置应与代码严格分离,并存储在环境变量中。这意味着只需构建一次应用程序,就可以在多个环境中进行测试、部署和运行。

十二要素应用没有说明如何存储密钥和 API token 等,为此我们建议使用Secrets管理工具来管理这些加密信息,如 HashiCorp Vault、AWS Secrets Manager 或任何其他类似工具。

4.后端服务

将后端服务视为附加资源

例如数据库(Mysql)、Redis、用于外发电子邮件的 SMTP 服务(如 POSTFIX)等。

后端服务可以是本地管理的服务,也可以是任何第三方服务,如 AWS、S3 或 AWS RDS,两者都是附加资源,可通过 URL 或存储在配置中的其他定位器/凭据访问。

十二要素应用的部署应能将本地 MYSQL 数据库与由第三方(如 AWS RDS)管理的数据库进行互换,而无需对应用程序代码进行任何更改。

5.构建、发布、运行

将构建和运行阶段严格分离。

十二要素应用将构建、发布和运行阶段严格分开。

将代码库转化为特定环境中运行的应用程序需要经历以下几个阶段。

1.构建阶段

构建阶段使用代码及其依赖关系来生成构建结果。

构建阶段是软件开发流程的初始阶段,在这一阶段,应用源代码将被编译、测试并转化为可部署的工件,这一阶段通常包括编译代码、运行单元测试以及将应用打包为容器镜像或其他可部署格式等操作。

2.发布阶段

发布阶段基于构建工件及其配置来生成发布版本。

一旦完成构建,就进入发布阶段,在这一阶段,需要定义运行多少个应用程序副本,使用哪些容器和镜像,如何将应用程序暴露给外部,以及应用正常运行所需的其他配置。

3.运行阶段

运行阶段运行一个或多个版本实例。

运行阶段包括将应用程序作为实时工作负载运行、处理多个并发请求并管理其生命周期,这些流程可实现明确的分工,并应使用 CI/CD 流水线实现自动化。

6.进程

将应用程序作为一个或多个无状态进程执行。

十二要素应用程序进程是无状态的,不共享任何东西,任何需要持久化的数据都必须存储在有状态的后端服务(通常是数据库)中,这种方法提供了重要的运维优势,使其更容易扩展并从故障中恢复。

无状态进程意味着应用程序的每个实例都应是独立的,不存储任何特定于单个用户或请求的状态或数据。

该要素是其他十二要素准则(如并发性和可处置性)的重要前提。

7.端口绑定

通过端口绑定暴露服务。

十二要素应用是自成一体的独立进程,不受父进程控制,通过监听端口暴露自己的服务,这也意味着它们可以充当其他应用程序的后端服务。

端口绑定的概念对于面向网络的服务或任何需要通过互联网访问的基于网络的服务至关重要,通过将应用程序绑定到特定端口,应用程序就可以通过该端口的网络地址进行访问,从而允许客户端(如网络浏览器或其他应用)与应用程序进行通信。

8.并发性

通过进程模型扩大规模。

并发性是指应用程序如何扩展和处理多个并发请求或任务。十二要素应用程序提倡无状态进程,这意味着应用程序的每个实例都应是独立的,不存储任何特定于单个用户或请求的状态或数据,这样就能轻松实现水平扩容。

十二要素应用主张基于负载均衡器来处理不断增加的并发量,将传入的请求分配到多个应用实例上。十二要素应用确保应用程序的设计能够处理多个并发请求,并能在不影响响应速度或资源利用率的情况下轻松扩展。

这种方法非常适合需要同时处理大量用户和请求的现代网络应用程序和服务。

9.可处置性

通过快速启动和优雅关机最大限度提高稳健性。

十二要素应用进程是一次性的,这意味着可以快速、无缝的启动和停止,从而可以在不中断系统的情况下轻松扩展和快速部署更新。

进程应力求缩短启动时间,最好只需几秒钟,以便灵活、轻松的在机器之间移动。

当进程需要关闭时,它们会优雅的完成正在进行的任务,并在收到信号后退出。对于网络进程来说,这意味着拒绝新请求,允许完成当前业务,然后关闭。

10.研发/生产环境一致

尽可能保持开发、预发和生产环境的相似性。

传统上,开发环境和生产环境之间存在巨大差距,这些差距主要体现在三个方面:

  • 时间差:开发人员可能需要几天、几周甚至几个月的时间才能将代码投入生产。
  • 人员差距:开发人员编写代码,运维工程师部署代码。
  • 工具差距:开发人员可能使用 Nginx、SQLite 和 OS X 等技术栈,而生产部署则使用 Apache、MySQL 和 Linux。

十二要素应用旨在通过保持开发与生产之间的较小差距来实现持续部署[2]

  • 缩短时间差:开发人员可能会在编写代码数小时甚至几分钟后就将其部署,频繁部署可最大限度缩短代码在不同环境下的时间差。
  • 缩小人员差距:编写代码的开发人员密切参与代码部署,并观察其在生产中的行为,让开发人员负责部署和监控自己的应用程序,而不是由单独的运维团队负责。
  • 缩小工具差距:尽可能保持开发和生产环境的相似性。

11.日志

将日志视为事件流。

十二要素应用非常清楚,写入日志的内容应该是可理解的,并像流一样处理,而不是将日志写入存储在计算机磁盘上的文件中。

应用不必担心日志的去向或存储方式,相反,应用程序的每个部分都会将日志或信息写入名为 stdout 的特殊输出。

在开发过程中,可以直接在屏幕或终端上看到这些信息。在预发或生产环境中,执行环境会收集所有消息或日志,并使用 LogplexFluentd 等工具将其发送到最终目的地。这些目的地可以是文件或日志,也可以是 HadoopELK 等分析系统。

12.管理程序

将管理任务作为一次性进程运行。

十二因素应用方法的管理进程原则建议,管理任务应与应用程序流程分开,并应在相同的设置下运行,而且应具有自动扩展性和可重复性。

因此这些管理任务,如

运行数据库迁移。

运行控制台或在某些情况下使用单独的命令。

运行提交到应用程序版本库的一次性脚本。

一次性管理程序应在完全相同的环境中运行,并与发布版本相对应。管理代码必须与应用程序代码一起发布,以避免同步问题。

结论

十二要素应用方法论为构建现代云原生应用程序提供了一套全面的最佳实践。在当今软件普遍以服务形式交付的时代,遵守这些原则对于实现可扩展性、可移植性和可维护性至关重要。

通过遵循十二要素应用,开发人员可以克服传统应用程序开发的挑战,因为传统应用程序开发的部署非常繁琐,而且与特定服务器绑定。由于注重代码库管理、明确的依赖关系声明和基于环境的配置,应用程序变得更加灵活,可以轻松适应不同的部署环境。

构建、发布和运行阶段的分离确保了开发和生产之间的明确区分,使开发人员能够快速迭代和部署变更,将干扰降到最低。无状态进程可实现水平扩容,而端口绑定可确保应用能够轻松将其服务暴露给外部访问。

十二要素应用强调可处置性,促进快速启动和优雅关闭,从而增强弹性和可扩展性。通过将日志视为事件流,将管理任务作为一次性进程运行,开发人员可以更好的洞察应用程序行为,并维护更简洁的代码库。

通过遵循"十二要素"指南,开发人员可以创建现代云原生软件即服务(cloud-native software-as-a-service)应用程序,这些应用具有可移植性、弹性,并专为云时代设计。


你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

参考资料
[1]

The Twelve-Factor App: Best Practices for Cloud-Native Applications: https://medium.com/@tech_18484/introduction-701b7a8f4730

[2]

持续部署: http://avc.com/2011/02/continuous-deployment


DeepNoMind
你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。