Envoy官网 https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/intro/terminology
以下信息皆参考官网
原理
Envoy采用单进程多线程的模式,监听器在接收到请求后会交给工作线程进行处理,工作线程采用100%非阻塞进行通讯(Epoll)。这里建议工作线程的数量配置为等于机器上的硬件线程的数量。
根据上面的流程图,我们开始对每个模块进行详解。
基本术语
在讲解之间,我们先了解一下他们的基本意思:
- 主机 是一个逻辑网络应用程序,一个物理硬件可能有多个主机在其上运行,只要它们中的每一个都可以独立寻址,比如说POD里面的一个容器就代表一个主机。
- 下游 向Envoy发送请求应用,相当于外部应用。
- 上游 接受Envoy发送的请求,相当于POD里面的容器应用。
- 监听器( Listeners ) 向外部暴露的端口或者unix套接字等。下游通过暴露的端口或者unix套接字对Envoy进行请求。
- 路由 (Routes) 主要适用于HTTP,因为HTTP有路径的概念。它可以根据Uri进行前缀、精准、正则表达式等匹配。
- 集群 ( Cluster) Envoy集群概念跟传统的集群有些区别,传统集群就是多台机器,
envoy集群是一组有逻辑相似的上游主机组成。这个逻辑通过Envoy服务发现机制进行设定,比如Envoy解析一个DNS域名获得的所有IP就组成一个集群,我们可以进行手动配置,具体细节一会讲解、 - 端点 (Endpoint) 上游应用称之为端点。
- 运行时配置 提供了动态更改配置,无需重启envoy就可以生效。
资源类型
在看该模块之前,请先跳转到下面阅读官网DEMO实例YAML分析,请注意观察"@type" 属性,该属性配置了一个URL,那么这个URL的作用是什么那?这就是接下来我们要讲解的内容。
- Envoy 通过文件系统或通过查询一个或多个管理服务器来发现其各种动态资源。这些发现服务及其相应的 API 统称为xDS
- xDS API 中的每个配置资源都有一个与之关联的类型。资源类型遵循 版本控制方案。资源类型的版本独立于下面描述的传输。
支持以下 v3 xDS 资源类型:
- envoy.config.listener.v3.Listener
- envoy.config.route.v3.RouteConfiguration
- envoy.config.route.v3.ScopedRouteConfiguration
- envoy.config.route.v3.VirtualHost
- envoy.config.cluster.v3.Cluster
- envoy.config.endpoint.v3.ClusterLoadAssignment
- envoy.extensions.transport_sockets.tls.v3.Secret
- envoy.service.runtime.v3.Runtime
typeURL的概念 出现在下面,并采用以下形式——例如, 对于资源。在来自 Envoy 的各种请求和管理服务器的响应中,都说明了资源类型 URL。type.googleapis.com/type.googleapis.com/envoy.config.cluster.v3.ClusterCluster
比如 type.googleapis.com/envoy.extensions.filters.listener.http_inspector.v3.HttpInspector 标明了Http检测监听过滤器类型
说白了就是,我们在使用插件的过程中只需要指定插件的名称即可,而在Envoy中我们需要指定当前插件类型在GRPC中的URL地址
监听器
:::info
Envoy支持TCP与UDP监听。
:::
监听过滤器
:::success
请求到达监听器后首先会经过监听器的过滤器,然后我们可以根据过滤器的结果选择网络过滤器链,对请求元数据进行更改。比如监听过滤器判断当前请求是否为Http,如果是,然后选择运行不同的网络过滤器。
:::
| ### Envoy内置了几个监听过滤器
| |
| — | — |
| #### 过滤器名称
| #### 过滤器作用
|
| Http检查器 | 检查是否为http请求 |
| 原始目的地 | 当传入连接通过 iptables REDIRECT 或 TPROXY 目标或使用代理协议重定向到 Envoy 时,可以使用原始目标集群。在这些情况下,路由到原始目标集群的请求被转发到由重定向元数据寻址的上游主机,而无需任何显式主机配置或上游主机发现。 |
| 原始来源 | 当Envoy向上游发送请求时,原始IP是Envoy的ip并不是下游的IP,所以该过滤器功能是修改原始IP将envoy的IP变成下游请求的IP再发送给上游主机。注意Linux 上需要 CAP_NET_ADMIN 功能。CAP_NET_ADMIN 提供了普通用户也可以修改原始IP地址的权限。 |
| 代理协议 | 如果下游源地址没有被转换或代理,Envoy就可以使用简单的原始来源过滤器 更换源IP。否则源地址已经被代理更换了,那么就是会使用该协议,该协议会将源IP地址添加到受信任的 http 标头或者使用 HAProxy 代理协议,在这种协议下假定下游连接来自将原始坐标(IP、PORT)放入连接字符串的代理。Envoy 然后提取这些并将它们用作远程地址。 |
| TLS 检查 | TLS Inspector 侦听器过滤器允许检测传输是 TLS 还是纯文本,以此来决定接下来要走的链 |
# 这里是监听过滤链,元素为监听过滤器
listener_filters:
- name: envoy.filters.listener.http_inspector
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.listener.http_inspector.v3.HttpInspector
# 该chains是当前监听器所有过滤链
filter_chains:
- filter_chain_match:
# 这里判断传输协议为h2
application_protocols: ["h2"]
filters: #该数组里的元素为网络过滤器
- name: my_http2_filter
...
- filter_chain_match:
application_protocols: ["http/1.1"]
filters:
- name: my_http1_filter
网络过滤器
当请求通过监听过滤器之后,通过过滤链匹配机制到达网络过滤器(比如,我们使用了Http检查过滤器,那么网络过滤链需要设置传输协议为h2或h1.1/h1.0才能进行匹配),网络过滤器是一个网络级别的过滤器可以进行连接限制、TLS握手、TCP代理等。
| ## Envoy有几个默认的网络过滤器
| |
| — | — |
| ### 过滤器名称
| ### 作用
|
| Dubbo代理 | dubbo 代理过滤器对 dubbo 客户端和服务器之间的 RPC 协议进行解码。解码后的 RPC 信息被转换为元数据。元数据包括基本请求ID、请求类型、序列化类型,以及路由所需的服务名、方法名、参数名和参数值。 |
| 客户端TLS身份验证 | 通过从 REST VPN 服务获取的证书列表执行 TLS 客户端身份验证。此过滤器将提供的客户端证书哈希与证书列表进行匹配,以确定是否应允许连接。还可以配置可选的 IP 许可名单。此功能可用于为 Web 基础架构构建边缘代理 VPN 支持。 调用的是GET /v1/certs/list/approved |
| 连接限制过滤器 | 连接限制过滤器将连接限制应用于由过滤器的过滤器链处理的传入连接。过滤器处理的每个连接都标记为活动连接,如果活动连接数达到最大连接数限制,将关闭连接,无需进一步过滤器迭代。 |
| TCP代理 | TCP 代理过滤器在下游客户端和上游集群之间执行基本的 1:1 网络连接代理。它可以单独用作 stunnel 的替代品,也可以与其他过滤器(例如MongoDB 过滤器或速率限制过滤器)结合使用。 |
| … | … |
:::info
多个网络过滤器称之为过滤器链,共同完成请求过滤作用。每个侦听器都有多个过滤器链和一个可选的默认过滤器链。与每个过滤器链相关联。如果找不到最佳匹配过滤器链,将选择默认过滤器链来服务请求。如果未提供默认过滤器链,则连接将关闭。
:::
路由(HCM)
Envoy的过滤器可以对元数据进行修改,那么既然如此我们对请求进行转发,重定向就可以直接在过滤器中实现而不必再进入应用内部处理.Envoy也是这么做的,接下来就让我们了解一下HTTP connection management(HCM) - 一个运行在envoy的网络过滤器插件.
:::info
HTTP 是现代面向服务架构的关键组件,Envoy 实现了大量的 HTTP 特定功能。Envoy 有一个内置的网络级过滤器,称为 HTTP 连接管理器。此过滤器将原始字节转换为 HTTP 级别的消息和事件(例如,收到的标头、收到的正文数据、收到的尾标等)。它还处理所有 HTTP 连接和请求共有的功能,例如访问记录、请求 ID 生成和跟踪、 请求/响应标头操作、路由表管理和统计。
:::
它主要提供的功能有:
- 路由匹配
- 流量转移
- 协议头管理
- RBAC
- 重试
- 超时
- …
HCM对于这些功能的实现也是采用过滤器的形式,字段http_filters 可以进行配置(有顺序性).比如envoy.filters.http.router提供流量路由的功能,可以说HCM的配置都是为http_filters 配置的过滤器提供服务的.
注意,在HCM配置中有一个字段是route_config 它的功能配置管理器的静态路由表.而envoy.filters.http.router过滤器就是读取静态路由表然后对请求进行路由.
下面是一个简单的路由配置:
static_resources:
# 创建监听器
listeners:
- name: listener_0
# 配置监听地址与端口号
address:
socket_address:
address: 0.0.0.0
port_value: 10000
# 当前监听器的过滤链
filter_chains:
- filters:
# 使用HCM 网络过滤器
- name: envoy.filters.network.http_connection_manager
typed_config:
# 通过该URL获取到HCM类型
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
# 适用于统计时的标识
stat_prefix: hello_service
# 设置http过滤器 ,这里使用的是路由转发过滤器,就是说开启了路由转发功能
http_filters:
- name: envoy.filters.http.router
# 配置路由表
route_config:
name: route
virtual_hosts:
- name: hello_vhost
# 匹配的域名
domains: ["hello.io"]
routes:
# 根据uri进行转发、重定向等功能
- match:
path: "/api"
# 这里可以配置直接响应,就是说直接返回给下游 状态码为200 内容为 hellp - path的数据
direct_response:
status: 200
body:
inline_string: "hello - path"
- match:
# 对于uri的匹配,可以是前缀匹配、完全匹配、正则表达式匹配等
safe_regex:
google_re2: {}
regex: ^/hello/\d+$
direct_response:
status: 200
body:
inline_string: "hello - regex"
- match:
prefix: "/"
direct_response:
status: 200
body:
inline_string: "hello - prefix"
集群
:::info
集群从推理的角度来将,由服务发现发现出来的主机的合集称之为集群(Envoy的集群)
:::
那么服务发现是如何发现的那?下面让我们对三种方式进行讲解
- 静态配置 静态是最简单的服务发现类型。配置明确指定每个上游主机的解析网络名称(IP 地址/端口、unix 域套接字等)。
- 严格DNS发现
- 当使用严格的 DNS 服务发现时,Envoy 会持续异步地解析指定的 DNS 目标。DNS 结果中返回的每个 IP 地址都将被视为上游集群中的显式主机。这意味着如果查询返回三个 IP 地址,Envoy 将假定集群有三个主机,并且所有三个主机都应该负载均衡。
- 如果从结果中删除主机,Envoy 会假定它不再存在,并将从任何现有的连接池中耗尽流量。因此,如果成功的 DNS 解析返回 0 个主机,Envoy 将假定集群没有任何主机。请注意,Envoy 从不同步解析转发路径中的 DNS。以最终的一致性为代价,永远不用担心会阻塞长时间运行的 DNS 查询。
- 如果一个 DNS 名称多次解析到同一个 IP,这些 IP 将被重复数据删除。
- 如果多个 DNS 名称解析到同一个 IP,则_不会_共享运行状况检查。这意味着如果将主动健康检查与解析为相同 IP 的 DNS 名称一起使用,则应小心:如果 IP 在 DNS 名称之间重复多次,则可能会导致上游主机的过度负载。
- 逻辑DNS
- 逻辑 DNS 使用与严格 DNS 类似的异步解析机制。但是,逻辑 DNS 集群并没有严格采用 DNS 查询的结果并假设它们包含整个上游集群,而是仅使用_需要启动新连接时_返回的第一个 IP 地址。因此,单个逻辑连接池可能包含到各种不同上游主机的物理连接。连接永远不会耗尽,包括返回 0 个主机的成功 DNS 解析。
- 这种服务发现类型最适合必须通过 DNS 访问的大规模 Web 服务。此类服务通常使用循环 DNS 来返回许多不同的 IP 地址。通常,每个查询都会返回不同的结果。如果在这种情况下使用严格的 DNS,Envoy 会假设集群的成员在每个解析间隔内都在发生变化,这会导致连接池耗尽、连接循环等。相反,使用逻辑 DNS,连接会一直保持活动状态,直到它们被循环。在与大规模 Web 服务交互时,这是所有可能的世界中最好的:异步/最终一致的 DNS 解析、长期连接以及转发路径中的零阻塞。
- 端点发现服务 (EDS) _端点发现服务_是基于 gRPC 或 REST-JSON API 服务器的 xDS 管理服务器,Envoy 使用它来获取集群成员。集群成员在 Envoy 术语中称为“端点”。对于每个集群,Envoy 从发现服务中获取端点。EDS 是首选的服务发现机制,原因如下:
- Envoy 对每个上游主机都有明确的了解(相对于通过 DNS 解析的负载平衡器进行路由),并且可以做出更智能的负载平衡决策。
- 每个主机的发现 API 响应中携带的额外属性会通知 Envoy 主机的负载平衡权重、金丝雀状态、区域等。这些附加属性在负载平衡、统计收集等期间由 Envoy 网格全局使用。
- Envoy 项目在Java 和Go中提供了 EDS 和其他发现服务的参考 gRPC 实现 。
例子分析
官网提供了详细的例子供我们进行理解,接下来就让我们走进这个例子
static_resources:
# 创建一个监听器
listeners:
# 监听器的名称
- name: listener_https
# 监听地址
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 443
# 这是监听过滤链
# 这是一个TLS监听过滤器,判断当前请求是否为TLS
listener_filters:
# 过滤链的名称,只标识一个名称
- name: "envoy.filters.listener.tls_inspector"
typed_config:
# 过滤链类型
"@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
# 网络过滤链
filter_chains:
- filter_chain_match:
# 这里获取当前域名用于sni证书判断
server_names: ["acme.com"]
# Downstream TLS configuration.
transport_socket:
# 使用TLS连接下游请求
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
# 配置TLS连接需要服务器证书与秘钥
common_tls_context:
tls_certificates:
- certificate_chain: {filename: "certs/servercert.pem"}
private_key: {filename: "certs/serverkey.pem"}
filters:
# 此过滤器将原始字节转换为 HTTP 级别的消息和事件(例如,收到的标头、收到的正文数据、收到的尾标等)。
# 它还处理所有 HTTP 连接和请求共有的功能,例如访问记录、请求 ID 生成和跟踪、 请求/响应标头操作、路由表管理和统计。
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
# 每个连接管理器都有一个以http.<stat_prefix> 为根的统计树
stat_prefix: ingress_http
# 如果设置为 true,则连接管理器将在确定内部与外部来源并处理各种标头时使用客户端连接的真实远程地址。
# 如果设置为 false 或不存在,连接管理器将使用 x-forwarded-for HTTP 标头。
use_remote_address: true
# 直接传递给 HTTP/2 编解码器的附加 HTTP/2 设置
http2_protocol_options:
# 连接同一个IP的最大数量
max_concurrent_streams: 100
# 管理器使用的日志配置
access_log:
- name: envoy.access_loggers.file
typed_config:
# 使用的日志插件
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
# 写入访问日志条目的本地文件的路径。
path: "/var/log/envoy/access.log"
# 路由配置
route_config:
name: local_route
# 虚拟主机
virtual_hosts:
# 虚拟主机的逻辑名称。这在发出某些统计信息时使用,但与路由无关。
- name: local_service
# 将与此虚拟主机匹配的域列表(主机/权限标头)。后缀或前缀形式支持通配符主机。
# 搜索顺序为 确切的域名:www.foo.com. 后缀域通配符:*.foo.com或*-bar.foo.com. 前缀域通配符:foo.*或foo-*. *匹配任何域的特殊通配符。
domains: ["acme.com"]
routes:
#
- match:
# 路由匹配参数
path: "/foo"
# 将请求路由到 some_service 集群上
route:
cluster: some_service
# 使用的过滤器,这里只使用了路由转发的功能
http_filters:
# - name: some.customer.filter
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
# 集群
clusters:
- name: some_service
# 指定与上游连接的类型,如果为指定则使用明文连接
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
load_assignment:
# 集群名称
cluster_name: some_service
# Static endpoint assignment.
# 这里使用静态配置
endpoints:
- lb_endpoints:
#配置了两个节点
# 这里并没有配置负载均衡策略,默认使用加权轮询
- endpoint:
address:
socket_address:
address: 10.1.2.10
port_value: 10002
- endpoint:
address:
socket_address:
address: 10.1.2.11
port_value: 10002
typed_extension_protocol_options:
# 与上游连接提供的连接协议
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options:
# 配置工作线程中连接池的最大个数
max_concurrent_streams: 100
总结
对于Envoy来说最重要的要数Filter了,我们从上面可以看到,Envoy的每个环节都有过滤器的身影,请求进入Envoy开始先是经过监听过滤器到达网络过滤器,网络过滤器处理完成后到达集群过滤器(是有集群过滤器的只是上面例子没有给出),给人的感觉为Envoy的应用功能(提供应用服务的)都是由过滤器完成的,比如说路由功能由网络过滤器中的HCM完成,日志记录由日志过滤器完成.