云原生应用的12要素 ######################### 方法论:: 1. 标准化流程自动配置 使新的开发者花费最少的学习成本加入 2. 操作系统间划清界限 在各个系统中提供最大的可移植性 3. 适合部署在现代的云平台 为在服务器和系统管理方面节省资源 4. 降低开发环境和生产环境的差异 使用持续交付 实施敏捷开发 5. 实现扩展 在工具、架构和开发流程不发生明显变化的前提下 6. 语言无关性 这套理论适用于任意语言和后端服务开发的app 灵感:: 作者: Martin Fowler Patterns of Enterprise Application Architecture, Refactoring 1.基准代码(CodeBase) ==================== * 一份基准代码(Codebase),多份部署(deploy) * 一旦有多个基准代码,就不能称为一个应用,而是一个分布式系统:: 分布式系统中的每一个组件都是一个应用 每一个应用可以分别使用 12-Factor 进行开发 * 多个应用共享一份基准代码是有悖于 12-Factor 原则的:: 解决方案是将共享的代码拆分为独立的类库 然后使用 依赖管理 策略去加载它们 * 每个应用可同时存在多份部署:: 每份 `部署` 相当于运行了一个应用的实例 通常有多个环境: dev, deploy, local... 每个环境可能有多份部署 不同环境间可使用不同版本但都共享同一基准代码 2.依赖(Dependencies) ==================== 显式声明依赖关系:: 不会隐式依赖某些系统工具 即使这些工具存在于几乎所有系统: 如curl 依赖管理工具:: Java Maven Gradle Ant(早期) Ruby 使用 Gemfile 作为依赖项声明清单 使用 bundle exec 来进行依赖隔离 Python Pip 用作依赖声明 Virtualenv 用作依赖隔离 C Autoconf 用作依赖声明 静态链接库用作依赖隔离 注意 无论用什么工具,依赖声明和依赖隔离必须一起使用 否则无法满足 12-Factor 规范 显式声明依赖的优点:: 为新进开发者简化了环境配置流程 通过一个 构建命令 安装所有的依赖项 3.配置(Config) ============== 环境变量与配置文件相比:: 环境变量与语言和系统无关 使用环境变量方便与 Docker 等基于容器的应用配合使用 也易于与 Kubernetes 的 ConfigMap 配合使用 属性文件仍然可能会被不小心地提交至源码仓库 在环境中存储配置:: 代码和配置严格分离 配置文件在各部署间存在大幅差异,代码却完全一致 标准: 基准代码立刻开源,而不用担心会暴露任何敏感的信息 推荐: 将应用的配置存储于 环境变量 中( env vars, env ) 「配置」在不同部署环境会有很大差异:: 这其中包括 数据库,Memcached,以及其他 后端服务 的配置 第三方服务的证书,如 Amazon S3、Twitter 等 每份部署特有的配置,如域名等 不赞同用配置分组的方式管理配置:: 应用将配置按照特定部署进行分组(或叫做“环境”) 如: development,test, 和 production 环境 不利于扩展且将导致各种配置组合的激增 要求: 环境变量的粒度要足够小,且相对独立 不会组合成一个所谓的“环境” 而是独立存在于每个部署之中 在扩展需要更多种类的部署时,能够做到平滑过渡 不包括应用程序的内部配置:: Spring 容器中 Bean 的依赖注入配置 Servlet 的映射配置文件 web.xml 等 它们更应该被认为是代码的一部分 4.后端服务(Backing Services) ============================ 把后端服务(backing services)当作附加资源:: 后端服务是指程序运行所需要的通过网络调用的各种服务 如数据库、消息/队列等 不区别对待本地或第三方服务:: 对应用程序而言,两种都是附加资源 通过一个 url 或是其他来获取数据 这些资源和它们附属的部署保持松耦合:: 部署可以按需加载或卸载资源 5.构建/发布/运行(Build/Release/Run) =================================== 严格分离构建和运行:: 严格区分构建,发布,运行这三个步骤 根本在于,要严格区分应用的非运行时状态和运行时状态 构建是将应用的源代码编译打包成可执行软件的过程,属于非运行时行为 `基准代码` 转化为一份部署:: 构建阶段: 将代码仓库转化为可执行包的过程 构建时会使用指定版本的代码 发布阶段: 将构建的结果和当前部署所需 `配置` 相结合 并能够立刻在运行环境中投入使用 运行阶段: 针对选定的发布版本,在执行环境中启动一系列应用程序进程 建议:: 运行阶段: 不一定需要人为触发,也可以自动进行 运行阶段应该保持尽可能少的模块 构建阶段: 需要开发人员触发构建操作 是可以相对复杂一些的 因为错误信息能够立刻展示在开发人员面前 从而得到妥善处理 规定:: 禁止在运行阶段改动代码 这样做会导致基准代码失去同步 建议每个发布版本对应一个唯一的发布 ID 发布版本只能追加而不能修改 除了回滚,其他变动都应该产生新的发布版本 6.进程(Processes) ================= 以一个或多个无状态进程运行应用:: 应用的进程必须无状态且无共享 任何需要持久化的数据都要存储在后端服务内 这样才能做到水平伸缩,从而利用云平台弹性伸缩的能力 粘性 session 是 12-Factor 极力反对的:: 粘性 session是指将用户 session 中的数据缓存至某进程的内存中 并将同一用户的后续请求路由到同一个进程 Session 的数据应存在带有过期时间的缓存中 7.端口绑定(Port Binding) ======================== 通过端口绑定(Port binding)来提供服务:: 对于发布服务的环境不应该有过多的要求 不需要依赖云平台提供应用运行容器 只要云平台分配某个端口对外发布服务即可 实现思路:: 将网络服务器类库通过 依赖声明 载入应用 调用方将服务方提供的相应 URL 当作资源存入 配置 以备将来调用 说明 通过端口绑定访问服务也意味着任何应用都可以成为另一个应用的后端服务 8.并发(Concurrency) =================== * 能够通过水平伸缩应用程序进程来实现并发 * 通过进程模型进行扩展:: 在 12-factor 应用中,进程是一等公民 主要借鉴于 unix 守护进程模型 12-Factor 应用的进程所具备的无共享,水平分区的特性 意味着添加并发会变得简单而稳妥 12-Factor的进程无需守护进程或是写入 PID 文件:: 相反的,应该借助操作系统的进程管理器来管理输出流 响应崩溃的进程,及处理用户触发的重启和关闭超级进程的请求 9.易处理(Disposability) ======================= 12-Factor 应用可快速启动和优雅终止:: 可最大化健壮性 进程是易处理(disposable)的 意思是说它们可以瞬间开启或停止 有利于快速、弹性的伸缩应用 迅速部署变化的代码或配置,稳健的部署应用 应设计能应对意外、不优雅的终结 Crash-only design 将这种概念转化为合乎逻辑的理论 进程应当追求「最小启动时间」:: 更少的启动时间提供了更敏捷的发布以及扩展过程 还增加了健壮性 因为进程管理器可以在授权情形下很容易的将进程搬到新的物理机器上 进程接收终止信号就优雅的终止:: 终止信号:SIGTERM 1. 对网络进程 优雅终止是指停止监听服务的端口, 即拒绝所有新请求, 并继续执行当前已接收的请求,然后退出 隐含的要求: * HTTP请求大多都很短(不会超过几秒钟) * 在长轮询中,客户端在丢失连接后应该马上尝试重连 2. 对于 worker 进程 优雅终止是指将当前任务退回队列,如: 2.1 RabbitMQ: worker 可以发送一个NACK信号 2.2 Beanstalkd: 任务终止并退回队列会在worker断开时自动触发 锁机制的系统诸如:Delayed Job: 需要确定释放了系统资源 隐含的要求: * 任务都应可重复执行 * 主要由将结果包装进事务 * 或是使重复操作「幂等」来实现 进程应在面对突然死亡时保持健壮:: 如底层硬件故障 情况比起优雅终止出现要少 但还是可能出现 推荐的方式 使用一个健壮的后端队列,例如 Beanstalkd 在客户端断开或超时后自动退回任务 10.开发与线上环境等价(Dev/Prod parity) ====================================== 尽可能的保持开发与线上环境相同:: 开发环境和线上环境3个主差异: 1. 时间差异 开发人员正在编写的代码可能需要几天,几周,甚至几个月才会上线 2. 人员差异 开发环境:开发人员编写代码并调试 线上环境:运维人员部署代码 不同的操作方式可能带来环境的差异 3. 工具差异 开发人员或许使用 Nginx,SQLite,OS X 而线上环境使用 Apache,MySQL 以及 Linux 要做到「持续部署」:: 就必须缩小本地与线上差异 1. 缩小时间差异 开发人员可以几小时,甚至几分钟就部署代码 2. 缩小人员差异 开发人员不只要编写代码 更应该密切参与部署过程以及代码在线上的表现 3. 缩小工具差异 尽量保证开发环境以及线上环境的一致性 反对在不同环境使用不同的后端服务:: 即使适配器已经可以几乎消除使用上的差异 不同的后端服务意味着会突然出现的不兼容 导致测试正常的代码在线上出现问题 这些错误会给持续部署带来阻力 从应用程序的生命周期来看 消除这种阻力需要花费很大的代价 注: 不同后端服务的适配器仍有用。如: 可使服务移植变得简单。 但部署这其中包括开发以及线上环境应该用同一个后端服务的相同版本 11.日志(Logs) ============= 把日志当作事件流:: 日志应该是「事件流」的汇总 将所有进程输出流按照时间顺序收集起来 本身从不考虑存储自己的输出流:: 不应该试图去写或者管理日志文件 相反,每一运行的进程都会直接标准输出(stdout)事件流 开发人员可通过这些数据流,实时在终端看到应用的活动 线上部署 输出流由运行环境截获然后处理,用于查看或存档 这些存档路径对于应用来说不可见也不可配置 是完全交给程序的运行环境管理 类似 Logplex 和 Fluent 的开源工具可实现 最重要的 输出流可以发送到 Splunk 这样的日志索引及分析系统 或 Hadoop/Hive 这样的通用数据存储系统 这些系统为查看应用的历史活动提供了强大而灵活的功能 12.管理进程(Admin Processes) ============================ 进程构成(process formation):: 指处理应用的常规业务(如处理 web 请求)的一组进程 把后台管理任务当作一次性进程运行:: 开发人员常希望执行一些管理或维护应用的一次性任务 运行一个控制台(REPL shell) 运行一些提交到代码仓库的一次性脚本 这类任务被称为后台管理任务 如检查和清理环境、迁移数据等 一次性管理进程应该和正常的 `常驻进程` 使用同样的环境:: 这些管理进程和常驻进程使用相同的 代码 和 配置 基于某个 发布版本 运行 后台管理代码应随常驻app代码一起发布,避免同步问题 所有进程类型应该使用同样的 依赖隔离 技术 12-factor 青睐提供了 REPL shell 的语言:: 那会让运行一次性脚本变得简单 在本地部署中 开发人员直接在命令行使用 shell 命令调用一次性管理进程 在线上部署中 开发人员依旧可以使用ssh等来运行这样的进程 参考 ==== * https://cloud.tencent.com/developer/article/1034398 * https://www.infoq.cn/article/xpFP8uclTKJQhhFqDyjZ * https://www.12factor.net/