Appearance
《云原生:运用容器、函数计算和数据构建下一代应用》精读笔记
写在前面
- 书籍介绍:本书首先介绍一些分布式系统的基本原理及其与云原生应用的关系,然后再进一步介绍容器和函数等相关技术,接着,本书会介绍服务间的通信模式、服务的弹性和数据模式,并讨论在什么情况下应该使用这些技术,最后会总结一些经验性的东西,例如如何结合DevOps方法,怎么兼顾可移植性,以及一些最佳实践。
- 我的简评:暂无
- !!福利:文末有笔记思维导图、随书资料打包等下载地址哦
第1章 云原生简介
1.1.分布式系统
- 在刚开始构建云原生应用时,开发者们遇到的最大障碍之一是这些应用服务往往运行于不同的机器之上,所以需要考虑机器间的网络通信模式。
- 大多数云服务供应商会使用便宜一些的商用机,然后依靠软件的解决方案来处理诸如高可用和可靠性等常见问题
- 几个误区,其含义同样适用于云原生应用:网络是安全的;延迟是不存在的;带宽是用之不尽的;网络是安全的;拓扑结构是不变的;一个管理员可以搞定一切;传输是没有成本的;网络通信都是一样的;
- 一个现代的云原生应用一般会由很多服务组成,这些服务需要协同工作,但是它们往往由不同的团队开发。
- CAP定理指出,任何一个通过网络连接的、共享数据的分布式系统最多只能同时满足以下三个需求中的两个:·一致性(Consistency,C);可用性(Availability,A);分区容错性(Partition tolerance,P);
- 很多NoSQL数据库(如Cassandra)会选择追求高可用性,而像关系型数据库会为了遵循ACID原则(原子性、一致性、隔离性和持久性)而追求一致性
1.2.十二要素应用
- 传统数据中心在需要扩容时经常采用纵向扩容的方式,即通过增加单台物理服务器的计算资源来进行扩容。而在云端,通常采用的是横向扩容的方式,即通过增加虚拟服务器的数量来分担负载。
- 十二要素的内容:基准代码(一份基准代码,多份部署);依赖(显式地声明依赖关系并隔离依赖);配置(配置和代码应该严格分开,这样你才能够轻松地配置不同的环境);后端服务(把后端服务当作附加资源);构建、发布和运行(严格分离构建和运行阶段);进程(一个或多个无状态的进程来运行应用);数据隔离(每个服务管理自己的数据);并发(通过进程模型进行扩展);易处理(通过快速启动和优雅退出来最大化应用的健壮性);开发环境与线上环境等价(尽可能地保持开发环境、预发布环境和生产环境相同);日志(把日志当作事件流);管理进程(把后台管理任务当作一次性进程来运行)
1.3.可用性和服务等级协议
- 大多数情况下,一个云原生应用是由多个服务组合而成的,这些服务常常被封装在容器中或者函数中,此外也经常会用到一些托管云服务,比如数据库服务、缓存服务和身份认证服务等。
第2章 云原生基础
- 构建一个云原生应用有很多不同的技术和工具,从计算的角度来看主要有两个,一个是容器,另一个是函数计算
- 理解了如何利用好函数计算和容器技术,配合事件通知和消息通信相关技术,开发者们就可以最有效和快速地构建下一代基于微服务架构的云原生应用。
2.1.容器
- 容器的最初想法是把操作系统分割成几块,每一块都可以安全地运行应用程序,它们之间不会产生相互干扰。
- VM为基础来构建云原生应用则会有以下缺点:启动需要整个操作系统;占用的空间大;扩容也是一个挑战;还会带来一些额外的开销,会消耗更多的内存、CPU和存储资源;
- 诸如此类的技术有Hyper-V容器、沙盒容器(sandbox container)和微虚机(MicroVM)等
- 最流行的一些微虚机技术:Nabla容器;谷歌的gVisor;微软的Hyper-V容器;Kata容器;亚马逊的Firecracker;
- 容器编排主要包含以下内容:在集群节点上创建和部署容器实例;容器的资源管理;监控容器以及集群节点的运行状况;在集群内对容器进行扩容或收缩;为容器提供网络映射服务;在集群内为容器提供负载均衡服务;
- Kubernetes常常被视为容器平台、微服务平台以及为云计算提供可移植性保障的中间层。
- Kubernetes集群包含三大类组件:主节点组件、节点组件以及插件
- 主组件提供了集群的控制管理功能,是集群的控制层,主要负责集群的全局任务,比如集群的任务调度、事件响应(当有服务故障时重启服务以及当某个服务的实例数量不够时启动新的实例等)。
- pod基本上可以认为是一个管理容器整个生命周期的一个封装。它里面包含了一个或者多个容器、存储资源、唯一的网络IP。尽管Kubernetes支持一个pod可以包含多个容器,但在大多数情况下每个pod只有一个运行真正应用程序的容器,其他都是边车容器(sidecarcontainer)
- Kubernetes从诞生开始就支持Docker运行时,但它并不是Kubernetes唯一支持的容器运行时。事实上,Kubernetes社区已经推出了一个将不同容器运行时集成到Kubernetes的通用方法。
- 作为一个Linux和Windows系统中的守护进程出现,负责管理它所在的这个主机上所有容器的整个生命周期(包括容器镜像的管理、容器的运行、底层存储、网络配置,等等)
2.2.无服务器架构
- 开发人员的角度看,无服务器架构通常会伴随着事件驱动的编程模型,而从经济角度看,无服务器架构意味着你只需要按执行所耗费的资源付费
- 函数即服务只是无服务器架构的一种。微软的Azure's Container Instances(ACI)和Azure SF Mesh,以及AWS Fargate和GCP的无服务器容器Cloud Functions就是很好的例子
2.3.函数计算
- 无服务器计算带来的快速启动和执行的优势,加上对应用程序的简化使得FaaS产品对开发人员产生了巨大的吸引力,因为他们只需要专注在写业务代码上,而不需要操心底层架构了
- Kubeless、OpenFaaS、Serverless和Apache OpenWhisk是几个比较受欢迎的可以自己安装的FaaS平台,而Azure Functions自开源以来变得越来越受欢迎。
- 许多可安装的FaaS框架利用Kubernetes来实现路由、自动伸缩以及监控等功能
2.4.从虚拟机到云原生
- 向云原生世界的演进主要有两种途径。第一种是“旧房改造”,意思是你有一个历史遗留应用,通过对这个应用的转变和提升,使其变得更现代化,这是一个程序优化的过程。另一种是“白手起家”,也就是从无到有创建一个云原生应用
- 现在,几乎所有的新应用都在使用容器技术,越来越多的历史遗留下的巨石应用(monolithic application)正在被容器化。
- 两个从巨石应用转变到微服务的主要模式值得一提:刀砍模式(通过微服务架构来实现新的服务,然后逐步替换掉老的应用中的部分组件功能,可以采用网关(gateway)或者外观层(facade)来将用户请求导向正确的应用);防损层模式(通过建立防损层可以使新服务访问老服务,反过来老服务也可以调用新服务);
- 像Istio或Linkerd之类的服务网格(service mesh)越来越受欢迎了,因为它们将一些分布式系统复杂性转移到平台层
- 可以暂时将服务网格理解为处理服务与服务间通信的专用网络基础架构层。服务网格还帮助你的应用实现弹性,提供了诸如重试、断路器、分布式追踪和路由等功能
- 改进应用程序的下一个阶段就是用无服务器架构来承担容器的工作负载,比如像Azure Container Instances或者AWS Fargate这样的容器即服务(CaaS)产品。
- 进一步改进应用的下一个阶段是如何从代码层级做优化,而不是仅仅进一步降低成本。函数计算在处理短任务方面是非常出色的,例如更新记录、发送电子邮件、消息转换,等等。
2.5.微服务
- 在巨石架构中,通常只有一份代码库,共享一个数据库和数据结构,而在微服务架构中,应用程序由多个较小的代码库组成,由独立团队开发和管理。
- 微服务架构的优势:敏捷性(对大型的巨石应用而言,快速、可靠地部署是一件具有挑战性的事情);持续创新(小型的独立团队甚至可以在业务最繁忙的时候仍旧发布新的功能,同时进行A/B测试,以便提升转化率或改善用户体验);渐进式设计(松耦合和高凝聚是系统设计很重要的两个方面,它们可以保障应用可以通过新技术不断地进行演进);小而专的团队(构建一个大规模的研发团队,还要让他们保持足够的专注和高效率是非常有挑战的事情);故障隔离(在巨石应用中,一个库或者模块的故障可能会导致整个应用的故障);更好的扩容和资源利用能力(应用通常可以通过纵向或者横向扩容。纵向扩容是指增加机器的资源或者更换一种更好的机器来实现扩容。而横向扩容是指部署更多的实例,然后通过路由的配置来使用户可以访问这些实例,从而达到扩容的目的);改善可观察性(通过把应用拆分独立的服务,团队可以使用工具深入了解各个功能的运行状况以及和其他服务的交互情况)
- 微服务架构带来的挑战:复杂性(无法假设网络总是可靠的、延迟是不存在的、带宽是用之不尽的、网络是安全的、拓扑结构是不变的、一个管理员可以搞定一切、传输是没有成本的、网络通信都是一样的);数据完整性和一致性(分布式系统中的数据往往都是分散的,这意味着数据通常存在于多个地方,并且相互之间的关系会跨越多个不同的系统);性能(网络请求和数据的序列化会增加开销。在基于微服务架构开发的应用中,网络请求的数量会增加);开发和测试(想要在发布到生产环境之前用所依赖的服务的各个版本跑一遍完整的测试是非常困难的);版本控制和集成(在做服务的版本控制时,必须要特别小心保持服务的向前和向后兼容性);监控和日志管理(微服务架构中,不同的服务可能会使用不同的日志记录工具);服务依赖管理(微服务架构下,管理服务依赖项的方法不太一样,需要基于特定环境的路由和服务发现);可用性(随着服务数量的增加,单个服务遇到故障的可能性也在增加)
第3章 云原生应用的设计
- 在开始设计一个云原生应用时,一个好的着手点是考虑这五方面:精益运营、安全性、可靠性、可扩展性和成本。从实际开发的角度讲,解决这些特定的问题都有一些非常有用的模块、模式和技术方法。
3.1.云原生应用的基础
- 微服务架构下是鼓励小型独立团队去构建其独立的服务的。
- 团队的每个成员都需要去了解应该如何调用另一个团队所开发的服务,或者每个人都应该理解如何去正确地部署和配置一个环境。
- 你应该使用一个私有的注册服务器,从而可以通过使用基于角色的访问控制(RBAC)策略来跟踪和审核谁有权访问该注册服务器。
- 你需要确保容器镜像只能从经过验证的容器注册服务器获取。
- 你需要确定是否需要通过公网来访问你集群编排工具所在的服务器,或者VPN是否足够了。
- 在设计一个云原生应用的时候,你不能光考虑如何去做应用的扩容,还需要考虑怎么最经济地去做扩容。
- 一个更好的解决方案是在初始时设置一个比较小的集群规模,然后当发现集群节点的资源不足以支撑所有实例运行的时候再通过自动扩容机制进行水平扩容
3.2.云原生与传统架构的对比
- 云原生应用和传统巨石应用最根本的一个区别在于它们如何处理状态信息,比如会话状态、应用及配置数据等。
- 云原生应用经常使用事件驱动的通信模式。我们把多个松耦合的服务间的相互调用称为服务编舞(service choreography)
- 每个服务都是被隔离的、自治的,并负责管理自己的状态
- 云原生架构总是会对故障有所准备,并且有处理故障的机制。而传统架构总是试图规避故障,比如说,通过数据库集群来避免数据库故障等
3.3.函数计算与服务
- 对于简单的、短期和独立的任务应该考虑使用FaaS。
- 适合使用函数计算的场景:简单的并行执行任务;许多物联网(IoT)场景使用函数来编排任务;某些应用可以完全使用FaaS产品来构建;
- 用函数计算来构建整个应用时,你需要牢记这些注意事项:从巨石应用转向微服务的挑战;有限的函数生命周期;无法利用专用硬件;函数是无状态的且不直接对外暴露;FaaS鼓励使用事件驱动的分布式编程模型或者使用API网关这样的方案来对外提供函数的功能;本地开发和调试;经济性;
- 大多数情况下,将函数计算和容器化服务结合在一起是一个很好的解决方案。
- 推荐的做法是将数据存入高可用的托管服务,比如关系型数据库管理系统(RDBMS)或者缓存服务。你也可以利用Kubernetes的StatefulSet特性来部署有状态应用,该特性利用了持久化存储卷
3.4.API设计与版本控制
- 根据Jean-Jacques Dubray所做的研究,API版本控制的成本取决于你采用的策略。他分类了三种不同的策略
- 研究结果表明,兼容性版本控制策略是最高效的
- REST本身不提供任何特定的版本控制约定,但有三种方法来实现版本控制:全局版本控制、资源版本控制和基于mime的方法。
3.5.服务间的通信
- 网络和服务通信是分布式系统最基础的话题,它们对一个应用的性能起着至关重要的作用
- 服务间的通信分为两类,一类是外部服务通信,另一类是内部服务通信。内部通信是指通信发生在同一个集群中(例如,同一个Kubernetes集群中的服务之间的通信),而外部通信是指与外部服务的相互通信,比如调用数据库服务(DBaaS)。
- 在Kubernetes环境下,入口(ingress)控制器用于南北通信,出口(egress)控制器可用于访问外部服务。
- 大多数情况下,HTTP协议会被用来作为客户端和云原生应用程序之间的通信协议。
- websocket使得客户端和服务器之间可以建立一个基于TCP套接字的长连接,从而可以在几乎不增加额外开销的情况下实现即时分发、双向全双工的消息通信。
- HTTP/2的主要设计目的是实现通信的低延迟,并在单个TCP连接上通过流(stream)来实现多路复用请求,从而提高数据传输的效率。
- 消息传递协议有很多,如STOMP、WAMP、AMQP和MQTT等。
- 消息队列遥测传输(MQTT)是一种二进制协议,主要应用于IoT和机器间通信的场景下。它是为有不稳定的网络连接的低带宽网络环境而设计的。
- MQTT的优势在于简单性和紧凑的二进制数据包
- 高级消息队列协议(AMQP)也是一种二进制协议,它的设计目的主要是提供具有丰富功能集的消息传递,这些功能集包括可靠的队列、基于主题的发布者/订阅者模式、消息路由、安全性和事务。
- 一般的经验是,如果你需要简单、可靠的消息传递,则使用MQTT。需要用到更多的功能并且更关注多供应商间的兼容性,则使用AMQP。
- JSON可能是目前使用最广泛的格式。protobuf使用的是二进制格式,因此,每种语言都需要有个生成器。尽管对于性能要求高并且数据量大的应用而言,protobuf常常是最佳的选择
- 发布者/订阅者(pub/sub)模式是云原生应用中最常见的异步通信模式之一。发布者将消息发布到一个主题,订阅该主题的所有订阅者将立即接收到该消息
- 队列是发布者/订阅者模式传递消息的基础
- 云原生架构中,选择发布者/订阅者模式还是请求/响应模式实际上取决于使用场景
- 在云原生世界中,基于事件和基于队列的异步消息传递是进程间通信最受欢迎的模式
3.6.网关
- 概括而言,网关可以分为两大类:API网关和应用程序网关。
- 网关是可以分层的:你可以让一个网关负责SSL终结,下一个网关进行鉴权和授权,然后最后一个网关可以实际将请求路由到后端服务
- 路由是网关最常见的功能之一。在这种情况下,网关充当一个反向代理,并将传入的请求路由到后端服务
- 反向代理通常位于内网中,负责管理用户请求,将其导向正确的后端服务
- 网关负责根据IP、端口、标头或URL将请求路由到对应的各种服务上。
- 网关也可以充当聚合器:它从客户端接收一个请求,然后把这个请求拆分成多个子请求,发送到不同的后端服务
- 用网关来做聚合的话有一点需要注意,你应该尽量避免在网关和后端服务之间产生任何耦合,另外还需要注意由此造成的额外负载。
- 聚合主要好处是可以减少客户端和服务之间的流量。将聚合的逻辑移出网关可以减轻网关的负载。
- 网关最常见的功能之一是为独立的服务减负,将一些后端服务的功能卸载到网关中实现。
- 一些可以从单个服务中卸载到网关级别处理的功能示例:鉴权和授权;速率限制、重试策略、断路;缓存;压缩;SSL终结;日志和监控
- 实现网关有很多种技术和方法。最受欢迎的网关代理是NGINX、HAProxy和Envoy。
3.7.出口网关
- 入口网关处理的是进入系统的流量,并且可以承担各种任务,例如路由或卸载部分后端服务的功能。
- 在内网运行的出口网关(egress gateway)可以帮助引导和控制所有离开内网的流量
3.8.服务网格
- 需要用一套方法来实现请求重试、超时定义、断路器等机制
- 服务网格背后的想法之一是通过将通用功能从每个服务抽出来移入到服务网格中,从而提高开发人员的生产力。这样做的另一个好处是实现了服务的业务功能和服务网格的通用功能之间的分离,使得开发人员能专注在业务上
- 代理的工作是监听所有进出服务的请求。每个代理都可以有其自己的配置,该配置定义了如何处理流入或流出的流量。除了处理流量和请求外,代理还可以向网格服务控制器发送各种数据。
- 可以将服务网格的主要功能分成以下几类:流量管理;故障处理;安全性;追踪和监控
- 服务网格中的流量管理功能的目的是管理网格内的服务之间的流量,以及网格内服务与外部服务之间的流量
- 网格中的每个服务都可能有多个实例,对应不同的访问接入点
- 分布式系统中,你应该始终假设服务通信可能会由于各种原因的故障而中断。
- 故障有两种类型:瞬时故障和非瞬时故障。瞬时故障随时可能发生,并且在大多数情况下,多试几次就能恢复。非瞬时性故障更为持久。
- 断路器模式通过对故障服务的访问限制来防止其进一步导致其他服务的故障,甚至导致对整个系统的压力。
- 故障处理的另一个功能是提供错误注入的能力
- 鉴权、双向TLS,以及JWT令牌:我们可以把鉴权分为两类:服务于服务之前的鉴权和终端用户的鉴权
- 任何在服务网格中定义的鉴权策略都可以在不同的范围去应用。在Istio中,策略的存储可以有不同的范围,如命名空间范围(namespace-scope)或者是网格范围(mesh scope)。两者的区别是,命名空间范围的存储中的策略只会作用于该命名空间内的服务,而网格范围的存储中的策略则会对所有该网格内的服务有效。
- Istio支持以下几种层级的访问控制:命名空间级别的访问控制;服务级别的访问控制;方法级别的访问控制
3.9.架构示例
- 任何一个好的架构设计都是基于业务需求的,架构上的重要需求是选择架构方法的主要因素
- 另一个订阅者将数据处理后保存到时序数据库服务中,如Azure Time Series Insights、Amazon Timestream或者Google BigTable。
- 另一个订阅者负责处理流中的数据,以执行复杂的事件处理或者流分析。
- 一些是在Kubernetes集群中运行的容器,而另一些是在云服务供应商的FaaS平台上运行的函数
- API网关用于减轻一些API管理任务。API网关负责对请求进行鉴权并限制发送过多请求的用户,以维护所有使用该服务的用户的服务质量
第4章 数据处理
- 微服务架构的一个趋势是鼓励数据去中心化,将数据分散到多个服务中,每个服务都有自己的数据存储。数据副本和数据分区也是很常见的一种扩展系统的办法
- 云原生应用充分利用了托管和无服务器数据存储和处理服务。所有主流的公共云服务商都提供了许多不同的托管服务来存储、处理和分析数据
- 混合持久化、去中心化的数据和数据分区有许多好处,但也需要仔细思虑,权衡利弊
4.1.数据存储系统
- 不同类型文件的存储系统:对象存储;文件存储;磁盘(块)存储
- 了解数据库的各种分类有助于你在设计应用时选择正确的数据库:键值数据库;文档数据库;关系型数据库;图数据库;列族数据库;时序数据库;搜索引擎;
- 迄今为止,最流行和最常用的数据库仍然是关系型数据库。
- 图数据库可以很好地分析实体之间的关系。图数据也可以存储在任何其他数据库中,但是当图的遍历变得越来越复杂时,其他类型的存储可能很难满足图数据对性能和伸缩性的需求
- 列族数据库(column family database)将数据组织成行和列,乍一看可能与关系型数据库非常相似。
- 时序数据库非常适合存储遥测数据。流行的用途包括物联网(IoT)传感器或应用程序/系统的计数器
- 搜索引擎数据库通常用于搜索保存在其他存储和服务中的数据。搜索引擎数据库可以对大量的数据建立索引,并提供近实时的索引查询。
- 一个不错的着手点是先考虑哪种存储模型最符合你的业务需求,然后再根据功能集、成本和易于管理等因素考虑应该选择这个类别中哪一款数据存储产品。
4.2.多数据存储下的数据
- 分散的数据存储带来的一些挑战:跨多个数据存储的数据一致性;多个数据存储下的数据分析;多个数据存储下的备份和恢复;
- 数据是从各种系统中提取或者导出来的,包括业务系统、数据存储系统、历史遗留系统、运营数据库、外部服务,甚至是事件企业资源计划(ERP)系统或客户关系管理(CRM)系统。
- 转换通常涉及许多数据清洗、转换和扩充任务。转换后的数据最后被加载到目标存储中,然后被用作商业智能的分析。
4.3.客户端访问数据
- 数据的访问通常需要经过一个服务,这个服务负责数据的授权、审核、验证和转换。
- GraphQL既不是一个数据库查询语言,也不是一个存储模型。它是一个API服务,可以根据定义的模式返回应用所需的数据,并且完全与数据的存储方式无关
4.4.可快速伸缩的数据
- 有一个很普遍的争论点是,在满足应用程序的数据质量要求的同时可能很难满足扩展性要求。
- 通过复制和分区可以达到扩展系统的目的。将数据复制到缓存、实例化视图或只读副本可以提高数据系统的伸缩性、可用性和性能。通过分片对数据进行水平分区,或者基于数据模型进行垂直分区,再或者是基于功能对数据进行分区都可以使系统的负载得到分摊,从而提高可伸缩性
- 数据分片是指将数据存储划分为水平分区。每个分片都包含相同的数据模式,但包含不同数据集。分片通常用于通过在多个数据存储系统间分摊负载来达到扩展系统的目的
- 内容分发网络(CDN)是一组分布在不同地理位置的数据中心,也称为接入点(Point Of Presence,POP)。CDN通常用于在更靠近用户的地方缓存静态内容。
4.5.数据分析
- 各种从数据中提取信息的工具和技术也在不断发展,以满足日益增长的从数据中挖掘价值的需求,即使是小企业也可以通过复杂的分析获得业务洞见
- 一些数据(例如股票市场的交易、点击事件流或设备中的传感器数据)会以事件流的形式出现,这些事件永远不会结束。对数据流的处理可用来检测一些特定的模式、识别序列及查看结果。
- 数据流的处理不同,流处理是在数据到达时实时进行的,而批处理通常是在非常大的有限数据集上进行的,比如探索一个数据科学假设,或者以特定的时间间隔来获取业务洞见任务。
- 数据分析系统通常会结合使用批处理和流处理。
- 数据湖是大型的、可伸缩的,通常是集中式的数据存储,可以用来存储结构化和非结构化数据。它们通常用于执行映射和归约任务以分析大量数据
- 数据湖通常用于存储原始数据和非结构化数据,而数据仓库中的数据已被处理并组织在定义良好的结构中。通常将数据写入数据湖,然后将其从数据湖处理后转存到数据仓库。
- 市场上已经有许多流行的开源查询引擎:Presto、Spark SQL、Drill和Impala等。这些查询引擎利用了提供者模型来访问各种不同的数据存储分区和系统
- Hadoop作业的设计目标是能够以作业的形式通过持续运行数分钟到数小时的任务来处理大量的数据
4.6.Kubernetes中的数据库
- Kubernetes pod可以被动态地创建和销毁,而集群的节点也可以被添加或删除,从而迫使pod移至新节点。
- 除了提供基础存储卷外,数据存储系统还会有一些其他不同的需求,如路由及可连接性需求、支持不同硬件的需求、调度需求和运营需求等
- StatefulSet旨在解决在Kubernetes上运行有状态服务(例如数据存储系统)的问题
- StatefulSet根据容器的规范管理一组pod的部署和扩展。StatefulSet会保证相关pod的顺序和唯一性。根据规范创建的pod每个都有一个永久性标识符,该标识符在所有该pod的重建过程中都会存在。
- 亲和性策略(affinity)和反亲和性策略(anti-affinity)是Kubernetes的一项功能,它可以让你决定将pod限制在哪些节点上运行。
- DaemonSet可以确保在一组节点内,每个节点都会运行一个指定pod的实例。
第5章 DevOps
5.1.什么是DevOps
- 最终的效果体现在部署频率的提升、产品上市时间的缩短、新版本故障率的降低、故障修复间隔的缩短及故障恢复平均等待时间的缩短上
- DevOps时可以使用一种称为CALMS的模型,它的意思是协作、自动化、精益、度量和分享
- 想要在几分钟内把一个刚刚实现的功能或代码变成一个在生产环境中实际可用的功能就需要大量可靠的自动化。需要自动化的关键元素是基础设施、持续集成(CI)的流程、构建代码后的测试、持续交付(CD)流程以及部署后的测试。
- 上云的主要好处之一是基础设施可以自动化。基础设施即代码(Infrastructure as Code,IaC)是一种用代码而不是通过手动流程来配置和管理基础设施的方法。所有的基础设施,如服务器、网络和数据库,都用代码来处理。
- 积极分享的目的是实现共同进步,改善整个行业
- 网站可靠性工程(Site Reliability Engineering,SRE)是在21世纪初期从Google诞生的
5.2.测试
- 根据不同函数和系统的复杂程度,你可以使用依赖项注入(dependency injection)或环境变量来确定接入点,但是大多数情况下,你可以利用一个或多个测试替身(test double)来完成任务
- 测试替身指的是一个你可以用来替代一个真实对象的模拟对象。
- 三种最常见的测试替身类型是模拟(mock)、伪造(fake)和桩(stub)。通过模拟,你可以定义函数如何被调用并定义预期行为。伪造指的是一种简化的实现,比如伪造一个API。桩指的是不包含任何业务逻辑,并且仅返回预设的值的一种测试替身。
- 金字塔的最重要部分,即底部,是单元测试。单元测试是你应该拥有的数量最多的测试。
- 在进行服务级别的测试时,你需要把服务或组件当成一个整体,并且和前端UI分割开。
- UI测试应该代表所有金字塔测试中最少的测试数量。
- Jepsen库可以部署一个分布式系统,对这个系统进行一系列的操作,然后验证这些操作是否合理。可以用Jepsen来分析数据库、协作服务以及队列服务,它可以帮你找出很多问题,包括数据丢失、陈旧数据、锁冲突,等等。
- 性能测试的目的是通过测量一些指标,如某个操作花费多长时间,来使你了解应用程序或服务的性能状况。
- 负载测试是性能测试的一种,可用于确定某些特定情况下系统的性能。
- 安全和渗透测试的目的是确定你的系统是否可能受到不同类型攻击的影响
- A/B测试通常是在生产环境中运行的服务上执行的。A/B测试的目的是确定服务的一个版本(A)是否比另一版本(B)更好。
- 可以使用验收测试来确定你的服务是否已经做好了发布到其他环境的准备。
- 顾名思义,这些测试用于验证将要应用于服务和代码的配置是否是正确的,并且是否已经为服务运行做好了准备。
- 冒烟测试指的是一组用来快速判断你的服务、组件或应用是否可靠的测试,通常在执行更多更深入的测试前进行。
- 集成测试通常指的是对多个服务以及这些服务之间的交互进行的测试。
- 混沌测试的目的是对系统造成破坏,并向系统中随机引入故障。
- 模糊测试指的是将一组随机的、无效的或预期之外的数据发送到你的服务或组件中,以使其出错。
- 你应当定期去做安全测试、模糊测试、负载测试以及性能测试,但是可能并不需要对每次构建或每个代码改动做这些测试,除非这个改动会影响系统的安全或者性能
- 根据我们的经验,使用独立的环境进行测试时的最大问题是如何保证它的状态是最新的并且是跟实际生产环境保持同步的。
- 部署是将编译、打包并经过测试的代码移到生产环境的过程。服务的发布涉及逐渐增加定向到已部署服务的实际流量。
5.3.开发环境和工具
- 本地的开发环境可以支持快速的开发流程,能够使开发者快速地迭代、测试、修复代码故障。
- MiniKube可以在一个VM中运行一个单节点的Kubernetes集群,通常在本地开发环境中使用。
- Skaffold是一个命令行工具,可用于将代码改动持续地部署到本地或远程Kubernetes集群中。
- Docker Compose可以启动构建和运行应用程序以及所有依赖项(例如数据库)所需的容器
- 面临的挑战之一是如何最小化将代码改动推送到远程环境所需的时间。
- Scaffold中的同步功能可以避免整个镜像的构建�C推送�C部署过程,并将改动快速推送到正在运行的容器中,从而可以节省大量时间
5.4.持续集成/持续交付
- 持续集成(CI)是一种自动化构建、测试和集成新代码与已有代码的实践,其最终目的是为了发布。
- 单代码库(mono-repo)的想法是将所有代码(所有服务、工具、应用,等等)保存在一个代码库中。
- 流量复制、影子流量或者暗流量。这种方案允许你复制或引用真实生产环境中的流量,并把它发送给部署好的服务。
- 搭建属于你自己的CI/CD流程时需要考虑的重要事项:构建应当是快速的(单代码库或者多代码库);测试应当是可靠的;容器镜像应该尽可能的小;决定生产环境流量的选择策略(所有流量、部分流量、基于特定标准,等等);服务的可监控性是一个成功的CI/CD流程的基石;
5.5.监控
- 整个CI/CD的流程中,监控都是最基本的要求。在发布阶段,监控尤其重要
- Grafana是最受欢迎的监控工具之一,它被称作“优美的分析及监控开放平台”。它采用多种不同的数据源,并用非常漂亮的图、表格、热力图以及其他一些视觉组件将数据可视化。
- Prometheus,这个云原生计算基金会(Cloud Native Computing Foundation,CNCF)下的已完成项目,是一个非常流行的抓取和收集服务数据的工具。
- 几乎每种流行的语言都有Prometheus的客户端库,这些库允许通过一个HTTP接入点来定义和暴露指标。
- 很多数据库(如MongoDB、MySQL、Redis)、消息系统(如Kafka、RabbitMQ)都有导出器,像一些API(GitHub、Docker Hub)以及日志组件(Fluentd)也有导出器,此外像Kubernetes、etcd、Grafana等软件也有Prometheus导出器。
- 当所有的日志都进入了一个集中的系统后,你需要确保每条日志记录都包含了一个ID(请求ID或关联ID[CID]),你可以用这个ID来追踪跨服务的请求和调用
- 分布式追踪工具也可以使用这个ID来串联整个系统中不同服务间的关联请求
- 分布式链路追踪是一种分析和监控服务的方法,它可以帮助你发现故障及性能问题,也可以帮助你调试服务
- OpenTracing致力于为分布式链路追踪创建标准化的API和监测工具
- 与健康检查的代码片段一样,你需要定义一个接入点和访问端口,以便平台可以发送请求。
5.6.配置管理
- 以下是一些常见的app会用到的配置:数据库/队列/消息系统的连接字符串;凭据(用户名、密码、API密钥、证书);超时、端口、依赖服务的名
- Kubernetes平台有一种名为Secret的专用资源,你可以用它来保存这类敏感配置。不用把密码直接放在pod的定义文件中,你可以把它存到一个单独的Secret资源中,然后把这个资源挂载进pod。
5.7.持续集成/持续交付流程示例
- 工作流中的各个步骤:1、代码完成;2、推送至git;3、获取代码;4、源代码分析;5、构建容器;6、单元和服务测试;7、推送到私有注册服务器;8、镜像安全扫描;9、测试配置;预发布环境(10、部署到k8s;11、集成测试;12、回滚;13、发布;14、发布到生产环境);生产环境(10、部署到k8s;11、持续金丝雀测试;12、渐进式发布;13、遥测)
- 无服务器应用CI/CD流程:1、代码完成;2、推送至git;3、获取代码;4、源代码分析;5、构建;6、单元(函数)测试;7、打包;8、创建测试环境;9、部署至测试环境;10、集成测试;11、清理环境;12、部署至生产环境
第6章 最佳实践
6.1.迈向云原生
- 1.找个合理的理由打破巨石应用
- 2.先解耦简单的服务
- 3.学会小规模的运维:1. 将第一个服务当作学习如何在云原生世界中进行服务运维的起点。
- 4.使用防损层模式
- 5.使用刀砍模式:1. 刀砍模式背后的想法是在服务之前用一个网关来充当服务入口,然后你可以逐步将后端的巨石应用切换到新的架构,无论新架构是微服务还是函数,亦或是两者的结合。
- 6.准备一个数据迁移策略
- 7.重写所有模板代码:1. 巨石应用通常会包含大量用于处理配置、数据缓存、数据存储访问等的代码,并且可能正在使用一些很陈旧的库和框架。
- 8.重新考虑框架、语言、数据结构和数据存储
- 9.淘汰老代码
6.2.确保弹性
- 1.用重试来解决瞬时故障:1. 决定重试间隔时间的一些常见策略:恒定时间、线性增长、指数增长
- 2.使用有限次的重试
- 3.用断路器来处理非瞬时故障:1. 断路器的作用是为了防止组件持续执行而导致非瞬时故障。
- 4.优雅地降级
- 5.使用隔离模式
- 6.实现健康及就绪检查:1. 在Kubernetes中,健康状况检查被称为探针。活动性探针用于确定何时应该重启容器,而就绪性探针用于确定pod是否应开始接收流量
- 7.为容器设定CPU和内存限制
- 8.实现限速和限流
6.3.确保安全性
- 云原生世界中的安全性基于共享责任模型。
- 1.安全性需求同其他需求一样重要
- 2.在设计时就考虑安全性
- 3.授予最小访问权限
- 4.使用独立的账号、订阅和租客
- 5.安全地存储所有密钥
- 6.模糊化数据
- 7.传输数据加密
- 8.使用联合身份管理:1. 使用现成的联合身份管理服务(例如Auth0)来处理用户的注册、登录和注销
- 9.使用基于角色的访问控制
- 10.Kubernetes pod的隔离:1. Kubernetes集群中运行的所有pod都是不隔离的,可以接受来自任何来源的请求。在pod上定义网络策略可以起到隔离pod的作用,并使它们能够拒绝任何该策略所不允许的连接
6.4.处理数据
- 1.使用托管数据库和数据分析服务
- 2.使用最符合数据需求的存储
- 3.将数据保存在多个地域或可用区中
- 4.使用数据分区和复制以提高扩展性
- 5.避免过度获取及频繁的I/O操作
- 6.不要把业务逻辑放在数据库中执行
- 7.使用类生产环境数据来测试
- 8.处理瞬时故障
6.5.性能和伸缩性
- 性能表示的是系统在特定时间窗口内执行操作的表现如何,而可伸缩性是指系统如何处理负载增加而不会影响性能。
- 1.设计可扩展的无状态服务
- 2.使用平台的自动伸缩功能
- 3.使用缓存:.1. 缓存是一种通过将经常使用的数据临时存放到离组件更近的存储中,以提高组件性能的一种技术;2. 最基本的一种缓存类型就是单个进程使用的内存。
- 4.用分区来实现超出服务限制的扩容
6.6.函数计算
- 1.编写单一用途的函数:遵循单一用途原则,让一个函数只负责一件事情。
- 2.避免串联函数
- 3.函数应保持轻量和简单
- 4.实现无状态函数
- 5.分离函数入口和函数的业务逻辑
- 6.避免长时间运行的函数
- 7.用队列解决跨函数通信问题
6.7.运维
- 运维实践是组织能否利用好云技术的基石
- 1.部署和发布是两项独立的活动:1. 部署是将已构建好的组件放到一个环境中的过程;2. 发布的过程是我们开始允许流量重定向到部署好的组件上
- 2.部署的内容要尽量小
- 3.组件层级的CI/CD定义
- 4.应用部署的一致性
- 5.采用零宕机发布
- 6.不要变更部署后的架构
- 7.使用容器化构建
- 8.用代码来描述基础设施
- 9.使用命名空间来组织Kubernetes中的服务
- 10.环境间的隔离
- 11.分隔函数源代码
- 12.关联代码提交和部署
6.8.日志、监控及告警
- 应用程序和基础架构的日志记录不仅可以提供故障原因的分析,还可以提供更多的价值
- 1.使用统一的日志系统
- 2.使用关联ID
- 3.在日志记录中包含上下文
- 4.统一的结构化日志格式
- 5.适当地标记指标
- 6.避免告警疲劳:1. 基于机器学习的自动问题分类和触发告警的方法越来越流行
- 7.定义基于重点性能指标的告警
- 8.在生产环境中持续测试:1. 常见的持续测试的实践:蓝/绿部署;金丝雀测试;A/B测试
- 9.从基础的指标开始
6.9.服务通信
- 服务通信是云原生应用的重要组成部分。无论是客户端与后端进行通信,服务与数据库进行通信,还是在分布式架构中相互间进行通信的单个服务,这些交互都是云原生应用程序的重要组成部分
- 1.设计时考虑前后兼容性
- 2.封装好服务避免泄露内部细节
- 3.优先考虑异步通信:1. 可能的话,尽量使用异步通信。异步通信非常适合分布式系统,能够帮助实现多个服务间的解耦。
- 4.使用高效的序列化技术
- 5.使用队列和流来应对高负载和流量高峰
- 6.用批处理来提高请求处理的效率
- 7.拆分大的消息
6.10.容器
- 1.将镜像存储在可信的注册服务器中
- 2.充分利用Docker的构建缓存
- 3.不要使用特权模式运行容器
- 4.使用显式的容器镜像标签
- 5.保持小的容器镜像
- 6.单个容器只运行一个应用
- 7.使用可信镜像仓库中经过验证的镜像
- 8.对镜像进行漏洞扫描
- 9.不要将数据保存在容器中
- 10.永远不要在容器中存放密钥和配置
第7章 可移植性
7.1.为什么要使应用可移植
- 利益相关者有时会要求应用程序具有可移植性,却忽视了潜在的风险,如可能影响产品上市时间、功能、工程成本,同时常常会导致运营成本增加
7.2.可移植性的代价
7.3.何时及如何实现可移植性
- 云服务商会负责Kubernetes控制层的创建和维护,然后通过特定云平台的插件在底层基础设施之上创建集群节点
- Kubernetes服务目录是一个扩展API,可用于提供Kubernetes托管服务。服务目录使用开放服务代理API列出云服务商提供的托管服务,可以创建这些服务的实例并与集群绑定。