容器、微服务与服务网格


【编者的话】本文结合dotCloud的发展为例,阐述了一个基本的服务网格应该具备什么样的能力,对诸如Istio、Linkerd、Consul Connect等现代服务网格系统的基本模式进行了阐述,最后对于“自建还是购买(或者使用开源)”这个老生常谈的话题作者给出了自己的见解。

如你所知,已经有很多关于服务网格的资料(1234),但这是另外一篇。是的!但是为什么会有这篇文章呢?因为我想给你们一些不同的视角,他们希望服务网格在10年前就已经存在,远早于Docker和Kubernetes这样的容器平台的兴起。我并不是说这个视角比其他视角更好或更差,但是由于服务网格是相当复杂的“野兽”,所以我相信多种视角有助于更好地理解它们。

我将讨论dotCloud平台,这是一个建立在100多个微服务之上的平台,支持数千个运行在容器中的生产应用程序;我将解释在构建和运行它时所面临的挑战;以及服务网格会(或不会)提供帮助。

dotCloud的历史

我已经写过关于dotCloud平台的历史和它的一些设计选择,但是我没有过多地讨论它的网络层。如果你不想深入了解我之前关于dotCloud的博客,你需要知道的是它是一个PaaS,允许客户运行各种应用程序(Java、PHP、Python等),支持广泛的数据服务(MongoDB、MySQL、Redis等)以及类似于Heroku的工作流程:你可以将代码推送到平台,平台将构建容器映像,并部署这些容器映像。

我将告诉你流量是如何在dotCloud平台上路由的;不是因为它是特别棒或其他什么(我认为现在是比较合适的时间),但主要是因为,如果一个普通的团队需要一种在一个微服务群或一个应用程序群之间路由流量的方法,那么这种设计可以在短时间内用现在已有的工具轻松实现。因此,它将为我们提供一个很好的比较点,“如果我们破解它,我们会得到什么”和“如果我们使用现有的服务网格,我们会得到什么”,也就是老生常谈的“构建与购买”的困境。

托管应用的流量路由

部署在dotCloud上的应用程序会暴露HTTP和TCP端点。

HTTP端点被动态地添加到Hipache负载平衡器集群的配置中。这与我们今天使用Kubernetes Ingress资源和Traefik这样的负载平衡器可以实现的功能类似。如果你想和更多Kubernetes技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

只要域名指向dotCloud的负载平衡器,客户端就可以使用它们的关联域名连接到HTTP端点。这里没有什么特别的。

TCP端点与端口号相关联,然后端口号通过环境变量与该堆栈上的所有容器通信。

客户端可以使用指定的主机名(类似于gateway-X.dotcloud.com)和端口号连接到TCP端点。

该主机名将解析为一个“nats”服务器集群(与NATS没有任何关系),该集群将把传入的TCP连接路由到正确的容器(或者,在负载平衡服务的情况下,路由到正确的容器)。

如果你熟悉Kubernetes,这可能会让你想起NodePort服务。

dotCloud平台没有集群IP服务的等价物:为了简单起见,从内部和外部访问服务的方式是相同的。

这非常简单,最初的HTTP和TCP路由网格的实现可能都是几百行Python代码,使用相当简单(我敢说,很天真)的算法,但是随着时间的推移,它们不断发展,以处理平台的增长和额外的需求。

它不需要对现有应用程序代码进行大量重构。十二因素应用程序尤其可以直接使用通过环境变量提供的地址信息。

它与现代服务网络有何不同?

可观察性有限。对于TCP路由网格根本没有度量标准。至于HTTP路由网格,后来的版本提供了详细的HTTP度量,显示错误状态码和响应时间;但是现代服务网格的功能远远不止于此,它还提供了与度量收集系统(例如Prometheus)的集成。

可观察性非常重要,不仅从操作角度(帮助我们解决问题),还可以提供安全的蓝/绿部署金丝雀部署等功能。

路由效率也受到限制。在dotCloud路由网格中,所有流量都必须经过一组专用路由节点。这意味着可能跨越几个AZ(可用性区域)边界,并显著增加延迟。我记得对一些代码进行故障排除,这些代码发出100多个SQL请求来显示给定的页面,并为每个请求打开了到SQL服务器的新连接。在本地运行时,页面会立即加载,但在dotCloud上运行时,需要几秒钟,因为每个TCP连接(以及随后的SQL请求)都需要几十毫秒才能完成。在这种特定的情况下,使用持久连接起了作用。

现代服务网络做得更好。首先,通过确保连接在源位置路由。逻辑流仍然是客户端-->网格-->服务,但是现在网格在本地运行,而不是在远程节点上运行,因此客户端-->网格连接是本地连接,因此速度非常快(微秒而不是毫秒)。

现代服务网格还实现了更智能的负载平衡算法。通过监控后端的运行及健康状况,它们可以在更快的后端上发送更多的流量,从而提高整体性能。

随着现代服务网络的出现,安全性也越来越强。dotCloud路由网格完全在EC2 Classic上运行,并且没有加密流量(假设如果有人设法嗅探EC2上的网络流量,那么无论如何都会遇到更大的问题)。现代服务网格可以透明地保护我们所有的通信,例如通过相互的TLS身份验证和随后的加密。

平台服务的流量路由

OK,我们已经讨论了应用程序是如何通信的,但是dotCloud平台本身呢?

平台本身由大约100个微服务组成,负责各种功能。其中一些服务接受来自其他服务的请求,而其中一些服务是后台工作应用,它们将连接到其他服务,但不能自己接收连接。无论哪种方式,每个服务都需要知道它需要连接到的地址的端点。

许多高级服务都可以使用上面描述的路由网格。事实上,dotCloud平台的100多个微服务中有很大一部分是作为常规应用程序部署在dotCloud平台上的。但是少数低级服务(特别是那些实现路由网格的服务)需要一些更简单的东西,需要更少的依赖关系(因为它们不能依靠自己来运行;这是一个老生常谈的“先有鸡还是先有蛋”的问题)。

通过直接在几个关键节点上启动容器,而不是依赖于平台的构建器、调度程序和运行器服务,部署了这些底层的基本平台服务。如果你想要与现代容器平台进行比较,这就像直接在节点上运行Docker来启动我们的控制平面,而不是让Kubernetes为我们做这件事。这与kubeadmbootkube在引导自托管集群时使用的静态Pod的概念非常相似。

这些服务以一种非常简单和粗糙的方式被公开:有一个YAML文件列出了这些服务,将它们的名称映射到它们的地址;作为其部署的一部分,这些服务的每个使用者都需要一份该YAML文件的副本。

一方面,这是非常强大的,因为它不涉及像ZooKeeper那样维护外部键值存储(记住,etcd或Consul在那个时候不存在)。另一方面,这使得服务难以移动。每次移动服务时,它的所有消费者都需要接收更新的YAML文件(并且可能会重新启动)。不太方便!

我们开始实现的解决方案是让每个消费者都连接到一个本地代理。使用者不需要知道服务的完整地址+端口,只需要知道它的端口号,并通过localhost进行连接。本地代理将处理该连接,并将其路由到实际后端。现在,当一个后端需要移动到另一台机器上,或按比例放大或缩小,而不是更新它的所有消费者,我们只需要更新所有这些本地代理;我们不再需要重新启动消费者。

(还计划将流量封装在TLS连接中,并在接收端使用另一个代理来打开TLS并验证证书,而不涉及接收服务,该服务将被设置为仅在本地主机上接受连接。稍后会详细介绍。)

这与AirBNB的SmartStack非常相似;与SmartStack实现并部署到生产环境的显著区别是,当dotCloud转向Docker时,它的新的内部路由网格被搁置了。

我个人认为SmartStack是诸如Istio、Linkerd、Consul Connect等系统的先驱之一,因为所有这些系统都遵循这种模式:
  • 在每个节点上运行代理
  • 消费者连接到代理
  • 后端改变时,控制平面更新代理的配置


今天实现一个服务网格

如果我们今天必须实现类似的网格,我们可以使用类似的原则。例如,我们可以设置一个内部域名系统区域,将服务名映射到127.0.0.0/8空间中的地址。然后在集群的每个节点上运行HAProxy,接受每个服务地址(在127.0.0.0/8子网中)上的连接,并将它们转发/负载平衡到适当的后端。HAProxy配置可以由confd管理,允许在etcd或Consul中存储后端信息,并在需要时自动将更新的配置推送到HAProxy。

这就是Istio的工作原理!但是有一些不同之处:
  • 它使用Envoy Proxy而不是HAProxy
  • 它使用Kubernetes API而不是etcd或Consul来存储后端配置
  • 服务在内部子网中分配地址(Kubernetes集群IP地址),而不是127.0.0.0/8
  • 它有一个额外的组件(Citadel),用于在客户机和服务器之间添加相互的TLS身份验证
  • 它增加了对诸如断路、分布式跟踪、金丝雀部署等新特性的支持


让我们快速回顾一下这些差异。

Envoy Proxy

Envoy Proxy由Lyft撰写。它与其他代理(如HAProxy、NGINX、Traefik)有许多相似之处,但Lyft编写它是因为它们需要当时这些代理中不存在的功能,而且构建一个新的代理比扩展现有代理更有意义。

Envoy可以单独使用。如果有一组给定的服务需要连接到其他服务,可以把它连接到Envoy,然后动态地配置和重新配置其他服务的Envoy的位置,而得到很多漂亮的额外的功能,比如域的可观测性。这里,没有使用定制的客户端库,也没有在代码中添加跟踪调用,而是将流量定向到Envoy,让它为我收集指标。

但Envoy也可以用作服务网格的数据平面。这意味着现在将由该服务网格的控制平面配置Envoy。

控制平面

说到控制平面,Istio依赖于Kubernetes API。这与使用confd没有太大的不同。confd依赖etcd或Consul来监视数据存储中的一组密钥。Istio依赖Kubernetes API来监视一组Kubernetes资源。

Aparte:我个人认为阅读Kubernetes API描述非常有帮助。


Kubernetes API服务器是一个“哑服务器”,它提供API资源上的存储、版本控制、验证、更新和监视语义。
Istio是为与Kubernetes合作而设计的;如果你想在Kubernetes之外使用它,则需要运行Kubernetes API服务器的实例(以及支持的etcd服务)。

服务地址

Istio依赖Kubernetes分配的集群IP地址,因此Istio得到一个内部地址(不在127.0.0.1/8范围)。

在没有Istio的Kubernetes集群上,前往给定服务的ClusterIP地址的流量被kube-proxy拦截,并发送到该代理的后端。更具体地说,如果你想确定技术细节:kube-proxy设置iptables规则(或IPVS负载平衡器,取决于它是如何设置的)来重写连接到集群IP地址的目标IP地址。

一旦Istio安装在Kubernetes集群上,就不会发生任何变化,直到通过将sidecar容器注入到使用者Pod中,显式地为给定的使用者甚至整个名称空间启用Istio。sidecar将运行一个Envoy实例,并设置一些iptables规则来拦截到其他服务的流量,并将这些流量重定向到Envoy。

结合Kubernetes DNS集成,这意味着我们的代码可以连接到一个服务名,一切都可以正常工作。换句话说,比如我们的代码向http://api/v1/users/4242发起一个请求,api将解析到10.97.105.48,一条iptables规则将解释连接到10.97.105.48并重定向到本地Envoy代理,本地代理将这个请求路由到实际的API后端。

额外的铃声和哨声

Istio还可以通过名为Citadel的组件通过mTLS(双向TLS)提供端到端加密和身份验证。

它还包括混合器,Envoy组件可以查询每一个请求,对请求进行一个临时的决定取决于各种因素,例如请求头、后端负载(别担心,有丰富的规定以确保混合高度可用,即使它休息,Envoy可以继续代理流量)。

当然,我提到了可观察性。Envoy在提供分布式跟踪的同时收集大量的度量指标。微服务架构,如果单个API请求必须经过微服务A、B、C和D,分布式跟踪将添加一个惟一的标识符请求进入系统,并保留标识符在子请求中,所有这些微服务允许收集所有相关的调用、延迟等。

自建还是购买

Istio以复杂著称。相比之下,使用我们今天拥有的工具,构建像我在本文开头描述的那样的路由网格相对比较简单。那么,构建我们自己的服务网格是否有意义呢?

如果我们有适度的需求(如果我们不需要可观察性,断路器,和其他细节),我们可能想建立自己的。但是如果我们正在使用Kubernetes,我们甚至可能不需要这样做,因为Kubernetes已经提供了基本的服务发现和负载平衡。

现在,如果我们有高级的需求,购买服务网格可能是一个更好的选择。(由于Istio是开源的,所以它并不总是真正的购买,但是我们仍然需要投入工程时间来理解它是如何工作、部署和运行的。)

如何选择Istio、Linkerd和Consul Connect

到目前为止,我们只讨论了Istio,但它并不是唯一的服务网格。Linkerd是另一个流行的选择,还有Consul Connect

我们应该选哪一个呢?

实际上在这一点上我也不好说,我不认为我有足够的了解能够帮助任何人做决策。不过,已经有一些有趣的文章比较它们(12),甚至基准测试

一种值得一提并且很有潜力的方法是使用像SuperGloo这样的工具。SuperGloo提供了一个抽象层来简化和统一服务网格公开的API。我们可以使用SuperGloo提供的更简单的构造,并无缝地从一个服务网格切换到另一个服务网格,而不是学习各种服务网格的特定API(在我看来,相对复杂)。有点像我们有一个描述HTTP前端和后端的中间配置格式,能够为NGINX、HAProxy、Traefik、Apache生成实际配置

我已经使用SuperGloo稍微涉足Istio,在未来的博客文章中,我想说明如何使用SuperGloo将Isio或Linkerd添加到现有的集群中,以及后者是否能实现它的承诺,即允许我在不重写配置的情况下从一个路由网格切换到另一个。

如果你喜欢这篇文章,并且想让我尝试一些具体的场景,我很乐意听到你的消息!

原文链接:Containers, microservices, and service meshes

译者:Mr.lzc,软件研发工程师、DevOpsDays深圳组织者&志愿者,目前供职于华为,从事云存储工作,以Cloud Native方式构建云文件系统服务,专注于K8s、微服务领域。

0 个评论

要回复文章请先登录注册