过来人谈容器、微服务和服务网格,其实不是新鲜事!


早在像Docker和Kubernetes这样的容器平台兴起之前的10年,有一个dotCloud平台,基于100多个微服务构建的平台,支持数千个以容器运行的生产应用程序,作者将分享构建和运行它时面临的挑战与经验,并讨论服务网格到底有没有用?

dotCloud
它是一个PaaS,允许客户运行各种各样的应用程序(Java,PHP,Python ......)。数据服务(MongoDB,MySQL,Redis ......)以及类似于Heroku的工作流程:您可以将代码推送到平台,平台将构建容器映像,并部署这些容器映像。

如何路由流量呢?部署在dotCloud上的应用程序可能会暴露HTTP和TCP端点。

HTTP端点被动态添加到Hipache负载均衡器集群的配置中。这类似于我们今天可以通过Kubernetes Ingress资源和Traefik 等负载均衡器实现的目标。客户端可以使用其关联的域名连接到HTTP端点,前提是域名将指向dotCloud的负载均衡器。

TCP端点与端口号相关联,然后通过环境变量传递给该堆栈的所有容器。客户端可以使用指定的主机名(例如gateway-X.dotcloud.com)和端口号连接到TCP端点。

如果您熟悉Kubernetes,这可能会提醒您想起NodePort服务。dotCloud平台没有相应的ClusterIP 服务:为简单起见,服务从平台的内部和外部以相同的方式访问。

HTTP和TCP路由网格的初始实现可能只有几百行Python,使用相当简单(我敢说,天真)的算法,但随着时间的推移演变以处理平台的增长和其他要求。它不需要对现有应用程序代码进行大量重构。特别是十二因子应用程序可以直接使用通过环境变量提供的地址信息。

与现代服务网格有什么不同?
1. 可观察性有限。TCP路由网格根本没有度量标准。至于HTTP路由网格,后来的版本提供了详细的HTTP指标,显示了错误代码和响应时间; 但是现代服务网格已经超越,并提供与Prometheus等指标收集系统的集成。可观察性不仅从运维角度(帮助我们解决问题)很重要,而且还提供安全蓝/绿部署或 金丝雀部署等功能

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

现代服务网格比这更好。首先,确保在源处路由连接。逻辑流仍然是client → mesh → service,但现代网格是在本地运行,而不是在远程节点上运行,因此client → mesh连接是本地连接,因此非常快(微秒而不是毫秒)。

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

现代服务网格的安全性也更强。dotCloud路由网格完全在EC2 Classic上运行,并没有对流量进行加密。现代服务网格可以透明地保护我们的所有流量,例如通过相互TLS身份验证和后续加密。

平台服务的流量路由
我们已经讨论了应用程序如何沟通,但是dotCloud平台本身又如何呢?
平台本身由大约100个微服务组成,负责各种功能。其中一些服务接受了其他人的请求,其中一些是后台工作者,可以连接到其他服务,但不能自己接收连接。无论哪种方式,每个服务都需要知道连接所需的地址的端点。
这些服务以非常简单粗暴的方式公开:有一个YAML文件列出了这些服务,将它们的名字映射到它们的地址; 并且这些服务的每个使用者都需要该YAML文件的副本作为其部署的一部分。

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

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

这与AirBNB的SmartStack非常相似; 
我个人认为SmartStack是Istio,Linkerd,Consul Connect等系统的先驱之一......因为所有这些系统都遵循这种模式:

  • 在每个节点上运行代理
  • 消费者连接到代理
  • 控制平面在后端更改时更新代理的配置
  • ......利润!

今天的服务网格
如果我们今天必须实现类似的网格,我们可以使用类似的原则。例如,我们可以设置内部DNS区域,将服务名称映射到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 ClusterIP地址)中分配地址而不是 127.0.0.0/8
  • 它有一个额外的组件(Citadel)来在客户端和服务器之间添加相互TLS身份验证
  • 它增加了对断路,分布式跟踪,金丝雀部署等新功能的支持......

Istio vs. Linkerd vs. Consul Connect
到目前为止,我们只讨论过Istio,但它并不是唯一的服务网格。Linkerd是另一个受欢迎的选择,还有Consul Connect

我们应该选哪一个?

实说,我不知道,在这一点上,我认为自己没有足够的知识来帮助任何人做出这个决定。有一些有趣的 文章 比较 它们,甚至是基准

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