简介
如今,软件通常会作为一种服务来交付,它们被称为网络应用程序,或软件即服务(SaaS)。12-Factor 为构建如下的 SaaS 应用提供了方法论:
使用标准化流程自动配置,从而使新的开发者花费最少的学习成本加入这个项目。
和操作系统之间尽可能的划清界限,在各个系统中提供最大的可移植性。
适合部署在现代的云计算平台,从而在服务器和系统管理方面节省资源。
将开发环境和生产环境的差异降至最低,并使用持续交付实施敏捷开发。
可以在 工具 、架构和开发流程不发生明显变化的前提下实现扩展。
这套理论适用于任意语言和后端服务(数据库、消息队列、缓存等)开发的应用程序。
背景
本文的贡献者参与过数以百计的应用程序的开发和部署,并通过 Heroku 平台间接见证了数十万应用程序的开发,运作以及扩展的过程。
本文综合了我们关于 SaaS 应用几乎所有的经验和智慧,是开发此类应用的理想实践标准,并特别关注于应用程序如何保持良性成长,开发者之间如何进行有效的代码协作,以及如何 避免软件污染 。
我们的初衷是分享在现代软件开发过程中发现的一些系统性问题,并加深对这些问题的认识。我们提供了讨论这些问题时所需的共享词汇,同时使用相关术语给出一套针对这些问题的广义解决方案。本文格式的灵感来自于 Martin Fowler 的书籍: Patterns of Enterprise Application Architecture , Refactoring 。
读者应该是哪些人?
任何 SaaS 应用的开发人员。部署和管理此类应用的运维工程师。
I. 基准代码
-- 一份基准代码(Codebase),多份部署(deploy)
12-Factor应用(译者注:应该是说一个使用本文概念来设计的应用,下同)通常会使用版本控制系统加以管理,如Git, Mercurial, Subversion。一份用来跟踪代码所有修订版本的数据库被称作 代码库(code repository, code repo, repo)。
在类似 SVN 这样的集中式版本控制系统中,基准代码 就是指控制系统中的这一份代码库;而在 Git 那样的分布式版本控制系统中,基准代码 则是指最上游的那份代码库。
基准代码和应用之间总是保持一一对应的关系:
一旦有多个基准代码,就不能称为一个应用,而是一个分布式系统。分布式系统中的每一个组件都是一个应用,每一个应用可以分别使用 12-Factor 进行开发。
多个应用共享一份基准代码是有悖于 12-Factor 原则的。解决方案是将共享的代码拆分为独立的类库,然后使用 依赖管理 策略去加载它们。
尽管每个应用只对应一份基准代码,但可以同时存在多份部署。每份 部署 相当于运行了一个应用的实例。通常会有一个生产环境,一个或多个预发布环境。此外,每个开发人员都会在自己本地环境运行一个应用实例,这些都相当于一份部署。
所有部署的基准代码相同,但每份部署可以使用其不同的版本。比如,开发人员可能有一些提交还没有同步至预发布环境;预发布环境也有一些提交没有同步至生产环境。但它们都共享一份基准代码,我们就认为它们只是相同应用的不同部署而已。
II. 依赖
-- 显式声明依赖关系( dependency )
大多数编程语言都会提供一个打包系统,用来为各个类库提供打包服务,就像 Perl 的 CPAN 或是 Ruby 的 Rubygems 。通过打包系统安装的类库可以是系统级的(称之为 “site packages”),或仅供某个应用程序使用,部署在相应的目录中(称之为 “vendoring” 或 “bunding”)。
12-Factor规则下的应用程序不会隐式依赖系统级的类库。 它一定通过 依赖清单 ,确切地声明所有依赖项。此外,在运行过程中通过 依赖隔离 工具来确保程序不会调用系统中存在但清单中未声明的依赖项。这一做法会统一应用到生产和开发环境。
例如, Ruby 的 Bundler 使用 Gemfile 作为依赖项声明清单,使用 bundle exec 来进行依赖隔离。Python 中则可分别使用两种工具 – Pip 用作依赖声明, Virtualenv 用作依赖隔离。甚至 C 语言也有类似工具, Autoconf 用作依赖声明,静态链接库用作依赖隔离。无论用什么工具,依赖声明和依赖隔离必须一起使用,否则无法满足 12-Factor 规范。
显式声明依赖的优点之一是为新进开发者简化了环境配置流程。新进开发者可以检出应用程序的基准代码,安装编程语言环境和它对应的依赖管理工具,只需通过一个 构建命令 来安装所有的依赖项,即可开始工作。例如,Ruby/Bundler 下使用 bundle install,而 Clojure/Leiningen 则是 lein deps。
12-Factor 应用同样不会隐式依赖某些系统工具,如 ImageMagick 或是curl。即使这些工具存在于几乎所有系统,但终究无法保证所有未来的系统都能支持应用顺利运行,或是能够和应用兼容。如果应用必须使用到某些系统工具,那么这些工具应该被包含在应用之中。
III. 配置
-- 在环境中存储配置
通常,应用的 配置 在不同 部署 (预发布、生产环境、开发环境等等)间会有很大差异。这其中包括:
数据库,Memcached,以及其他 后端服务 的配置
第三方服务的证书,如 Amazon S3、Twitter 等
每份部署特有的配置,如域名等
有些应用在代码中使用常量保存配置,这与 12-Factor 所要求的代码和配置严格分离显然大相径庭。配置文件在各部署间存在大幅差异,代码却完全一致。
判断一个应用是否正确地将配置排除在代码之外,一个简单的方法是看该应用的基准代码是否可以立刻开源,而不用担心会暴露任何敏感的信息。
需要指出的是,这里定义的“配置”并不包括应用的内部配置,比如 Rails 的 config/routes.rb,或是使用 Spring 时 代码模块间的依赖注入关系 。这类配置在不同部署间不存在差异,所以应该写入代码。
另外一个解决方法是使用配置文件,但不把它们纳入版本控制系统,就像 Rails 的 config/database.yml 。这相对于在代码中使用常量已经是长足进步,但仍然有缺点:总是会不小心将配置文件签入了代码库;配置文件的可能会分散在不同的目录,并有着不同的格式,这让找出一个地方来统一管理所有配置变的不太现实。更糟的是,这些格式通常是语言或框架特定的。
12-Factor推荐将应用的配置存储于 环境变量 中( env vars, env )。环境变量可以非常方便地在不同的部署间做修改,却不动一行代码;与配置文件不同,不小心把它们签入代码库的概率微乎其微;与一些传统的解决配置问题的机制(比如 Java 的属性配置文件)相比,环境变量与语言和系统无关。
配置管理的另一个方面是分组。有时应用会将配置按照特定部署进行分组(或叫做“环境”),例如Rails中的 development,test, 和 production 环境。这种方法无法轻易扩展:更多部署意味着更多新的环境,例如 staging 或 qa 。 随着项目的不断深入,开发人员可能还会添加他们自己的环境,比如 joes-staging ,这将导致各种配置组合的激增,从而给管理部署增加了很多不确定因素。
12-Factor 应用中,环境变量的粒度要足够小,且相对独立。它们永远也不会组合成一个所谓的“环境”,而是独立存在于每个部署之中。当应用程序不断扩展,需要更多种类的部署时,这种配置管理方式能够做到平滑过渡。
IV. 后端服务
-- 把后端服务(backing services)当作附加资源
后端服务是指程序运行所需要的通过网络调用的各种服务,如数据库(MySQL,CouchDB),消息/队列系统(RabbitMQ,Beanstalkd),SMTP 邮件发送服务(Postfix),以及缓存系统(Memcached)。
类似数据库的后端服务,通常由部署应用程序的系统管理员一起管理。除了本地服务之外,应用程序有可能使用了第三方发布和管理的服务。示例包括 SMTP(例如 Postmark),数据收集服务(例如 New Relic 或 Loggly),数据存储服务(如 Amazon S3),以及使用 API 访问的服务(例如 Twitter, Google Maps, Last.fm)。
12-Factor 应用不会区别对待本地或第三方服务。 对应用程序而言,两种都是附加资源,通过一个 url 或是其他存储在 配置 中的服务定位/服务证书来获取数据。12-Factor 应用的任意 部署 ,都应该可以在不进行任何代码改动的情况下,将本地 MySQL 数据库换成第三方服务(例如 Amazon RDS)。类似的,本地 SMTP 服务应该也可以和第三方 SMTP 服务(例如 Postmark )互换。上述 2 个例子中,仅需修改配置中的资源地址。
每个不同的后端服务是一份 资源 。例如,一个 MySQL 数据库是一个资源,两个 MySQL 数据库(用来数据分区)就被当作是 2 个不同的资源。12-Factor 应用将这些数据库都视作 附加资源 ,这些资源和它们附属的部署保持松耦合。
部署可以按需加载或卸载资源。例如,如果应用的数据库服务由于硬件问题出现异常,管理员可以从最近的备份中恢复一个数据库,卸载当前的数据库,然后加载新的数据库 – 整个过程都不需要修改代码。
V. 构建,发布,运行
-- 严格分离构建和运行
基准代码 转化为一份部署(非开发环境)需要以下三个阶段:
构建阶段 是指将代码仓库转化为可执行包的过程。构建时会使用指定版本的代码,获取和打包 依赖项,编译成二进制文件和资源文件。
发布阶段 会将构建的结果和当前部署所需 配置 相结合,并能够立刻在运行环境中投入使用。
运行阶段 (或者说“运行时”)是指针对选定的发布版本,在执行环境中启动一系列应用程序 进程。
12-factor 应用严格区分构建,发布,运行这三个步骤。 举例来说,直接修改处于运行状态的代码是非常不可取的做法,因为这些修改很难再同步回构建步骤。
部署工具通常都提供了发布管理工具,最引人注目的功能是退回至较旧的发布版本。比如, Capistrano 将所有发布版本都存储在一个叫 releases 的子目录中,当前的在线版本只需映射至对应的目录即可。该工具的 rollback 命令可以很容易地实现回退版本的功能。
每一个发布版本必须对应一个唯一的发布 ID,例如可以使用发布时的时间戳(2011-04-06-20:32:17),亦或是一个增长的数字(v100)。发布的版本就像一本只能追加的账本,一旦发布就不可修改,任何的变动都应该产生一个新的发布版本。
新的代码在部署之前,需要开发人员触发构建操作。但是,运行阶段不一定需要人为触发,而是可以自动进行。如服务器重启,或是进程管理器重启了一个崩溃的进程。因此,运行阶段应该保持尽可能少的模块,这样假设半夜发生系统故障而开发人员又捉襟见肘也不会引起太大问题。构建阶段是可以相对复杂一些的,因为错误信息能够立刻展示在开发人员面前,从而得到妥善处理。
VI. 进程
-- 以一个或多个无状态进程运行应用
运行环境中,应用程序通常是以一个和多个 进程 运行的。
最简单的场景中,代码是一个独立的脚本,运行环境是开发人员自己的笔记本电脑,进程由一条命令行(例如python my_script.py)。另外一个极端情况是,复杂的应用可能会使用很多 进程类型 ,也就是零个或多个进程实例。
12-Factor 应用的进程必须无状态且 无共享 。 任何需要持久化的数据都要存储在 后端服务 内,比如数据库。
内存区域或磁盘空间可以作为进程在做某种事务型操作时的缓存,例如下载一个很大的文件,对其操作并将结果写入数据库的过程。12-Factor应用根本不用考虑这些缓存的内容是不是可以保留给之后的请求来使用,这是因为应用启动了多种类型的进程,将来的请求多半会由其他进程来服务。即使在只有一个进程的情形下,先前保存的数据(内存或文件系统中)也会因为重启(如代码部署、配置更改、或运行环境将进程调度至另一个物理区域执行)而丢失。
源文件打包工具(Jammit, django-compressor) 使用文件系统来缓存编译过的源文件。12-Factor 应用更倾向于在 构建步骤 做此动作——正如 Rails资源管道 ,而不是在运行阶段。
一些互联网系统依赖于 “粘性 session”, 这是指将用户 session 中的数据缓存至某进程的内存中,并将同一用户的后续请求路由到同一个进程。粘性 session 是 12-Factor 极力反对的。Session 中的数据应该保存在诸如 Memcached 或 Redis 这样的带有过期时间的缓存中。
参考:
https://www.hiredevops.org/the-twelve-factor-app-successful-microservices-guideline/
https://12factor.net
以上所述就是小编给大家介绍的《「转」SAAS原则 -- 12-Factor(上)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 面向对象基本原则(1)- 单一职责原则与接口隔离原则
- 面向对象基本原则(2)- 里式代换原则与依赖倒置原则
- 面向对象基本原则(3)- 最少知道原则与开闭原则
- 设计模式之软件开发原则(1)开闭原则和依赖倒置原则
- 面向对象设计原则之开闭原则
- 面向对象设计原则之开闭原则
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。