云原生架构的方法论与最佳实践。
12-factors
I. 基准代码
一份基准代码,多份部署
- 基准代码和应用之间总是保持一一对应的关系:
-
一旦有多个基准代码,就不能称为一个应用,而是一个分布式系统。分布式系统中的每一个组件都是一个应用,每一个应用可以分别使用12-Factor进行开发。
-
多个应用共享一份基准代码是有悖于12-Factor原则的。解决方案是将共享的代码拆分为独立的类库,然后使用依赖管理策略去加载它们。 尽管每个应用只对应一份基准代码,但可以同时存在多份部署。
-
部署在不同环境通过修改配置的方式实现。
II. 依赖
显式声明依赖关系 12-Factor规则下的应用程序不会隐式依赖系统级的类库。 它一定通过依赖清单 ,确切地声明所有依赖项。
此外,在运行过程中通过 依赖隔离 工具来确保程序不会调用系统中存在但清单中未声明的依赖项。这一做法会统一应用到生产和开发环境。
应用程序不会隐式依赖系统级的类库。一定是通过依赖清单 ,确切地声明所有依赖项。
在 JAVA 开发时,使用 Maven 或 Gradle 即可满足。
- 运维:基础设施的依赖关系管理(特别是部署相关)
III. 配置
在环境中存储配置 12-Factor推荐将应用的配置存储于环境变量 中 (env vars, env) 。环境变量可以非常方便地在不同的部署间做修改,却不动一行代码;与配置文件不同,不小心把它们签入代码库的概率微乎其微;与一些传统的解决配置问题的机制(比如Java的属性配置文件)相比,环境变量与语言和系统无关。
12-Factor应用中,环境变量的粒度要足够小,且相对独立。它们永远也不会组合成一个所谓的“环境”,而是独立存在于每个部署之中。当应用程序不断扩展,需要更多种类的部署时,这种配置管理方式能够做到平滑过渡。
代码和配置严格分离,判断一个应用是否正确地将配置排除在代码之外,一个简单的方法是看该应用的基准代码是否可以立刻开源,而不用担心会暴露任何敏感的信息。
不推荐给配置分组(e.g. dev、test、pro),因为随着项目的深入,开发人员可能还会添加自己的环境,导致配置组合激增,从而给管理部署增加了不确定的因素。【个人理解是不能有“完整的配置分组”,有一份“基准配置”,像配置中心读取配置那样,按优先级读取内容,覆盖“基准配置”读取的内容,这样每个环境的配置只需要留有变动内容的参数即可】
- 解耦
- 配置注入时机,如魔改gitlab,配置更新,通过CI/CD将配置更新至K8S集群,应用读取k8s注入容器环境变量(解决配置基准化问题、避免配置中心可用性影响应用挂死)
IV. 后端服务
把后端服务当作附加资源 12-Factor应用不会区别对待本地或第三方服务。 对应用程序而言,两种都是附加资源,通过一个url或是其他存储在 配置 中的服务定位/服务证书来获取数据。
12-Factor应用的任意 部署 ,都应该可以在不进行任何代码改动的情况下,将本地MySQL数据库换成第三方服务(例如 Amazon RDS)。类似的,本地SMTP服务应该也可以和第三方SMTP服务(例如Postmark)互换。
每个不同的后端服务都是一份资源。后端服务是指程序运行所需要的通过网络调用的各种服务(e.g. DB、MQ、cache)。
12-Factor 应用不会区别对待本地或第三方服务。在符合 12-Factor 的应用部署后,
都应该可以在不进行任何代码改动的情况下,通过修改配置中的资源地址替换服务,例如:将本地 MySQL 数据库换成第三方服务。
- 简单来讲, 即不要与资源强耦合, 他的标志是, 切换资源不需要进行修改代码, 仅进行切换配置就可以了.
V. 构建,发布,运行
严格分离构建和运行 12-facfor应用严格区分构建,发布,运行这三个步骤。
每一个发布版本必须对应一个唯一的发布ID。
新的代码在部署之前,需要开发人员触发构建操作。但是,运行阶段不一定需要人为触发,而是可以自动进行。
- 基准代码 转化为一份部署(非开发环境)需要以下三个阶段
1. 构建:指定版本,打包,编译;
1. 发布:构建内容 + 配置;
1. 运行:启动应用程序进程。
不要直接修改运行状态的代码,因为这些修改很难再同步回构建步骤。任何的变动都应该产生一个新的发布版本。(保证全量发布)
VI. 进程
以一个或多个无状态进程运行应用
12-factor应用的进程必须无状态且无共享 。任何需要持久化的数据都要存储在后端服务内,比如数据库。粘性Session是twelve-factor极力反对的。Session中的数据应该保存在诸如Memcached 或 Redis 这样的带有过期时间的缓存中。
12-Factor 应用的进程必须无状态且无共享。任何需要持久化的数据都要存储在后端服务内,比如数据库。
利于应用横向拓展,发生意外情况也利于恢复。
- 还存在一种负载均衡方式, 按节点 hash 进行负载均衡, 即把特定的流量根据其 hash 特征分发给特定实例. 这是违反了无状态特性的, 因为这种负载均衡模式粒度还是太粗了, 不能假定每单位连接所需求的资源都是固定的, 这种负载均衡模式会造成局部过热, 因此不应该实施. 或仅可作为某些情况下的调试行为存在.
VII. 端口绑定
通过端口绑定提供服务
12-factor应用完全自我加载而不依赖于任何网络服务器就可以创建一个面向网络的服务。互联网应用 通过端口绑定来提供服务,并监听发送至该端口的请求。
12-Factor 应用完全自我加载而不依赖于任何网络服务器就可以创建一个面向网络的服务。
【早期的应用需要用运行在服务器容器中,如 Tomcat,目前都是通过 SpringBoot 开发,使用容器技术,通过与宿主机绑定端口的方式运行应用。每个应用可以独立运行,重启,对外提供服务。符合 12-Factor 应用完全自我加载的要求。】
互联网应用通过端口绑定来提供服务 ,并监听发送至该端口的请求。
【在线上环境中,请求都是统一发送至一个共有域名,其后的路由器(gateway)转发至绑定了端口的网络进程】
VIII. 并发
通过进程模型进行扩展
在12-factor应用中,进程是一等公民。 12-factor应用的进程主要借鉴于 unix守护进程模型 。开发人员可以运用这个模型去设计应用架构,将不同的工作分配给不同的进程类型 。
根据第六条(进程),12-Factor 应用的进程所具备的无共享,水平分区的特性意味着添加并发会变得简单而稳妥。
可以将不同的工作分配给不同进程类型,这些进程的类型以及每个类型中进程的数量就被称作进程构成。
12-Factor 应用的进程不需要守护进程或是写入 PID 文件。
应该将进程管理与日志处理交由操作系统的进程管理器(例如 systemd ,分布式的进程管理云平台,或是类似 Foreman 的工具)。
IX. 易处理
快速启动和优雅终止可最大化健壮性
12-factor应用的进程是可支配的,意思是说它们可以瞬间开启或停止。 这有利于快速、弹性的伸缩应用,迅速部署变化的代码或配置,稳健地部署应用。进程应当追求最小启动时间;进程一旦接收终止信号(SIGTERM) 就会优雅的终止 。进程还应当在面对突然死亡时保持健壮
12-Factor 应用的进程是易处理(disposable)的,意思是说它们可以瞬间开启或停止。
这有利于快速、弹性的伸缩应用,迅速部署变化的代码或配置,稳健的部署应用。
进程应当追求最小启动时间。便于发布和拓展,增加了健壮性。
应该优雅的 shut down。
任务都应该可重复执行,保障幂等性。
充分考虑应用异常死亡的情况,使用后端队列,在应用突然死亡时仍然保持健壮。
- 要求业务的通信尽可能趋于原子性, 这样可以让启停进程对业务造成的冲击降到最低. 如果业务通信始终处于事务当中, 那么一旦遇到启停, 就会造成事务回滚, 这对于性能和稳定性是冲击性的. 这种情况可能就需要针对业务做出修改, 比如采用补偿性事务来解决这些问题.
X. 开发环境与线上环境等价
尽可能的保持开发,预发布,线上环境相同
12-factor应用想要做到持续部署就必须缩小本地与线上差异。12-factor应用的开发人员应该反对在不同环境间使用不同的后端服务 ,即使适配器已经可以几乎消除使用上的差异。
12-Factor 应用想要做到持续部署就必须缩小本地与线上差异。
- 缩小时间差异:使用 Jenkins 之类的自动发布工具,缩短发布时间。
- 缩小人员差异:开发人员不只要编写代码,更应该密切参与部署过程以及代码在线上的表现。
- 缩小工具差异:尽量保证开发环境以及线上环境的一致性。
- 组件,镜像化 版本化管理
XI. 日志
把日志当作事件流
12-factor应用本身从不考虑存储自己的输出流。 不应该试图去写或者管理日志文件。相反,每一个运行的进程都会直接的标准输出(stdout)事件流。开发环境中,开发人员可以通过这些数据流,实时在终端看到应用的活动。
日志应该是事件流的汇总,将所有运行中进程和后端服务的输出流按照时间顺序收集起来。
应用本身从不考虑存储自己的输出流。在预发布或线上部署中,每个进程的输出流由运行环境截获,
并将其他输出流整理在一起,一并发送至最终的处理程序(e.g. Splunk、Hadoop)。
XII. 管理进程
后台管理任务当作一次性进程运行
一次性管理进程应该和正常的 常驻进程 使用同样的环境。这些管理进程和任何其他的进程一样使用相同的代码和配置,基于某个发布版本运行。后台管理代码应该随其他应用程序代码一起发布,从而避免同步问题。所有进程类型应该使用同样的依赖隔离技术。12-factor尤其青睐那些提供了REPL shell的语言,因为那会让运行一次性脚本变得简单。
开发人员经常希望执行一些管理或维护应用的一次性任务,例如:跑脚本,运行控制台等。
一次性管理进程应该和正常的常驻进程使用同样的环境。这些管理进程和任何其他的进程一样使用相同的 代码 和 配置 ,
基于某个 发布版本 运行。后台管理代码应该随其他应用程序代码一起发布,从而避免同步问题。