《云原生:运用容器、函数计算和数据构建下一代应用》

2021-11-19 14:03:51 浏览数 (1)

第1章 云原生简介

1.1 分布式系统

  • 云原生应用的核心也是分布式系统
  • 延迟指的是数据从发送到接收需要多少时间。而带宽指的是在给定时间窗口内可以传输多少数据。因为延迟对用户体验和性能有很大影响
  1. 避免频繁的网络调用和一些不必要的请求
  2. 在设计云原生应用时,可以考虑采用缓存、内容分发网络(CDN)、多区域部署等技术或方法来使得数据离客户端更近
  3. 采用“发布/订阅”模式,以通知有新数据到达,并将其存储在本地以便可以立即使用这些数据
  • “领域驱动设计”(DDD)模式和类似“命令查询职责分离”(CQRS)这样的数据模式在此类带宽要求较高的场景下是很有用的
  • 一个现代的云原生应用一般会由很多服务组成,这些服务需要协同工作,但是它们往往由不同的团队开发。因此要让一个人完全搞明白整个应用是如何工作的几乎是件不可能的事情,更别说去修复问题了。所以你需要确保你的应用有完善的治理措施,使得排查故障变得相对容易
  • CAP定理指出,任何一个通过网络连接的、共享数据的分布式系统最多只能同时满足以下三个需求中的两个:
  1. ·一致性(Consistency,C):指所有节点访问同一份最新的数据副本
  2. ·可用性(Availability,A):指系统提供的数据或服务必须一直处于可用状态
  3. ·分区容错性(Partitiontolerance,P):指系统在遇到网络分区故障的时候,仍然能够对外提供服务

1.2 十二要素应用

  • 十二要素应用这一方法论是由Heroku的工程师从云端应用开发的最佳实践中总结出来的,可以被认为是云原生应用的基础。尽管云计算一直在不停地发展,但是十二要素宣言中的这些原则仍然适用。以下是十二要素的内容及其对于云原生应用的意义:
  1. 基准代码:一份基准代码,多份部署
  2. 依赖:显式地声明依赖关系并隔离依赖
  3. 配置:在环境中存储配置,配置和代码应该严格分开,这样你才能够轻松地配置不同的环境
  4. 后端服务:把后端服务当作附加资源
  5. 构建、发布和运行:严格分离构建和运行阶段
  6. 进程:用一个或多个无状态的进程来运行应用
  7. 数据隔离:每个服务管理自己的数据
  8. 并发:通过进程模型进行扩展
  9. 易处理:通过快速启动和优雅退出来最大化应用的健壮性
  10. 开发环境与线上环境等价:尽可能地保持开发环境、预发布环境和生产环境相同
  11. 日志:把日志当作事件流
  12. 管理进程:把后台管理任务当作一次性进程来运行

1.3 可用性和服务等级协议

  • 应用的综合服务等级协议(ServiceLevelAgreement,SLA)永远不可能达到单个服务中的最高SLA的水平。SLA通常是按年来衡量的

表11:可用性百分比及对应服务宕机时间


第2章 云原生基础

2.1 容器

  • HyperV容器、沙盒容器(sandboxcontainer)和微虚机(MicroVM)等。以下是最流行的一些微虚机技术(排名不分先后):
  1. Nable容器通过使用Solo5项目中的unikernel技术来实现更好的隔离,这种技术限制了从容器对宿主机内核的系统调用。Nable容器的运行时环境(runc)是一个符合OCI(OpenContainerInitiative)规范的运行时环境
  2. 谷歌的gVisor这是一个用Go语言编写的运行于用户空间的内核,它提供了容器的运行时环境。新内核是一个可以满足系统调用需求的非特权进程,它防止了容器内与用户空间的直接交互
  3. 微软的HyperV容器在几年前就推出了,它是基于虚拟机的工作进程(vmwp.exe)的。这种容器能够在兼容OCI标准的同时提供完整的虚拟机级别的隔离。但是如果你想在生产环境中使用Kubernetes来管理HyperV容器的话,你得先等到Kubernetes能够正式在Windows上运行
  4. Kata容器结合了Hyper.sh和Intel的clearcontainer,并提供了经典的硬件辅助虚拟化
  5. Firecracker是亚马逊的Lambada服务的底层技术基础,并且已经在Apache2.0许可下开源。它是一种用户模式的VM解决方案,位于KVMAPI之上,旨在运行现代Linux内核。Firecracker的目标是以管理程序隔离(hypervisorisolated)的方式来运行Linux容器,该隔离和Kata容器这样的容器隔离技术有些类似

图22提供了对这些技术隔离等级的概览

  • 容器编排主要包含以下内容:
  1. 在集群节点上创建和部署容器实例
  2. 容器的资源管理,即把容器部署在有足够运行资源的节点上
  3. 监控容器以及集群节点的运行状况
  4. 在集群内对容器进行扩容或收缩
  5. 为容器提供网络映射服务
  6. 在集群内为容器提供负载均衡服务
  • Moby是Docker创建的一组开源工具,用于方便和快速地将软件容器化
  • Moby使用containerd作为默认的容器运行时

2.2 无服务器架构

  • 无服务器架构意味着服务的伸缩以及底层的基础架构都是由云服务提供商来管理的
  • 从本质上讲,任何你可以在不需要操心底层基础架构的情况下使用的服务并且又是采用按使用支付这种模式的产品都可以被视为是无服务器产品

2.3 函数计算

表21:函数即服务与服务容器化的对比

  • 有两种情况使用FaaS产品可能不是最佳选择,尽管它可能是最经济的
  1. 你希望避免供应商锁定
  2. 你希望在本地或者你自己的集群上也能运行你的函数。Kubeless、OpenFaaS、Serverless和ApacheOpenWhisk是几个比较受欢迎的可以自己安装的FaaS平台

2.4 从虚拟机到云原生

  • 学会利用函数计算的前提是,你需要首先从你的应用中鉴别出哪些功能可以作为短任务,然后把它们改造成一个个函数
  • 订单服务是一个很好的例子,其中实现了容器化的微服务可以涵盖订单的创建、查询、更新和删除(CRUD)操作,而当订单成功下达后可以通过一个函数来实现消息通知的功能

2.5 微服务

  • 微服务架构的优势
  1. 敏捷性:通过把一个应用拆分成多个小服务,那么改动所需的验证时间会被缩短,发布速度可以得到提高,同时也会更可靠
  2. 持续创新:小型的独立团队甚至可以在业务最繁忙的时候仍旧发布新的功能,同时进行A/B测试,以便提升转化率或改善用户体验
  3. 渐进式设计:通过把应用按功能拆分成小的、松耦合的服务,可以更轻松地更改单个服务而避免对整个应用程序的影响。根据业务需求,可以在不同的服务中使用不同的编程语言、框架和库
  4. 小而专的团队
  5. 故障隔离
  6. 更好的扩容和资源利用能力
  7. 改善可观察性

第3章 云原生应用的设计

  • 在开始设计一个云原生应用时,一个好的着手点是考虑这五方面:精益运营、安全性、可靠性、可扩展性和成本

3.1 云原生应用的基础

精准运营
  • 精益运营的意思是,你在设计这个应用的时候就需要考虑如何去运行你的程序,如何去监控它,并且持续去改进它。构建、测量和学习这几个动词常常用来描述这个过程,而DevOps是其实现方式
  1. 全自动化:云端自动化与基础设施即代码(InfrastructureasCode,IaC)密切相关。它可以帮助你在配置环境和部署应用时最大限度地减少错误,因为整个环境的管理是完全通过代码来实现的
  2. 监控一切:通过监控,你不仅可以了解应用程序和环境的状态,还可以了解程序的使用情况
  3. 完善文档:写文档在任何软件开发项目中都是十分重要的,但在云原生应用的开发中尤其关键。团队的每个成员都需要去了解应该如何调用另一个团队所开发的服务,或者每个人都应该理解如何去正确地部署和配置一个环境
  4. 为故障而设计:在云端,故障总是会时不时地出现。你需要考虑的不仅仅是当故障发生时你的应用该如何进行容错,更需要考虑当故障发生时应该进入什么样的流程来处理故障
安全性
  • 所有的主流云服务供应商都聘请了大量安全领域的专家,确保它们的环境非常安全。现在,云环境比本地环境更安全这个说法已经得到了公认
可靠性与可用性
  • 可靠性的意思是当故障发生了,应用程序仍然处于一个可接受的工作状态。而可用性指的是你的应用程序在一个时间窗口内可以正常提供服务的时间
  • 从可靠性的角度来看,你需要确保你的设计可以使应用从故障中恢复

3.2 云原生与传统架构的对比

  • 云原生应用和传统巨石应用最根本的一个区别在于它们如何处理状态信息,比如会话状态、应用及配置数据等。传统应用本质上通常都是有状态的,这也就意味着应用程序的状态一般都保存在该机器实例上
  • 云原生架构处理故障的方式与传统应用有很大的不同。如前所述,云原生架构总是会对故障有所准备,并且有处理故障的机制。而传统架构总是试图规避故障,比如说,通过数据库集群来避免数据库故障等

3.3 函数计算与服务

  • 对于简单的、短期和独立的任务应该考虑使用FaaS。但有很多FaaS产品已经足够成熟,你甚至可以将整个应用放在FaaS中运行。唯独有一个制约因素在大部分FaaS产品中仍然存在,那就是函数会有执行时间的限制
  • 使用函数计算的场景:
  1. 简单的并行执行任务
  2. 许多物联网(IoT)场景使用函数来编排任务
  • 将函数计算和容器化服务结合在一起是一个很好的解决方案。这样既利用了FaaS的简便性,又利用了容器服务的灵活性
  • 投票服务通过容器封装,接受用户的投票请求。当一个票被投出后,消息发送到了事件系统,事件系统会触发一个函数,该函数基于消息中的某些头信息(例如,设备类型)将此投票信息存入数据库。结果展现服务从数据库获取投票信息,然后在前端展现出来。这里实现的这个模式也被称为事件溯源(eventsourcing)模式

3.4 API设计与版本控制

  • API版本控制的成本取决于你采用的策略。他分类了三种不同的策略:
  1. 无版本(knot):API只有一个版本,API的调用者永远只调用最新的API。当API接口发生更改时,所有使用者也需要跟着改。对于调用者而言,这是最昂贵的方法,因为每次发布新的API版本时,他们都必须升级
  2. 点对点(pointtopoint):所有版本的API都在正常运行,每个调用者都使用他们需要的版本。但是对于API开发人员而言,维护较旧的API版本成本很高
  3. 兼容性版本控制(compatibleversioning):兼容性版本控制策略是最高效的。尽管对API的开发者而言,它确实会带来一些额外的工作,因为需要保持向后兼容性
  • 维护向后兼容性的一些最佳实践:
  1. 为新API提供合理的默认值或可选值。如果做不到这一点,请创建一个新资源
  2. 绝对不要去重命名或者删除已有的字段
  3. 绝对不要把一个可选字段变成必需字段
  4. 如果老的API接口已经不用了,把它标记为已过时
  5. 通过在新旧版本的服务间传递老的消息来测试其兼容性。确保向前兼容的主要方法是忽略任何未知字段,而不是抛出异常
语义版本号
  • 使用语义版本号几乎已经是一种标准做法了
  1. ·当你修改了API,使其不再向前兼容时,应该增加主版本号(major)
  2. ·当你用向后兼容的方式增加了一些功能时,应该增加次版本号(minor)
  3. ·当你用向后兼容的方式解决了几个Bug时,应该增加补丁版本号(patch)

3.5 服务间的通信

  • 从客户端到集群的外部通信通常称为南北通信,内部服务通信通常称为东西通信。在Kubernetes环境下,入口(ingress)控制器用于南北通信,出口(egress)控制器可用于访问外部服务
通信协议
  1. WebSockets的握手过程从客户端向服务器发送一个常规HTTP请求开始,这个请求中包含一个Upgrade标头,该标头通知服务器客户端要建立WebSocket连接。当握手完成后,初始的HTTP连接被替换为使用相同的底层TCP/IP连接的WebSocket连接。WebSocket可以用来传输大量数据,而不会产生使用传统HTTP请求所需的额外开销。所以,WebSocket是一种非常低延迟的连接
  2. HTTP/2的主要设计目的是实现通信的低延迟,并在单个TCP连接上通过流(stream)来实现多路复用请求,从而提高数据传输的效率。HTTP/2是二进制协议,而HTTP1.x是文本协议。二进制协议的解析效率更高,因为只有一个代码路径,这使得它们在网络上非常高效
  3. gRPC是一个比较新的协议,其出色的性能和对开发人员的友好性使得它在微服务社区中迅速流行起来。gRPC是一个使用HTTP/2作为传输协议的高性能、轻量级的通信框架,它提供了诸如身份验证、双向通信、流控制、阻塞或非阻塞绑定以及取消和超时等功能。gRPC也使用了协议缓冲区(也称为protobuf),它提供了一种将结构化数据定义和序列化为高效的二进制格式的方法
  4. protobuf使用的是二进制格式,因此,每种语言都需要有个生成器。这一点和JSON不同,JSON仅仅是一种字符串格式,每种现代语言中都可以理解。好消息是,几乎所有现代语言都有可用的protobuf格式的生成器。使用protobuf时,需要事先在proto文件中声明结构,而不是像JSON中那样将结构与消息体一起传递。每一个需要序列化和反序列化数据的服务都需要添加相应的proto文件,然后使用生成器生成一个包含数据的对象,不需要人为编写序列化代码
幂等性
  • 能够多次执行同一个操作而不改变结果的特性被称为幂等性
  • 确保幂等性操作的一种常见方法是在消息中添加唯一的标识符,并确保仅当标识符不重复时,服务才对消息进行处理
同步与异步
  • 在云原生世界中,基于事件和基于队列的异步消息传递是进程间通信最受欢迎的模式

3.6 网关

  • 网关可以分为两大类:API网关和应用程序网关。后者不一定与API有任何关系,它们通常用于安全套接字层(SSL)的终结和路由静态资源(HTML、CSS文件等)或路由到对象存储

3.8 服务网格

  • 服务网格背后的想法之一是通过将通用功能从每个服务抽出来移入到服务网格中,从而提高开发人员的生产力。这样做的另一个好处是实现了服务的业务功能和服务网格的通用功能之间的分离,使得开发人员能专注在业务上

表31:不同服务网格方案的比较

  • Istio支持以下几种层级的访问控制:
  1. 命名空间级别的访问控制
  2. 服务级别的访问控制
  3. 方法级别的访问控制

第4章 数据处理

  • 云原生应用的数据处理的特征:
  1. 倾向使用托管数据存储和处理服务
  2. 使用混合持久化、数据分区和缓存
  3. 倾向使用最终一致性,只在必要时使用强一致性
  4. 优先选择具有可扩展性、容错性且针对云存储进行了优化的云原生数据库
  5. 处理分布在多个数据存储中的数据

4.1 数据存储系统

  • 主题是一个在发布者/订阅者消息模型中使用的概念。主题和队列唯一的区别是,队列中的消息将发送给一个订阅者,而主题中的消息将发送给多个订阅者。你可以将队列视为有且仅有一个订阅者的主题
  • 一个跟踪数据库流行度的网站dbengines(https://dbengines.com)列出了329个不同的数据库

4.3 客户端访问数据

  • GraphQL非常适合作为以数据为中心的后端服务,偶尔需要调用服务方法。像GitHub这样的服务实际上正在将其整个API迁移至GraphQL,因为这为API的用户提供了更大的灵活性

图418:有多个数据源和执行功能的GraphQL服务

  • GraphQL的一个好处是,它可以轻松定义你所需的数据,并且仅限于所需的数据,而无须进行多次调用或获取多余的数据。该规范支持授权、分页、缓存等功能。这样可以轻松快捷地创建一个后端服务,该后端服务可以处理以数据为中心的应用程序所需的大多数功能

4.4 可快速伸缩的数据

  • 使用缓存时,最大的挑战之一是如何保持缓存的数据与源数据同步
  • 使缓存失效和更新的常用方法:
  1. 可以通过设置TTL值来使得缓存项在时间到期后自动删除
  2. 使用CDC来更新缓存项或者使之失效。一个订阅了数据存储更改事件的进程可以对缓存数据进行更新
  3. 应用程序可以实现一个业务逻辑,该业务逻辑负责在更新源数据的时候同时更新缓存中的数据或者使之失效
  4. 使用缓存透传层来管理缓存的数据。这样可以从应用程序中消除对数据缓存实现的担忧
  5. 运行一个后端服务,以一个配置好的时间间隔去更新缓存
  6. 使用数据库或者其他服务的数据复制功能来把数据同步到缓存中
  7. 缓存层根据访问请求和可用的缓存资源来更新缓存项

4.5 数据分析

  • 数据湖是大型的、可伸缩的,通常是集中式的数据存储,可以用来存储结构化和非结构化数据。它们通常用于执行映射和归约任务以分析大量数据。分析任务可以高度并行化,因此可以很轻松地分布式地处理数据存储中的数据

数据湖通常用于存储原始数据和非结构化数据,而数据仓库中的数据已被处理并组织在定义良好的结构中。通常将数据写入数据湖,然后将其从数据湖处理后转存到数据仓库。数据科学家能够通过探索和分析数据来发现趋势,从而有助于业务人员决定将哪些内容放到数据仓库中


第5章 DevOps

5.1 什么是DevOps

  • DevOps的目标是改进开发和运维团队之间的协作,这种协作的改进需要通过整个软件开发流程的改善来实现,流程涵盖了从计划到交付的整个过程。最终的效果体现在部署频率的提升、产品上市时间的缩短、新版本故障率的降低、故障修复间隔的缩短及故障恢复平均等待时间的缩短上

5.2 测试

  • 三种最常见的测试替身类型是模拟(mock)、伪造(fake)和桩(stub)
  1. 通过模拟,你可以定义函数如何被调用并定义预期行为。模拟可以用于测试对象之间的交互
  2. 伪造指的是一种简化的实现,比如伪造一个API,它看上去跟真的API一样工作,但事实上并非如此
  3. 桩指的是不包含任何业务逻辑,并且仅返回预设的值的一种测试替身
  • 无论是谈论云原生还是分布式系统,我们都会提到一个工具,那就是Jepsen库。Jepsen库可以部署一个分布式系统,对这个系统进行一系列的操作,然后验证这些操作是否合理。你可以用Jepsen来分析数据库、协作服务以及队列服务,它可以帮你找出很多问题,包括数据丢失、陈旧数据、锁冲突,等等

表51:不同类型测试的运行频率

  • 你应当定期去做安全测试、模糊测试、负载测试以及性能测试,但是可能并不需要对每次构建或每个代码改动做这些测试,除非这个改动会影响系统的安全或者性能

5.3 开发环境和工具

  1. KSync工具可以通过将本地文件复制到远程集群上运行的容器中来更新该容器。开发人员可以使用他们最喜欢的本地编辑器和源代码管理工具来构建、运行和测试一个在远端集群上的应用。任何更新都会被复制集群里的对应容器中。有时这可以使迭代更快速,而避免了重复构建镜像、推送镜像和更新运行中的容器的开销
  2. Skaffold是一个命令行工具,可用于将代码改动持续地部署到本地或远程Kubernetes集群中。在代码发生改动时,它通过自动构建镜像并将其推送到集群来实现开发工作流程的自动化。如果存在可以同步的文件,Skaffold可以将文件更改推送到容器中,或者可以选择创建镜像并部署新的容器实例
  3. Draft是一款开源工具,它可以自动将应用程序更改部署到远程或本地Kubernetes集群。你可以使用Draft生成简单的Dockerfile和Helm图表。该工具会检测生成文件时使用的应用程序语言。你可以对其进行定制化,以简化在Kubernetes上运行的应用程序或服务的开发。Draft使得在本地编辑和远程部署变得更容易
  4. Telepresence也是一款开源工具,可用于将本地运行的容器连接到远程Kubernetes集群中。当开发多服务应用程序(如基于微服务架构的应用程序)时,这很有用。你可以在本地开发服务,利用丰富的调试功能实现快速迭代,同时可以与群集中的其他服务透明地交互。这几乎就像本地计算机是群集中的一部分一样工作
  • Skaffold、Draft和KSync等工具可以通过自动化流程推送到远程Kubernetes集群以节省时间

图54:Skaffold开发工作流

  • 在这个开发流程中,就像本地开发流程一样,服务在本地开发。而Telepresence工具会在远程群集中运行一个代理,同时也会作为本地服务在云端的一个大使,负责将远端的请求代理到本地服务,同时也会把本地的请求回传给云中的其他服务
  • Telepresence代理将请求发送到在本地开发环境中运行的服务上。而本地开发环境中的服务发送给云端另一个服务的请求则会被Telepresence处理,它会负责将该请求代理到集群中的实际服务。Telepresence还可以将集群环境中的设置(例如配置)复制到本地环境运行的服务上。当你需要在本地开发和调试服务而其他服务依赖都在远端的云环境中运行时,此复制就非常有用

图55:本地开发协同远程云端的集群

5.4 持续集成/持续交付

  • 如果你像这样构建,那么你最终会得到一个大约8MB大小的容器镜像。而在第一步构建的那个容器镜像的大小大概是在800MB左右。这100倍的大小差距很显著,你可以想象到当它在不同的镜像仓库或不同主机间迁移时的速度差异
  • 瘦身版的镜像,这种镜像通常会带上“slim”标记
  • 容器镜像名称的一个示例可以是这样的:myimage:ed3ee931.0.0。使用这种命名格式,你可以快速发现镜像包含哪些更改
  • 一个全自动的工作流程应该是一个处于成熟阶段的DevOps的一部分,这个工作流程包含了持续集成、持续交付和持续部署

5.5 监控

  • Loggly、SumoLogic、Splunk等日志分析和管理工具来处理大量的日志数据

5.7 持续集成/持续交付流程示例

图510:CI/CD流程示意图


第6章 最佳实践

6.3 确保安全性

  • Kubernetes集群中运行的所有pod都是不隔离的,可以接受来自任何来源的请求
  • 使用Kubernetes中的NetworkPolicy资源,你可以定义pod选择器以及详细的入口(ingress)和出口(egress)策略

6.4 处理数据

  • 有时,数据是从生产系统中提取的,经过清洗后变成类生产环境数据,然后再被加载到测试系统中使用。你应该自动化这个过程,以便当生产环境产生新数据后能更容易地更新到测试环境中

6.7 运维

  • 区分部署和发布这两件事情是很重要的
  • 部署是将已构建好的组件放到一个环境中的过程,这个组件已经配置好并且随时可以运行了
  • 而发布的过程是我们开始允许流量重定向到部署好的组件上。对这两项活动的划分可以使你实现渐进式的发布、A/B测试,以及受控的金丝雀部署
  • 一个好的实践是能够在一天内完成一次修复严重故障的部署

6.10 容器

  • 为了充分利用Docker的构建缓存,请将更改频率更高的命令放置在Dockerfile的末尾(例如,将源代码添加到映像中、构建源代码)
  • 以特权模式运行容器会导致该容器有权限访问主机上的任何内容。可以通过在pod上使用安全策略来防止容器用特权模式运行。如果容器出于某种原因确实需要特权模式来更改主机环境,请考虑不要将此功能放在容器中,而是应该在基础架构的创建过程中做此操作

第7章 可移植性

7.2 可移植性的代价

  • 数据引力(datagravity)是DaveMcCrory创造的一个术语。其概念很简单:数据期望离使用它的应用近一些。随着数据量的增长,引力也会随之增长,从而拉近应用程序和其他数据。数据越大,引力越大

7.3 何时及如何实现可移植性

  • MinIO服务是一个很好的例子,该服务不仅提供本地对象存储,还可以用作存储适配器

0 人点赞