[编者的话]Envoy 是一款由 Lyft 开源的 7 层代理和通信总线, 本文作者就 Envoy 的背景, 主打功能特性以及一些配置细节做了简单介绍.
使用微服务来解决现实世界中遇到的问题常常会比简单地编写代码更加深入. 你需要测试你的服务. 你需要弄清楚如何进行持续部署. 你需要找出一个服务之间干净, 优雅, 弹性的交互方式.
Lyft 公司出品的 Envoy https://lyft.github.io/envoy/ 是一款非常有趣的工具, 它可以帮助服务之间 "互相交谈".
Lyft Envoy 概览
Envoy Proxy https://www.envoyproxy.io/ 是一款现代化的, 高性能, 小体积的边缘及服务代理. Enovy 为用户的服务加入了弹性和监测能力, 它是通过一种对服务透明的方式做到这一点的. 你可能会觉得很奇怪, 我们为什么要给一个自称为 代理 https://softwareengineeringdaily.com/2017/02/14/service-proxying-with-matt-klein/ 的家伙加油打气呢 -- 毕竟, 在此之前业内已经出现了无数款代理软件, 其中还包括一些重量级的明星产品, 像 NGINX http://nginx.org/en/ 和 HAProxy http://www.haproxy.org/ , 是吧? 然而, 这也正是 Enovy 的有趣之处:
它能够代理任何 TCP 协议
它支持双向 SSL
它将 HTTP/2 视为一等公民, 并且可以在 HTTP/2 和 HTTP/1.1 之间相互转换(双向)
它在服务发现和负载均衡方面很灵活
它被设计用于提高系统的可见性
尤其是, Enovy 可以生成许多流量方面的统计数据, 这是其它代理软件很难取代的地方
- 在某些情况下 (如 MongoDB 和 Amazon RDS),Enovy 可以很确切地知道如何查看压缩协议(wire protocol) 并进行透明监控
相比于其它代理软件, Envoy 更容易搭建
Envoy 是一个 sidecar 进程, 因此它对服务的实现语言完全无感知
Enovy 可以通过一些相对高端 -- 而复杂 -- 的方式扩展, 我们会在之后深入了解这块 -- 可能要很久以后. 现在我们先简单了解一下.(如果你感兴趣, 想了解更多关于 Envoy 的细节, Matt Klein https://thenewstack.io/lyfts-envoy-provides-move-monolith-soa/ 在 2017 年微服务实践者峰会 https://www.microservices.com/summit/?__hstc=118178582.8fe193846e01f9e9abb7d8dd7c55649b.1535158833057.1535852898631.1536365386208.3&__hssc=118178582.1.1536365386208&__hsfp=2062320011 上有过一次 很棒的演讲 https://www.microservices.com/talks/lyfts-envoy-monolith-service-mesh-matt-klein/?__hstc=118178582.8fe193846e01f9e9abb7d8dd7c55649b.1535158833057.1535852898631.1536365386208.3&__hssc=118178582.1.1536365386208&__hsfp=2062320011 )
Envoy 支持任意 TCP 协议的代理, 包括 SSL, 做到这一点是相当了不起的. 想要代理 websockets?Postgres?Raw TCP? 都没问题. 另外, Envoy 支持同时接收和发起 SSL 连接, 这在某些时候很方便: 你可以让 Enovy 做客户端证书验证, 而与此同时仍然在 Envoy 和你的服务之间维持一个 SSL 连接.
当然, HAProxy 也支持任意协议的 TCP 和 SSL 代理, 但在 HTTP/2 方面, 它只支持将整个流转发到一个支持 HTTP/2 的后端服务器. NGINX 不支持任意协议的代理(公平地讲, Enovy 也不支持像 FastCGI 这样的协议, 因为 Envoy 不是一个 Web 服务器). 无论是开源的 NGINX 还是 HAProxy 都不能很好的处理服务发现(尽管 NGINX Plus 有提供一些参数), 并且它们也无法提供和一个配置好的 Envoy 代理所给出的统计数据等同的功能.
总的来说, 我们认为 Envoy 前景良好 http://lethain.com/envoy-design/ , 它通过一款单一的软件满足了我们的众多需求, 而不需要我们去搭配一些工具混合使用.
Envoy 架构
虽然我说过, 相比一些曾经使用过的其他产品, Envoy 的配置并不是那么繁琐. 但是你可能会注意到, 我并没有说过它一定很容易. Envoy 的学习曲线刚开始是有些陡峭的, 这是有原因的, 而且也是值得的.
Envoy 和它的网络栈
假设你要编写一款 HTTP 网络代理. 有两种显而易见的方式: 在 HTTP 层工作, 或者在 TCP 层工作.
在 HTTP 层的话, 你将会从传输线路上读取整个 HTTP 请求的数据, 对它做解析, 查看 HTTP 头部和 URL, 并决定接下来要做什么. 随后, 你将从后端读取整个响应的数据, 并将其发送给客户端. 这即是一款 OSI 7 层 (应用层) 代理: 代理完全明白用户想要做什么, 并且可以利用这些认知做出非常聪明的决策.
这种做法的缺点就是非常复杂和缓慢 -- 试想在做出任何决定之前因为读取和解析整个请求而引入的延迟! 更糟糕的是, 有时候最高级别的协议根本没有决策所需的信息. 一个很好的例子便是 SSL: 在添加 SRI 扩展之前, SSL 客户端永远不会说明它在尝试连接哪台主机 -- 因此尽管 HTTP 服务器能够很好地处理虚拟主机(使用 HTTP/1.1 协议中定义的 Host 头部), 一旦涉及到 SSL, 你就必须给你的服务器专门分配一个 IP 地址, 因为一个 7 层代理根本没有正确代理 SSL 所需的信息.
因此, 也许更好的选择是下沉到 TCP 层操作: 只读取和写入字节, 并使用 IP 地址, TCP 端口号等来决定如何处理事务. 这即是 OSI3 层 (网络) 或 4 层 (传输) 代理, 具体取决于用户要交互的对象. 我们将借用 Envoy 里的术语, 将其称之为 3/4 层代理.
在这个模型中, 代理处理事务的速度很快, 某些事情变得更优雅和简单(参见上面的 SSL 示例). 另一方面, 假设用户要根据不同的 URL 代理到不同的后端呢? 对于经典的 L3 / 4 代理, 这是不可能的: 在这些层上无法访问更高层的应用程序信息.
Envoy 支持同时在 3,4 层和 7 层操作, 以此应对这两种方法各自都有其实际限制的现实. 这很强大, 并且也非常高效... 但是用户通常付出的代价便是配置的复杂性.
我们面临的挑战是让简单的事务维持它的简便, 同时允许复杂的事务成为可能, 而 Envoy 在 HTTP 代理等方面做得相当不错.
Envoy 网格
关于 Envoy, 接下来这一点可能会让人感到多少有些惊讶, 那便是大多数应用涉及到的是两层 Envoy, 而不是一层:
首先, 会有一个 "边缘 Envoy" 在某个地方单独运行. 边缘 Envoy 的工作是给其他地方提供一个入口. 来自外部的传入连接请求到这里, 边缘 Envoy 将会决定他们在内部的转发路径.
其次, 服务的每个实例都有自己的 Envoy 与它一起运行, 这是一个运行在服务一侧的独立进程. 这些 "服务 Envoy" 会密切关注他们的服务, 并且记住哪些是正在运行的, 哪些不是.
所有的 Envoy 形成一个网格, 然后在他们之间共享路由信息.
如果需要的话(通常都是这样), 服务间调用也可以通过 Envoy 网格. 我们稍后会讨论到这个问题.
注意, 你当然也可以只使用边缘 Envoy, 然后去掉 Envoy 服务这一层. 但是, 使用完整网格的话, 服务 Envoy 可以对应用服务进行健康监控等, 让网格知道尝试联系一个挂掉的服务是否是毫无意义的. 此外, Envoy 的统计数据收集最适合用在全网格上(尽管这块更多内容会放到一篇单独的文章里).
网格中的所有 Envoy 使用同一份代码运行, 但是它们的配置显然是不同的... 这就来到了下一节, 关于 Envoy 的配置文件.
Envoy 配置概览
Envoy 的配置看起来很简单: 它主要由监听器 (listener) 和集群 (cluster) 组成.
一个监听器告诉 Envoy 它应该监听的 TCP 端口, 以及决定 Envoy 应该收听处理的一组过滤器(filter). 集群会告诉 Envoy, 它可以代理传入请求的一个或多个后端主机. 到目前为止一切还挺顺利. 但是, 这里有两个方法可以让事情变得更简单:
过滤器可以 - 通常也是必须的 - 拥有他们自己的配置, 而且通常他们会比监听器的配置更加复杂!
集群和负载平衡可以放在一起, 并且可以用到像 DNS 之类的一些外部事物.
由于我们一直在谈论 HTTP 层的代理, 我们不妨继续看一下 http_connection_manager 这个过滤器. 该过滤器工作在 3/4 层, 因此它可以访问来自 IP 和 TCP 的信息(如连接两端的主机和端口号), 但是它也可以很好地理解 HTTP 协议, 获取 HTTP URL, 标题等, 这均适用于 HTTP/1.1 和 HTTP/2. 每当新连接抵达时, http_connection_manager 都会利用上述的所有这些信息来决定哪个 Envoy 集群最适合处理该连接. 然后, Envoy 集群将会使用其负载平衡算法挑选出单个成员来处理该 HTTP 连接.
http_connection_manager 的过滤器配置是一个包含很多选项的字典, 但是目前最重要的一个参数是 virtual_hosts 数组, 它定义了过滤器该如何做出路由决策. 数组中的每个元素都是包含如下属性的字典:
name domains routes
每个路由字典至少需要包含:
- prefix
- cluster
- timeout_ms
所有这一切即代表着一套最简单的 HTTP 代理 - 在 HTTP 的指定端口上侦听, 然后根据 URL 路由到不同的主机 - 实际上在 Envoy 中配置起来也非常简单.
例如: 将以 /service1 开头的 URL 代理到名为 service1 的集群, 将以 /service2 开头的 URL 代理到名为 service2 的集群, 你可以使用:
- "virtual_hosts": [
- {
- "name": "service",
- "domains": ["*"],
- "routes": [
- {
- "timeout_ms": 0,
- "prefix": "/service1",
- "cluster": "service1"
- },
- {
- "timeout_ms": 0,
- "prefix": "/service2",
- "cluster": "service2"
- }
- ]
- }
- ]
就是这样. 请注意, 我们使用 domains ["*"] 表明我们不太关心请求哪个主机, 并且值得一提的是我们可以按需添加多条路由. 最后, 边缘 Envoy 和服务 Envoy 之间的这个监听器配置基本相同: 主要区别在于服务 Envoy 可能只有一个路由, 它只会代理 localhost 上的服务而不是包含多个主机的一个集群.
当然, 我们仍然需要定义上面 virtual_hosts 部分中引用的 service1 和 service2 集群. 我们可以在 cluster_manager 配置部分实现这一点, 它也是一个字典, 并且还有一个称为 clusters 的关键组件. 它的值, 又是一组词典:
name : 集群的一个可读名称
type : 此集群将如何知道哪些主机已启动?
lb_type : 这个集群将如何处理负载均衡?
hosts : 定义集群所属主机的一个 URL 数组(实际上通常是 tcp:// URLs).
type 的可选值有:
- static
- strict_dns
- logical_dns
- sds
而 lb_type 的可选值有:
- round_robin
- weighted_least_request
- random
关于负载均衡还有一个有趣的注意事项: 集群还可以定义一个恐慌阈值(panic threshold), 其中, 如果集群中健康主机的数量低于恐慌阈值, 集群将判定健康检查算法可能存在问题, 并且假定所有主机在集群中是健康的. 这可能会导致一些意外情况发生, 因此这一点最好留意一下!
一个边缘 Envoy 的简单用例可能是这样的:
- "clusters": [
- {
- "name": "service1",
- "type": "strict_dns",
- "lb_type": "round_robin",
- "hosts": [
- {
- "url": "tcp://service1:80"
- }
- ]
- },
- {
- "name": "service2",
- "type": "strict_dns",
- "lb_type": "round_robin",
- "hosts": [
- {
- "url": "tcp://service2:80"
- }
- ]
- }
- ]
由于我们将这个集群标记为 strict_dns 类型, 它将依赖于在 DNS 中查找 service1 和 service2 , 我们假定任何新起的服务实例将会被添加到 DNS 中 ---- 这可能适用于像使用 docker-compose 配置的类似情况. 针对服务 Envoy(比如 service1 ), 我们可能会采取一个更直接的路由方式:
- "clusters": [
- {
- "name": "service1",
- "type": "static",
- "lb_type": "round_robin",
- "hosts": [
- {
- "url": "tcp://127.0.0.1:5000"
- }
- ]
- }
- ]
想法类似, 只是目标不太一样: 我们总是在本地主机上转发到我们的服务, 而不是重定向到其他主机.
接下来
这就是 Envoy 千里之行的一小步, 我们还介绍了一些深入内容, 关于 Envoy 的背景和配置. 接下来, 我们将使用 Kubernetes,Postgres,Flask 和 Envoy 实际部署一个简单的应用程序, 并在这个过程中观察我们扩容和缩容时会发生什么. 敬请关注.
来源: http://www.tuicool.com/articles/NZbIbiB