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 自动化部署。
  • 可移植性:应用可以在笔记本、私有云、公有云之间无缝迁移。
  • 弹性伸缩:适应云环境的动态扩缩容需求。