原文作者 | Andrew Jenkins
译者 | Vash
校对 | Andy Shi
原文链接 | 文末“阅读原文”
如果你正在围绕微服务来创建你的软件和团队 ,那么意味着你正在寻找更快迭代和更灵活扩展的方式。网格服务可以满足你的需要,同时保持(或增强)服务的可视化和可控性。本文将讨论,到底什么是网格服务,以及在选用和部署网格服务的过程中需要考虑些什么。
那么,什么是网格服务呢?它与你现有的技术栈有什么不一样呢?网格服务是一个基于请求/响应方式的通信层,为健壮的微服务提供了一些重要的(通信)模式。以下是我喜欢的一些特性:
不做可信边界假定的零互信安全机制
可展示微服务间通信方式的链路跟踪功能
允许试验应用弹性的故障注入和容错机制
允许进行一些类似A/B测试、快速版本管理、部署和请求跟踪的高级路由功能
1
为什么我们需要一个新的概念?
看了上面的理由,你可能会想“没有网格服务我也能做到”,是的,你说的不错,同样的话也能用来说滑动窗口协议和请求框架。但是,一旦有一个新的标准能够实现你的需求,那么直接使用会比自己实现高效得多。网格服务就是为微服务形式而生的通信层。
网格服务还是一门很新的技术,成文的规范还没有确立,但一些指导最佳实践的规则越来越清晰了,业内积累了足够的经验。掌握高新技术的负责人在开发自己的系统时,参考方案和提炼最佳实践总是大有裨益的。我们已经见过,Kubernetes是如何发展为Web应用容器生产部署的标准方式的。我喜欢的标准都是自然形成而不是强制规定的:标准何时与公共接口、协议和概念达成一致,这绝对是一门艺术。
回顾一下计算机网络的历史,我们就会发现,在分组交换网络发明出来后,很多人在上面构造了虚电路,通过握手协议、重传和网际交换等方式将一堆数据报转化为有序的字节流。出于互用性和简洁性的考虑,一项最佳实践(基于数据报的流协议)产生了——TCP协议(对RFC675的介绍很好地解释了它依赖的底层)。除了TCP,还有其他的选择:我就在空间网路中使用了Licklider传输协议,空间网络中复杂的分布式拥塞控制多余而低效;你的浏览器可能已经在使用QUIC协议了。但是,TCP协议的标准化,将整代的程序员从处理滑动窗口、异常重试、拥塞崩溃的无聊任务中解放出来了(好吧,除了那些满脑子数据报的协议实现者)。
然后,我们就看到有很多请求/响应式协议运行在TCP上面了。其中很多最后都移植到了HTTP里(或后来出现的HTTP/2、gRPC里)。如果你能把通信协议分解为“方法、元数据、消息体”,那么你就应该把HTTP类协议理解为对管理数据帧、从消息体中分离元数据、给数据块的头部添加寻址等过程。这种扩展不仅限于浏览器应用,由于HTTP的普及,产生了很多新的工具和开发知识,像Mongo一类的数据库都开始提供HTTP操作接口了。
我们会给微服务提供一个通信方式层,你可以把网格服务看作这层的一套语汇、API和实现。那么,通信层存在哪里呢?有以下几种选择:
在你的微服务源代码和使用的库里
在容器服务器的代理里
在和你的应用容器一起运行的Sidecar容器里
2
库引用
库引用是最原始的方式,简单直接。在这种方式下,每个微服务应用需要引入实现了网格服务特性的库代码,像Hystrix和Ribbon之类的库就是这种方式的例子。
库引用的方式在单语言开发的应用上工作得很好(易于导入库),而且不需要太多底层的支持——负责容器编排的调度服务(如Kubernetes)不需要知道你运行的是支持限流的应用。
多语言库需要一些额外的工作(相同概念的重复实现),主要的问题是简单重复实现所消耗的精力。
在我们的用户中,很少有用库引用模型实现网格服务的,因为他们的应用都是用不同的语言(多语言)开发的,而且有些不是自己开发的,所以引入库的方式就行不通了。
这种模式在作业审计方面有优势:代表微服务执行作业的代码实际上就运行在微服务里。信任边界很小,你只需要信任本地进程调用的库,而不是网络某处的远程服务。库代码只有和执行作业的微服务相同的权限,而且运行在相同的上下文,所以可以很方便地给它公平地分配CPU时间、内存等资源——操作系统可能已为你代劳了。
3
节点代理
节点代理模型是另一个选择。这种架构下,每个节点都有一个独立的代理(通常是用户空间进程),服务于异构的混合负载。对比来看,与库模型相反:它不关心应用的开发语言,并且能够同时服务多个不同的微服务租户。
在Kubernetes中部署Linkerd的推荐方式工作如下:
由于每个节点都需要一个代理,这种部署方式需要底层架构提供一些支持——没有协作将无法工作。同样,大多数应用不能使用私有的TCP协议栈,随意选一个端口号,直接进行数据报的发送和接收,而是把这些工作委托给底层(操作系统)。
这种模型并不关注良好的作业审计而是作业资源共享:如果节点代理为我的微服务分配了一块内存用于数据缓存,很快它就可能被用来为你服务。这可能很有效,但也可能被滥用。如果我的微服务申请所有的缓存空间,节点代理需要确保你的微服务也有机会申请到。总之,你需要一些额外代码来管理所有的共享资源。
配置信息是另一个从共享中获益的作业资源。跟为每个节点的每个pod分发配置副本比起来,给每个节点提供一份相同配置的成本要小得多。集装箱式的微服务依赖的许多功能都是节点代理或拓扑等价网络提供的,你可以想想kubelet是如何初始化你的pod的,以及你喜欢的CNI守护进程(如flanneld),或者脑洞再大些,甚至操作系统的内核本身都是采用的节点代理模型。
4
节点代理Sidecar
Sidecar是网格服务领域的新生儿。Istio对Envoy的使用就是这种模型,Conduit也是。部署时,我们会给每个应用容器部署一个关联的容器。对网格服务来说,由Sidecar负责处理所有进出应用容器的网络流量。
相较于已讨论的损益考虑,这是一种介于库引用和节点代理之间的(网格服务实现)方式。举例来说,你不用给每个节点运行一个新的代理,就可以部署Sidecar型网格服务(这样你就无需底层范围的支持来部署共享代理),但是你将运行多个相同的Sidecar实例。另一个好处是:我可以为一组微服务安装一个网格服务,你可以安装一个不同的,我们之间(除特殊情况外)无需协调即可通信。在网格服务的早期这是非常强大的,我们的目标不同、需要的特性集不同、对新技术和可靠性的容忍度不同,但是我们可能需要共享同一个Kubernetes集群。
Sidecar有利于作业审计,尤其是一些安全相关的方面。举例来说,假如现在我用网格服务来提供零互信式的安全服务,希望网格服务能帮我验证加密信道的两端(客户端和服务端)。先考虑用节点代理的情况:当我的pod想成为另一服务端pod的客户端时,节点代理将替我的pod进行身份验证,它还同时为其他的pod提供服务,所以必须谨防其他pod假冒我的pod身份进行安全验证。而如果使用Sidecar的话,我的pod的Sidecar并不为其他pod服务。按照最小权限和最少需求的原则,我们只为Sidecar提供它所服务的pod所需的认证码、内存和网络能力。
所以,一方面,从外部看,Sidecar拥有和绑定的应用相同的权限。而另一方面,Sidecar需要介入应用和外界的交互,这会带来一些安全隐患:我们希望给Sidecar的权限尽可能小,但又要足以控制应用的进出网络流量。例如,在Istio中,init容器负责设置Sidecar的临时网络管理员(NET_ADMIN)权限(配置IP过滤规则所需的),初始化的过程遵循最佳安全实践——“按需授权,用完失效",但不可避免的,所有具有网络管理员权限的接口都容易成为攻击面(好消息是,一群聪明的家伙正在改进它)。
一旦Sidecar和应用绑定,从安全的角度看,它们就休戚相关了:没有本地进程内(如库)的方法调用那样紧密,但比唤起多租户的节点代理紧密得多。所以,在Kubernetes中使用Istio,当你的应用容器和Sidecar通过共享的网络命名空间的内部回调接口通信时,其他pod和节点代理通常是感觉不到的。
大多数Kubernetes集群的每个节点都有不只一个pod(所以每个节点都有多个Sidecar)。如果每个Sidecar都需要知道“全局配置”(不用管它是什么),你将需要更多的带宽用于分发配置信息(以及更多的内存用于存储配置副本)。所以,限制分发给每个Sidecar的配置信息的范围是非常有好处的,但负面效应是:为了减少分发给每个Sidecar的配置信息,某些部件(在Istio中,是Pilot)需要消耗更多的算力。
其他跨Sidecar的重复资源也是一样的结果。好消息是,驱动使用得当的话,容器运行时会复用相同的容器硬盘镜像,所以通常硬盘访问的代价不是很大,而且像代码页之类的内存空间常常是可共享的。但是每个Sidecar的进程私有内存是唯一的,要小心控制,不要变得笨重,避免在Sidecar内执行大量重复的作业。总之,Sidecar型网格服务很好地平衡了功能的完备性和部署的轻量性。
5
节点代理和Sidecar模型,哪个会火?
我认为都有可能。目前,Sidecar型网格服务正如日中天:新兴的技术、快速的迭代和逐步的认可。随着网格服务的成熟和变动的减少,我们将看到更多节点代理模型的应用,它的优点随着实现方案的成熟和集群的变大会更加突出:
跨节点共享资源(特别是内存)的开销更少
配置信息分发的扩展更容易
健壮的节点代理转移服务资源的效率更高
Sidecar是一种优雅的为应用提供服务(类似网格服务型的高级通信代理)的方式,特别适用于容器和Kubernetes。一些重大的优点包括:
无需控制中心的协调即可逐渐加入现有集群
不介入应用(对应用零侵入)
面向Sidecar的通信比面向代理的通信更容易做安全监控
今日推文
点击下方图片即可阅读