聊聊ServiceMesh 数据面板 Envoy

聊聊ServiceMesh 数据面板 Envoy

閱讀本文約花費: 27 (分鐘)

Envoy 简介

在 Service Mesh 模式中,每个服务都配备了一个代理“sidecar”,用于服务之间的通信。这些代理通常与应用程序代码一起部署,并且它不会被应用程序所感知。Service Mesh 将这些代理组织起来形成了一个轻量级网络代理矩阵,也就是服务网格。这些代理不再是孤立的组件,它们本身是一个有价值的网络。其部署模式如图所示:

绿色部分代表应用程序

蓝色部分则是sidecar

服务网格是用于处理服务到服务通信的“专用基础设施层”。它通过这些代理来管理复杂的服务拓扑,可靠地传递服务之间的请求。 从某种程度上说,这些代理接管了应用程序的网络通信层。

Envoy是 Service Mesh 中一个非常优秀的 sidecar 的开源实现。我们就来看看 Envoy 都是做些什么工作。

Envoy 用到的几个术语

Host: 通常我们将 Host 看做是一个具备网络通信功能的实体(可以是一台物理机,也可以是一台移动设备等等) 。在 Envoy 中,host 是一个逻辑网络中的应用. 可能运行在由有多个主机组成的底层硬件,只要它们各自独立寻址。

Downstream: 请求发起者(服务请求方)。

Upstream: 请求接收者(服务提供方)。

Listener: 服务(程序)监听者。就是真正干活的。 envoy 会暴露一个或者多个listener监听downstream的请求。

Cluster: upstream 集群。Envoy 通过服务发现定位集群成员并获取服务。具体请求到哪个集群成员是由负载均衡策略决定。通过健康检查服务来对集群成员服务状态进行检查。

Mesh: 在本文中 “Envoy mesh” 指的是由一组 Envoy 代理组成的,为不同服务之间可靠传递请求的服务网格。

Runtime configuration: Envoy 配置是热更新的,无需重启。

Filter: 过滤器。在 Envoy 中指的是一些“可插拔”和可组合的逻辑处理层。是 Envoy 核心逻辑处理单元。

Envoy 基础概念

线程模型

Envoy 使用单进程多线程模式。一个主线程,多个工作线程。主线程协调和管理这多个线程来工作。每个线程都独立监听服务,并对请求进行过滤和数据的转发等。

一个连接建立后,这个线程将会管理该连接的整个生命周期。通常 Envoy 是非阻塞的,对于大多数情况建议每个 Envoy 配置的工作线程数等于机器的 CPU 线程数。

Listeners

Envoy 中真正干活的(通常是一个监听服务端口的工作线程)。

Envoy 会启动一个或者多个listener,监听来自 downstream 的请求。当 listener 接收到新的请求时,会根据关联的filters模板初始化配置这些 filters,并根据这些 filters 链对这些请求做出处理(例如:限速、TLS 认证、HTTP 连接管理、MongoDB 嗅探、TCP 代理等等)。

Envoy 是多线程模型,支持单个进程配置任意数量的 listeners。通常建议一个机器上运行一个 Envoy 进程,而不关心配置了多少个listerners(如上:大多数情况listener数量等于机器的CPU线程数)。

目前 Envoy 只支持 TCP 类型的 listeners。每个 listener 都可以独立配置一些L3/L4层的 filters。

Listener 还可以通过 listener 发现服务来动态获取。

Network (L3/L4) filters

network (L3/L4) filters 构成了Envoy连接处理的核心。 在 listener 部分我们介绍过, 每个 listener 可以组合使用多个 filters 来处理连接数据。

目前有三种类型的 network (L3/L4) filters:

Read: 当 Envoy 接收来自下游服务请求数据时被调用。

Write: 当 Envoy 向上游服务发送数据时被调用。

Read/Write: 上面两种fileter都是单向控制,Read/Write filters 在接收来自下游服务请求数据和向上游服务发送数据时被调用,是双向控制。

这些 filter 通过分析原始字节流和少量连接事件(例如,TLS握手完成,本地或远程连接断开等)对连接进行处理。

Network Filter(L7)/HTTP Filter

HTTP 协议是当前许多服务构建的基础协议,作为核心组件,Envoy 内置了 HTTP 连接管理 filter。 该 filter 将原始数据字节转换成 HTTP 协议类型数据(比如: headers、body、trailers等)。它还会处理一些通用的问题(比如:request日志、request ID生成和request追踪、请求/响应头控制、路由表管理和状态数据统计等)。

HTTP 连接管理提供了三种类型的filter:

HTTP 协议是当前许多服务构建的基础协议,作为核心组件,Envoy 内置了 HTTP 连接管理 filter。 该 filter 将原始数据字节转换成 HTTP 协议类型数据(比如: headers、body、trailers等)。它还会处理一些通用的问题(比如:request日志、request ID生成和request追踪、请求/响应头控制、路由表管理和状态数据统计等)。

HTTP 连接管理提供了三种类型的filter:

Decoder: 解析请求数据流时(headers,body,trailers等)调用,属于入口单方向控制。

Encoder: 编码响应数据流时(headers, body, and trailers)调用,属于出口单方向控制.

Decoder/Encoder: Decoder/Encoder 用于入/出口双向控制.

HTTP Filters

HTTP protocols

Envoy HTTP 连接管理原生支持HTTP/1.1, WebSockets 和 HTTP/2,暂不支持 SPDY。

Envoy 对 HTTP 的支持在设计之初就是一个HTTP/2的多路复用代理。对于 HTTP/1.1 类型连接,编解码器将 HTTP/1.1 的数据转换为类似于 HTTP/2 或者更高层的抽象处理。这意味着大多数代码不用关心底层连接使用的是 HTTP/1.1 还是 HTTP/2。

access log

HTTP 连接管理支持 access log,可以记录访问日志,且可以灵活的配置。

HTTP 路由

Envoy 包含了一个 HTTP router filter,该 filter 可以用来实现更高级的路由功能。它可以用来处理边缘流量/请求(类似传统的反向代理),同时也可以构建一个服务与服务之间的 Envoy 网格(典型的是通过对HTTP header等的处理实现到特定服务集群的转发)。

每个HTTP连接管理 filter 都会关联一个路由表。每个路由表会包含对 HTTP 头、虚拟主机等的配置信息。

{
“cluster”: “…”,
“route_config_name”: “route_config_example”,
“refresh_delay_ms”: “3000”
}
route_config_example:
{
“validate_clusters”: “example”,
“virtual_hosts”: [
{
“name”: “vh01”,
“domains”: [“test.foo.cn”],
“routes”: [],
“require_ssl”: “…”,
“virtual_clusters”: [],
“rate_limits”:
“request_headers_to_add”: [
{“key”: “header1”, “value”: “value1”},
{“key”: “header2”, “value”: “value2”}
]
},
],
“internal_only_headers”: [],
“response_headers_to_add”: [],
“response_headers_to_remove”: [],
“request_headers_to_add”: [
]
}

路由表有两种配置方式:

静态配置文件。

通过RDS(Route discovery service) API动态配置。

RDS 是一组API用来动态获取变更后的路由配置。

router filter 支持如下功能:

支持 Virtual hosts。映射 domains/authorities 到一系列的路由规则上。[和nginx等一样]。

基于前缀和精确path的规则匹配(有的对大小写既敏感,有的不敏感)。 由于 Regex/slug 会使得用程序来判定路由规则是否与其它规则冲突很困难, 所以,目前暂不支持。由于这个原因,我们不建议在反向代理层面使用基于regex/slug的路由, 当然了,未来我们会根据需求添加对它的支持。

Virual host 层面的 TLS 重定向。 分两类:

all: 所有请求都必须使用TLS。如果请求没有使用TLS,返回302。

external_only: 只要求外网请求使用TLS。如果来自外网的请求没有使用TLS。 如果,改参数没有配置,该virtual host将不会对TLS有要求。

路由层面对 Path/host 重定向。

host重写。 支持两种重写方式:

1. 固定值。host_rewrite参数配置。

2. 动态配置。根据upstream 主机的 DNS 类型动态配置。 具体的值是由cluster manager从upstream中选出来的,其主机名作为重写的值。 这种方式只用在route的目的集群是 strict_dns or logical_dns 类型的场景。其它集群类型不起作用。 将 auto_host_rewrite 设置true即可。这两个参数不能同时使用。

前缀重写(prefix)。

路由层面对 Websocket upgrades. 配置该规则后,来自 HTTP/1.1 客户端到该路由规则的连接都会被转换成 WebSocket 的连接。 如果配置为 true, Envoy 对于该路由的第一个请求需带 WebSocket upgrade headers。如果没有添加该header,请求江北拒绝。如果设置了, Envoy 将会在client和upstream server之间设置TCP代理 。upstream 负责断开该连接,否则 Envoy 任然会转发数据到该upstream server。

请求重试和超时设置 Envoy 有两种方式来设置请求重试。

1. 通过route设置。

2. 通过request header设置。 支持的配置项有: 2.1 最大重试次数: 每次重试之间会使用指数退避算法.另外,所有重试都包含在整体请求超时之内。这避免了由于大量重试而需要较长的请求时间。 2.2 重试条件: 可以根据应用的需求配置触发重试的条件。例如: 网络错误, 5xx 返回码, 幂等的4xx返回码, 等等。

运行时对来自上下游数据的嗅探。

使用基于 weight/percentage-based 的路由,对来自多个上游的数据进行拆分。

任意 HTTP 头匹配路由规则。

支持虚拟集群。

基于路由的优先级。

基于路由的 hash 负载均衡。需要在 header 中设置 hash 使用的策略。

对于非 TLS 的转发支持绝对 urls。

其中:重定向、超时、重试对于 websocket upgrades 是不支持的。

Connection pooling

对于 HTTP 类型,Envoy 提供了对连接池的抽象,连接池屏蔽底层协议类型(HTTP/1.1、HTTP/2),向上层提供统一的接口。用户不用关心底层是基于HTTP/1.1的多线程还是基于HTTP/2的多路复用方式实现细节。

TCP proxy

TCP 代理,L3/L4层连接的转发。这应该是 Envoy 最基础的功能。一般是作为 downstream 客户端与 upstream 服务集群之间的连接代理。TCP 代理既可以单独使用,也可以与其它 filter 组合使用,例如( MongoDB filter 或者 限速filter)。

在 TCP 代理层还可以配置 route 策略,比如: 允许哪些IP段和哪些端口进来的请求访问,允许访问哪些IP段和哪些端口的服务。

TCP 代理配置如下:

{
“name”: “tcp_proxy”,
“config”: {
“stat_prefix”: “…”,
“route_config”: “{…}”
}
}

stat_prefix: 统计数据前缀,主要是用于区分统计数据。

route_config: filter 的路由表。

例如:

{
“name”: “tcp_proxy”,
“config”: {
“stat_prefix”: “…”,
“route_config”: “{
“routes”: [
{
“cluster”: “…”,
“destination_ip_list”: [
“192.168.3.0/24”,
“50.1.2.3/32”,
“10.15.0.0/16”,
“2001:abcd::/64”
],
“destination_ports”: “1-1024,2048-4096,12345”,
“source_ip_list”: [
“192.168.3.0/24”,
“50.1.2.3/32”,
“10.15.0.0/16”,
“2001:abcd::/64”
],
“source_ports”: “1-1024,2048-4096,12345”
},
]
}”
}
}

简单说,就是上下游服务的访问控制。

TPC 代理支持的一些统计数据:

downstream_cx_total 处理的连接总数.

downstream_cx_no_route 不匹配route的总数.

downstream_cx_tx_bytes_total 发送给下游的总字节数

downstream_cx_tx_bytes_buffered Gauge 当前为下游服务缓存的字节数

downstream_flow_control_paused_reading_total 被流控暂停从下游服务读取数据的次数

downstream_flow_control_resumed_reading_total 被流控控制重新从下游服务读取数据的次数

gRPC 的支持

Envoy 在传输层和应用层两个层给予gRPC的高度支持。

Envoy 是当前极少数能同时正确支持HTTP/2 trailers和传输gRPC请求和响应的的HTTP代理。

gRPC 运行时对于一些语言而言还是不太成熟。为此,Envoy 支持一个叫 gRPC bridge 的 filter,它允许gRPC请求能够通过HTTP/1.1发送给Envoy。 Envoy 会将该请求转换成HTTP/2传输到目的server。响应会被转换成 HTTP/1.1 返回。

当装了bridge filter后, bridge filter 除了收集全局HTTP统计之外,桥接过滤器还收集每个RPC统计信息。

gRPC-Web is supported by a filter that allows a gRPC-Web client to send requests to Envoy over HTTP/1.1 and get proxied to a gRPC server. It’s under active development and is expected to be the successor to the gRPC bridge filter.

支持 gRPC-web。通过 filter 能够将使用 HTTP/1.1 发送到Envoy 的 gRPC-Web 客户端请求代理到 gRPC server。该 feature 正在开发阶段。

JSON 转换器支持基于 JSON 的 RESTFUL 客户端通过 HTTP 发送请求给 Envoy 并代理给 gRPC 服务.

WebSocket 的支持

Envoy 支持HTTP/1.1连接到WebSocket连接的切换(默认是支持的)。

条件:

client 需要显示添加 upgrade headers 。

HTTP 路由规则中显示的设置了对 websocket的支持(use_websocket)。

因为 Envoy 将 WebSocket connections 作为 TCP connection 来处理,因此,一些HTTP的特性它不支持,例如: 重定向、超时、重试、限速、 shadowing . 但是, prefix 重写, host 重写, traffic shifting and splitting 都是支持的.

Envoy对WebSocket的代理是TCP层,它理解不了WebSocket层的语义,所以对于连接断开应该由upstream的client来主动关闭。

Envoy对WebSocket的支持与nginx对WebSocket的支持是相同的。

高级概念

集群管理器(Cluster manager)

Envoy 集群管理器管理所有 upstream 集群节点。

upstream 集群节点都由一些列 L3/L4/L7 层 filter 链组成,它们可用于任意数量的不同代理服务。

集群管理器向 filter 链暴露一组API,这组API允许 filters 获取发往 upstream 集群的L3/L4层的连接或抽象的 HTTP 连接池的数据。在 filter 处理阶段通过对原始字节流的分析确定是一个连接是 L3/L4 层的连接还是一个新的 HTTP 流。

除了基本的连接类型分析外,集群管理器还要处理一些列的复杂工作,例如:知道哪些主机可用和健康,负载均衡,网络连接数据的本地存储,连接类型(TCP/IP, UDS),协议类型(HTTP/1.1,HTTP/2)等。

集群管理器支持两种方式获取它管理的集群节点:

通过静态的配置文件

通过动态的集群发现API(CDS)。

CDS:Cluster discovery service,是一个可选的API,Envoy用它来动态的获取cluster manager的成员。

集群管理器配置项如下:

{
“clusters”: [],
“sds”: “{…}”,
“local_cluster_name”: “…”,
“outlier_detection”: “{…}”,
“cds”: “{…}”
}

Service discovery(SDS)

服务发现有几种方式:

1.静态配置。通过配置文件配置(IP/PORT、unix domain socket等)。

2.基于DNS的服务发现。

3.Original destination

4.Service discovery service (SDS)

5.On eventually consistent service discovery

更多服务发现内容

主动健康检查

根据配置的不同, Envoy 支持3种健康检查方式。

1.基于 HTTP

Envoy 向 upstream 节点发送一个 HTTP 请求,返回 200 代表健康, 返回 503 代表该host不再接收请求/流量。

基于 HTTP 的健康检查支持3种策略:

1.1 No pass through

这种模式 Envoy 不会将健康检查的请求转发给本地的服务,而是根据当前节点是否被 draining 返回 200 或者 503.

1.2 Pass through

与第一种模式不同,这种模式 Envoy 会将健康检查的请求转发给本地服务,调用本地服务的健康检查接口,返回 200 或 503.

1.3 Pass through with caching

这种模式是前两种模式的高级版,第一种方案数据不一定准,第二种请求太频繁会对性能有影响。

该模式加了个缓存的支持,在缓存周期内结果直接从缓存中取,缓存失效后再请求一次本地服务加载到缓存中。

这是推荐的一种模式。 健康检查时 Envoy 与 Envoy之间是长连接,他们不会消耗太大性能;对于 upstream 节点而言,则是新请求新连接。

2.基于 HTTP 的健康检查支持身份认证。

如果你在云平台中用了最终一致性的服务发现服务或者容器环境中,赶上服务水平扩展,这个时候其中一个节点挂掉后又”回到平台”且使用的是同一个 IP 是有可能的,但是确是不同的服务(在容器服务中尤为明显)。一种解决方案是,对不同的服务使用不同的健康检查URL,但是这种配置复杂度非常高。Envoy 采用的方案是在 header 中添加一个 service_name 选项来支持。如果设置了该选项,在健康检查时会对比 header 中的 x-envoy-upstream-healthchecked-cluster 是否和该选项值匹配,如果不匹配则会忽略该请求。

3.L3/L4

基于L3/L4层的健康检查, Envoy 向 upstream 节点发送定义好的一个字符串. 如果 upstream 节点返回该值,则代表健康, 否则不健康。

4.Redis

Envoy 向 Redis 发送一个 PING 命令, 返回 PONG 代表健康, 其它的代表不健康。

Passive health checking(钝态检查)

Envoy 通过 Outlier detection 进行钝态(实在是找不出太合适的词)检查

Outlier detection,用来检查某些集群成员在给定范围内是否“正常”,不正常则将其从负载均衡列表中移除。

有时候一个节点虽然在进行主动健康检查是是正常的,但是会存在某些不正常的状态被遗漏的情况,而 Outlier detection 则是弥补这个“漏洞”的 。它通过跟高级的一些算法来判定该节点是否是正常的。

Outlier detection 有两种检查类型:

基于连续的 5xx 错误码

upstream 成员连续N次返回5xx错误码, N默认为5(可配置)。

基于成功率

基于成功率的检查在两种情况下是不处理的:

针对集群中单个节点

单个节点的请求数量在聚合区间内少于outlier_detection.success_rate_request_volume值时(默认100)。

集群级别

集群中 outlier_detection.success_rate_minimum_hosts 个节点在检查周期内请求量都小于 outlier_detection.success_rate_request_volume 时。

配置项:

{
“consecutive_5xx”: “…”,
“interval_ms”: “…”,
“base_ejection_time_ms”: “…”,
“max_ejection_percent”: “…”,
“enforcing_consecutive_5xx” : “…”,
“enforcing_success_rate” : “…”,
“success_rate_minimum_hosts” : “…”,
“success_rate_request_volume” : “…”,
“success_rate_stdev_factor” : “…”
}

主动健康检查和钝态检查可以配合使用,也可以单独使用。

Circuit breaking(断路器)

断路器是一种分布式的限速机制,它针对每个upstream的host设置,有时候也需要针对整个cluster进行限制, 这个时候全局的限速就非常有必要了。Envoy支持全局限速(L3/L4、HTTP 都支持),它有一个集中的限速服务, 对于到达该集群的每个连接,都会从限速服务那里查询全局限速进行判断。 Envoy 是通过一个全局的gRPC限速服务来实现全局限速。通过redis来做后端存储。

Envoy 的断路器可以控制 envoy 与 downstream 节点的最大连接数、集群最大支持的 pending 请求数、集群最大支持的请求数(适用HTTP/2)、集群存活最大探测次数。

断路器配置:

{
“max_connections”: “…”,
“max_pending_requests”: “…”, # 默认 1024
“max_requests”: “…”, # 默认 1024
“max_retries”: “…”, 默认 3
}

max_connections:Envoy 与 upstream 集群所有节点能够建立的最大连接数量。该参数适用于HTTP/1.1,因为HTTP/2是使用单个连接与每个host建连,连接复用(默认1024)。

max_pending_requests: 等待线程池有可用连接时的最大排队请求数量。该参数适用于HTTP/1.1,HTTP/2采用多路复用方式,无需排队请求(默认 1024)。

max_requests: 给定时间内最大请求数,该参数适用于HTTP/2,HTTP/1.1 通过max_connections来限制。(默认 1024)。

max_retries: 给定时间内Envoy与请求upstream集群时的最大重试次数,该值不宜设置过大,重试过多可能会带来更多其它的级联故障,甚至导致雪崩。(默认 3)。

热更新

简化操作是Envoy一个非常重要的设计目标。除了强大的统计和本地管理接口, Envoy还具备自身热重启的功能。 这意味着 Envoy 能够全自动的更新自己(包括代码和配置的变更),而不会丢失任何连接。

看下热更新的过程:

统计数据和一些lock都放到了共享内存中。进程在重启时这些数据是持久的,不会丢失。

新旧进程通过RPC协议进行通信。

新的进程在接管旧进程的unix domain socket前,先完成一系列的初始化(比如:加载配置, 初始化服务发现和健康检查, 其它)。然后,新的进程开始监听服务,并告诉老的Envoy进程进入驱逐阶段。

在旧进程驱逐阶段, 旧的进程尝试平滑的关闭已存在的连接。具体如何做要依赖于配置的filters。 –drain-time-s 配置项用来配置等待平滑退出的时间。如果平滑退出花费的时间超过了这个值,进程会强制关闭和回收。

驱逐过程结束后, 新的Envoy进程告诉旧的Envoy进程关闭自己。参数 –parent-shutdown-time-s 用来配置关闭自己的超时时间。

Envoy 的热重启的设计支持新老进程同时存在时也能正常工作。新旧进程之间的通信只能是通过unix domain socket。

Envoy 部署方式

这一块是大家关注的重点,也就是应用程序如何与 Envoy 结合来使用的、请求是如何转到 Envoy 的等等。

根据不同的使用场景,Envoy有不同的部署方式。

Service to service only

这是最简单的部署和使用方式,在这种方式中 Envoy 作为内部与外部服务通信的总线。Envoy 启动多个 listeners 用于本地流量转发和服务与服务之间的流量转发。

上图展示了最简单的 Envoy 部署方式。在这种部署方式中 Envoy 承担的是SOA服务内部流量的消息总线角色。在这种场景中, Envoy 会暴露一些 listeners 用于本地流量或者本地服务与远端服务之间流量的转发。

listener 类型:

Service to service egress listener

本地服务到远端服务的出口 listener。该类型 listener 会监听在某个指定的端口上,所有内部应用出去的请求都重定向到该端口上,由该 listener 处理并转发到目的服务集群节点。

例如:http://localhost:9001 或 tcp://localhost:9001。 HTTP 和 gRPC 类型请求使用 host header,HTTP/2使用 authority header 来指定访问的远端服务集群。 在数据流经 Envoy 过程中会进行服务发现、负载均衡、限速等处理。

本地 Services 只需要知道本地的Envoy,无需关心它们自己所处的网络拓扑及环境。

Service to service ingress listener

本地服务到远端服务的入口 listener。该 listener 提供远端 Envoy 调用本地 Envoy 的端口。

例如:http://localhost:9211。 进入本地 Envoy 的请求都被路由/重定向到本地 service 的监听端口。根据需要,本地的Envoy 会进行一些缓存、断路检查等处理。

Optional external service egress listeners

有时,需要访问外部的服务,此时需要提供一个端口提供访问。因为,有些外部服务SDK不支持host header的重写来支持标准的HTTP反向代理行为。

例如:http://localhost:9250 might be allocated for connections destined for DynamoDB.我们建议为所有外部服务使用本地端口路由,而不是使用主机路由和专用本地端口路由

Discovery service integration

集成外部服务发现组件来提供服务到服务的发现功能。

service to service 模式配置模板

上图展示了在 service to service 模式前增加 Envoy 集群作为7层反向代理的部署模式。

该部署模式有以下特点:

TLS 卸载

同时支持 HTTP/1.1 和 HTTP/2

完整的 HTTP 7层路由支持

前端的 Envoy 代理集群使用标准的 ingress 端口与后端的 service to service 集群通信。对于后端服务集群节点使用服务发现方式获取。前端的 Envoy 集群节点是完全对等的提供服务,没有任何差异。

这种方式和 service to service 方式相比多出了 前端七层代理的部分。可以适配更多的使用场景。

Service to service plus front proxy 配置模板

双代理模式

双代理模式的设计理念是: 更加高效的卸载TLS、更快速的与client端建立连接(更短的TLS握手时间,更快的TCP拥塞窗口调整,更少的丢包等等)。 这些在双代理上卸载TLS后的连接最终都会复用 已经与数据中心完成连接建立的 HTTP/2 连接。

Service to service, front proxy, and double proxy 配置模板

总结

以上就是ServiceMesh 数据面板 Envoy的基本介绍。

Rate this post

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注