12-Factor,构建原生软件应用方法论

官方地址:https://12factor.net/zh_cn/前端

原则1:一份基准代码,多份部署

这个原则无论对微服务模式仍是其余软件开发模式来讲都很是基本,因此被列为12原则的第一条,该原则包括以下四个子原则python

  1. 使用代码库管理代码,通常是Git或者SVN,这个要求很是初级,相信本书的读者都会遵照。web

  2. 一份基准代码(即一个代码库)对应一个应用。若是经过一份基准代码能够编译出多个应用,那么应该考虑将该基准代码按应用拆分为多份;若是一个应用须要多份基准代码,那么要么考虑将多份基准代码合并,要么考虑将该应用按基准代码拆分为多个。shell

  3. 不容许多个应用共享一份基准代码,若是确实须要共享,那就把须要共享的基准代码的稳定版本发布为类库,而后经过依赖管理策略进行加载。数据库

  4. 同一应用的多份部署可使用同一份基准代码的不一样版本,可是不可使用不一样的基准代码,相似原则2,使用不一样基准代码的应用不该被视为同一应用。后端

违反子原则2和3,会给代码管理和编译工做带来麻烦:缓存

  1. 若是一份基准代码能够编译出多个应用,那么这几个应用之间必然会存在不清晰的依赖关系,随着时间的推移,这种依赖关系会变得越发混乱,以致于修改一个应用的代码,会给其余应用带来不可预知的影响。这样的基准代码显然极难维护。安全

  2. 基准代码的划分和应用的划分很是相似,也是系统边界的一种体现,若是一个应用须要从多份基准代码编译,那么多数状况下这个应用的内外部边界问题会存在问题。若是边界不存在问题,那么请将多份基准代码合并为一份,而不是维持这种古怪的设计。服务器

  3. 若是多个应用不是经过类库,而是直接共享一份基准代码,那么这份被共享的基准代码会很难维护,对这份基准代码的修改必须谨慎考虑对多个应用可能形成的影响。正确的方式是将这份基准代码发布为类库,保持清晰的边界和接口约定供其它应用调用。网络

原则2:显式声明依赖关系

  • 打包系统
  • 包类型:site packages、 vendoring| bunding
  • 依赖清单: 12-Factor规则下的应用程序不会隐式依赖系统级的类库
  • 依赖隔离:不管用什么工具,依赖声明和依赖隔离必须一块儿使用,不然没法知足 12-Factor 规范
  • 构建命令:显式声明依赖的优势之一是为新进开发者简化了环境配置流程。

原则3:在环境中存储配置

首先须要明确的是,这里的配置指与部署环境有关的配置,例如:

  • 数据库、消息代理、缓存系统等后端服务的链接配置和位置信息,如URL、用户名、密码等。

  • 第三方服务的证书。

  • 每份部署独有的配置,例如:域名、链接数、与部署目标环境资源规模有关的JVM参数等。

全部部署中都相同的信息,例如原则2里讲到的依赖信息,不在本原则所讨论的范围内。一些虽然在不一样的部署中有所差别、可是和业务相关的信息,例如资金结算的转换比例,也不属于本原则所讨论的配置。

我想大多数的开发者都知道如何经过使用配置文件实现配置和代码的分离,可是这种方式仍然存在一些缺点,例如:

  1. 配置文件容易被开发人员不当心提交到代码库中,形成密码、证书等敏感信息泄露。提交到代码库中的配置文件还容易被和应用一块儿部署到目标环境中,极可能会致使在目标环境中应用了错误的配置或者形成配置冲突。

  2. 配置文件会分散在不一样的目录中,而且有不一样的格式(配置文件的格式每每与开发语言和框架相关),这会给配置的统一管理形成困难。

为了不上述问题,本原则要求将在环境中存储配置。一种典型的方式是把配置存储在环境变量中,这会使配置和代码完全的分离,格式上也与开发语言和框架再无瓜葛,而且也不会被误提交到代码库中。还可使用Spring Cloud Config Server这类配置管理服务进行配置推送,并将配置的历史版本和变动缘由也一块儿管理起来。

原则4:把后端服务看成附加资源

这里的后端服务指的是应用运行所依赖的各类服务,例如数据库、消息代理、缓存系统等,对于云原生应用来讲,每每还会有日志收集服务、对象存储服务、以及各类经过API访问的服务;看成附加资源指的是把这些服务做为外部的、经过网络调用的资源。

该原则有以下几层含义:

  1. 不要将这些服务放在应用本地:云原生应用要求应用自己无状态化,那么状态信息就应该存储在外部服务中(参见不可变服务器)。同时,微服务模式要求应用责权单一以实现可靠性和扩展性,若是在应用本地放置数据库,那么微服务平台将没法经过更换应用的故障实例实现应用的高可用性,也没法经过自动化的横向伸缩实现扩展性,由于应用实例内包含两种性质彻底不一样的软件(应用和数据库),没法对二者使用同一种方式进行横向扩展。另外,若是将这些服务放在应用本地,那么也没法经过充分利用云平台提供的能力简化运维工做,例如,若是在应用本地放置数据库,而不是使用云平台提供的数据库服务,那么显然没法利用数据库服务提供的自动备份、安全、和高可用等特性。

  2. 经过URL或者服务注册/认证中心访问这些后端服务:应用应该可以在不进行任何代码修改的状况下,在不一样的目标环境中进行部署,应用不该该和后端服务的任何一种具体实现存在紧耦合关系。

  3. 相似“显式声明依赖关系”原则,应用最好也可以对其使用的这些后端服务进行显示声明,以方便云平台对服务资源进行自动绑定,在后端服务出现故障的时候,云平台也可以对其进行自动恢复。

原则5:严格分离构建、发布和运行

在本原则中,构建、发布和运行这三个概念可能和从前有所不一样,所以有必要首先对其进行明确:

  • 构建指的是将应用代码转化为执行体的过程:构建时会拉取特定版本的代码和依赖项,将其编译为二进制文件(针对编译型语言),并和资源文件一块儿打包。

  • 发布指的是将构建的结果和部署所需的配置相结合,并将其放置于运行环境之中。

  • 运行指的是将发布的结果启动为运行环境中的一个或多个进程。

本原则要求构建、发布和运行这三个步骤严格区分:

  1. 禁止直接修改运行状态的代码或者对应用进行打补丁,由于这些修改很难再同步回构建步骤,这时运行状态的代码就成为了“孤本”。同时,也不该该在运行期间修改应用的配置,配置的修改应该仅限于发布阶段(参见不可变服务器)。

  2. 运行这一步骤应该很是简单,仅限于启动进程,资源文件的关联应仅限于构建阶段,配置的结合应仅限于发布阶段。

同时,每一次发布都应该对应一个惟一的发布ID,发布的版本应当像一个只能追加的帐本,一旦发布就不能修改。这么作的好处是:

  1. 每一份运行状态的代码均可以在对应的发布和构建阶段找到它的来源,这是实现从新发布、故障实例的自动替换、发布出错后的版本回退等机制的基础。

  2. 运行步骤很是简单,这样在硬件重启、实例故障和横向扩展等状况下,应用能够简单和快速的实现重启。

原则6:以一个或多个无状态的进程运行应用

本原则要求应用进程的内部不要保存状态信息,任何状态信息都应该被保存在数据库、缓存系统等外部服务中。应用实例之间的数据共享也要经过数据库和缓存系统等外部服务进行,直接的数据共享不但违反无状态原则,还引入了串行化的单点,这会为应用的横向扩展带来障碍。

在微服务模式下,应用不该该在自身进程内部缓存数据以供未来的请求使用,由于微服务模式以多实例方式运行应用,未来的请求多半会被路由到其余实例,此时虽然可使用粘滞会话将请求保持在同一个实例上,可是不管是云原生应用仍是微服务模式都极力反对使用粘滞会话,缘由以下:

  1. 很难对粘滞会话实现负载均衡,由于粘滞会话的均衡性不只决定于负载均衡策略,还和会话自己的行为相关,例如,可能存在应用某些实例上的会话已经大量退出,而另外一些实例上的会话依然处于活动状态,此时这两部分实例的负载处于不均衡状态,而负载均衡器没法将活动会话转移到空闲的应用实例。

  2. 启动新的应用实例不会当即提升应用的总体处理能力,由于这些新实例只能承接新会话,旧的会话依旧粘滞在旧的应用实例上。

  3. 应用实例退出会致使会话丢失,因此在实例发生故障时,即便云平台能够对故障实例进行自动替换,也会致使用户数据丢失。即便是对应用实例进行人工维护,也须要在维护以前对该实例上的会话进行转移,这每每意味着须要编写复杂的业务代码。

    在传统模式下,能够经过在双机之间进行会话复制来实现对用户无感知的单机下线维护(虽然会付出处理能力减半的代价),可是在微服务模式下,应用的实例数量每每远不止两个,在大量的实例之间进行会话复制会使实例之间本来很是简单的逻辑关系复杂化,此时将没法经过云平台对其进行无差异的自动化维护。另外,在实例之间进行会话复制也意味着实例之间存在着直接的数据共享,这会为应用的横向扩展带来障碍。

因此,粘滞会话是应用实现可用性和扩展性的重要障碍,使用粘滞会话显然是种得不偿失的选择。更好的实现方式是将会话信息存储在缓存服务中。

原则7:经过端口绑定提供服务

服务端应用经过网络端口提供服务,这点毋庸置疑,可是本原则还有以下两个深层次的含义:

  1. 不管是云原生应用仍是微服务模式都要求应用应该彻底自我包含,而不是依赖于外部的应用服务器,端口绑定指的是应用直接与端口绑定,而不是经过应用服务器进行端口绑定。

    若是必定要使用应用服务器,那就使用嵌入式应用服务器,不管是云原生应用仍是微服务模式都极力反对将多个应用放置于同一个应用服务器上运行,由于在这种模式下,一个应用出错会对同一个应用服务器上的其余应用形成影响,也没法针对单一应用作横向扩展。

  2. 端口绑定工做应该由云平台自动进行,云平台在实现应用到端口的绑定以外,还须要实现内部端口到外部端口的映射和外部端口到域名的映射。在应用的整个生命周期内,应用实例会经历屡次的从新部署、重启或者横向扩展,端口会发生变化,但URL会保持不变。

原则8:经过进程模型进行扩展

与经过进程模型进行扩展相反的方式是经过线程模型进行扩展,这是一种相对较为传统的方式,典型的例子是Java应用。当咱们启动一个Java进程的时候,一般会经过JVM参数为其设置各个内存区域的容量上下限,同时还可能会在应用层面为其设置一个或者多个线程池的容量上下限,当外部负载变化时,进程所占用的内存容量和进程内部的线程数量能够在这些预先设置好的上下限之间进行扩展,这种方式也被称为纵向扩展或者垂直扩展。

可是这种方式存在一些问题,首先,在进程的内存容量和线程数量提升时,应用的某些性能指标可能不会获得同步提升,甚至可能会降低(这每每是由于程序对某些没法扩展的资源进行争用所形成的),这种良莠不齐的性能扩展对外部负载提升的承接能力会很不理想,有时甚至会拔苗助长;

其次,为了使进程自己能够完成纵向扩展,还须要在虚拟机层面或者容器层面为其预留内存资源和对应的CPU资源,这会形成大量的资源浪费(固然,也可使虚拟机或者容器跟随进程一块儿进行纵向扩展,这在技术上是可行的,可是会为虚拟机或者容器管理平台的资源调度形成一些没必要要的困难,例如频繁的虚拟机迁移或者容器重启)。

因此,如今更为推崇使用“固定的”进程(对前面Java应用的例子来讲,就是固定的内存容量和线程池容量),在外部负载提升时,启动更多的进程,在外部负载下降时,中止一部分进程,这种方式就是本原则所说的经过进程模型进行扩展,有时候也被称为横向扩展或者水平扩展。

这种扩展方式的好处是,在进程数量增长的时候,应用的各类性能指标会获得同步的提升,这种提升即便不是线性的,也会按照一种平滑和可预期的曲线展开,能够更为稳定的应对外部负载的变化。

云原生应用和微服务模式极力推崇将经过进程模型进行扩展做为惟一的扩展方式,除了前文所述,还有一个缘由是进程是云平台能够操做的最小运行单元(固然,能够经过其余技术手段去操做线程,可是那不会成为云平台的通用技术特性),云平台能够根据各个层面的监控数据,经过预设规则决定是否为应用增长或者减小进程,例如,当前端的负载均衡器检测到访问某个后端应用的并发用户数超过某个阈值时,能够当即为这个后端应用启动更多的进程,以承接更大的负载,同时还能够选择是否对该应用后端的数据库进行扩展。

若是此时选择对应用进行纵向扩展,则云平台既不知道应用处理能力的变化,也没法对这种变化进行预期管理,更没法使应用的先后端对这种变化进行联动,即该应用的扩展行为脱离了云平台的管理。在微服务模式下,若是大量的进程都采用纵向扩展方式,则会为平台的资源调度带来极大的混乱。

注3:该原则彷佛更适合被称为横向扩展原则,可是为了和12原则的原文保持一直,这里咱们仍然将其称为“经过进程模型进行扩展”。

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

该原则要求应用能够瞬间(理想状况下是数秒或者更短)启动和中止,由于这将有利于应用快速进行横向扩展和变动或者故障后的从新部署,而这二者都是程序健壮性的体现。

前文不止一次提到过应用的快速启动,在理念章节的开头,咱们提到过平价的进程生成对多道程序设计相当重要,而微服务模式在某种程度上能够认为是多道程序设计在Web领域和分布式系统下的进一步扩展,这里所说的平价进程生成指的是操做系统的一种特性,是应用快速启动的基础,除此以外为了保证应用能够在数秒内完成启动,还须要大量的优化工做,须要开发人员掌握复杂的调优技术与工具,有些工做必须在应用的初始设计阶段完成,例如:若是应用体积过大或者是引用了太多的库文件,那么再多的后期优化也没法将启动时间下降到数秒之内。

“原则5:严格分离构建、发布和运行”中咱们还提到,应用的运行步骤应该很是简单,这里的“简单”也隐含着快速的意思,目的是为了在硬件重启、实例故障和横向扩展等状况下,应用能够快速的实现重启。除此以外,“原则6:以一个或多个无状态的进程运行应用”也与应用的快速启动有关,遵照无状态原则,使用云平台提供的缓存服务,而不是在应用内部加载缓存,能够避免在应用启动期间进行耗时的缓存预热。

比起应用的快速启动,优雅终止(Graceful Shutdown)须要考虑的问题会更为普遍一些。优雅终止须要尽量下降应用终止对用户形成的不良影响(对于微服务应用,用户多是人,也多是其余微服务)。

对于短任务来讲,这通常意味着拒绝全部新的请求,并将已经接收的请求处理完毕后再终止;对于长任务来讲,这通常意味着应用重启后的客户端重连和为任务设置断点并在重启后继续执行。除此以外,优雅终止还须要释放全部被进程锁定的资源,并对事务的完整性和操做的幂等性作出完备的考虑。

最后,应用还必须应对突如其来的退出,在硬件出现故障时或者进程崩溃时,应用须要保证不会对其使用的数据形成损坏,遵照无状态原则、将数据交由后端服务处理的应用能够很容易的将应对忽然退出的复杂度外部化。

  • 12-Factor应用的进程是易处理(disposable)的,意思是说它们能够瞬间开启或中止。这有利于快速、弹性的伸缩应用,迅速部署变化的 代码 或 配置 ,稳健的部署应用。
  • 进程应当追求最小启动时间
  • 进程一旦接收终止信号(SIGTERM)就会优雅的终止
  • 进程还应当在面对忽然死亡时保持健壮,例如底层硬件故障
  • 12-Factor应用都应该能够设计可以应对意外的、不优雅的终结。

原则10:开发环境与线上环境等价

本原则的浅层次含义是要求在开发环境和线上环境中使用相同的软件栈,并尽量为这些软件栈使用相同的配置,以免“It works on my machine.”这类问题。本原则反对在不一样的环境中使用不一样的后端服务,虽然可使用适配器或者在代码中作出兼容性考虑以消除后端服务的差别,可是这将牵扯开发人员和测试人员大量的精力以保证这些适配器和代码确实能够按预期工做,在应用的整个开发周期中,这将积累极大的额外工做量,是一种很是没必要要的资源浪费。

近年来我的电脑的性能大幅提升,开发人员一度得以在本地开发环境中运行与生产环境中一致的软件栈,而不是像曾经那样采用轻量的替代方案。可是随着云原生应用和微服务模式的流行,状况又发生了微妙的变化:开发微服务时须要依赖云平台提供的基础服务和其余微服务,愈来愈难以把这些服务完整的运行在本地,与此同时,彻底的在线开发愈发成为一种趋势,那样的话至少在软件栈上开发环境和线上环境就真的没有任何区别了。

在我编写这段文字的时候,Red Hat公司恰好在洽购在线开发环境创业公司Codenvy用以充实他们的云平台产品OpenShift,而另外一家与Codenvy相似的创业公司Cloud9在差很少一年前被Amazon公司收购。

本原则的深层次含义是尽可能缩小开发环境和线上环境中时间和人员的差别。开发环境中的代码天天都在更新,而这些更新每每会累积数周甚至数月才会被发布到线上环境,这是开发环境和线上环境在时间上的巨大差别;开发人员只关心开发环境,运维人员只关心线上环境,开发人员和运维人员在工做上鲜有交集,这是开发环境和线上环境在人员上的巨大差别。

对于前一个差别,本原则要求更为密集和频繁的向线上环境发布更新,要求创建机制以保障开发人员能够在数小时甚至数分钟内既可将更新发布到线上,这也正是本章理念部分中持续交付所提倡的;对于后一个差别,本原则要求开发人员不能只关心开发环境中本身的代码,更要密切关注代码的部署过程和代码在线上的运行状况,这也正是DevOps所提倡的。

  • 环境之间的差别:部署时间差别、人员分工差别、工具差别
  • 12-Factor应用想要作到持续部署就必须缩小本地与线上差别
  • 12-Factor 应用的开发人员应该反对在不一样环境间使用不一样的后端服务
  • 使用相似 Chef 和 Puppet 的声明式配置工具,结合像 Vagrant 这样轻量的虚拟环境就可使得开发人员的本地环境与线上环境无限接近。

原则11:把日志看成事件流

  • 日志使得应用程序运行的动做变得透明。在基于服务器的环境中,日志一般被写在硬盘的一个文件里,但这只是一种输出格式。
  • 日志应该是 事件流 的汇总,将全部运行中进程和后端服务的输出流按照时间顺序收集起来。尽管在回溯问题时可能须要看不少行,日志最原始的格式确实是一个事件一行。日志没有肯定开始和结束,但随着应用在运行会持续的增长。
  • 12-factor应用自己从不考虑存储本身的输出流,不该该试图去写或者管理日志文件。
  • 在预发布或线上部署中,每一个进程的输出流由运行环境截获,并将其余输出流整理在一块儿,而后一并发送给一个或多个最终的处理程序,用于查看或是长期存档。这些存档路径对于应用来讲不可见也不可配置,而是彻底交给程序的运行环境管理。相似 Logplex 和 Fluentd 的开源工具能够达到这个目的。
  • 这些事件流能够输出至文件,或者在终端实时观察。最重要的,输出流能够发送到 Splunk 这样的日志索引及分析系统,或 Hadoop/Hive 这样的通用数据存储系统。这些系统为查看应用的历史活动提供了强大而灵活的功能,包括:
    • 找出过去一段时间特殊的事件。
    • 图形化一个大规模的趋势,好比每分钟的请求量。
    • 根据用户定义的条件实时触发警报,好比每分钟的报错超过某个警惕线。

原则12:后台管理任务看成一次性进程运行

  • 进程构成(process formation)是指用来处理应用的常规业务(好比处理 web 请求)的一组进程。 与此不一样,开发人员常常但愿执行一些管理或维护应用的一次性任务,例如:
    • 运行数据移植(Django 中的 manage.py migrate, Rails 中的 rake db:migrate)。
    • 运行一个控制台(也被称为 REPL shell),来执行一些代码或是针对线上数据库作一些检查。大多数语言都经过解释器提供了一个 REPL 工具(python 或 perl) ,或是其余命令(Ruby 使用 irb, Rails 使用 rails console)。
    • 运行一些提交到代码仓库的一次性脚本。
  • 12-factor 尤为青睐那些提供了 REPL shell 的语言,由于那会让运行一次性脚本变得简单。

总结

官方地址:https://12factor.net/zh_cn/

往上看到一个pdf讲的还行吧,去下载收费25元,去他妈的,下载下来给你:连接:https://pan.baidu.com/s/1EZJJrgkvlpU1_d1xbVaPWg  提取码:5gfp 

辅助理解:https://www.jianshu.com/p/bbdccd020a1d

相关文章
相关标签/搜索