作为一个开源项目,Envoy 拥有大量的拥护者并且由于可以用来解决任意大型分布式系统中出现的网络问题,它的用户数量还在持续增长。 但它究竟是什么呢? 又如何开始使用呢?
欢迎来到 Envoy 101,对于刚接触 Envoy 的新手的理想选择。 本文将提供一个简单易懂的介绍,介绍如何将 Envoy 设置为网关、提供 yaml 示例并解释 yaml 在每个步骤中的做法和原因。 最后会提供完整的 envoy.yaml 文件,读者可以根据它自己尝试设置网关并使用它将流量引流到两个服务上。
Envoy 的用途
Envoy 代理有两个常见用途。一是用作服务代理(sidecar),二是用作网关。
用作 sidecar 时,Envoy 是一个位于服务旁边的四层或七层的应用代理,可以生成指标、应用策略和控制流量。
用作API网关时,Envoy 作为一个“前置代理”接受 inbound 流量,核对请求中的信息并将其定向到目的地。 本文的例子将演示如何使用 Envoy 作为前置代理。 我们将编写一个静态配置,返回例如 HTTP 和 IPv4 等不会改变的静态数据。 正如你将在本例中看到的那样,这一用途很简单,适合处理几乎不变化的信息。
这个配置能做什么?
该 yaml 配置是一个好起点,它展示了如何使用 Envoy 将流量路由到不同的端点并介绍了一些关键概念。
下图的箭头展示了请求在配置中的流程。五个关键元素分别是 “listener”、“filter chain”、“route”、“cluster” 和 “endpoint”。 其中 route 是 filter chain 的一部分,filter chain 是 listener 的一部分。
如果还是不太能理解,希望以下的说明会对你有帮助。 简单来说,它们是静态 API yaml 中的重要部分,描述了 Envoy 网关应该如何处理流量。
listener 的工作最为重要。 它与端口“绑定”并监听到达网关的 inbound 请求。 listener 只接受来自它绑定的端口的请求。 任何通过其他端口进来的请求都不会被 Envoy 看到或处理,发送该请求的用户只会获得错误响应。
一旦请求被listener接受,它就会经过一条 filter chain, 该 filter chain 描述请求一旦进入 Envoy 后应当被如何处理。 filter chain 由一些 filter 组成,filter 决定请求能否被传递到下一个 filter 或发生短路并向用户发送 404 错误。
在本例中,如果一个请求通过了 filter chain 中的所有 filter,route(作为 filter chain 的扩展)将会获取 HTTP 请求信息并将其定向到正确的服务。
现在,在了解 Envoy 的功能和基本的请求流程后,我们开始介绍 yaml。
了解yaml
在运行完整的配置之前,最好先了解每部分都在做些什么。 在每个部分,它将向你介绍一些核心概念(和术语)。随着你不断使用 Envoy 和阅读文档,你会越来越多的看到这些概念。
声明静态资源
static_resources: listeners: - address: socket_address: address: 0.0.0.0 port_value: 8080
我们做的第一件事是声明这个一个对于静态资源的配置,这意味着其中的信息是不能改变的。 以上配置描述了上图中 listener 的信息。
listener 将预期地址设置为 IPv4(0.0.0.0)并将 “port_value” 设为 8080。 IPv4 是 IP 地址的基本标准,我们想让 Envoy 能够监听世界上几乎所有的流量。listener 会自己绑定到 8080 端口 并将不会接受通过其他任何接口传来的流量。
过滤链和路由
filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager codec_type: auto stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: backend domains: - "*" routes: - match: prefix: "/service/1" route: cluster: service1 - match: prefix: "/service/2" route: cluster: service2 http_filters: - name: envoy.filters.http.router typed_config: {}
filter chain 由一些形成 chain 的 filter 组成,yaml 描述了请求进入 Envoy 后应该被如何过滤和路由。 首先将该 filter 定义为 http_connection_manager. 接着写明经过该 filter 的请求将被发送给 http_filters 和 http.router。
该 filter 的另一部分是告诉 chain 如何根据它匹配到的前缀向对应的集群路由流量。 路由通常匹配 HTTP 相关的名词,包括请求头、路径名或主机名等。在本例中,请求是根据路径名而非请求头或主机名进行路由的(如 match: prefix 行所示)。
设置集群
clusters: - name: service1 connect_timeout: 0.25s type: strict_dns lb_policy: round_robin http2_protocol_options: {} load_assignment: cluster_name: service1 endpoints: - lb_endpoints: - endpoint: address: socket_address: address: service1 port_value: 8000 - name: service2 connect_timeout: 0.25s type: strict_dns lb_policy: round_robin http2_protocol_options: {} load_assignment: cluster_name: service2 endpoints: - lb_endpoints: - endpoint: address: socket_address: address: service2 port_value: 8000
类似的,在这儿设置两个集群也很平常且容易。 为什么设置两个集群? 因为它将流量路由到两个不同的端点集合。 这些服务(集群)已经被命名好了。 它们设置了 0.25 秒的连接超时和 round-robin 负载均衡策略。 在生产环境中,round-robin 可能不是最好的选择,但对于 demo 解释来说,它是有效的。 如果想了解更多关于 Envoy 中可以配置什么类型的超时,建议查看 Envoy 文档。
特别值得注意的是 HTTP/2 的使用,与前代相比它改变了数据的格式和传输方式以减少延迟。 如果想了解更多关于 HTTP/2 的知识,建议阅读 Google 关于 Web 基础知识的介绍性文章。
管理
admin: access_log_path: "/dev/null" address: socket_address: address: 0.0.0.0 port_value: 8001
这里设置了对 Envoy 管理平面的管理权限。 这意味着你可以在 localhost 中访问管理数据。
完整的 yaml 文件
static_resources: listeners: - address: socket_address: address: 0.0.0.0 port_value: 8080 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager codec_type: auto stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: backend domains: - "*" routes: - match: prefix: "/service/1" route: cluster: service1 - match: prefix: "/service/2" route: cluster: service2 http_filters: - name: envoy.filters.http.router typed_config: {} clusters: - name: service1 connect_timeout: 0.25s type: strict_dns lb_policy: round_robin http2_protocol_options: {} load_assignment: cluster_name: service1 endpoints: - lb_endpoints: - endpoint: address: socket_address: address: service1 port_value: 8000 - name: service2 connect_timeout: 0.25s type: strict_dns lb_policy: round_robin http2_protocol_options: {} load_assignment: cluster_name: service2 endpoints: - lb_endpoints: - endpoint: address: socket_address: address: service2 port_value: 8000 admin: access_log_path: "/dev/null" address: socket_address: address: 0.0.0.0 port_value: 800
自己试一试吧!
首先,确保 Docker Compose 正在运行。 如果没在运行,按这个说明操作: https://docs.docker.com/compose/gettingstarted/。
然后,你要运行的所有东西都在这儿: https://github.com/envoyproxy/envoy/tree/master/examples/front-proxy。
如果跟随Github repo中的说明进行操作,你将会看到输出结果! 别担心 service.py 中的任何错误,它们既不重要也不影响脚本运行。
运行命令 “docker-compose up” 就行了。
一旦在 bash 终端确认 service 1 和 service 2 正在运行。
在浏览器里打开 http 链接并在网址后面加上 /service/1 或者 /service/2。如果没加上,你会看到 404 错误。
这就可以啦! 你已经为自己设置好了一个 Envoy 网关并用它将流量定向到了两个服务。
要点重述
现在让我们来看看为什么配置以这样的方式运作。 下图显示请求通过 Envoy 到达 Service 2 端点的流程。
以上每一步中都会进行校验以确保消息的正确性并将其送到正确的地方。 例如,如果试图向 /service/3 提出请求,请求会一直抵达路由器(http_router)处才会确定没有地方可以路由该请求。 服务 3 不存在。
静态配置在可预测且简单的情况下很棒。 然而在经常变化的动态环境中,它并不实用。 如果你试图在动态环境中使用静态配置,将会进行大量的手动更改(这很费时)。
因此,这篇文章已经介绍了 Envoy 的关键概念,但我并不建议将其投入生产。
如果你想了解更多关于Envoy的信息,请查看我们的资源库以及我们的开源项目 GetEnvoy 。
Tia 是 Tetrate 的内容作者。 Tetrate 专注为以开源项目 Istio、Envoy、Zipkin 和 Apache SkyWalking 为基础开发产品的企业,提供支持和解决方案。
特别鸣谢 Cynthia Coan、周礼赞和 Vikas Choudhary 为文章进行技术校对。