从API网关到服务网格

本文有 6201 字,大约需要 15 分钟可以读完, 创建于 2018-01-20

API网关是微服务结构中的一个重要的边界组件,用于隔离外部用户和内部的服务部署,完成诸如安全、鉴权等集中化的功能。 边车代理则是服务网格架构中的一个重要概念,两者从逻辑上都是作为基础设施和业务的核心功能逻辑相分离的,有一定的相似性而又有很大的不同。

微服务和API网关

微服务架构本身强调服务本身需要关注于系统的某一部分领域逻辑,并按照系统的领域问题拆分达到每一个微服务仅仅关注一个领域逻辑, 而这些微服务的整体组合成为一个大的系统;并根据不同的领域特点采用合适的软件技术实现;只要服务之间的接口协议是标准的跨语言协议即可; 大部分情况下微服务采用RESTful API(或者RPC )加上异步消息队列来作为接口。

从架构静态设计上来说,这样解决了服务抽象和切分的问题,只是大部分业务逻辑呈现典型的前后端逻辑,一般情况下是 后端有一系列微服务组合而成,最终被部署在企业的云服务器(数据中心)上;这些云服务器可能是公有云、私有云乃至混合云。 不同的服务可能有不同的水平扩展需求,以及可用性、可靠性需求。还有一部分业务逻辑可能是在用户终端设备上, 包括浏览器页面、Android手机、iOS系统、桌面应用乃至或者其它的智能设备等。 很多情况下,前端对后端业务的访问有一些额外的需求,包括诸如安全、授权、审计等需求,并且大部分高可用的需求都是在后端上。

让每个微服务都提供这些跟领域逻辑无关的负载均衡、备份、流量控制、安全等功能,明显和关注点分离的设计原则相违背。 自然没有人再想回到传统SOA架构中的基于类似ESB总线的厚基础设施的工作方式上来,毕竟如果这样做,就和微服务设计的智能协议接口的思想背道而驰了。

微服务采用的思路将这些后端的具体的领域逻辑组成的微服务看作一个子系统,在这个子系统和外部服务调用者之间再加上一个网关, 让这个网关来负责这些比较通用的、和领域逻辑非紧密相关的需求;因为它本身是基于已有的服务访问的APi做的中间处理,所以被称为API网关。 这样问题就得到了解决,毕竟计算机科学中的复杂问题都可以通过加一个中间层来分而治之。

API网关解决的主要问题

API网关主要解决以下这些微服务架构中的问题

  • 前端访问的API的粒度和后端微服务的API不匹配的问题 - 典型的微服务架构可能有数百个后端微服务组成,每一个微服务可能提供很多个粒度很细的API, 而这些API可能很多不是客户端直接需要的
  • 不同的客户端可能有不同的API访问版本需求,譬如桌面客户端需要访问的微服务API可能就比手机端的要丰富一些
  • 不同的客户端访问可能具有不同的网络性能、带宽和时延需求,这些需求和核心领域逻辑是非紧密相关的;桌面计算机上的浏览器可以在同时发起很多条并发的HTTP请求, 而移动手机端则可能出于性能、能耗考虑需要前置其连接数量;本地局域网中的客户端访问和跨越骨干网的客户端访问的要求可能也是不一样的
  • 后端微服务的动态扩展(通过启动新的服务示例增加处理能力)需要对外部访问者做隐藏,负载均衡以及集群控制逻辑需要对客户端做隐藏
  • 后端微服务可能采用不同的协议,有些协议可能不需要暴露给客户端
  • 安全、审计、计费等需求

典型的解决方法

一种常用的做法是在前端和后端之间,让API网关成为一个单一的访问点,它可以处理两者的请求, 处理请求可以被转发或者内部路由到某个服务节点上,也可以被扇出到多个微服务实例上做负载均衡,或者根据安全配置直接拒绝。

api_gw_solution

实现上,API网关也可以根据客户端的不同而暴露不同的API,比如Netflix的API 网关内部有依据客户端的不同而动态选择适配的代码,使不同的客户端可以得到不同的访问API。

netflix_api_gw

基于前端的变种

另外一种解决方案是依据前端的不同,设置多个API网关,每个网关针对一种客户端提供一个具体的API网关;这些网关分别和后端的服务交互完成整个处理流程。 其架构如下图

api_gw_backend_for_frontend

API网关的特点

API网关是对后端微服务的整体封装,它提供了一个受控的API环境,由此

  • API网关会调用被其隐藏的更下游的微服务,并确保这些调用满足高度可用和弹性设计模式,包括断路控制、超时控制、负载均衡和自动切换等。 很多API网关实现会包含这些基本弹性设计功能。
  • API网关往往也提供对服务发现和注册,性能指标统计、监控、分布式日志、跟踪和安全审计等功能
  • 同时API网关也会和API发布、计费等一些通用的API管理功能相结合,共同提供服务

无服务器架构和服务网格

基于上述讨论的微服务架构自身在通用基础设施方面的不足,以及API网关本身容易成为系统灵活性和扩展性的瓶颈的隐忧, 致力于进一步缩小运维成本的无服务器架构用服务网格来作为底层基础设施的封装, 使每个微服务都有一个基础设施的部分负责上述API网关完成的通用功能,使得系统设计的灵活性得到更大的提高。 无服务器架构在很多场景下被称为是”下一代微服务”;可以看作它是微服务架构的进一步延伸; 其本身是为了更好的满足云原生应用的要求而被提出的。

云原生应用

很多企业应用程序都带着”历史包袱”并采用云计算技术出现之前的建构方式进行设计、开发、部署和维护的; 云原生应用(Cloud Native Application)的想法是直接将云计算基础设施当作基本前提, 充分利用云计算技术提供的持续集成、持续交付等技术优势来设计、开发和维护应用程序;采用微服务架构和分布式设计的最佳设计以便开发出来的应用更适合云计算时代的IT环境。

云原生应用的开发需要商业组织具备基本的云计算部署平台,可以方便地和持续交付、DevOps、容器化和微服务这些技术相辅相成,充分发挥云计算高深缩性、灵活性和易扩展的优势。 从软件过程上而言,云原生应用的方法更具有敏捷性,可以更快将最小可行产品以不损失目标质量要求的情况下,以尽可能快的速度推向市场,获取客户的反馈,持续迭代赢得竞争优势。

cloud_native

云原生应用从设计上来说和传统的企业软件应用有如下不同

  • 软件行为的可预测性上来说,前者更强调充分利用容器技术来驱动自动化的开发,这比传统的自上而下的传统分层架构方法有更高的可预测性。
  • 底层操作系统和平台的抽象隔离上,基于容器技术或虚拟化技术的云原生应用可以天然隔离底层操作系统平台的维护、升级、安全补丁等繁琐的IT细节的影响, 使应用开发人员更专注于业务逻辑;而传统的应用则必须考虑这些底层硬件、存储、网络、操作系统的细节;实现动态可扩展性上则更加有挑战。
  • IT资源的使用率上来说,云原生可以利用容器编排技术动态分配工作负载,而传统的企业应用则往往通过预留资源的方式应对不确定的流量和负载。
  • 开发模式上来说,前者更具有敏捷性,可以结合高度的测试自动化和持续发布模型实现快速迭代;后者则更为依赖一些笨重的开发模型
  • 架构独立性上来说,前者更强调微服务架构和领域驱动建模方法,比基于传统单体架构的企业应用有更小的系统耦合。
  • 扩展性和自动恢复方面看,云原应用可以利用云计算的基础设施实现快速、灵活的自动水平扩展,并可以利用诸如Kubernetes或者MeSoS这样的开源平台提高可用性和可扩展性; 传统的架构则则多可以实现虚拟机粒度的扩展性和自动恢复控制。

服务网关的方式则明显带有传统企业应用架构模式下的分层思维,和微服务的领域解耦思路有些不太协调的地方。 服务网格则试图简化这一基础设施层面的工作,将这些基础设施的部分用一个单独的层来维护的同时,保持单个服务自身的完整性。

服务网格的基本思路

服务网格的基本想法是,让微服务的设计者无需再关心服务于服务之间如通过复杂的拓扑结构进行相互通信;作为一个新的抽象层, 服务网格提供一个轻量级的代理来和微服务一一绑定,并使微服务本身无需关注该代理的存在。这些代理总是处于一个一个具体的微服务的边界上,所以被称为边车代理。 这些边车代理自身可以形成一个网格,之间可以相互通信,设置路由,进行超时、断路器控制,进行安全管控等。

服务网格的基本结构可以参考下图

service_mesh_basic

结合微服务架构的基本思想和云原生应用设计的原则,边车代理一般和具体的微服务实例是一一配对的,即每一个微服务都有一个相关的代理处理和其他微服务的通信; 两者往往部署在同一个微服务容器中。不同的微服务之间的底层通信则是通过这些代理之间的通信的完成的。 某种程度上来说,服务网格仍然是想用轻量级的方式来提供ESB类似的能力,并不破坏架构的灵活性以更敏捷的方式架构复杂多变的软件系统。

显然边车代理的功能和API网关有某些逻辑上相似的地方,并且边车代理可以提供更灵活的配置和管理。

边车代理和API网关的简单比较

一个最明显的不同是,边车代理是一个专门为不同的微服务之间提供服务间通信的基础设施;而API网关则是一个比较纯粹的用于隔离系统架构的边界微服务。 前者可以和具体的微服务示例一一绑定提供底层通信服务,而后者则往往处于系统前后端的隔离边界上实现系统视图上的分离和控制。 从业务领域逻辑上来说,边车代理基本不关心具体的业务逻辑而仅仅和软件技术架构的弹性设计需求以及控制密切相关; API网关则和需要控制的API功能息息相关。

API网关可以看作是中心化的服务管理和控制;而边车代理则具备更强的分布式控制能力;具有更多去中心化的想象空间(读过凯文凯利的《失控 》的话会更明白去中心化的威力)。

一个软件系统中,边车代理也可以和API网关同时存在,如下图

api_gw_coexist_with_sidecar_proxy

边车代理/服务网格的几种实现主要实现

边车代理和服务网格仍然是比较新的软件架构技术;尽管其出现的时间不算太长,借助于开源社区的力量,该技术却取得了比当初微服务更多的关注, 同时既有的一些实现也有更快的迭代速度。

Linkerd

Linkerd是Buoyant推出的第一代服务网格技术;辅一推出即在开源社区引起了极大的关注。 Linkerd支持产品级别的全功能的服务网格功能,涵盖服务发现、负载均衡、路由、错误处理、服务状态跟踪、跨平台、跨语言等功能, 支持HTTP/2、gRPC、Kubernetes等技术,具备强大而又灵活的处理能力和上好的扩展性。

可能是出于开发效率的选择,Linkerd是用Java开发的,而JVM的开销对于一个边车代理而言有可能太厚重了一些,毕竟基于Docker 的容器技术的一大挑战就是image的尺寸问题; 作为轻量级代理嵌入容器的Linkerd占用的地盘比上层的微服务本身还大可不是什么好事情。

Buoyant的官方博客的数据显示,默认情况下,Linkerd本身可能会占掉数百MB的内存;即使经过JVM虚拟机JIT配置的优化(比如使用32位的虚拟机和禁用C2即使编译),其内存使用开销 在QPS超过10000的情况下,仍然可能达到100MB以上;参考下图的数据

linkerd_memory

lstio和Envoy

lstio是另外一个服务网格领域的后起之秀,严格来说它和Linkerd并不完全类似;尽管从功能上来说,lstio也提供了一个 开源的基础设施,方便对微服务之间的通信流量进行管理、安全控制、负载均衡和流量控制。

lstio显示地将它的架构分成2个逻辑部分

  • 数据平面负责边车代理以及这些代理之间的数据通信和传输控制
  • 控制平面仅仅负责管理代理之间的路由、安全等配置数据和策略设定,并支持在运行期进行调整和定制 数据平面实际上是通过Envoy项目实现的,控制平面则有另外一些组件组成,包括Pilot, Mixer, lstio-Autio 等部分。

lstio_arch

Envoy是一个用C++语言写成的高性能代理,用于处理进出该代理(以及封装的微服务)的数据流量。 lstio使用了Envoy内置的动态服务发现、负载均衡、TLS终止、HTTP/2以及gRPC路由、请求断路器、健康状态检查功能和丰富的诊断度量参数。 部署上,Envoy被部署在所负责的微服务相同的pod上,方便lstio通过Mixer组件施加代理策略,并可以将系统监控信息发送给控制平面,以方便对整个服务网格的控制。

Mixer是一个平台无关的专门用于在整个服务网格上施加访问控制策略和使用策略的组件;它可以从Envoy上提取电报数据, 并对从服务网格中的各个Envoy实例发送的请求属性信息进行评估,根据以插件方式提供的策略决策逻辑来决定如何对运行中的Envoy加以调整。 这些请求属性包括源地址、请求时间、请求大小等信息。

Pilot组件则专门用于支持这些边车代理服务的服务发现,提供服务流量和路由管理能力以便支持A/B测试、金丝雀发布,以及服务超时、重试、断路器等控制。 它可以将高层的路由控制信息转换为具体的Envoy配置,在运行期动态地下发给对应的Envoy代理,对流量进行控制。 Pilot提供了高度抽象的服务发现机制API,以避免和具体的实现相绑定;这种松散耦合设计允许lstio运行在多种异构的云平台上; 同时它和Envoy的接口仅仅是抽象的 Envoy数据平面API,使得其它的代理也可能被lstio所控制。

lstio-Auth 用于安全和认证控制;基于内置的双向TLS认证,身份和证书管理,可以将为提供TLS服务的微服务自动提升为TLS保护的访问, 方便用户根据需要依据特定的安全策略选择性对微服务添加不同的安全控制。未来版本的lstio可能会增加更细粒度的请求级别的访问认证和控制; 使得用户可以选择灵活的访问控制策略,如属性控制,基于角色的控制或者授权钩子。

lstio的不足是目前仅仅支持Kubernetes的架构,并且很多功能都没有完全开发完毕,尚不具备足够的成熟度以便在生产环境进行部署。 这一项目目前仍然在快速演进。

Conduit

Conduit可以看作是Buoyant在lstio对Linkerd提出挑战的情况下的应对之策 - 不是在优化Linkerd上下功夫而是另起炉灶创建了一个新的以轻量级、高性能为目标的新项目。 Conduit的设计目标包括

  • 轻量级
  • 高性能
  • 安全、高可用性
  • 重用Linkerd设计中踩的一些”坑”

Conduit采用和lstio类似的设计,并处于安全和性能的考虑,这次不再执着于JVM平台而是用Rust语言重写数据平面; 关于Rust为什么可以做到更安全的编程,可以参考之前这篇文字。 控制平面则选用了更适合云计算基础应用的Golang来开发;并刻意保证两者都有很高的性能。

一个随之而来的问题是,作为同一个组织推出的2个类似产品,两者是否会互相竞争? Buoyant给出的答案是不会,目前Conduit主要定位于Kubernetes平台的应用,虽然没有明说但明显是冲着防御lstio来的。 Linkerd则会继续开发,并致力于产品级的支持和维护,支持ECS、Consul、Mesos、Zookeeper、Nomad等各种已有的云平台; 提供丰富的功能集吸引商业用户。

显然服务网格和边车代理的战争才刚刚拉开序幕。

参考

  1. API Gateway Pattern
  2. Netflix Techblog - Embrace the differences: Inside the Netflix API Redesign
  3. Service Mesh vs API Gateway
  4. Buoyant blog - what’s a service mesh and why I need one?
  5. Pattern: service mesh
  6. Cloud Native Application

Leave a Comment

Your email address will not be published. Required fields are marked *

Loading...