作者:Eliza Weisman

局部因为Linkerd的性能数字和一流的平安审计报告,最近对Linkerd2-proxy(Linkerd应用的底层代理)的趣味激增。作为一名Linkerd2维护者,我大部分工夫都在Linkerd2-proxy上工作,所以这个主题十分贴近我的心田。在本文中,我将更具体地介绍Linkerd2-proxy是什么以及它是如何工作的。

代理能够说是服务网格中最要害的组件。它能够随应用程序的部署而扩大,因而低附加提早和低资源耗费至关重要。它也是解决应用程序所有敏感数据的中央,因而安全性至关重要。如果代理速度慢、臃肿或不平安,那么服务网格也是如此。

当初的Linkerd2-proxy就是为了满足这些严格的要求而设计的。事实上,我认为它可能是服务网格用例和世界上最令人兴奋的一些技术的最佳代理。正如William Morgan最近所说的,Linkerd2-proxy是古代网络编程的最先进的状态:

不像Envoy、NGINX和haproxy这样的通用代理,开源的Linkerd2-proxy被设计为只做一件事,并且比任何人做得都好:成为一个服务网格边车(sidecar)代理。

事实上,咱们认为Linkerd2-proxy代表了平安、古代网络编程的最新技术。它是齐全异步的,用古代类型平安和内存平安的语言编写。它充分利用了古代Rust网络生态系统,与亚马逊的Firecracker等我的项目共享根底。它对古代网络协议(如gRPC)有原生反对,能够基于实时提早实现负载平衡申请,并对零配置应用进行协定检测。它是齐全开源的、通过审计的和大规模宽泛测试的。

所以,如果你想理解最先进的网络编程是什么样子的,请持续浏览!

为什么应用Rust呢?

谈到Linkerd2-proxy,如果不探讨Rust,那就不残缺。当咱们在2017年开始着手Linkerd2-proxy的开发时,咱们无意识地决定应用Rust,只管过后Rust的网络生态系统十分十分早。为什么咱们抉择这条冒险的路线,而不是保持应用Scala,或者一些更“传统”的代理语言,如C++或C?

决定应用Rust的起因有几个。首先,服务网格代理有一些十分严格的要求:因为它是作为每个pod根底上的边车部署的,所以它必须领有尽可能小的内存和CPU占用。因为应用程序的大部分或所有网络流量都要通过代理,所以它须要有最小的提早开销,尤其是最坏状况下的尾部提早。兴许最重要的是,因为代理解决应用程序数据(可能包含难以置信的敏感数据,如金融交易或集体衰弱),因而必须保障平安。

让咱们顺次从资源耗费开始。在咱们编写Linkerd2-proxy之前,咱们构建了Linkerd 1.x。Linkerd的第一个版本有一个用Scala编写的代理组件,并利用强壮的Scala和Java网络生态系统来实现大规模的卓越性能。然而,因为它运行在Java虚拟机上,所以它占用了相当大的资源。(JVM擅长于“伸”,但不善于“缩”,正如William在他的InfoQ文章中对于决定从新实现Linkerd的文章中所写的那样。)只管Linkerd社区在调优JVM的内存应用以最小化内存占用方面做得很好,但在按pod服务网格部署模型中,要求这么做还是太多了。所以咱们晓得咱们须要一种能够编译成原生二进制文件的语言,比方Rust、Go和C++。

当初,谈一谈提早。咱们从Linkerd 1中学到的另一个教训。通知咱们抉择Rust的是垃圾收集的影响。在垃圾收集运行时中,GC必须偶然遍历内存中的对象图,以找到不再应用且能够回收的对象。这个过程须要工夫,而且可能在不可预测的点产生。如果申请是在垃圾收集器通过期间传入的,那么它可能具备显著的提早。这个尖尖的、不可预测的提早配置文件与咱们对服务网格代理的冀望相同。因而,只管咱们喜爱Go(Linkerd 2.x管制立体是用它写的),Go,也是一种垃圾收集语言。这就给咱们留下了没有垃圾收集的语言,比方Rust和C++。

最初,是谈平安。确保服务之间的平安和公有通信是服务网格的次要价值支柱。然而,在数据门路中插入另一个跳也会向攻击者裸露一个新的攻击面。在咱们思考进步应用程序的安全性之前,咱们必须确保咱们没有使它变得更糟。咱们曾经确定,垃圾收集语言不适宜Linkerd2-proxy的用例,然而Scala、Java、Ruby和Go所有依赖垃圾收集一个要害起因是:确保内存平安与手动内存治理的语言,像C和C++,比看起来要艰难得多。

为什么内存平安如此重要?很简略:绝大多数可利用的安全漏洞——Chromium和Windows中70%的重大安全漏洞,以及最近内存中一些最重大的安全漏洞,如heartbleed——都是由缓冲区溢出和开释后应用等内存安全漏洞造成的。与C和C++不同,Rust解决了这些问题,但它是在编译时解决的,不会受到垃圾收集的性能影响。换句话说,Rust让咱们避开了大量潜在的数据立体破绽,否则这些破绽会困扰Linkerd。

思考到所有这些因素,Rust是Linkerd2-proxy的惟一抉择。它提供了闪电般的性能、可预感的低提早和咱们晓得服务网格代理须要的平安属性。它还为咱们提供了古代语言个性,如模式匹配和富裕表现力的动态类型零碎,以及工具,如内置的测试框架和包管理器,使在其中编程变得十分欢快。

Rust的生态系统

令人高兴的是,自2017年以来,Rust网络生态系统曾经蓬勃发展——这在很大水平上要归功于Buoyant公司在几个要害我的项目上的投资。明天,Linkerd2-proxy是建设在一些根底的Rust网络库上的:

  • Tokio:Rust的异步运行时
  • Hyper:疾速、平安、正确的HTTP实现
  • Rustls:平安的古代TLS实现
  • Tower:模块化和可组合的网络软件组件库

让咱们一一来看一下。

Tokio是一个构建疾速、牢靠、轻量级网络应用的平台。它提供了一个与操作系统的非阻塞I/O性能、高性能计时器和任务调度集成的事件循环。对于相熟Node的读者。能够认为Tokio表演的角色相似于C库libuv——事实上,应用Tokio是Node创建者Ryan Dahl抉择在他的下一代JavaScript运行时Deno应用Rust的次要起因之一。自从Linkerd在2016年首次应用Tokio以来,它曾经在TiKV、微软Azure的iot-edge和Facebook的Mononoke等开源我的项目,以及从AWS到Discord等公司中失去了迅速而宽泛的采纳。

Hyper是Rust当先的异步HTTP实现,以其最佳的类内性能和正确性而著称。和Tokio一样,Hyper因大规模应用而久经沙场。

为了应用互相TLS来爱护网络通信,Linkerd代理应用rustls,这是TLS协定的一种实现,构建在ring和webpki之上,这些库提供了底层的加密性能。一项由CNCF资助的独立平安审计发现,这个加密堆栈的品质十分高,来自Cure53的审计人员“对所出现的软件印象十分粗浅”。

明天,这些组件形成了Rust网络生态系统的外围构建块,毫不夸大地说,大部分开发都是由Linkerd2-proxy驱动的。2017年,当咱们开始Linkerd2-proxy的工作时,还没有一个可生产的HTTP/2或gRPC实现,所以咱们率先开发了h2库和tower-gRPC。当初,h2加强了Hyper的HTTP/2反对,而tower-gRPC(当初被称为Tonic)曾经成为Rust最风行的gRPC库。受Linkerd 1.x提供能源的Scala库Finagle的启发,咱们还推动了Tower的开发,这是一个用于以模块化、可组合的形式实现网络服务和中间件的形象层。

申请的生命周期

有了这些构建块,让咱们来讨论一下代理实际上做了什么。作为一个服务网络,Linkerd的最大益处之一能够总结为“零配置就能工作”:如果你把Linkerd增加到一个失常运行的应用程序,它应该持续运行,用户不应该做任何配置。(如果你是从其余服务网我的项目来的Linkerd,这看起来很神奇。)

Linkerd是如何实现这一惊人壮举的?当然是应用了Linkerd2-proxy。因而,让咱们合成通过代理的申请的生命周期。代理在不进行配置的状况下如何智能地解决通信量,同时对网格化的应用程序放弃通明?

第一步是协定检测。要使零配置成为事实,当代理收到申请时,咱们须要确定正在应用的协定。所以咱们做的第一件事是从连贯的客户端读取几个字节,而后问几个问题:

  • “这是HTTP申请吗?”
  • “这是TLS客户端Hello message吗?”

如果申请是客户端hello,则查看服务器名称批示(server name indication,SNI)值,该值蕴含客户端心愿终止的服务器的主机名。如果SNI值表明TLS连贯是为注入的应用程序筹备的,代理将间接转发音讯。透明性的一个重要局部是,如果代理接管到一个音讯,它不能做任何聪慧的事件,它应该间接发送它——在这种状况下,音讯是加密的,而代理没有解密它的密钥,所以咱们没有别的方法。相似地,未知协定中的TCP流量将通明地转发到其原始目的地。

另一方面,如果加密连贯是为咱们提供的,作为Linkerd的主动互TLS个性的一部分呢?网格中的每个代理都有本人独特的加密身份,代理在启动时为其生成要害资料,并且从不来到pod边界或写入磁盘。管制立体的身份服务对这些身份进行签名,以批示对代理进行身份验证,以便为注入代理的pod的Kubernetes ServiceAccount服务流量。如果SNI名称与代理的服务帐户匹配,那么咱们对其进行解密,并将其作为服务网格的一部分进行解决。

接下来,如果申请被网格化,代理会做什么?让咱们思考这样一种状况,网格化的客户机向其代理发送出站申请。代理执行咱们下面探讨的协定检测,并确定这是一个HTTP/1、HTTP/2或gRPC申请协定,Linkerd了解并能够智能路由。因而,当初咱们须要确定申请的去向。Linkerd依据指标权限(target authority)来路由HTTP流量,指标权限是HTTP/1.1和1.0申请的Host: header或申请URL的权限局部的值,或者HTTP/2中的:authority头字段的值。代理查看申请,并依据应用的协定版本查找指标权限,并执行DNS查问以确定该名称的标准模式。

当代理晓得了申请的指标权限,它就通过从Linkerd管制立体的指标服务中查找权限来执行服务发现。是否征询管制立体取决于一组搜寻后缀:默认状况下,代理被配置为查问位于默认Kubernetes集群本地区.cluster.local的服务。然而能够为应用自定义域的集群笼罩此性能。指标服务向代理提供组成该权限的Kubernetes服务的所有端点的地址,以及特定于链接的元数据和配置重试、超时和其余策略的服务配置文件。所有这些数据都流到代理,所以如果有任何变动——例如。如果一个服务被放大或放大,或者服务概要配置被编辑——管制立体将在产生时将新状态推送到代理。

而后,代理将在管制立体提供的一组端点上对申请进行负载平衡。当申请被转发到目的地时,代理会应用一种名为指数加权挪动均匀(exponentially weighted moving averages,EWMA)的负载平衡算法来计算负载估算。实质上,这意味着代理在一个无限的工夫窗口内放弃一个挪动均匀提早,以便在提早产生时对其做出反馈,并且该负载预计是基于向该端点运行的申请的数量进行加权的。在传统上,负载平衡决策通常是通过抉择预计负载最低的端点来做出的,比方应用有序堆。然而,放弃端点的有序汇合从最小到最大的负载在计算上是低廉的,所以Linkerd实现了“两种抉择的能力”(power of two choices,P2C)负载平衡。在这种办法中,咱们通过从两个随机抉择的可用端点中抉择负载较少的端点来做出每个负载平衡决策。只管这仿佛违反直觉,但从数学上来说,这至多在规模上与总是抉择负载最小的正本一样无效,而且它防止了多个负载均衡器都将流量发送到负载最小的正本、导致重载的问题。此外,这种快捷方式效率更高,因而在速度上有很大差异。

当指标端点有本人的Linkerd代理时,管制立体将向代理批示它能够发动互相TLS,以确保连贯是平安和公有的。同样的,当HTTP/1.x申请在网格中发送,代理将通明地将它们降级为HTTP/2,这样多个申请能够在一个连贯上多路复用,并由指标代理降级为HTTP/1,这样降级对应用程序是不可见的。与Linkerd的智能、反对协定的负载平衡相结合,这是网状流量通常比非网状流量具备更低提早的起因之一,只管采纳了额定的网络跳数。

把它们放在一起,代理中的根本逻辑流程看起来如下:

只管Linkerd2-proxy提供了很多性能,但咱们尽可能放弃它的简略和最低限度。最重要的是,代理的模块化体系结构意味着大多数个性能够实现为小的、自蕴含的模块,并插入到堆栈的适当地位,放弃整体代码复杂度低。

代理是要害

明天,Linkerd是惟一一个以数据立体代理为个性的服务网格,它是为服务网格用例显式地从头设计的。通过关注服务网格的独特需要,充分利用Rust令人印象粗浅的性能、平安保障和尖端的异步网络栈,咱们置信Linkerd2-proxy是Linkerd胜利的秘诀。

那么,你是否想参加一个在世界各地的要害零碎中应用的最前沿的开源Rust我的项目?好消息,Linkerd是开源的,所以你能够!在GitHub上退出咱们,并查看Slack上的#contributors频道。咱们心愿你能退出。

Linkerd实用于所有人

Linkerd是一个社区我的项目,由CNCF托管。Linkerd致力于凋谢治理。如果你有性能要求、问题,或评论,咱们心愿你退出咱们快速增长的社区!Linkerd代码托管在GitHub上,咱们在Slack、Twitter和邮件列表上有一个蓬勃发展的社区。快来退出咱们乐趣满满的我的项目吧!

点击浏览网站原文。


CNCF (Cloud Native Computing Foundation)成立于2015年12月,隶属于Linux  Foundation,是非营利性组织。

CNCF(云原生计算基金会)致力于培养和保护一个厂商中立的开源生态系统,来推广云原生技术。咱们通过将最前沿的模式民主化,让这些翻新为公众所用。扫描二维码关注CNCF微信公众号。