The “12 Factor App”(十二要素应用)是由 Heroku 的创始人 Adam Wiggins 提出的现代云原生应用开发方法论。它的核心目的是让应用易于在云环境中部署、扩展和维护,特别是针对 SaaS(软件即服务)应用。
虽然它提出已经十多年了,但随着 Docker、Kubernetes 和微服务架构的兴起,这 12 条原则反而变得比以往任何时候都更加重要,被视为现代云原生应用开发的"圣经"。
基准代码(Codebase)
一份基准代码(Git Repo),多份部署(Deployments)
- 核心: 使用版本控制系统(如 Git)。
- 原则: 一个应用对应一个代码仓库(Git)。不能多个应用共享一个仓库,也不能一个应用分散在多个仓库。如果有多个应用共享代码,应该将共享代码拆分为独立的依赖库。
- 云原生意义: 确保 CI/CD 流程的唯一性。同一份代码库可以被部署到开发(Dev)、测试(Staging)和生产(Prod)环境,区别仅仅是版本(Commit ID)不同。
依赖 (Dependencies)
显式声明依赖关系
- 原则: 使用包管理工具(如 Go Modules, Maven, pip, npm)显式声明所有依赖,并锁定版本。
- 云原生意义: 环境隔离。这与 Docker 的理念完美契合——你的容器镜像里包含了应用运行所需的一切,不依赖宿主机环境,解决了"在我机器上能跑"的问题。
配置 (Config)
在环境中存储配置
- 原则: 代码和配置必须严格分离。绝对不要把密码、数据库地址、API Key 写死在代码里。配置应该通过环境变量注入。生产环境必须禁用文件加载,只信任环境变量。
- 云原生意义: Kubernetes 的 ConfigMap 和 Secret 就是为此设计的。同一份 Docker 镜像,在不同环境下(Dev/Prod)运行时,通过注入不同的环境变量来连接不同的数据库,而无需重新打包镜像。
后端服务 (Backing services)
把后端服务当作附加资源
- 原则: 数据库、缓存(Redis)、消息队列(RabbitMQ)等服务,应该被视为通过网络调用的"附加资源"。应用不应区分是本地的 MySQL 还是云厂商提供的 RDS。
- 云原生意义: 解耦。这使得你可以随意切换服务提供商(例如从自建 MySQL 切换到 AWS RDS),只需要修改配置(环境变量),而不需要修改代码。
构建,发布,运行 (Build, Release, run)
严格分离构建和运行阶段
- 原则:
- 构建 (Build): 代码 + 依赖 -> 编译后的包。
- 发布 (Release): 包 + 配置 -> 可运行的发布版本。
- 运行 (Run): 在执行环境中启动发布版本。
- 云原生意义: 这就是现代 CI/CD 流水线的基础。Docker 镜像一旦构建完成(Build),就是不可变的。发布时只是给镜像挂载了不同的配置。禁止在运行阶段(Runtime)去修改代码。
进程 (Processes)
以一个或多个无状态进程运行应用
- 原则: 应用进程必须是无状态(Stateless) 且无共享(Share-nothing) 的。任何需要持久化的数据(Session、上传的文件)都必须存储在有状态的后端服务(Redis、S3、数据库)中,而不是内存或本地文件系统里。
- 云原生意义: 弹性伸缩的前提。只有应用是无状态的,K8s 才能随意杀掉一个 Pod 并在另一台机器上重启它,或者瞬间将 1 个实例扩展到 100 个,而不会丢失用户数据。
端口绑定 (Port binding)
通过端口绑定提供服务
- 原则: 应用应该是完全自我包含的,不应该依赖外部的应用服务器(如 Tomcat 容器注入)。应用自己启动一个 Web Server(如 Jetty, Gunicorn),监听端口并对外提供服务。
- 云原生意义: 容器化的标准方式。Docker 容器启动后,应用监听 8080 端口,K8s Service 将流量转发进来。应用即服务。
并发 (Concurrency)
通过进程模型进行扩展
- 原则: 不要试图通过把一个进程搞得超级复杂(多线程黑魔法)来提升性能。应该通过水平扩展(增加进程数量)来处理更多并发。
- 云原生意义: 这就是 K8s 的 HPA(Horizontal Pod Autoscaling)。负载高了?增加 Pod 副本数(Replicas)。负载低了?减少副本数。
易处理 (Disposability)
快速启动和优雅终止可最大化健壮性
- 原则:
- 快速启动: 进程应该在几秒钟内准备好接收请求。
- 优雅终止 (Graceful Shutdown): 收到 SIGTERM 信号时,应该停止接收新请求,处理完现有请求,然后退出。
- 云原生意义: 云环境是不稳定的,实例随时可能被销毁或迁移。如果启动太慢,扩容就来不及;如果不能优雅退出,就会导致报错。这是编写 K8s 友好型应用的关键。
开发环境与线上环境等价 (Dev/prod parity)
尽可能的保持开发,预发布,线上环境相同
- 原则: 缩小时间差异(代码尽快上线)、缩小人员差异(开发也参与部署)、缩小工具差异(Dev 用 SQLite,Prod 用 Oracle 是大忌)。
- 云原生意义: Docker 彻底解决了这个问题。开发环境跑的镜像和生产环境跑的镜像是完全一样的(Binary compatible)。
日志 (Logs)
把日志当作事件流
- 原则: 应用不应该自己管理日志文件(不写到本地)。应用只管把日志输出到标准输出(stdout)和标准错误(stderr)。
- 云原生意义: 日志的收集和存储由基础设施负责。在 K8s 中,应用打印到控制台,由 Fluentd/Filebeat 等 Sidecar 或 DaemonSet 收集,然后发送到 ELK 或 Loki 进行集中存储和分析。
管理进程 (Admin processes)
后台管理任务当作一次性进程运行
- 原则: 数据库迁移(Migration)、清理数据脚本等管理任务,应该作为一次性进程(One-off process)运行,并且使用与正常应用完全相同的代码和配置。
- 云原生意义: 在 K8s 中,这通常对应 Job 或 CronJob 资源,或者在部署流程中作为一个 initContainer 或单独的步骤执行(如 Helm Hooks)。不要通过 SSH 连到服务器上手工跑脚本。
[!TIP] 演进:API 优先、遥测与认证
- API 优先(API First): 明确定义服务契约(OpenAPI/Swagger/Proto),并在此基础上进行开发;
- 遥测 (Telemetry): 关注不仅仅是日志,还包括指标和链路追踪;
- 认证与授权 (Authentication & Authorization): 将安全视为一种独立的服务,而不是应用的一部分。应该通过 OAuth 等标准协议或者 API 网关、Sidecar 处理。
总结
12-Factor App 的核心目标是:
- 自动化:使得应用易于通过 CI/CD 自动化部署。
- 可移植性:应用可以在笔记本、私有云、公有云之间无缝迁移。
- 弹性伸缩:适应云环境的动态扩缩容需求。