Twelve Factor App

Table of content:

About

https://12factor.net/ 是任何 SaaS 应用的开发人员, 部署和管理此类应用的运维工程师都应该阅读的准则。在很多技术书籍和官方文档上都有对其引用足以说明它的重要性。以下是主要是 12 factor 笔记,和自己在 PaaS 的开发中的一些理解。

准则

1. 基准代码, 一份基准代码(Codebase),多份部署(deploy)

多个应用共享一份基准代码是有悖于 12-Factor 原则的。共享的代码拆分为独立的类库,然后使用依赖管理策略去加载它们。

2. 依赖,显式声明依赖关系( dependency )

通过打包系统安装的类库可以是系统级的(称之为 「site packages」,或仅供某个应用程序使用,部署在相应的目录中(称之为 「vendoring」或 「bunding」)。

应用程序应该通过依赖清单,确切地声明所有依赖项和锁定对应的版本。此外,在各个环境运行过程中通过依赖隔离工具来确保程序不会调用系统中存在但清单中未声明的依赖项。

3. 配置, 代码和配置严格分离

在不同环境(如开发环境,金丝雀环境,生产环境)有很大差异的配置,如:

  • 一些资源连接串,如 MySQL, Redis,Beanstalk, 或者其他依赖的域名
  • 第三方服务的证书

代码和配置应该严格分离,并推荐放如环境变量中。环境变量可以非常方便地在不同的部署间做修改。

判断一个应用是否正确地将配置排除在代码之外,一个简单的方法是看该应用的基准代码是否可以立刻开源,而不用担心会暴露任何敏感的信息。

4. 后端服务, 把后端服务(backing services)当作附加资源

后端服务是指程序运行所需要的通过网络调用的各种服务,如数据库(MySQL,CouchDB),消息/队列系统(RabbitMQ,Beanstalkd),SMTP 邮件发送服务(Postfix),Memcached, Redis 之类的。

也就是按照 3 的标准,这些后端服务都应该呈现为资源环境变量,要替换服务的时候只需要修改环境变量中的地址,不需要进行代码改动。

5. 构建,发布,运行, 严格分离构建和运行

基准代码 转化为一份部署(非开发环境)需要以下三个阶段:

  • 构建阶段 是指将代码仓库转化为可执行包的过程。构建时会使用指定版本的代码,获取和打包 依赖项,编译成二进制文件和资源文件。
  • 发布阶段 会将构建的结果和当前部署所需 配置 相结合,并能够立刻在运行环境中投入使用。
  • 运行阶段 (或者说“运行时”)是指针对选定的发布版本,在执行环境中启动一系列应用程序 进程。

每一个发布版本必须对应一个唯一的发布 ID,例如可以使用发布时的时间戳(2011-04-06-20:32:17),亦或是一个增长的数字(v100)。发布的版本就像一本只能追加的账本,一旦发布就不可修改,任何的变动都应该产生一个新的发布版本。

严格区分构建,发布,运行这三个步骤。举例来说,直接修改处于运行状态的代码是非常不可取的做法,因为这些修改很难再同步回构建步骤。

6. 进程, 以一个或多个无状态进程运行应用

运行环境中,应用程序通常是以一个和多个进程运行的。

应用的进程必须无状态且无共享。 任何需要持久化的数据都要存储在后端服务内,比如数据库。

7. 端口绑定, 通过端口绑定(Port binding)来提供服务

应用完全自我加载 而不依赖于任何网络服务器就可以创建一个面向网络的服务。互联网应用通过端口绑定来提供服务,并监听发送至该端口的请求。

本地环境中,开发人员通过类似 http://localhost:5000/ 的地址来访问服务。在线上环境中,请求统一发送至公共域名而后路由至绑定了端口的网络进程。

8. 并发, 通过进程模型进行扩展

在 12-factor 应用中,进程是一等公民。12-Factor 应用的进程主要借鉴于 unix 守护进程模型 。开发人员可以运用这个模型去设计应用架构,将不同的工作分配给不同的 进程类型 。例如,HTTP 请求可以交给 web 进程来处理,而常驻的后台工作则交由 worker 进程负责。

9. 易处理, 快速启动和优雅终止可最大化健壮性

应用的进程是易处理(disposable)的,意思是说它们可以瞬间开启或停止。进程应当追求 最小启动时间 。 理想状态下,进程从敲下命令到真正启动并等待请求的时间应该只需很短的时间。更少的启动时间提供了更敏捷的 发布 以及扩展过程,此外还增加了健壮性,因为进程管理器可以在授权情形下容易的将进程搬到新的物理机器上。

进程 一旦接收 终止信号(SIGTERM) 就会优雅的终止 。就网络进程而言,优雅终止是指停止监听服务的端口,即拒绝所有新的请求,并继续执行当前已接收的请求,然后退出。此类型的进程所隐含的要求是HTTP请求大多都很短(不会超过几秒钟),而在长时间轮询中,客户端在丢失连接后应该马上尝试重连。

对于 worker 进程来说,优雅终止是指将当前任务退回队列。例如,RabbitMQ 中,worker 可以发送一个NACK信号。 Beanstalkd 中,任务终止并退回队列会在worker断开时自动触发。有锁机制的系统诸如 Delayed Job 则需要确定释放了系统资源。此类型的进程所隐含的要求是,任务都应该 可重复执行 , 这主要由将结果包装进事务或是使重复操作 幂等 来实现。

进程还应当在面对突然死亡时保持健壮,例如底层硬件故障。虽然这种情况比起优雅终止来说少之又少,但终究有可能发生。一种推荐的方式是使用一个健壮的后端队列,例如 Beanstalkd ,它可以在客户端断开或超时后自动退回任务。无论如何,12-Factor 应用都应该可以设计能够应对意外的、不优雅的终结。Crash-only design 将这种概念转化为 合乎逻辑的理论。

10. 开发环境与线上环境等价, 尽可能的保持开发,预发布,线上环境相同

要做到持续部署, 就必须缩小本地与线上差异,可以使用 docker 或者 Vagrant 这样的方案来让本地开发和发布环境更贴近。

在实际工作中,我们尝试了构建部署使用 docker,并提供基于 docker 的本地开发 cli 来尽可能缩小不同环境之间的差异。

Reference

关于头图

  • 拍摄自今日美术馆
Docker Cookbook
2018 年 11 月摘要