云原生一词已经被过度的采用,很多软件都号称是云原生,很多打着云原生旗号的会议也如雨后春笋般涌现。
云原生本身甚至不能称为是一种架构,它首先是一种基础设施,运行在其上的应用称作云原生应用,只有符合云原生设计哲学的应用架构才叫云原生应用架构。
云原生系统的设计理念如下:
- 面向分布式设计(Distribution):容器、微服务、API 驱动的开发;
- 面向配置设计(Configuration):一个镜像,多个环境配置;
- 面向韧性设计(Resistancy):故障容忍和自愈;
- 面向弹性设计(Elasticity):弹性扩展和对环境变化(负载)做出响应;
- 面向交付设计(Delivery):自动拉起,缩短交付时间;
- 面向性能设计(Performance):响应式,并发和资源高效利用;
- 面向自动化设计(Automation):自动化的 DevOps;
- 面向诊断性设计(Diagnosability):集群级别的日志、metric 和追踪;
- 面向安全性设计(Security):安全端点、API Gateway、端到端加密;
以上的设计理念很多都是继承自分布式应用的设计理念。虽然有如此多的理念但是我们仍然无法辨认什么样的设施才是云原生基础设施,不过可以先用排除法,我将解释什么不是云原生基础设施。
云原生基础设施不等于在公有云上运行的基础设施。光是租用服务器并不会使您的基础设施云原生化。管理 IaaS 的流程与运维物理数据中心没什么两样,将现有架构迁移到云上也未必能获得回报。
云原生不是指在容器中运行应用程序。Netflix 率先推出云原生基础设施时,几乎所有应用程序部署在虚拟机中,而不是在容器中。改变应用程序的打包方式并不意味着就会增加自治系统的可扩展性和优势。即使应用程序是通过 CI/CD 渠道自动构建和部署的,也不意味着您就可以从增强 API 驱动部署的基础设施中受益。
这也并不意味着您只能运行容器编排器(例如 Kubernetes 和 Mesos)。容器编排器提供了云原生基础设施所需的许多平台功能,但并未按预期方式使用这些功能,这意味着您的应用程序会在一组服务器上运行,被动态调度。这是一个非常好的起步,但仍有许多工作要做。
调度器与编排器
术语 “调度器” 和 “编排器” 通常可以互换使用。
在大多数情况下,编排器负责集群中的所有资源利用(例如:存储,网络和 CPU)。该术语典型地用于描述执行许多任务的产品,如健康检查和云自动化。
调度器是编排平台的一个子集,仅负责选择运行在每台服务器上的进程和服务。
云原生不是微服务或基础设施即代码。微服务意味着更快的开发周期和更小的独特功能,但是单片应用程序可以具有相同的功能,使其能够通过软件有效管理,并且还可以从云原生基础设施中受益。
基础设施即代码以机器可解析语言或领域特定语言(DSL)定义、自动化您的基础设施。将代码应用于基础架构的传统工具包括配置管理工具(例如 Chef 和 Puppet)。这些工具在自动执行任务和提供一致性方面有很大帮助,但是它们在提供必要的抽象来描述超出单个服务器的基础设施方面存在缺陷。
配置管理工具一次自动化一台服务器,并依靠人员将服务器提供的功能绑定在一起。这将人类定位为基础设施规模的潜在瓶颈。这些工具也不会使构建完整系统所需的云基础设施(例如存储和网络)的额外部分自动化。
尽管配置管理工具为操作系统的资源(例如软件包管理器)提供了一些抽象,但它们并没有抽象出足够的底层操作系统来轻松管理它。如果一位工程师想要管理系统中的每个软件包和文件,这将是一个非常艰苦的过程,并且对于每个配置变体都是独一无二的。同样,定义不存在或不正确的资源的配置管理仅消耗系统资源并且不能提供任何价值。
虽然配置管理工具可以帮助自动化部分基础设施,但它们无法更好地管理应用程序。我们将在后面的章节中通过查看部署,管理,测试和操作基础架构的流程,探讨云原生基础设施的不同之处,但首先,我们将了解哪些应用程序是成功的以及应该何时与原生基础设施一起使用。
就像云改变了业务和基础设施之间的关系一样,云原生应用程序也改变了应用程序和基础设施之间的关系。我们需要了解与传统应用程序相比,云本身有什么不同,因此我们需要了解它们与基础设施的新关系。
为了写好本书,也为了有一个共享词汇表,我们需要定义 “云原生应用程序” 是什么意思。云原生与 12 因素应用程序不同,即使它们可能共享一些类似的特征。如果你想了解更多细节,请阅读 Kevin Hoffman 撰写的 “超越 12 因素应用程序”(O'Reilly,2012)。
云原生应用程序被设计为在平台上运行,并设计用于弹性,敏捷性,可操作性和可观察性。弹性包含失败而不是试图阻止它们;它利用了在平台上运行的动态特性。敏捷性允许快速部署和快速迭代。可操作性从应用程序内部控制应用程序生命周期,而不是依赖外部进程和监视器。可观察性提供信息来回答有关应用程序状态的问题。
云原生定义
云原生应用程序的定义仍在发展中。还有像 CNCF 这样的组织可以提供其他的定义。
云原生应用程序通过各种方法获取这些特征。它通常取决于应用程序的运行位置以及企业流程和文化。以下是实现云原生应用程序所需特性的常用方法:
- 微服务
- 健康报告
- 遥测数据
- 弹性
- 声明式的,而不是命令式的
作为单个实体进行管理和部署的应用程序通常称为单体应用。最初开发应用程序时,单体有很多好处。它们更易于理解,并允许您在不影响其他服务的情况下更改主要功能。
随着应用程序复杂性的增长,单体应用的益处逐渐减少。它们变得更难理解,而且失去了敏捷性,因为工程师很难推断和修改代码。
对付复杂性的最好方法之一是将明确定义的功能分成更小的服务,并让每个服务独立迭代。这增加了应用程序的灵活性,允许根据需要更轻松地更改部分应用程序。每个微服务可以由单独的团队进行管理,使用适当的语言编写,并根据需要进行独立扩缩容。
只要每项服务都遵守强有力的合约,应用程序就可以快速改进和改变。当然,转向微服务架构还有许多其他的考虑因素。其中最不重要的是弹性通信,我们在附录 A 中有讨论。
我们无法考虑转向微服务的所有考虑因素。拥有微服务并不意味着您拥有云原生基础设施。如果您想阅读更多,我们推荐 Sam Newman 的 Building Microservices(O'Reilly,2015)。虽然微服务是实现您的应用程序灵活性的一种方式,但正如我们之前所说的,它们不是云原生应用程序的必需条件。
停止逆向工程应用程序并开始从内部进行监控。 —— Kelsey Hightower,Monitorama PDX 2016:healthz
没有人比开发人员更了解应用程序需要什么才能以健康的状态运行。很长一段时间,基础设施管理员都试图从他们负责运行的应用程序中找出 “健康” 该怎么定义。如果不实际了解应用程序的健康状况,他们尝试在应用程序不健康时进行监控并发出警报,这往往是脆弱和不完整的。
为了提高云原生应用程序的可操作性,应用程序应该暴露健康检查。开发人员可以将其实施为命令或过程信号,以便应用程序在执行自我检查之后响应,或者更常见的是:通过应用程序提供 Web 服务,返回 HTTP 状态码来检查健康状态。
Google Borg 示例
Google 的 Borg 报告中列出了一个健康报告的例子:
几乎每个在 Borg 下运行的任务都包含一个内置的 HTTP 服务器,该服务器发布有关任务运行状况和数千个性能指标(如 RPC 延迟)的信息。Borg 会监控运行状况检查 URL 并重新启动不及时响应或返回 HTTP 错误代码的任务。其他数据由监控工具跟踪,用于仪表板和服务级别目标(SLO)违规警报。
将健康责任转移到应用程序中使应用程序更容易管理和自动化。应用程序应该知道它是否正常运行以及它依赖于什么(例如,访问数据库)来提供业务价值。这意味着开发人员需要与产品经理合作来定义应用服务的业务功能并相应地编写测试。
提供健康检查的应用程序示例包括 Zookeeper 的 ruok 命令和 etcd 的 HTTP / 健康端点。
应用程序不仅仅有健康或不健康的状态。它们将经历一个启动和关闭过程,在这个过程中它们应该通过健康检查,报告它们的状态。如果应用程序可以让平台准确了解它所处的状态,平台将更容易知道如何操作它。
一个很好的例子就是当平台需要知道应用程序何时可以接收流量。在应用程序启动时,如果它不能正确处理流量,它就应该表现为未准备好。此额外状态将防止应用程序过早终止,因为如果运行状况检查失败,平台可能会认为应用程序不健康,并且会反复停止或重新启动它。
应用程序健康只是能够自动化应用程序生命周期的一部分。除了知道应用程序是否健康之外,您还需要知道应用程序是否正在进行哪些工作。这些信息来自遥测数据。
遥测数据是进行决策所需的信息。确实,遥测数据可能与健康报告重叠,但它们有不同的用途。健康报告通知我们应用程序生命周期状态,而遥测数据通知我们应用程序业务目标。
您测量的指标有时称为服务级指标(SLI)或关键性能指标(KPI)。这些是特定于应用程序的数据,可以确保应用程序的性能处于服务级别目标(SLO)内。如果您需要更多关于这些术语的信息以及它们与您的应用程序、业务需求的关系,我们推荐你阅读来自 Site Reliability Engineering(O'Reilly)的第 4 章。
遥测和度量标准用于解决以下问题:
- 应用程序每分钟收到多少请求?
- 有没有错误?
- 什么是应用程序延迟?
- 订购需要多长时间?
通常会将数据刮取或推送到时间序列数据库(例如 Prometheus 或 InfluxDB)进行聚合。遥测数据的唯一要求是它将被收集数据的系统格式化。
至少,可能最好实施度量标准的 RED 方法,该方法收集应用程序的速率,错误和执行时间。
请求率
收到了多少个请求
错误
应用程序有多少错误
时间
多久才能收到回复
遥测数据应该用于提醒而非健康监测。在动态的、自我修复的环境中,我们更少关注单个应用程序实例的生命周期,更多关注关于整体应用程序 SLO 的内容。健康报告对于自动应用程序管理仍然很重要,但不应该用于页面工程师。
如果 1 个实例或 50 个应用程序不健康,只要满足应用程序的业务需求,我们可能不会收到警报。度量标准可让您知道您是否符合您的 SLO,应用程序的使用方式以及对于您的应用程序来说什么是 “正常”。警报有助于您将系统恢复到已知的良好状态。
如果它移动,我们跟踪它。有时候我们会画出一些尚未移动的图形,以防万一它决定为它运行。
——Ian Malpass,衡量所有,衡量一切
警报也不应该与日志记录混淆。记录用于调试,开发和观察模式。它暴露了应用程序的内部功能。度量有时可以从日志(例如错误率)计算,但需要额外的聚合服务(例如 ElasticSearch)和处理。
一旦你有遥测和监测数据,你需要确保你的应用程序对故障有适应能力。弹性是基础设施的责任,但云原生应用程序也需要承担部分工作。
基础设施被设计为抵制失败。硬件用于需要多个硬盘驱动器,电源以及全天候监控和部件更换以保持应用程序可用。使用云原生应用程序,应用程序有责任接受失败而不是避免失败。
在任何平台上,尤其是在云中,最重要的特性是其可靠性。
——David Rensin,e ARCHITECT Show:来自 Google 的关于云计算的速成课程
设计具有弹性的应用程序可能是整本书本身。我们将在云原生应用程序中考虑弹性的两个主要方面:为失败设计和优雅降级。
唯一永远不会失败的系统是那些让你活着的系统(例如心脏植入物和刹车系统)。如果您的服务永远不会停止运行,您需要花费太多时间设计它们来抵制故障,并且没有足够的时间增加业务价值。您的 SLO 确定服务需要多长时间。您花费在工程设计上超出 SLO 的正常运行时间的任何资源都将被浪费掉。
您应该为每项服务测量两个值,即平均无故障时间(MTBF)和平均恢复时间(MTTR)。监控和指标可以让您检测您是否符合您的 SLO,但运行应用程序的平台是保持高 MTBF 和低 MTTR 的关键。
在任何复杂的系统中,都会有失败。您可以管理硬件中的某些故障(例如,RAID 和冗余电源),以及某些基础设施中的故障(例如负载平衡器)。但是因为应用程序知道他们什么时候健康,所以他们也应该尽可能地管理自己的失败。
设计一个以失败期望为目标的应用程序将比假定可用性的应用程序更具防御性。当故障不可避免时,将会有额外的检查,故障模式和日志内置到应用程序中。
知道应用程序可能失败的每种方式是不可能的。假设任何事情都可能并且可能会失败,这是一种云原生应用程序的模式。
您的应用程序的最佳状态是健康状态。第二好的状态是失败状态。其他一切都是非二进制的,难以监控和排除故障。 Honeycomb 首席执行官 CharityMajors 在她的文章 “Ops:现在每个人都在工作” 中指出:“分布式系统永远不会起作用;它们处于部分退化服务的持续状态。接受失败,设计弹性,保护和缩小关键路径。”
无论发生什么故障,云原生应用程序都应该是可适应的。他们期望失败,所以他们在检测到时进行调整。
有些故障不能也不应该被设计到应用程序中(例如,网络分区和可用区故障)。该平台应自主处理未集成到应用程序中的故障域。
云原生应用程序需要有一种方法来处理过载,无论它是应用程序还是负载下的相关服务。处理负载的一种方式是优雅降级。 “站点可靠性工程” 一书中描述了应用程序的优雅降级,因为它提供的响应在负载过重的情况下 “不如正常响应准确或含有较少数据的响应,但计算更容易”。
减少应用程序负载的某些方面由基础设施处理。智能负载平衡和动态扩展可以提供帮助,但是在某些时候,您的应用程序可能承受的负载比它可以处理的负载更多。云原生应用程序需要知道这种必然性并作出相应的反应。
优雅降级的重点是允许应用程序始终返回请求的答案。如果应用程序没有足够的本地计算资源,并且依赖服务没有及时返回信息,则这是正确的。依赖于一个或多个其他服务的服务应该可用于应答请求,即使依赖于服务不是。当服务退化时,返回部分答案或使用本地缓存中的旧信息进行答案是可能的解决方案。
尽管优雅的降级和失败处理都应该在应用程序中实现,但平台的多个层面应该提供帮助。如果采用微服务,则网络基础设施成为需要在提供应用弹性方面发挥积极作用的关键组件。有关构建弹性网络层的更多信息,请参阅附录 A。
可用性数学
云原生应用程序需要在基础设施之上建立一个平台,以使基础设施更具弹性。如果您希望将现有应用程序 “提升并转移” 到云中,则应检查云提供商的服务级别协议(SLA),并考虑在使用多个服务时会发生什么情况。
让我们拿运行我们的应用程序的云来进行假设。
计算基础设施的典型可用性是每月 99.95%的正常运行时间。这意味着您的实例每天可能会缩短到 43.2 秒,并且仍在您的云服务提供商的 SLA 中。
另外,实例的本地存储(例如 EBS 卷)也具有 99.95%的可用性正常运行时间。如果幸运的话,他们都会同时出现故障,但最糟糕的情况是他们可能会在不同的时间停机,让您的实例只有 99.9%的可用性。
您的应用程序可能还需要一个数据库,而不是自己安装一个计算可能的停机时间为 1 分 26 秒(99.9%可用性)的情况下,选择可靠性为 99.95%的更可靠的托管数据库。这使您的应用程序的可靠性达到 99.85%,或者每天可能发生 2 分钟和 9 秒的宕机时间。
将可用性乘到一起可以快速了解为什么应以不同方式处理云。真正不好的部分是,如果云提供商不符合其 SLA,它将退还其账单中一定比例的退款。
虽然您不必为停机支付费用,但我们并不知道世界上存在云计算信用的单一业务。如果您的应用程序的可用性不足以超过您收到的信用额度,那么您应该真正考虑是否应该运行这个应用程序。
因为云原生应用程序被设计为在云环境中运行,所以它们与基础设施以及相关依赖应用程序的交互方式不同于传统应用程序。在云原生应用程序中,与任何事物的通信都需要通过网络来进行。很多时候,网络通信是通过 RESTful HTTP 调用完成的,但是也可以通过其他接口实现,比如远程过程调用 (RPC)。
传统的应用程序会通过向消息队列发送消息、在共享存储上写入文件或触发本地 shell 脚本来执行自动化任务。通信方法基于发生的事件作出反应(例如,如果用户单击提交,运行提交脚本)并且通常需要存在于同一物理或虚拟服务器上的信息。
Serverless
无服务器平台是云原生化的,并被设计为对事件做出反应。他们在云中工作得很好的原因是他们通过 HTTP API 进行通信,(这些 API)是单一用途的函数,并且在它们的调用中是声明性的。该平台还使它们可伸缩并可从云内访问。
传统应用程序中的反应式通信通常是构建弹性的一种尝试。如果应用程序(以反应式的方式)在磁盘上或消息队列中写入了一个文件,然后应用程序死亡,那么该消息或文件的结果仍然可以完成。
这里并不是说不应该使用像消息队列这样的技术,而是说在动态且经常出现故障的系统中,不能将它们作为惟一的弹性层来依赖。从根本上说,在云原生环境之中,应用程序之间的通信方法应该有所变化 - 这不仅是因为还存在其他方法来构建通信弹性(请参阅附录 A),而且还因为如果要让传统的通信方法在云中实现复制,我们往往需要做更多工作。
当应用程序可以信任通信的弹性时,它们应该放弃反应式并使用声明式。声明式通信信任网络会将消息送达。它也相信应用程序将返回成功或错误。这并不是说让应用程序观察变化不重要。Kubernetes 的控制器对 API 服务器做的就是这个。但是,一旦发现变更,他们就会声明一个新的状态,并相信 API 服务器和 kubelets 会做必要的事情。
声明式通信模型由于多种原因而变得更加健壮。最重要的是,它规范了通信模型,并且它将(如何从某种状态到达期望状态的)功能实现从应用程序转移到远程 API 或服务端点。这有助于简化应用程序,并使它们彼此的行为更具可预测性。
希望你可以知道云原生应用程序与传统应用程序不同。云原生应用程序不能直接在 PaaS 上运行或与服务器的操作系统紧密耦合。它们期望在一个拥有大多数自治系统的动态环境中运行。
云原生基础设施在提供自主应用管理的 IaaS 之上创建了一个平台。该平台建立在动态创建的基础设施之上,以抽象出单个服务器并促进动态资源分配调度。
自动化与自治不一样。自动化使人类对他们所采取的行动产生更大的影响。
云原生是关于不需要人类做出决定的自治系统。它仍然使用自动化,但只有在决定了所需的操作之后。只有在系统不能自动确定正确的事情时才应该通知人。
具有这些特征的应用程序需要一个能够实际监控,收集度量标准并在发生故障时做出反应的平台。云原生应用程序不依赖于人员设置 ping 检查或创建 Syslog 规则。他们需要从选择基本操作系统或软件包管理器的过程中提取自助服务资源,并依靠服务发现和强大的网络通信来提供丰富的功能体验。