呓语 | 杨英明的个人博客

专注于c++、Python,欢迎交流

By

对于 12-Factor 开发规范的一些理解

前言

12-Factor 是 Heroku 的工程师结合大量实践经验总结出的软件开发规范,它包含 12 条开发规范。在当前万物上“云”的时代,不管对于开发人员还是运维人员,12-Factor 都十分有借鉴作用。而遵循 12-Factor 规范开发的应用程序,被称为 12-Factor App。

官方文档(中文):https://12factor.net/zh_cn/

这篇英文博文总结的很精炼:"12-FACTOR APP" SUMMARY

由于官方文档翻译的不太流畅,所以笔者在这里结合原文聊聊个人的粗浅理解。

I. 基准代码

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

这一节的规范强烈建议使用 版本控制工具(比如 Git、SVN) 管理项目代码。

在类似 SVN 这样的集中式版本控制系统中,基准代码(Codebase) 就是指控制系统中的这一份代码库;而在 Git 那样的分布式版本控制系统中,基准代码 则是指最上游的那份代码库。

尽管每个应用只对应一份基准代码,但可以同时存在多份 部署。每份 部署 相当于运行了一个应用的实例。通常会有一个生产环境(production),一个或多个预发布环境(staging)。此外,每个开发人员都会在自己本地环境运行一个应用实例,这些都相当于一份部署。

这一条没什么好说的,使用版本控制工具几乎是公司和开发者的标配。

II. 依赖

显式声明依赖关系( dependency )

这一节建议将项目的依赖显式写在一个 依赖清单 中,比如 python 的 requirements.txt,或者 golang 中的 go.mod。

显式声明依赖的优点之一是简化了环境配置流程,只需通过一个 构建命令 就可以安装所有的依赖项,比如 python 中的 pip install -r requirements.txt,golang 中的 go mod tidy

依赖管理的另一点是使用 依赖隔离,用来保持环境的干净,确保程序不会调用系统中存在但清单中未声明的依赖项。这类依赖隔离的工具很常见,比如 python 中的 virtualenv,C 中的静态链接库。

值得注意的是,12-Factor 规范建议:无论用什么工具,依赖声明依赖隔离 必须一起使用,否则无法满足 12-Factor 规范。

III. 配置

在环境中存储配置

12-Factor 不建议将配置写死在代码中,也不建议写在单独的配置文件中,而是建议将项目的配置(比如暴露的端口号,数据库IP等)在系统的 环境变量 中声明。

这样做的好处是可以将配置和代码完全分离,不用担心将个人部署信息不小心提交到版本库中,也可以非常方便的在不同部署间做修改,却不用动一行代码。

这一条值得实践,但是感觉会有一些问题,比如环境变量重复声明(这要求项目的环境变量要足够独特)或者环境变量的隐私问题。当然,如果完全用容器启动项目,问题就少许多,因为每个容器都相当于一个独立的系统,环境变量自然也是隔离的。总的来说,践行这一条规范有些犹疑。

Tip: Golang 可以使用 caarlos0/env 库方便的解析环境变量到结构体中。

IV. 后端服务

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

这一条建议将项目中使用的本地或者第三方服务都作为 附加资源,并用上一节介绍的配置方法统一管理。

其中,本地和第三方服务 包括数据库(MySQL,CouchDB),消息/队列系统(RabbitMQ,Beanstalkd),SMTP 邮件发送服务(Postfix),以及缓存系统(Memcached)等,它们都通过一个 url 或是服务定位/服务证书来获取数据。

这样做的好处是将服务和代码分离,服务信息则存储在环境变量中,比如开发者想把项目使用的一个本地数据库切换为 AWS 中的云数据库,只需要修改配置中的数据库地址就可以,而不用修改任何代码。

V. 构建,发布,运行

严格分离构建和运行

这一条建议 严格区分构建,发布,运行这三个步骤,其中:

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

举个例子,你不能直接修改运行中的代码,而是需要修改源代码,然后重新构建 => 发布 => 运行,否则这些修改很难再同步回构建步骤。

这一条也是公司和开发者中普遍被使用的规范,正规公司和团队会严格区分构建、发布、运行步骤,同时会尽量将这些步骤自动化,比如使用 Jenkins、容器技术等。

VI. 进程

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

这一条建议你的应用程序尽量是 无状态且无共享 的,即应用程序中任何需要持久化的数据都要存储在 后端服务 内,比如业务数据存放在 MongoDB 或者 Mysql 中,应用缓存存放在 Redis 等数据库 中(而不要存放在内存中)。

这一条十分赞同,无状态且无共享的应用十分容易水平扩容,以应对陡然增加的压力,而不用担心多实例造成的互相影响或者数据不一致问题。

VII. 端口绑定

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

这一条建议 通过端口绑定来提供服务 ,并监听发送至该端口的请求。

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

这一条没什么好说的,通过端口提供服务是一种常见做法。

VIII. 并发

通过进程模型进行扩展

这一条还不理解,可见原文:https://12factor.net/zh_cn/concurrency

IX. 易处理

快速启动和优雅终止可最大化健壮性

这一条建议应用程序应该是易处理(disposable)的,意思是说它们可以瞬间开启或停止。

这一条可以通过容器实现,比如 docker rundocker stop

X. 开发环境与线上环境等价

尽可能的保持开发,预发布,线上环境相同

这一条 不建议在不同环境间使用不同的后端服务,读起来有些绕口,举个例子,比如本地使用 SQLite 线上使用 PostgreSQL;又如本地缓存在进程内存中而线上存入 Memcached,这样在开发环境和线上环境使用不同的存储工具是不建议的。

这一条没什么好说的,保持线下线上开发、运行环境尽量一致,可以在测试、部署应用时避免很多莫名其妙的问题。

XI. 日志

把日志当作事件流

这一条 不建议应用存储自己的输出流,意思是说应用不应该试图去写入或者管理日志文件,而应该直接输出到标准输出(stdout)中。

这样做的好处之一是输出流可以发送到 Splunk 这样的日志索引及分析系统,或 Hadoop/Hive 这样的通用数据存储系统,这些工具可以提供更强大的日志管理和查看功能。

日志应该是 事件流 的汇总,日志最原始的格式应该是一个事件一行。日志没有确定开始和结束,但随着应用在运行会持续的增加。

XII. 管理进程

后台管理任务当作一次性进程运行

这一条还不太理解,可见原文:https://12factor.net/zh_cn/admin-processes

原创声明

转载请注明:呓语 » 对于 12-Factor 开发规范的一些理解