简介
软件通常会作为一种服务来交付,它们被称为网络应用程序,或软件即服务(SaaS)。12因子为构建SaaS提供了方法论,让SaaS具有以下的特点和优势:
- 使用标准化流程自动配置,从而使新的开发者花费最少的学习成本加入这个项目。
- 和操作系统之间尽可能的划清界限,在各个系统中提供最大的可移植性。
- 适合部署在现代的云计算平台,从而在服务器和系统管理方面节省资源。
- 将开发环境和生产环境的差异降至最低,并使用持续交付实施敏捷开发。
- 可以在工具、架构和开发流程不发生明显变化的前提下实现扩展。
可以看见这套理论是一套语言无关的方法论,适合所有开发者和运维人员熟悉与应用。
I.基准代码
一份基准代码(Codebase),多份部署(deploy)
12因子中要求基准代码和应用之间总是保持一一对应的关系:
- 即一个应用只能有一份基准代码。一旦有多个基准代码,就不能称为一个应用,而是一个分布式系统。分布式系统中的每一个组件都是一个应用,每一个应用可以分别使用 12-Factor 进行开发。
- 反过来一份基准代码也只能对应一个应用。多个应用共享一份基准代码是有悖于 12-Factor 原则的。解决方案是将共享的代码拆分为独立的类库,然后使用 依赖管理 策略去加载它们。
尽管每个应用只对应一份基准代码,但可以同时存在多份部署。通常会有一个生产环境,一个或多个预发布环境。此外,每个开发人员都会在自己本地环境运行一个应用实例,这些都相当于一份部署。
所有部署的基准代码相同,但每份部署可以使用其不同的版本。比如,开发人员可能有一些提交还没有同步至预发布环境;预发布环境也有一些提交没有同步至生产环境。这里认为它们只是同一份代码的不同部署。
II. 依赖
显式声明依赖关系( dependency )
依赖声明和依赖隔离必须一起使用,否则无法满足 12-Factor 规范。例如nodejs
中使用package.json来显示的声明依赖,然后通过npm install
安装依赖,并将依赖隔离到node_module
文件夹中。
显式声明依赖的优点之一是为新进开发者简化了环境配置流程。新进开发者可以检出应用程序的基准代码,安装编程语言环境和它对应的依赖管理工具,只需通过一个 构建命令 来安装所有的依赖项,即可开始工作。例如,nodejs
的npm
12-Factor 应用同样不会隐式依赖某些系统工具,如 curl
。即使这些工具存在于几乎所有系统,但终究无法保证所有未来的系统都能支持应用顺利运行,或是能够和应用兼容。如果应用必须使用到某些系统工具,那么这些工具应该被包含在应用之中
。
III. 配置
在环境中存储配置
通常,应用的配置在不同部署(预发布、生产环境、开发环境等等)间会有很大差异。这其中包括:
- 数据库,Memcached,等后端的配置。
- 第三方服务的证书,如 Amazon S3、Twitter 等。
- 其他配置,如域名等。
12因子要求代码和配置严格分离,也就是说不应该把配置当做常量写入代码中,这里的配置不包括内部配置,如 Rails 的 config/routes.rb
。
把配置写入配置文件相对于在代码中使用常量已经是长足进步,但仍然有缺点:总是会不小心将配置文件签入了代码库;配置文件的可能会分散在不同的目录,并有着不同的格式,这让找出一个地方来统一管理所有配置变的不太现实。更糟的是,这些格式通常是语言或框架特定的。
12因子推荐将应用的配置存储于_环境变量_ 中。环境变量可以非常方便地在不同的部署间做修改,却不动一行代码;与配置文件不同,不小心把它们签入代码库的概率微乎其微。
在node中我们可以通过dotenv
库来读取.env
环境变量。
IV. 后端服务
把后端服务(backing services)当作附加资源
每个不同的后端服务是一份_资源_。例如,一个 MySQL 数据库是一个资源,两个 MySQL 数据库(用来数据分区)就被当作是 2 个不同的资源。12因子应用将这些数据库都视作_附加资源_ ,这些资源和它们附属的部署保持松耦合。
部署可以按需加载或卸载资源。例如,如果应用的数据库服务由于硬件问题出现异常,管理员可以从最近的备份中恢复一个数据库,卸载当前的数据库,然后加载新的数据库 – 整个过程都不需要修改代码。仅仅是替换了一份资源。
V. 构建,发布,运行
严格分离构建和运行
基准代码转换为部署一般有三步:
- 构建阶段 是指将代码仓库转化为可执行包的过程。
- 发布阶段 会将构建的结果和当前部署所需配置相结合,并能够立刻在运行环境中投入使用。
- _运行阶段_是指针对选定的发布版本,在执行环境中启动一系列应用程序进程。
每一个发布版本必须对应一个唯一的发布 ID,例如可以使用发布时的时间戳(2011-04-06-20:32:17
),亦或是一个增长的数字(v100
)。任何的变动都应该产生一个新的发布版本。这样可以溯源。
新的代码在部署之前,需要开发人员触发构建操作。但是,运行阶段不一定需要人为触发,而是可以自动进行。如服务器重启,或是进程管理器重启了一个崩溃的进程。因此,运行阶段应该保持尽可能少的模块,构建阶段是可以相对复杂一些的,因为错误信息能够立刻展示在开发人员面前,从而得到妥善处理。
VI. 进程
以一个或多个无状态进程运行应用
12因子应用的进程必须无状态且 无共享 。 任何需要持久化的数据都要存储在 后端服务 内,比如数据库。
也就是说不要在内存或文件系统中缓存资源以供接下来的请求使用。
比如说将用户 session 中的数据缓存至某进程的内存中,并将同一用户的后续请求路由到同一个进程。这是12因子极力反对的。Session 中的数据应该保存在诸如 Memcached 或 Redis 这样的带有过期时间的缓存中。
VII. 端口绑定
通过端口绑定(Port binding)来提供服务
12因子应用完全自我加载 而不依赖于任何网络服务器就可以创建一个面向网络的服务。互联网应用 通过端口绑定来提供服务 ,并监听发送至该端口的请求。
通常的实现思路是,将网络服务器类库通过 依赖声明 载入应用。例如,Python 的 Tornado, Ruby 的Thin , Java 以及其他基于 JVM 语言的 Jetty。完全由 用户端 ,确切的说应该是应用的代码,发起请求。和运行环境约定好绑定的端口即可处理这些请求。
PHP 经常作为 Apache HTTPD 的一个模块来运行,正如 Java 运行于 Tomcat 。这些方式都不符合12因子的自我加载,都是把应用当做了其他服务的一部分来运行。
VIII. 并发
通过进程模型进行扩展
**在 12因子应用中,进程是一等公民。**12因子应用的进程主要借鉴于 unix 守护进程模型 。开发人员可以运用这个模型去设计应用架构,将不同的工作分配给不同的 进程类型 。例如,HTTP 请求可以交给 web 进程来处理,而常驻的后台工作则交由 worker 进程负责。
上述进程模型会在系统急需扩展时大放异彩。 12-Factor 应用的进程所具备的无共享,水平分区的特性 意味着添加并发会变得简单而稳妥。因为所有进程之间没有共享任何数据,也没有状态保留,所以添加进程不会破坏现有的程序结构。
IX. 易处理
快速启动和优雅终止可最大化健壮性
12因子应用的 进程 是 _易处理(disposable)_的,意思是说它们可以瞬间开启或停止。 这有利于快速、弹性的伸缩应用,迅速部署变化的 代码 或 配置 ,稳健的部署应用。
进程应当追求 最小启动时间 。 理想状态下,进程从敲下命令到真正启动并等待请求的时间应该只需很短的时间。更少的启动时间提供了更敏捷的 发布 以及扩展过程,此外还增加了健壮性,因为进程管理器可以在授权情形下容易的将进程搬到新的物理机器上。
进程 一旦接收 终止信号(SIGTERM
) 就会优雅的终止 。就网络进程而言,优雅终止是指停止监听服务的端口,即拒绝所有新的请求,并继续执行当前已接收的请求,然后退出。
X. 开发环境与线上环境等价
尽可能的保持开发,预发布,线上环境相同
传统应用 | 12因子应用 | |
---|---|---|
每次部署间隔 | 数周 | 几小时 |
开发人员 vs 运维人员 | 不同的人 | 相同的人 |
开发环境 vs 线上环境 | 不同 | 尽量接近 |
12因子应用的开发人员应该反对在不同环境间使用不同的后端服务 ,即使适配器已经可以几乎消除使用上的差异。这是因为,不同的后端服务意味着会突然出现的不兼容,从而导致测试、预发布都正常的代码在线上出现问题。这些错误会给持续部署带来阻力。从应用程序的生命周期来看,消除这种阻力需要花费很大的代价。
XI. 日志
把日志当作事件流
在预发布或线上部署中,每个进程的输出流由运行环境截获,并将其他输出流整理在一起,然后一并发送给一个或多个最终的处理程序,用于查看或是长期存档。
XII. 管理进程
后台管理任务当作一次性进程运行
进程构成(process formation)是指用来处理应用的常规业务(比如处理 web 请求)的一组进程。与此不同,开发人员经常希望执行一些管理或维护应用的一次性任务,比如运行一些提交到代码仓库的一次性脚本。
一次性管理进程应该和正常的 常驻进程 使用同样的环境。这些管理进程和任何其他的进程一样使用相同的 代码 和 配置 ,基于某个 发布版本 运行。后台管理代码应该随其他应用程序代码一起发布,从而避免同步问题。