Envoy学习笔记
Envoy是一个大规模面向服务架构设计的7层代理和通信总线,它的信条是 —— 网络应该对应用程序透明,当出现问题时,应该很容易定位到源头在网络还是应用。
Envoy的高层特性包括:
- 进程外架构:Envoy以独立的进程、伴随着每个应用服务运行。每个应用服务都和localhost通信而不关注网络拓扑,Envoy监听localhost并转发给其它Envoy,所有Envoy构成一个网格。进程外架构的优势是:
- 让Envoy可以和任何编程语言交互
- 不会像库那样引入升级、分发的负担
- 基于C++ 11编写:性能好
- L3/L4过滤器架构:Envoy的核心包含L3/L4网络代理功能,可拔插的过滤器链能实现各种TCP层代理行为,以支持HTTP、TLS等协议
- HTTP L7过滤器架构:Envoy为HTTP单独设计了一个过滤器链,实现缓冲、限速、路由/转发等功能
- HTTP/2支持:Envoy同时支持HTTP 1.1和HTTP 2
- HTTP L7路由:支持基于URL路径、内容类型、各种上下文属性来路由HTTP请求
- gRPC支持:gRPC是Google开源的、以HTTP/2为传输机制的RPC框架。Envoy支持gRPC请求的路由、负载均衡
- MongoDB、DynamoDB的L7支持
- 服务发现和动态配置:Envoy支持一组动态配置API,能够在运行时更改流量管理配置
- 健康检查:Envoy支持对上游服务集群进行主动健康检查、被动健康检查(断路器)
- 高级流量管理特性:自动重试、断路器、全局限速、请求复制等等
- 前置/边缘代理支持:尽管Envoy主要用作服务之间的代理,但是它包含TLS Termination、HTTP 1/1和HTTP/2支持、HTTP L7路由等特性,可以作为大部分现代Web应用的边缘代理
- 可观察性:Envoy的所有子系统都支持指标统计,指标可以落地到Statd或其它兼容的服务。统计信息也可以通过管理端口查询
- 分布式跟踪:支持基于第三方Tracer的分布式跟踪
此图来自https://jimmysong.io。
Envoy可以作为服务网格的数据平面。
Envoy提出的资源发现协议集 —— xDS(包括CDS、EDS、LDS、RDS,以及后续补充的密钥发现服务/SDS、聚合发现服务/ADS、健康发现服务/HDS、限速服务/RLS等),已经被多个服务网格项目支持,其中包括著名的Istio。这些项目主要实现服务网格的控制平面。
xDS有时候也用于代指实现xDS API的那些发现服务。Envoy通过订阅(subscription)方式获取资源的更新,具体有三种:
- 文件订阅:监控指定路径下的文件,最简单的动态资源发现方式
- gRPC订阅:向发现服务发起gRPC请求,后者主动将更新推送过来
- REST-JSON轮询订阅:向发现服务发起REST长轮询
Envoy采用单进程多线程的线程模型,其中一个master线程控制一些零散的协作任务,若干worker线程负责监听、过滤、转发。一旦监听器接受了连接,连接的后续生命周期都绑定到单个工作线程。
Envoy完全使用非阻塞的网络调用,大部分工作负载下,worker的数量应该设置的和硬件线程数量一致。
Envoy支持在单个进程中创建任意数量的监听器,通常建议在一台机器上仅部署单个Envoy,不管需要多少监听器。目前Envoy仅仅支持TCP监听器。
每个监听器都可以独立的配置若干网络(L3/L4)过滤器。一旦监听器接收到新的连接,过滤器链就会被初始化,并负责处理连接的后续事件。
你还可以为监听器配置“监听器过滤器”,这些过滤器在网络过滤器之前执行,能够操控连接元数据,影响连接的后续处理行为。
监听器发现服务(Listener discovery service,LDS)用于动态的发现监听器。
通过LDS加载的监听器必须在预热(warmed)之后才能接收流量。例如,如果监听器引用一个RDS配置,RDS必须被解析、抓取,然后监听器才能预热。
这种过滤器用于操控连接的元数据信息。监听器过滤器的API比较简单,它仅仅操控新接受的套接字。
网络层(L3/L4)过滤器是Envoy连接处理的核心。Envoy的过滤器API允许若干过滤器被编排、匹配、关联到指定的监听器。
网络过滤器分为三类:
- 读过滤器:当从下游连接接收到数据后调用
- 写过滤器:当写入数据给下游服务器时调用
- 读写过滤器:对下游连接读写数据时都调用
网络过滤器的API也比较简单,因为过滤器仅仅处理原始字节和少量连接事件(例如TLS握手完成、连接断开)。
过滤器链中某个成员可以停止处理并迭代到下一个过滤器。
过滤器可以共享针对同一下游连接的静态、动态的状态信息。
HTTP对于现代SOA架构非常重要,因此Envoy实现了大量针对HTTP协议的功能。其中一个是网络过滤器 —— HTTP连接管理器。该管理器将原始字节转换为HTTP报文和事件(报文头已接收、报文体已接收…),并且负责访问日志、请求ID生成和追踪、请求/响应头操控、路由表管理、统计指标收集。
HTTP连接管理器支持HTTP 1.1、HTTP 2、WebSocket协议,但是不支持SPDY。
Envoy在HTTP连接管理器内部支持HTTP过滤器链。HTTP过滤器操控HTTP消息,不需要理解底层协议(HTTP/1.1,HTTP/2…)。
HTTP过滤器分为三种:
- 解码过滤器:当HTTP连接管理器执行请求解码时调用
- 编码过滤器:当HTTP连接管理器进行响应编码时调用
- 编解码过滤器
Envoy包含了一个HTTP Router Filter,用于执行高级的路由任务,不管是处理边缘流量还是网格内部流量,都可以用到此过滤器。
Envoy具有作为正向代理的能力,网格的客户端需要将其HTTP代理设置为一个Envoy实例,以使用代理。
此过滤器实现的特性包括:
- 虚拟主机:将Domain/Authority映射到一组路由规则(作为路由决策的一部分)
- 前缀或精确的路径匹配
- 在虚拟主机级别的TLS重定向
- 在路由级别的Path/Host重定向
- 在路由级别直接返回响应
- 显式的Host改写
- 基于选中的上游Host的DNS名称进行自动的Host改写
- 路径前缀改写
- 根据请求头或者路由配置,进行HTTP重试
- 根据请求头或者路由配置,设定HTTP超时
- 流量转移:根据运行时值将流量从一个上游集群转移到另外一个
- 流量分割:在多个上游集群之间分配流量比例
- 根据请求头路由
- 基于优先级的路由
- 基于哈希策略的路由
- 对于非TLS正向代理,支持绝对URL
HTTP连接管理器维护所有HTTP过滤器所使用(尽管路由过滤器是主要使用者)的路由表,这样过滤器可以根据目的地作出决策。
Envoy提供一系列静态、动态状态,用于在过滤器之间或者向其它核心子系统(例如访问日志)传递配置、元数据、请求/连接的状态。
静态状态是在配置加载阶段(例如通过xDS)生成的不可变信息。静态状态分为三类:
静态状态 | 说明 |
元数据 |
某些Envoy配置(例如监听器、路由、集群)包含元数据,元数据是任意数量的键值对。通常以过滤器名称的DNS反转写法为Key,以过滤器特有的配置为Value。 元数据是不可变的,且被所有请求/连接共享。这些元数据要么在Bootstrap时提供,要么作为xDS的一部分提供 |
强类型元数据 |
过滤器可以注册一个转换器,在接收到(通过xDS)配置元数据时将指定的Key转换为强类型的C++对象,这样过滤器不需要在每次处理连接/请求时都执行ProtobufWkt::Struct到某种内部结构的转换 |
HTTP路由过滤器配置 | HTTP过滤器可以提供针对单个虚拟主机的配置,用于覆盖全局的、面向所有虚拟主机的配置 |
动态状态为每个网络连接或者HTTP流生成。取决于生成动态状态的过滤器,这些状态可以变化。
Envoy::Network::Connection和Envoy::Http::Filter提供StreamInfo对象,此对象包含当前TCP连接/HTTP流的信息。StreamInfo除了包含一系列静态字段外,还包含map<string, FilterState::Object>映射,用于存储任意类型的状态。
负责管理上游集群,可以配置任意数量的上游集群。此管理器支持两种方式获得集群:
- 静态配置
- 通过集群发现服务(CDS)API动态获取
上游集群从网络/HTTP过滤器栈中剥离出来,以便被不同的代理任务重用。集群管理器向过滤器暴露了API,允许它们:
- 获取到上游集群的L3/L4连接
- 获取用于处理上游集群的抽象(隐藏协议类型)连接池的句柄
过滤器根据需要来获取L3/L4连接或者新的HTTP流,集群管理器负责处理其它的复杂性,包括:
- 确定哪些主机可用、健康
- 进行负载均衡
- 在ThreadLocal存储上游连接数据
不管是通过静态配置,还是通过CDS加载,只有当集群经过以下处理后,才是warmed状态,才可以使用:
- 初始的服务发现加载,例如DNS解析、EDS更新
- 初始的主动健康检查(如果配置的话)。Envoy会向每个发现的主机发送健康检查,确定其初始健康状态
讨论集群预热(cluster warming)时,所谓集群可用,是指:
- 对于新添加的集群,仅当集群预热完毕,它才被Envoy的其它部分可见。否则,一个引用集群的HTTP Route会可能会返回404/503
- 对于被更新的集群,在新配置预热之前,旧的集群仍然会提供服务。预热之后,新集群替换旧集群,不会发生任何的流量中断
服务发现是Envoy确定集群成员的过程,支持的服务发现类型包括:
- 静态发现:在配置中明确指出每个上游主机的网络名称(IP地址、端口,或者UDS)
- 严格DNS发现:Envoy持续异步的解析目标服务的DNS名称,DNS结果中返回的每个IP地址都视为上游主机
- 逻辑DNS发现:类似于上一个,但是在需要初始化新连接时,仅仅使用DNS结果中第一个IP地址。大型Web服务常常使用循环DNS返回很多IP地址,如果使用严格DNS发现,连接池太多(每个上游主机需要一个连接池)会导致系统资源枯竭
- 原始目的地:如果传入的连接来自Iptables重定向(可以通过conntrack表或读取SO_ORIGINAL_DST选项得到原始目的地),或者使用了代理协议(从代理协议头得到原始目的地),则可以使用其原始目的地,这种情况下实际上不需要发现上游主机
- 端点发现服务(Endpoint Discovery Service,EDS):一个基于gRPC/REST-JSON的xDS管理服务,Envoy使用它来获取集群成员。每个集群成员都叫端点。EDS是首选的服务发现机制
很多RPC系统的服务发现是一个完全一致性过程,它们使用一致性的领导选举算法,例如ZK、Etcd、Consul。大规模使用完全一致性会存在性能问题,因此Envoy使用最终一致性的方式。
可以为每个上游集群配置主动健康检查,对于EDS服务发现来说,主动健康检查是和服务发现过程一同进行的。其它类型的服务发现也可以联用主动健康检查。
Envoy目前支持三种健康检查:
- HTTP:将上游主机发送HTTP请求,如果返回200则认为正常,如果返回503则立即禁止转发流量给目标端点
- L3/L4:发送一个可配置的字节缓冲给上游主机,如果上游主机Echo了此字节缓冲,则认为通过检查
- Redis:发送PING命令给上游Redis服务,如果返回PONG应答则认为通过检查。Envoy也可以针对特定的键进行EXISTS操作,如果键不存在则认为通过检查
Envoy目前支持检查间隔、标记为不健康需要的连续失败次数、标记为健康需要的连续成功次数等健康检查相关配置。此外,可以针对每个端点配置健康检查逻辑。
联用主动健康检查+被动健康检查时,通常会延长主动检查的间隔,避免过大的流量。你甚至可以通过管理端点 /healthcheck/fail来快速的Drain掉某个上游主机。
如果你通过管理端点标记主机为失败,则该主机的Envoy中的HTTP健康检查过滤器会自动设置响应头 x-envoy-immediate-health-check-fail。请求方的路由过滤器会识别这个头,并立即将目标主机标记为主动健康检查失败。
仅仅简单的检查一个URL,不足以判断某个服务的主机正常。原因是,在云环境下,服务实例可能被销毁,而其IP地址被其它类型服务的实例重用。
为了解决此问题,HTTP健康检查支持配置service_name选项,此选项设置后,健康检查器会额外检查x-envoy-upstream-healthchecked-cluster响应头,只有它的值和service_name选项一致,才可能通过健康检查。
Envoy支持基于异常检测(outlier detection)的被动健康检查(不刻意发起请求,而是检测业务流量是否正常)
Envoy支持为HTTP监听器配置HTTP健康检查过滤器,此过滤器允许以下操作模式:
- 不穿透:此模式下健康检查请求不会转发给Envoy背后的上游服务,Envoy根据服务当前的draining state决定返回200还是503
- 不穿透,根据上游集群健康状态决定检查结果:如果一个或者多个上游集群中,至少特定比例的主机是健康的,则返回200,否则503
- 穿透:直接将健康检查请求转发给背后的服务
- 穿透+缓存:支持一定时间的缓存
太多的穿透可能引发大量的健康检查流量。
对于HTTP流量来说,Envoy支持连接池。HTTP/1.1和HTTP/2连接池的行为有所不同:
- HTTP/1.1:按需创建到上游主机的TCP连接,当连接可用时,将请求绑定到连接。如果TCP连接意外断开,则仅仅一个请求会被重置
- HTTP/2:创建到上游主机的单个TCP连接,所有请求复用此连接。如果接收到GOAWAY帧或者到达最大Stream限制,则连接池会创建一个新的连接
如果针对上游主机的主动/被动健康检查失败,则此主机的连接池会被关闭;反之,如果上游主机恢复健康,则连接池会重新创建。
Envoy根据知晓的上游集群成员信息,自己决定应该把负载分发给哪个端点。
一个全局性的权威负责决定负载如何在主机之间分发。对于Envoy来说,则可以通过控制平面达成。控制平面可以调整优先级、权重、端点健康状况来满足复杂的负载均衡需求。
算法 | 说明 |
加权轮询 | Weighted round robin,轮询上游主机,但是主机可以设置权重 |
加权最少请求 | Weighted least request,使用 O(1) 算法选择两个随机健康主机,并选择主动请求较少的主机 |
环哈希 |
Ring hash,针对上游主机实现一致性哈希,此算法将所有主机映射到一个环上,这样添加/删除一个主机,仅仅1/N的请求受到影响 也叫做ketama hashing 目前不支持权重 |
磁悬浮 | Maglev,类似针对上游主机实现一致性哈希,但具有快得多的查表编译时间以及主机选择时间(当使用 256K 条目的大环时大约分别为 10 倍和 5 倍),缺点是不如环哈希稳定,主机移除时更多请求需要移动(以达到再平衡) |
随机 | Random,随机选择一个健康主机 |
异常检测属于被动健康检查,是被动的(观测业务流量,而非主动发起流量)检查上游集群,并发现其中的不正常主机,然后将其剔除出负载均衡池的过程。
异常检测通常和主动健康检查联合使用。
根据异常检测的类型,驱除行为要么定期触发,要么在连续的异常时间发生后触发。算法工作方式如下:
- 主机被确定为异常
- 如果集群中没有主机被驱除,则立即驱除当前主机。否则检查被驱除主机是否小于阈值outlier_detection.max_ejection_percent,如果大于则不驱除
- 主机被驱除,也就是标记为不健康,且不参与负载均衡。除非上游集群处于Panic状态(也就是健康主机太少)
- 驱除持续的时间是 outlier_detection.base_ejection_time_ms * 主机已经被驱除的次数,超过此时间后,自动回到负载均衡池。主机被驱除的次数越多,则返回负载均衡池需要等待的时间越长
检测类型 | 说明 |
连续5xx | 如果上游主机连续返回若干(由outlier_detection.consecutive_5xx 控制)5xx响应,则会被驱除 |
连续网关故障 | 如果上游主机连续返回若干(由outlier_detection.consecutive_gateway_failure控制)网关错误(502 503 504),则会被驱除 |
成功率 |
如果在指定聚合周期内,请求处理成功率低于阈值,则驱除 如果聚合周期内,主机请求量小于outlier_detection.success_rate_request_volume,或者聚合周期内请求量小于outlier_detection.success_rate_request_volume的主机总数小于outlier_detection.success_rate_minimum_hosts ,都不进行驱除 |
Envoy支持多种类型的完全分布式的断路器,以保护上游集群不因过载而崩溃:
- 集群最大连接数:为上游集群的所有主机设置总和最大连接数。注意这仅仅适用于HTTP/1.1,因为HTTP/2为每个上游主机维护单个连接
- 集群最大挂起请求数:排队等待可用连接的最大请求数。注意这仅仅适用于HTTP/1.1,因为HTTP/2允许多个请求复用TCP连接
- 集群最大请求数:在给定的时间内,集群总计可以处理的请求数量
- 集群最大活动重试次数:在给定的时间内,集群总计允许发送的重试次数
你可以针对每个上游集群进行断路器配置。
对于HTTP请求,被断路后,路由过滤器会设置响应头x-envoy-overloaded。
分布式系统中,断路器对吞吐量的控制一般非常有效。但是某些场景下,可能需要引入全局的限速机制。典型的场景例如数据库,来自大量主机的请求被转发给少量的服务器,很难在所有下游主机上配置断路器来防止级联性失败(雪崩)。
Envoy集成了全局的gRPC限速服务,Lyft提供该服务的参考实现,此外任何实现了RPC/IDL协议的程序都可以作为服务实现。
限速服务的功能包括:
- 网络层限速过滤器:安装此过滤器的监听器,在接受新连接时就会调用限速服务。支持根据每秒连接次数进行限制
- HTTP级限速过滤器
对于监听器,Envoy支持TLS Termination。向上游发起请求时,Envoy支持TLS Origination。Envoy的SSL支持由BoringSSL库提供。
Envoy的一个目标是使网格可理解(观察),为此它可以收集大量的统计信息,统计信息可以分为三类:
- 下游:牵涉到传入的连接/请求。由监听器、HTTP连接管理器、TCP代理过滤器生成
- 上游:牵涉到传出的连接/请求。由连接池、路由器过滤器、TCP代理过滤器生成
- 服务器:牵涉到服务实例,统计指标包括服务器正常运行时间、内存分配量等
在v1版本的API中,Envoy仅仅支持Statd作为输出格式。在v2版本的API中,可以使用自定义,可拔插的统计信息接收器。
统计信息都使用规范化的字符串来标识,字符串的动态部分被抽取成为标签(Tag)。
Envoy支持Counter、Gauge、Histogram三种指标类型。
在大型分布式架构中,分布式追踪让调用过程能够被可视化的展示,在了解串并行执行、延迟分析时非常重要。
Envoy包含三个和分布式追踪相关的特性:
- 请求ID生成:如果需要,Envoy会自动生成 x-request-id 头表示请求UUID
- 外部追踪服务集成:Envoy支持可拔插的、外部的追踪可视化服务。目前支持LightStep,Datadog,Zipkin,以及任何Zipkin兼容的服务(例如Jaeger)
- 客户端Trace ID连接:头x-client-trace-id可以用于将不受信任的请求ID连接到内部受信任的x-request-id
初始化追踪的方式有多种:
- 外部客户端,使用 x-client-trace-id 头
- 内部服务,使用x-envoy-force-trace头
- 随机采样,使用运行时设置random_sampling
路由过滤器可以使用start_child_span为出口调用创建下一级Span。
Envoy提供了根据网格中服务之间通信来报告追踪信息的能力,但是要想把一个调用流中各个代理产生的追踪片断关联起来,服务必须在入口、出口请求之间传递一些上下文信息。
不管使用哪种Tracer,所有服务都必须传播x-request-id头,服务实现者可能需要在代码层次上保证这一点(技术上无法实现通用的自动传播x-request-id的机制)。
Tracer还会需要额外的上下文信息,确保Span之间的父子关系能够被理解。
可以在服务中直接调用LightStep(OpenTracing API)或Zipkin来抽取入站请求的上下文信息,并注入到出站请求中。使用这种方式,服务也可以创建额外的Span。
另外一种方式是由服务手动的传播追踪信息:
- 如果使用LightStep,Envoy依赖服务在发起请求时传播x-ot-span-context头
- 如果使用Zipkin,Envoy依赖服务B3请求头(x-b3-traceid, x-b3-spanid, x-b3-parentspanid, x-b3-sampled, x-b3-flags)。外部客户端也可以提供x-b3-sampled头,以启用/禁用对某个请求的追踪
- 如果使用Datadog,Envoy依赖服务在发起请求时传播 x-datadog-trace-id, x-datadog-parent-id, x-datadog-sampling-priority头
一个端到端的Trace包含1-N个Span。一个Span代表了一个逻辑的工作单元。工作单元包括开始时间,持续时间,以及一系列的元数据:
- 源自的服务集群,通过--service-cluster指定
- 开始时间、持续时长
- 源自的主机,通过--service-node指定
- 下游服务集群,通过x-envoy-downstream-service-cluster头指定
- HTTP URL
- HTTP方法
- HTTP响应码
- Tracer相关的信息
Envoy自动将Span发送给跟踪收集器(Tracing collector),Span根据一些公共信息(例如x-request-id)组合在一起,产生单个Trace。
TCP代理过滤器可以实现下游 - 上游的基本的1:1代理功能。
TCP代理过滤器受到上游集群全局资源管理器的连接限制的约束。
HTTP 连接管理器与 TCP 代理支持可扩展的访问记录,并拥有以下特性:
- 可按每个 HTTP 连接管理器或 TCP 代理记录任何数量的访问记录
- 异步 IO 架构。访问记录将永远不会阻塞主网络处理线程
- 可定制化的访问记录格式,可使用预定义的字段,也可使用任意的 HTTP 请求和应答
- 可定制化的访问记录过滤器,可允许不同类型的请求以及应答写入至不同的访问记录
Envoy实现了网络层的MongoDB嗅探过滤器,特性包括:
- MongoDB的数据格式BSON的解析
- 详细的MongoDB查询/操作统计,例如耗时、Scatter/multi-get次数
- 查询日志
- 基于$comment 参数做每个调用点的统计分析报告
- 故障注入
Envoy可以作为Redis代理,在Redis实例之间进行命令分区(partitioning commands)。Envoy的目标是维持可用性、分区容忍性(partition tolerance),它实现一个尽最大努力的缓存,而不会尝试维护数据一致性或者Redis集群成员的一致性。这和Redis Cluster不同。
Envoy Redis的特性包括:
- Redis协议编解码
- 基于哈希的分区
- 基于一致性哈希(Ketama)的分区
- 详细的命令统计
- 主动和被动健康检查
未来计划添加的特性包括:
- 额外的耗时统计
- 断路器
- 对片断化命令进行请求合并
- 数据复制
- 内置重试
- 追踪支持
- Hash Tagging
Redis上游集群应该以哈希环负载均衡的方式配置(ring hash load balancing)。
此代理支持Redis流水线,但是不支持MULTI。此外,不是所有Redis命令被支持,仅仅那些可以被可靠的哈希到一个服务器的操作被支持。
一个特殊命令是PING,Envoy会直接答复PONG,为PING指定参数是不允许的。
gRPC是Google开源的RPC框架,它使用Protocol Buffer作为串行化格式。在传输层,它使用HTTP/2进行请求/应答的多路复用。
Envoy对gRPC在传输层、应用层都做了支持:
- gRPC使用HTTP/2 Trailers来递送请求状态。Envoy是少数几个能正确支持HTTP/2 Trailers的代理——也就是能支持gRPC的代理
- 某些语言的gRPC实现尚不成熟,Envoy提供了一些过滤器,能够将gRPC桥接到这些语言
- Envoy通过过滤器grpc_web支持gRPC-Web,允许客户端向Envoy发送HTTP/1.1请求,由Envoy将其转换为gRPC协议并转发给上游服务器。此过滤器将在未来代替grpc_http1_bridge
- Envoy通过过滤器支持gRPC-JSON转码,允许客户端基于REST JSON API发送请求,由Envoy转换其为gRPC请求给上游服务器
Envoy支持两种gRPC桥接:
- grpc_http1_bridge filter:允许gRPC请求通过HTTP/1.1发送给Envoy,后者通过标准的gRPC协议转发给gRPC服务器
- grpc_http1_reverse_bridge filter:能够将gRPC请求转换为HTTP/1.1并发送给上游服务器。此过滤器能够管理gRPC帧的头,允许上游服务器对gRPC协议一无所知
Envoy在它的控制平面中大量使用gRPC协议,例如从管理服务器订阅资源配置信息时。一些过滤器,例如限速、授权,也使用gRPC协议。
Envoy gRPC client是最小化的gRPC客户端实现,利用Envoy的HTTP/2上游连接管理机制。
Google C++ gRPC提供完备的gRPC实现,包含Envoy所缺失的特性,具有自己的负载均衡、重试、超时、端点管理等功能。
进行Envoy相关的开发时,建议使用Envoy gRPC client。
Envoy 可以在不丢失任何连接的情况下完全地重载自己,包括代码与配置。
你可以提供一组监听器(和过滤器链)、集群、HTTP路由配置。动态主机仅通过DNS发现。
如果变更配置,则可以利用Envoy的热重启机制加载最新配置。
通过服务发现服务(SDS)API,Envoy可以发现上游集群的成员,在v2版本的API中,SDS已经更名为EDS。
使用SDS可以规避DNS发现的缺点(记录数量受限),并遵从负载均衡、路由配置。
通过集群发现服务(CDS)API,Envoy可以发现上游集群。
通过路由发现服务(RDS)API,Envoy可以在运行时发现用于HTTP连接管理器过滤器的整个路由配置。路由配置可以优雅的切换,而不影响处理中的请求。
通过监听器发现服务(LDS)API,Envoy可以发现整个监听器,包括它的完整过滤器栈。
配合使用SDS/EDS、CDS、RDS 和 LDS,几乎可以动态配置Envoy的所有方面。只有非常少见的配置更改(管理员、追踪驱动程序等)或二进制更新时才需要热重启。
任何监听器启动监听并接收新连接前,会发生如下事件序列:
- 启动期间,集群管理器会执行多步骤的初始化过程:
- 初始化静态/DNS集群
- 初始化预定义EDS集群
- 如果可用,初始化CDS并等待响应,然后初始化CDS提供的集群
- 如果集群启用了健康检查,则执行一轮检查
- 集群管理器初始化完毕后,RDS和LDS开始初始化
- 如果LDS本身返回的监听器要求RDS响应,则Envoy会等待RDS响应的到达
- 监听器开始接受连接
关闭(Draining)发生在以下时机:
- 通过healthcheck/fail管理客户端,对服务器执行手工的健康检查失败
- Envoy被热重启
- 单个监听器通过LDS添加或移除
每个监听器都具有drain_type配置,决定何时发生Draining:
- default,上述三种时机都发生
- modify_only,仅仅上述2-3时发生
HTTP过滤器支持使用Lua脚本。
这是一个可扩展组件,用于保护Envoy本身(和断路器不同),防止过载导致各种系统资源(内存、CPU、文件描述符)不足。和断路器用于保护上游服务的目的不同,过载管理器是为了保护Envoy代理所在机器本身。
- 访问日志:
- 支持字段%DOWNSTREAM_LOCAL_PORT%
- 引入连接级别的日志记录器
- 自适应并发:
- 修复允许的并发限制小于配置的最小值的缺陷
- 管理:
- 支持再certs页面显示IP地址的SAN
- POST /reopen_logs用于控制日志轮换
- API:冻结v2 xDS API,新特性将在v3 xDS中开发。v2 xDS API目前已经启用,支持在2020年底结束
- AWS特性:
- 过滤器aws_lambda,能够将HTTP请求转换为Lambda调用
- aws_request_signing,一些缺陷修复
- 配置:
- 添加统计项update_time
- 数据源:为远程异步数据源天津爱重试策略
- DNS:
- 为DNS缓存添加dns_failure_refresh_rate配置项,可以设置失败期间的DNS刷新率
- 如果DNS查询返回0主机,则STRICT_DNS类型的集群仅仅解析0主机
- EDS:
- 为端点、端点的健康检查增加hostname字段。可以实现自动hostname重写,定制健康检查使用的host头
- HTTP:
- HTTP1.1泛洪保护
- 配置 headers_with_underscores_action,指定遇到具有下划线的HTTP头时怎么处理,可以允许头、丢弃头、拒绝请求
- 配置max_stream_duration,允许指定现有流持续时间
- LB:
- 支持使用hostname进行一致性哈希负载均衡(consistent_hash_lb_config)
- Lua:
- 为httpCall添加一个参数,是其可以异步化
- 添加moonjit支持
- 网络过滤器:
- 添加envoy.filters.network.direct_response
- RBAC:
- 添加remote_ip、 direct_remote_ip 用于匹配下游远程地址。启用source_ip
- request_id_extension:可以在HTTP连接管理器中扩展request id处理逻辑
- 完全移除对v1 API的支持
- 添加对xDS v3 API的支持
- 集群:支持聚合集群,在多个集群之间进行负载均衡
- 监听器:添加reuse_port选项
- 限速:支持本地限速
- RBAC:支持基于所有SAN进行匹配principal_name
- 路由:支持基于百分比的retry budgets
- 路由:允许基于查询参数进行一致性哈希
- 路由:添加auto_sni,支持基于下游HTTP host/authority头来设置SNI
- TCP代理:添加hash_policy
- UDP代理:初步支持
- 支持HTTP/1.1泛洪保护,此特性可以通过运行时特性envoy.reloadable_features.http1_flood_protection停用
- 配置:支持增量xDS(包括ADS)传递
- 配置:确保终止过滤器(例如L4的HTTP连接管理器、L7的路由器)位于过滤器链的尾部
- HTTP:支持自动合并Path中连续的 /
- HTTP:支持在正向代理中进行host rewrite
- 支持Dubbo Proxy过滤器
- 健康检查:添加initial jitter,在第一次健康检查时添加jitter,防止Envoy启动时的惊群效应
- 热重启:新旧进程不再通过共享内存共享状态,而是gRPC
- HTTP:支持正向代理
- 监听器:添加基于source IP、source Port的过滤器链匹配
- 添加original_src过滤器,可以获取原始的源地址
- 添加CSRF过滤器
- 支持将gRPC请求转换为HTTP/1.1请求
- 添加MySQL过滤器,能够基于MySQL wire协议解析SQL查询
- 路由:虚拟主机域名支持前缀统配
- 添加Tap HTTP过滤器,用于录制流量
名词 | 说明 |
Host |
一个支持网络通信的实体,例如手机、服务器、虚拟机上的应用程序 这里的Host是逻辑的网络应用,一个物理设备上可以有很多Host,只要它们可以被独立寻址 |
Downstream | 向Envoy发送请求、接收响应的Host |
Upstream | Envoy向其发送请求、接收响应的Host |
Listener |
命名的网络位置,例如端口、UNIX域套接字,可以被下游Host连接 Envoy暴露一个或多个Listener供下游主机连接 |
Cluster |
一组逻辑相似的上游Host。Envoy通过服务发现机制识别Cluster的成员,并可选的通过主动健康检查确认Cluster成员的健康状态 Envoy根据负载均衡策略来决定将请求路由给哪个成员 |
Mesh | 一组Host通过协作产生的网络拓扑。Envoy Mesh是指由Envoy代理构成的Mesh |
Runtime Config | 不需要重启就可以重新配置Envoy的机制 |
这种场景下,Envoy仅仅代理一个SOA架构的内部的通信,Envoy需要暴露若干监听器,供本地发起的流量、服务到服务的流量使用。
用于本地应用程序和基础设施中其它服务进行交互。HTTP/gRPC请求通过HTTP/1.1的host头或HTTP/2的authority来指示它的目标是哪里。Envoy负责处理服务发现、负载均衡、限速。
本地应用程序仅仅需要和Envoy通信,而不需要知道网络拓扑,不需要知道自己运行在开发环境还是线上环境。
这种监听器供需要和本地Envoy通信的外部Envoy使用。流量被路由到本地服务的端口。本地代理负责缓冲、断路器等功能。
通常来说,对于每个本地服务需要与之通信的外部服务,都分配一个明确的、独占的出口端口。这是由于某些外部服务的SDK无法容易的基于重写host头来实现标准的HTTP反向代理行为。
可以在服务到服务部署方式基础上,配合基于Envoy实现的边缘代理。并在边缘代理实现:
- TLS Termination
- 同时支持HTTP/1.1和HTTP/2
- 完整的HTTP L7路由支持
- 基于标准的Ingress端口和S2S的Envoy集群通信
尝试Envoy功能最轻松的途径是使用官方的Docker镜像:
1 |
docker pull envoyproxy/envoy:latest |
此镜像包含了一个默认的配置,将入站请求路由给某个外部网站:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# 管理端口配置 admin: access_log_path: /tmp/admin_access.log address: socket_address: protocol: TCP address: 127.0.0.1 port_value: 9901 # 硬编码的静态资源 static_resources: # 唯一的监听器,监听10000端口 listeners: - name: listener_0 address: socket_address: protocol: TCP address: 0.0.0.0 port_value: 10000 # 可以为监听器配置过滤器链 filter_chains: - filters: # HTTP连接管理器,内部可以配置多个HTTP过滤器 - name: envoy.http_connection_manager config: stat_prefix: ingress_http # 路由配置,不论域名是什么,都发给上游集群service_google,同时改写 route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: prefix: "/" route: host_rewrite: blog.gmem.cc cluster: service_gmem # 启用路由过滤器 http_filters: - name: envoy.router # 上游集群配置 clusters: - name: service_gmem connect_timeout: 1s type: LOGICAL_DNS # 仅仅基于IPv4网络进行测试 dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN hosts: - socket_address: address: blog.gmem.cc port_value: 443 tls_context: { sni: blog.gmem.cc } |
在容器中执行下面的容器启动Envoy:
1 2 |
docker run -it --rm --name envoy --network local --ip 172.21.0.2 --dns 172.21.0.1 docker.gmem.cc/envoyproxy/envoy bash envoy --config-path /etc/envoy/envoy.yaml |
启动完毕后,Envoy会监听环回网卡的9901端口,以及0.0.0.0:10000端口。可以在容器中
1 |
curl http://localhost:10000 |
验证是否访问到了blog.gmem.cc。
在Envoy源码的examples目录下,有若干基于Docker Compose创建的沙盒环境,可以用来试验Envoy各方面的功能。
Envoy使用Bazel作为构建系统。
尽管基于GCC 4.9编译的Envoy已经在生产环境中运行,最好使用GCC 5以上版本,原因是std::string线程安全有关的一个缺陷。
从GCC 5.1开始,libstdc++引入了一个新的库ABI,此ABI包含新的std::string、std::list实现,遵循2011 C++标准:
- 禁止Copy-On-Write字符串
- List应当跟踪自身的尺寸
为了保持对已经链接到libstdc++的程序的向后兼容,libstdc++的soname没有修改,老版本和新版本并行存在于同一个库文件中。具体实现方式是,在内联的命名空间中定义新的实现,例如新版本的 std::list<int>实际上定义在std::__cxx11::list<int>。
宏_GLIBCXX_USE_CXX11_ABI决定了libstdc++库的头文件使用新的还是就的ABI,这意味着每个源文件都可以决定自己在编译时使用新的还是就的ABI。GCC 5默认配置此宏为1也就是新ABI,但是某些Linux发行版配置为0,你需要手工定义此宏为1才能使用新ABI。
Clang 4.0以上版本也被支持。但是注意默认情况下Clang丢弃了格式化打印所需的一些调试符号,你需要在.bazelrc文件中添加 --copt=-fno-limit-debug-info解决。
Envoy支持三种编译模式,可以通过Bazel的-c参数传递:
- fastbuild,即 -O0,默认值,用于开发环境
- opt,即 -O2 -DNDEBUG -ggdb3,用于产品环境和性能基准测试
- dbg,即 -O0 -ggdb3,不优化,保留调试符号
修改编译模式的例子:
1 |
bazel build -c opt //source/exe:envoy-static |
下面的可选特性可通过Bazel命令行参数禁用:
- 热重启,通过--define hot_restart=disabled禁用
- Google C++ gRPC客户端,通过--define google_grpc=disabled禁用
- 捕获到信号时打印堆栈,通过--define signal_trace=disabled禁用
- tcmalloc,通过-define tcmalloc=disabled禁用
下面的可选特性可通过Bazel命令行参数启用:
- 在链接时导出符号:--define exported_symbols=enabled,如果你的Lua脚本加载共享对象库,可以启用
- Perf注解:--define perf_annotation=enabled,用于配合性能剖析
- 在FIPS兼容模式下构建BoringSSL:--define boringssl=fips
- 内存调试:--define tcmalloc=debug
Envoy使用模块化的构建风格,不需要的扩展可以被移除。所有可以被禁用的扩展列在文件中extensions_build_config.bzl中。
使用下面的步骤来定制你需要的扩展集:
- 构建Envoy时,默认假设@envoy_build_config//:extensions_build_config.bzl存在,此文件默认包含所有扩展
- 创建一个新的WORKSPACE,在其中包含:
- 空白的WORKSPACE文件
- 空白的BUILD文件
- extensions_build_config.bzl的拷贝
- 注释掉你不需要的扩展
要想让你的本地构建使用修改后的extensions_build_config.bzl,可以使用下面两种方法之一:
- 使用--override_repository选项,覆盖@envoy_build_config存储库
- 在你的WORKSPACE中添加如下片断:
1234567workspace(name = "envoy")local_repository(name = "envoy_build_config",# Relative paths are also supported.path = "/somewhere/on/filesystem/envoy_build_config",)
Envoy把构建环境固化在Docker镜像中,可以执行下面的命令在容器中构建Envoy:
1 |
./ci/run_envoy_docker.sh './ci/do_ci.sh bazel.dev' |
1 |
bazel --bazelrc=/dev/null build -c opt //source/exe:envoy-static.stripped |
为了便于开发者,Envoy提供了一个WORKSPACE、构建最近版本的依赖的规则集,这仅仅适用于开发和测试,因为依赖可能不是最新的而包含安全缺陷。
构建步骤如下:
- 安装Bazel
- 安装必要的库和工具:
12345678910111213141516echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main" | sudo tee /etc/apt/sources.list.d/llvm.listsudo apt-get install \libtool \cmake \realpath \clang-format-7 \automake \ninja-build \curl \unzip# Ubuntu 14.04执行下面的步骤,因为需要GCC-5,因此添加下面的源并安装sudo add-apt-repository ppa:ubuntu-toolchain-r/testsudo apt-get updatesudo apt-get install gcc-5 g++-5sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 1 --slave /usr/bin/g++ g++ /usr/bin/g++-5 - 安装Golang SDK,构建BoringSSL、Buildifer(用于格式化BUILD文件)需要用到
- 安装buildifier:
12go get github.com/golang/protobuf/protogo get github.com/bazelbuild/buildtools/buildifier - 进入Envoy源码根目录,执行:
1bazel build --copt "-DENVOY_IGNORE_GLIBCXX_USE_CXX11_ABI_ERROR=1" //source/exe:envoy-static
1 |
bazel clean --expunge |
可以手工删除~/.cache/bazel,以清除全部缓存。
如果想理解Bazel在做什么,可以启用-s、--explain选项,Bazel会提供更多的输出。
Bazel提供了支持CLion的插件,可以将Bazel项目导入到CLion中。但是CLion是基于CMake的,必须生成CMakeLists.txt才能进行代码导航,否则到处都是红色的错误。
bazel-cmakelists是一个Python小工具,能够从Bazel的C++目标生成CMakeLists.txt。
执行下面的命令,可以生成CMakeLists.txt:
1 |
bazel-cmakelists --targets //source/exe:envoy-static |
Istio为Envoy添加了mixer、authn等多个过滤器,这些过滤器代码是独立在Istio Proxy项目中维护的。
Istio Proxy项目以Bazel外部依赖的形式引入Envoy的源码
1 2 3 4 5 6 7 8 9 10 |
ENVOY_SHA = "925810d00b0d3095a8e67fd4e04e0f597ed188bb" ENVOY_SHA256 = "26d1f14e881455546cf0e222ec92a8e1e5f65cb2c5761d63c66598b39cd9c47d" http_archive( name = "envoy", sha256 = ENVOY_SHA256, strip_prefix = "envoy-" + ENVOY_SHA, url = "https://github.com/envoyproxy/envoy/archive/" + ENVOY_SHA + ".tar.gz", ) |
你可以修改ENVOY_SHA来引用其它版本的Envoy源码。
执行下面的命令,构建出包含了Istio特有过滤器的Envoy二进制文件:
1 2 3 |
bazel build -c dbg --copt "-DENVOY_IGNORE_GLIBCXX_USE_CXX11_ABI_ERROR=1" \ --experimental_cc_skylark_api_enabled_packages=tools/build_defs,@foreign_cc_impl// \ //src/... //test/... |
运行下面的命令,执行所有测试:
1 |
bazel test //test/... |
如果报错ERROR: circular symlinks detected,说明存在符号链接错误。存在错误的路径会打印在控制台,手工修改一下即可。
运行下面的命令,仅仅执行的单元测试test/common/http:async_client_impl_test:
1 |
bazel test //test/common/http:async_client_impl_test |
要查看更多的测试日志输出,使用下面的选项:
1 |
bazel test --test_output=streamed |
要为测试用例传递额外的命令行参数,使用下面的选项:
1 2 |
# 传参 bazel test --test_output=streamed //test/common/http:async_client_impl_test --test_arg="-l trace" |
默认情况下,用例同时针对IPv4和IPv6测试,在仅仅支持IPv4或IPv6的环境下,可以使用下面的环境变量:
1 2 3 |
# 禁用IPv6或IPv4 bazel test //test/... --test_env=ENVOY_IP_TEST_VERSIONS=v4only bazel test //test/... --test_env=ENVOY_IP_TEST_VERSIONS=v6only |
默认情况下,Envoy运行测试时使用gperftools的normal模式来检查堆内存泄漏,你可以禁用gperftools或者修改模式:
1 2 3 4 |
# 禁用 bazel test //test/... --test_env=HEAPCHECK= # 使用minimal模式 bazel test //test/... --test_env=HEAPCHECK=minimal |
内存泄漏的偏移量需要通过addr2line来解释,可以利用 --config=clang-asan选项自动化的进行地址到代码行的转换。
默认情况下,已经通过的测试结果会被缓存,不再测试。如果要强制重新测试,使用选项:
1 |
bazel test //test/common/http:async_client_impl_test --cache_test_results=no |
默认情况下,所有测试在沙盒中运行,不能访问本地文件系统。 使用下面的选项可以禁用沙盒:
1 |
--strategy=TestRunner=standalone |
某些测试要求特殊权限,例如CAP_NET_ADMIN。在物理机上运行测试时,你可以用sudo提权。
如果在容器环境下测试且需要特权,可以利用tools/bazel-test-docker.sh脚本,此脚本可以在本地或者远程Docker引擎上以--privileged运行测试。此脚本的调用格式如下:
1 2 3 |
tools/bazel-test-docker.sh <bazel-test-target> [optional-flags-to-bazel] # 示例,在容器中执行集成测试 tools/bazel-test-docker.sh //test/integration:integration_test --jobs=4 -c dbg |
此脚本读取两个环境变量:
- RUN_REMOTE=yes|no,是否在远程Docker引擎上执行
- LOCAL_MOUNT=yes|no,是否拷贝或挂载本地库到容器中
你可以启用GCC的ASAN(address sanitizer,地址消毒剂,检测内存访问错误)、UBSAN(undefined behavior sanitizer,未定义行为消毒剂),来构建和测试Envoy:
1 |
bazel test -c dbg --config=asan //test/... |
包含行号的ASAN错误栈会打印出来,如果看不见符号,尝试将环境变量ASAN_SYMBOLIZER_PATH设置到 llvm-symbolizer所在位置,后者将 llvm-symbolizer放入$PATH。
如果使用clang-5.0或更高版本构建,可以启用额外的sanitizer:
1 2 |
bazel test -c dbg --config=clang-asan //test/... bazel test -c dbg --config=clang-tsan //test/... |
Envoy能够根据需要生成堆栈追踪(backtraces),也可以由断言或其它失败(例如段错误)触发栈追踪。
在不支持absl::Symbolization的系统上,打印到日志或者标准错误的栈追踪包含地址而非符号。脚本tools/stack_decode.py可以来将这些地址转换为符号+行号。用法示例:
1 2 3 |
bazel test -c dbg //test/server:backtrace_test --run_under=`pwd`/tools/stack_decode.py --strategy=TestRunner=standalone --cache_test_results=no --test_output=all |
你需要使用dbg或者opt构建类型,这样才能从二进制文件中获取符号信息。
下面的命令示意了如何在GDB之下运行单个Bazel测试:
1 2 |
# Bazel的-c dbg选项让二进制文件包含符号信息 tools/bazel-test-gdb //test/common/http:async_client_impl_test -c dbg |
下面的命令示意了如何测试符号表正常:
1 2 |
cd ~/CPP/projects/clion/envoy gdbtui bazel-bin/source/exe/envoy-static |
如果调试符号表可用,则可以看到envoy-static的main.cc的源码。
你可能需要设置souce path,否则GDB找不到源码而进行反汇编:
1 2 3 4 5 |
# 这些外部依赖的源码路径,可能总是需要添加 (gdb) directory /home/alex/CPP/lib/libevent/2.1.8-stable # 根据你IDE配置、调试方式的不同,可能需要添加Envoy的源码路径 (gdb) directory /home/alex/CPP/projects/clion/envoy |
如果想Attach到现有Envoy进程,可以点选Run - Attach to Process...并选中envoy-static进程。关于Clion Attach方式调试的注意点参考:CLion知识集锦
Envoy v2 API以Protocol Buffers的proto3语言定义,该API支持:
- 通过gRPC进行xDS API更新的流式递送,这可以降低更新延迟。Envoy可以从文件系统或管理服务器动态发现资源定义,这一功能由一系列发现服务完成,这些发现服务统称为xDS
- 一个新的REST-JSON API,支持以YAML/JSON格式传递配置,底层依靠proto3 canonical JSON mapping实现
- 通过文件系统,REST-JSON或gRPC端点来递送更新
- 如果需要,可以保证更强的一致性。最终一致性总是得到保证
旧版本的Envoy v1 API已经废弃,但是其功能尚未完全迁移到v2中,有些情况下,需要使用 deprecated_v1字段使用v1 API。
Envoy的配置API文档比较零散,本节使用从一个Istio proxy中dump出的Envoy完整配置来总览的了解Envoy配置规格:
1 |
kubectl exec -it ubuntu -c istio-proxy -- curl http://127.0.0.1:15000/config_dump > /tmp/config |
转换为yaml格式后,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 |
# 配置主要分为以下几个段落 # configs: # bootstrap: 自举配置 # clusters: 上游集群配置 # listeners: 监听器配置 # routes: 路由配置 configs: ### 自举配置 ### bootstrap: # 这个类型提示,用于辅助反串行化 '@type': type.googleapis.com/envoy.admin.v2alpha.BootstrapConfigDump # 这一段是自举配置,启动时需要提供的就是这种(bootstrap的值)格式 bootstrap: # 管理服务器的配置 admin: # 如何输出访问日志 access_log_path: /dev/null # 监听地址,默认在环回网卡上监听15000 address: socket_address: address: 127.0.0.1 port_value: 15000 # 动态资源获取方式配置,在Istio环境下使用基于GRPC的xDS协议,准确的说是使用ADS dynamic_resources: ads_config: api_type: GRPC grpc_services: - envoy_grpc: # 在这里指定xDS管理服务器集群(istio-pilot服务)的名称 cluster_name: xds-grpc refresh_delay: 1s # CDS和LDS都通过ADS获得 cds_config: ads: {} lds_config: ads: {} # 节点信息,也就是当前Pod信息 node: # Envoy版本 build_version: 0/1.8.0-dev//RELEASE # 所属集群,也就是所属的K8S Service cluster: ubuntu # 对于边车模式的代理,ID格式为: # sidecar~POD地址~POD名称.POD命名空间~DNS名称后缀 id: sidecar~172.27.121.134~ubuntu.default~default.svc.k8s.gmem.cc # 元数据列表 metadata: # 各种环境变量 INTERCEPTION_MODE: REDIRECT ISTIO_PROXY_SHA: istio-proxy:930841ca88b15365737acb7eddeea6733d4f98b9 ISTIO_PROXY_VERSION: 1.0.2 ISTIO_VERSION: 1.0.5 POD_NAME: ubuntu # POD标签 app: ubuntu istio: sidecar # 上一次应用的POD的Spec kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"app":"ubuntu"},"name":"ubuntu","namespace":"default"},"spec":{"containers":[{"args":["-c","sleep 365d"],"command":["/bin/sh"],"image":"docker.gmem.cc/ubuntu:16.04","imagePullPolicy":"Always","name":"ubuntu"}]}} # 这里配置静态资源 static_resources: # 上游集群列表,注意,在Istio场景下,每个服务子集,都映射为不同的集群 clusters: ### 这个集群暴露Prometheus Exporter - connect_timeout: 0.250s # 集群的访问地址,从此地址解析出端点 hosts: - socket_address: address: 127.0.0.1 port_value: 15000 # 集群的名称 name: prometheus_stats # 其它集群属性 # type 集群服务发现类型 # eds_cluster_config 对于EDS集群来说,提供EDS配置 # connect_timeout 对集群主机的新连接的默认超时 # per_connection_buffer_limit_bytes 每个连接的读写缓冲的软限制 # lb_policy 负载均衡类型 # hosts 对于 STATIC, STRICT_DNS, LOGICAL_DNS类型的集群,在此指定主机列表 # load_assignment 代替hosts # cluster_name # named_endpoints 从string到endpoint.Endpoint的映射,为可以在endpoints引用的端点分配名字 # endpoints: # - locality 提示上游主机的位置 # lb_endpoints 属于此位置的主机列表 # load_balancing_weight 每priority/region/zone/sub_zone的权重值,1-128之间 # 一个locality的优先级,在所有相同优先级的locality中的占比 # 决定了它接收多少流量。仅当启用Locality weighted load balancing才有用 # priority 此locality的优先级,0最高。 # 在不常见的场景下,Envoy仅仅使用最高优先级的端点。如果最高优先级的端点全部不可用/不健康 # 才使用次高优先级的端点 # health_checks 可选的主动健康检查配置 # max_requests_per_connection 每个连接最大服务的请求数,设置为1相当于禁用keepalive # circuit_breakers 断路器配置 # tls_context TLS上下文配置 # common_http_protocol_options 处理HTTP请求时额外的通用选项 # http_protocol_options 处理HTTP1请求时额外的选项 # http2_protocol_options # dns_refresh_rate DNS刷新速率 # dns_lookup_family DNS IP地址解析策略 # dns_resolvers 指定DNS服务器 # outlier_detection 异常检测配置 # cleanup_interval 对于ORIGINAL_DST类型的集群,移除陈旧主机的间隔。超过此间隔某个主机一直没有作为上游使用认为陈旧 # upstream_bind_config 绑定新建立的上游连接时的选项 # lb_subset_config 负载均衡子集配置 # common_lb_config 负载均衡通用配置,各负载均衡器配置: # ring_hash_lb_config original_dst_lb_config least_request_lb_config # transport_socket 为上游连接指定自定义的传输套接字实现 # ### 这个集群指定xDS服务器也就是Pilot的配置信息 # 断路器配置 - circuit_breakers: # 触发断路的并发度 thresholds: - max_connections: 100000 max_pending_requests: 100000 max_requests: 100000 - max_connections: 100000 max_pending_requests: 100000 max_requests: 100000 priority: HIGH # 超时配置 connect_timeout: 10s # 集群的访问地址 hosts: - socket_address: # K8S集群中的pilot服务 address: istio-pilot.istio-system port_value: 15010 http2_protocol_options: {} name: xds-grpc # 如何发现端点,STRICT_DNS表示通过解析域名来发现 type: STRICT_DNS # 对上游每个端点所创建的TCP连接池配置 upstream_connection_options: tcp_keepalive: keepalive_time: 300 ### 这个集群指定Tracer - connect_timeout: 1s hosts: - socket_address: address: zipkin.istio-system port_value: 9411 name: zipkin type: STRICT_DNS # 监听器列表,注意监听器不一定真正绑定到网络端口 listeners: # 监听地址 - address: socket_address: address: 0.0.0.0 port_value: 15090 # 过滤器链 filter_chains: - filters: # TCP过滤器 envoy.http_connection_manager,内部管理多个HTTP过滤器,任何HTTP协议都使用此过滤器 - config: # 传输时如何压缩 codec_type: AUTO # HTTP过滤器列表,通常至少包含envoy.router,以实现流量路由 http_filters: name: envoy.router # 路由配置,供HTTP过滤器envoy.router使用 route_config: # 虚拟主机,根据请求的HTTP头:authority或host匹配归哪个虚拟主机处理 virtual_hosts: # 此虚拟主机匹配所有流量,名为backend - domains: - '*' name: backend # 路由规则是将针对路径/stats/prometheus的请求转发给上面定义的prometheus_stats集群 routes: - match: prefix: /stats/prometheus route: cluster: prometheus_stats stat_prefix: stats name: envoy.http_connection_manager # 这里是全局的监控统计配置 stats_config: # config.metrics.v2.TagSpecifier,用来指定抽取指标标签名称、值的规则 # tag_name 抽取到的标签名叫什么 # fixed_value 使用固定标签值 # regex 从原始统计指标名中抽取标签值的正则式 # 例如,对于名为cluster.foo_cluster.upstream_rq_timeout的原始统计指标名 # 如果指定tag_name=envoy.cluster_name,regex="^cluster\.((.+?)\.)" # 则子串 foo_cluster. 被捕获分组((.+?)\.)匹配,从而从抽取后的统计指标名 # 中移除,导致抽取后统计指标名变为 cluster.upstream_rq_timeout # 内部捕获分组 (.+?) 将作为抽取得到的标签envoy.cluster_name的值 # 因此最终统计指标变为 cluster.upstream_rq_timeout { tag_name="foo_cluster"} stats_tags: # 抽取cluster_name、tcp_prefix、response_code、response_code_class # http_conn_manager_listener_prefix、http_conn_manager_prefix、listener_address - regex: ^cluster\.((.+?(\..+?\.svc\.cluster\.local)?)\.) tag_name: cluster_name - regex: ^tcp\.((.*?)\.)\w+?$ tag_name: tcp_prefix - regex: _rq(_(\d{3}))$ tag_name: response_code - regex: _rq(_(\dxx))$ tag_name: response_code_class - regex: ^listener(?=\.).*?\.http\.(((?:[_.[:digit:]]*|[_\[\]aAbBcCdDeEfF[:digit:]]*))\.) tag_name: http_conn_manager_listener_prefix - regex: ^http\.(((?:[_.[:digit:]]*|[_\[\]aAbBcCdDeEfF[:digit:]]*))\.) tag_name: http_conn_manager_prefix - regex: ^listener\.(((?:[_.[:digit:]]*|[_\[\]aAbBcCdDeEfF[:digit:]]*))\.) tag_name: listener_address # 是否使用Envoy默认的基于正则式的tag抽取规则 use_all_default_tags: false # 这里是全局调用链跟踪配置 tracing: http: config: # 将追踪信息发送到哪里 collector_cluster: zipkin name: envoy.zipkin last_updated: "2019-02-14T10:35:05.077Z" ### 集群配置 ### clusters: '@type': type.googleapis.com/envoy.admin.v2alpha.ClustersConfigDump # 动态获取的、活动的集群列表 dynamic_active_clusters: # 黑洞集群,处理无匹配监听器的连接 - cluster: connect_timeout: 1s name: BlackHoleCluster # 此集群可以用于获取代理状态 - cluster: circuit_breakers: thresholds: - {} connect_timeout: 1s hosts: - socket_address: address: 127.0.0.1 port_value: 15020 # 注意命名方式 # 供入站流量使用 # 端口 # 子集 # 域名 name: inbound|15020||mgmtCluster # 此集群由于Pod属于一个K8S服务而创建,用于处理入站8086请求 - cluster: circuit_breakers: thresholds: - {} connect_timeout: 1s hosts: - socket_address: address: 127.0.0.1 port_value: 8086 name: inbound|8086||ubuntu.default.svc.k8s.gmem.cc # 下面这个集群是出站集群,用于服务本Pod对外的请求 - cluster: circuit_breakers: thresholds: - {} connect_timeout: 1s # 此集群的端点如何获取(EDS) eds_cluster_config: eds_config: # 通过ADS获取端点 ads: {} # 可以指定一个和集群名称不一样的名字给EDS服务器 service_name: outbound|14267||jaeger-collector.istio-system.svc.k8s.gmem.cc # 集群名称 name: outbound|14267||jaeger-collector.istio-system.svc.k8s.gmem.cc # 端点发现类型Cluster.DiscoveryType,取值: # STATIC 静态发现 # STRICT_DNS 严格DNS发现 # LOGICAL_DNS 逻辑DNS发现 # EDS 使用EDS协议发现 # ORIGINAL_DST 总是使用原始请求指定的目的地 type: EDS ### 监听器配置 ### listeners: '@type': type.googleapis.com/envoy.admin.v2alpha.ListenersConfigDump # 动态获取的、活动的监听器列表 dynamic_active_listeners: - last_updated: "2019-02-14T10:35:10.423Z" # 监听器 172.27.121.134_8086,在Pod IP上监听的,一般都是入站监听器 listener: # 此监听器的监听地址 address: socket_address: address: 172.27.121.134 port_value: 8086 # 指定Envoy v1 API属性 deprecated_v1: # 不绑定到端口,表示此监听器不创建监听套接字,因此只能接收其它监听器转发来的连接请求 bind_to_port: false # 过滤器链列表 filter_chains: # 此过滤器链,普通HTTP请求匹配 - filter_chain_match: # destination_port: 当监听器的use_original_dst=true,则当目的地端口是该字段指定的值时,匹配 # prefix_ranges: 当监听器的use_original_dst=true,或者监听器绑定到0.0.0.0:***时 # 什么样的目的地IP范围匹配此链,core.CidrRange形式 # source_type: 根据来源匹配,ANY|LOCAL|EXTERNAL,LOCAL表示连接来自当前主机 # server_names: 根据服务器名称(例如TLS协议的SNI)进行匹配,字符串列表 # transport_protocol: 根据连接的传输协议匹配 # raw_buffer 默认值,如果没有检测到传输协议时匹配 # tls 如果检测到TLS协议, envoy.listener.tls_inspector 设置为该值 # application_protocols: 根据连接的应用层协议匹配 # transport_protocol: raw_buffer # 此链的过滤器列表 filters: # 唯一的过滤器envoy.http_connection_manager - config: # 访问日志格式和位置 access_log: - config: format: | [%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%%PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%" "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%" %UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% path: /dev/stdout name: envoy.file_access_log # 是否为请求生成request_id generate_request_id: true # HTTP过滤器列表 http_filters: # 部分过滤器是在https://github.com/istio/proxy上开发的 # istio_authn过滤器 - config: policy: peers: - mtls: mode: PERMISSIVE name: istio_authn # mixer过滤器 - config: default_destination_service: default # 为Mixer提供Attributes mixer_attributes: attributes: context.reporter.kind: string_value: inbound context.reporter.uid: string_value: kubernetes://ubuntu.default destination.ip: bytes_value: AAAAAAAAAAAAAP//rBt5hg== destination.namespace: string_value: default destination.port: int64_value: "8086" destination.uid: string_value: kubernetes://ubuntu.default service_configs: default: {} transport: attributes_for_mixer_proxy: attributes: source.uid: string_value: kubernetes://ubuntu.default check_cluster: outbound|9091||istio-policy.istio-system.svc.k8s.gmem.cc network_fail_policy: policy: FAIL_CLOSE report_cluster: outbound|9091||istio-telemetry.istio-system.svc.k8s.gmem.cc name: mixer # 其它HTTP过滤器 - name: envoy.cors - name: envoy.fault # 这个过滤器总是有,用于支持路由 - name: envoy.router # 路由配置 route_config: name: inbound|8086||ubuntu.default.svc.k8s.gmem.cc # 集群管理器是否应该验证此路由表所引用的集群 # 引用不存在集群时,如果此字段设置为: # true:路由表不被加载 # false:路由表加载,但是如果选路到此表,总是返回404 validate_clusters: false # 虚拟主机列表 virtual_hosts: # 匹配所有域名 - domains: - '*' # 此虚拟主机的逻辑名称 name: inbound|http|8086 # 逐条匹配下面的路由,找到匹配请求的立即使用 routes: # 路由装饰器 - decorator: # 关联到匹配此路由的请求的operation name,在调用链追踪场景下 # 此字段用作span name operation: ubuntu.default.svc.k8s.gmem.cc:8086/* # 什么情况下匹配此路由 route.RouteMatch # prefix: 根据路径(:path头)前缀匹配 # path: 精确匹配路径 # regex: 正则式匹配路径 # case_sensitive: false 指定匹配时是否大小写敏感 # headers: 根据请求头匹配 # query_parameters: 根据查询参数匹配 # grpc: 如果存在此字段,仅仅匹配gRPC请求 route.RouteMatch.GrpcRouteMatchOptions match: prefix: / # 覆盖过滤器配置 per_filter_config: mixer: mixer_attributes: attributes: destination.service: string_value: ubuntu.default.svc.k8s.gmem.cc destination.service.host: string_value: ubuntu.default.svc.k8s.gmem.cc destination.service.name: string_value: ubuntu destination.service.namespace: string_value: default destination.service.uid: string_value: istio://default/services/ubuntu # route.RouteAction,如何转发请求 route: # cluster: 发往的上游集群名称 # cluster_header: 该字段的值为一请求头名称,从此请求头读取上游集群名称 # weighted_clusters: 为单个路由指定多重、权重化的上游集群route.WeightedCluster # cluster_not_found_response_code: 如果上游集群不存在,返回什么错误码,默认503 # metadata_match: 仅上游集群中元数据匹配的端点有资格参与负载均衡 # prefix_rewrite: 改写URL前缀 # host_rewrite: 改写host头为此值 # auto_host_rewrite: 自动改写host头,不能和host_rewrite同时指定 # timeout: 访问上游集群的超时,默认15s # idle_timeout: 连接超时 # retry_policy: 重试策略 # request_mirror_policy: 镜像策略 # priority: 路由优先级 # rate_limits: 限速配置 # include_vh_rate_limits: 虚拟主机的限速配置是否忽略,默认忽略 # hash_policy: 环哈希负载均衡下使用的哈希策略 # cors: CORS策略 # max_grpc_timeout: 最大gRPC超ishi cluster: inbound|8086||ubuntu.default.svc.k8s.gmem.cc max_grpc_timeout: 0.000s timeout: 0.000s # http_connection_manager的统计指标前缀 stat_prefix: 172.27.121.134_8086 # 此HTTP连接管理器管理的连接,其上的流的空闲超时,默认5m stream_idle_timeout: 0.000s # 调用链追踪配置 tracing: client_sampling: value: 100 overall_sampling: value: 100 random_sampling: value: 1 # 为每种Upgrade类型配置 upgrade_configs: - upgrade_type: websocket name: envoy.http_connection_manager # 此过滤器链,TLS请求匹配 - filter_chain_match: transport_protocol: tls # 其它配置和普通HTTP的过滤器链一致,但是多了tls_context配置 # TLS上下文配置,例如数字证书信息、是否要求客户端证书 tls_context: common_tls_context: alpn_protocols: - h2 - http/1.1 tls_certificates: - certificate_chain: filename: /etc/certs/cert-chain.pem private_key: filename: /etc/certs/key.pem validation_context: trusted_ca: filename: /etc/certs/root-cert.pem require_client_certificate: true # 此监听器的监听器过滤器 listener_filters: - config: {} # 探测连接是基于TLS还是明文的,并导致选择适当的过滤器链 name: envoy.listener.tls_inspector # 监听器名称 name: 172.27.121.134_8086 - last_updated: "2019-02-14T10:35:10.491Z" # 监听器 0.0.0.0_8086,针对任何地址的8086端口的请求都走这个监听器 listener: address: socket_address: address: 0.0.0.0 port_value: 8086 deprecated_v1: bind_to_port: false filter_chains: - filters: - config: http_filters: - config: default_destination_service: default forward_attributes: mixer_attributes: attributes: context.reporter.kind: string_value: outbound name: mixer - name: envoy.cors - name: envoy.fault - name: envoy.router # 和入站监听器不同,没有使用route_config静态指定路由表 # 而是通过RDS,查询名为8086的路由表 rds: config_source: ads: {} route_config_name: "8086" stat_prefix: 0.0.0.0_8086 tracing: operation_name: EGRESS name: envoy.http_connection_manager name: 0.0.0.0_8086 - last_updated: "2019-02-14T10:35:10.428Z" # 上面两个都是基于HTTP协议的,下面这个是TCP协议的出站监听器 listener: address: socket_address: # 对于TCP协议,每个IP+端口组合都需要独立的监听器 address: 10.96.0.10 port_value: 53 deprecated_v1: bind_to_port: false filter_chains: - filters: - config: name: mixer - config: # 简单的TCP代理 # cluster 连接发往的上游集群名称 # weighted_clusters 带权重的上游集群配置 # access_log 访问日志配置 # metadata_match 只有元数据匹配的端点有资格参与负载均衡 cluster: outbound|53||kube-dns.kube-system.svc.k8s.gmem.cc name: envoy.tcp_proxy name: 10.96.0.10_53 - last_updated: "2019-02-14T10:35:10.491Z" # 下面这是一个特殊的监听器,它的名字叫virtual,真正绑定并监听端口 listener: address: socket_address: address: 0.0.0.0 port_value: 15001 filter_chains: - filters: - config: # 直接发给黑洞 cluster: BlackHoleCluster name: envoy.tcp_proxy name: virtual # 转发连接给虚拟监听器处理时,依据原始目的地址判断目标监听器 use_original_dst: true # 静态配置的监听器 static_listeners: - last_updated: "2019-02-14T10:35:05.082Z" # 来自Bootstrap配置 listener: address: socket_address: address: 0.0.0.0 port_value: 15090 routes: '@type': type.googleapis.com/envoy.admin.v2alpha.RoutesConfigDump # 动态获取的路由配置 dynamic_route_configs: - last_updated: "2019-02-15T02:53:56.778Z" route_config: # 引用此路由配置所需要的名称 name: "8086" validate_clusters: false virtual_hosts: - domains: # 虚拟主机匹配的域名 - ubuntu.default.svc.k8s.gmem.cc - ubuntu.default.svc.k8s.gmem.cc:8086 - ubuntu - ubuntu:8086 - ubuntu.default.svc.k8s.gmem - ubuntu.default.svc.k8s.gmem:8086 - ubuntu.default.svc.k8s - ubuntu.default.svc.k8s:8086 - ubuntu.default.svc - ubuntu.default.svc:8086 - ubuntu.default - ubuntu.default:8086 - 10.100.207.211 - 10.100.207.211:8086 name: ubuntu.default.svc.k8s.gmem.cc:8086 # 虚拟主机的路由表 routes: # 匹配规则 - match: prefix: / # 为每个过滤器覆盖配置 per_filter_config: mixer: disable_check_calls: true # 路由目的地 route: # 上游集群名称 cluster: outbound|8086||ubuntu.default.svc.k8s.gmem.cc max_grpc_timeout: 0s timeout: 0s # 静态配置的路由 static_route_configs: - last_updated: "2019-02-14T10:35:05.081Z" # 来自Bootstrap配置 route_config: virtual_hosts: - domains: - '*' name: backend routes: - match: prefix: /stats/prometheus route: cluster: prometheus_stats |
1 2 3 4 5 6 7 8 9 10 |
static_resources: listeners: name: listener_0 address: socket_address: address: 127.0.0.1 port_value: 15001 filter_chains: - filters: - name: echo |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
static_resources: listeners: name: listener_0 address: socket_address: address: 127.0.0.1 port_value: 15001 filter_chains: - filters: - name: envoy.http_connection_manager config: stat_prefix: sample route_config: name: gmem virtual_hosts: - name: gmem domains: ["*"] routes: - match: prefix: "/" route: cluster: gmem http_filters: - name: sample config: key: via val: sample-filter - name: envoy.router clusters: - name: gmem connect_timeout: 1s type: STRICT_DNS dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN hosts: - socket_address: address: gmem.cc port_value: 80 |
Envoy同时支持TLS Termination(接受下游HTTPS请求)和TLS Origination(向上游发送HTTPS请求):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
static_resources: clusters: - name: zircon type: static hosts: - socket_address: address: 10.0.11.10 port_value: 443 # 必须配置此字段,否则会发送HTTP流量给上游 tls_context: # 这个字段不填写也可以 sni: kiali.k8s.gmem.cc connect_timeout: 0.25s listeners: name: gmem-tls address: socket_address: address: 127.0.0.1 port_value: 4433 listener_filters: # 必须配置监听器过滤器tls_inspector - name: envoy.listener.tls_inspector filter_chains: - filter_chain_match: # 才能匹配到这种传输协议 transport_protocol: tls # 指定此Envoy的HTTPS服务器端配置 tls_context: common_tls_context: alpn_protocols: - h2 - http/1.1 # 服务器端证书,一个类型的证书只能指定一个 tls_certificates: - certificate_chain: # 需要提供完整的证书链 filename: /etc/letsencrypt/live/kiali.k8s.gmem.cc/fullchain.pem private_key: filename: /etc/letsencrypt/live/kiali.k8s.gmem.cc/privkey.pem require_client_certificate: false filters: - name: envoy.http_connection_manager config: stat_prefix: ingress_gmem http_filters: - name: envoy.router route_config: name: inbound|443|gmem.cc virtual_hosts: - name: zircon # 端口、域名部分的通配,只能支持一个 # 因为Envoy的Router过滤器支持域名的前缀、后缀通配,但是不能同时支持 domains: [ "kiali.k8s.gmem.cc:*" ] routes: - route: cluster: zircon match: prefix: "/" |
要使用v2 API,Envoy实例需要一个Bootstrap配置信息。Bootstrap配置中可以包含静态的服务器信息,告诉Envoy如何访问动态配置。自举配置通过-c传入:
1 |
./envoy -c <path to config>.{json,yaml,pb,pb_text} |
配置的根是一个Bootstrap消息,该消息的一个核心特点是将静态资源(static_resources)和动态资源分离(dynamic_resources)
第一个例子就是最简单的全静态配置。
下面的示例,在clusters配置中使用了EDS,以动态的发现上游集群包含哪些端点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
static_resources: clusters: - name: some_service connect_timeout: 0.25s lb_policy: ROUND_ROBIN # 集群类型为EDS type: EDS eds_cluster_config: eds_config: api_config_source: api_type: GRPC grpc_services: envoy_grpc: # 引用EDS服务集群 cluster_name: xds_cluster # 静态配置的EDS集群 - name: xds_cluster connect_timeout: 0.25s type: STATIC lb_policy: ROUND_ROBIN load_assignment: cluster_name: xds_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.1 port_value: 5678 |
EDS管理服务器可能会提供如下的DiscoveryResponse:
1 2 3 4 5 6 7 8 9 10 11 |
version_info: "0" resources: - "@type": type.googleapis.com/envoy.api.v2.ClusterLoadAssignment cluster_name: some_service endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.2 port_value: 1234 |
从而为some_service提供端点。
下面是一个完全动态的自举配置,除了和管理服务器相关的信息之外,都是通过xDS发现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# 动态资源 dynamic_resources: # 动态发现监听器 lds_config: api_config_source: api_type: GRPC grpc_services: envoy_grpc: cluster_name: xds_cluster # 动态发现集群 cds_config: api_config_source: api_type: GRPC grpc_services: envoy_grpc: cluster_name: xds_cluster # 静态的xDS服务器集群配置 static_resources: clusters: - name: xds_cluster connect_timeout: 0.25s type: STATIC lb_policy: ROUND_ROBIN http2_protocol_options: {} load_assignment: cluster_name: xds_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.1 port_value: 5678 |
xDS管理服务器可能这样应答一个LDS请求:
1 2 3 4 5 6 |
version_info: "0" resources: - "@type": type.googleapis.com/envoy.api.v2.Listener name: listener_0 address: ... filter_chains: ... |
这样应答一个RDS请求:
1 2 3 4 5 6 7 8 9 10 |
version_info: "0" resources: - "@type": type.googleapis.com/envoy.api.v2.RouteConfiguration name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: { prefix: "/" } route: { cluster: some_service } |
这样应答一个CDS请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
version_info: "0" resources: - "@type": type.googleapis.com/envoy.api.v2.Cluster name: some_service connect_timeout: 0.25s lb_policy: ROUND_ROBIN type: EDS eds_cluster_config: eds_config: api_config_source: api_type: GRPC grpc_services: envoy_grpc: # 集群的端点也依靠xDS发现 cluster_name: xds_cluster |
这样应答一个EDS请求:
1 2 3 4 5 6 7 8 9 10 11 |
version_info: "0" resources: - "@type": type.googleapis.com/envoy.api.v2.ClusterLoadAssignment cluster_name: some_service endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.2 port_value: 1234 |
一个v2 xDS管理服务器需要实现若干gRPC/REST端点,请求总是以 DiscoveryRequest发送,应答则以遵守xDS协议的DiscoveryResponse发送:
gRPC端点如下表:
POST /envoy.api.v2.ClusterDiscoveryService/StreamClusters,服务定义参考cds.proto 如果在Envoy的自举配置的dynamic_resources段包含以下形式的配置,Envoy调用此端点:
|
||
POST /envoy.api.v2.EndpointDiscoveryService/StreamEndpoints ,服务定义参考eds.proto 如果在Envoy的Cluster配置的eds_cluster_config段包含以下形式的配置,Envoy调用此端点:
|
||
POST /envoy.api.v2.ListenerDiscoveryService/StreamListeners,服务定义参考lds.proto 如果在Envoy的自举配置的dynamic_resources段包含以下形式的配置,Envoy调用此端点:
|
||
POST /envoy.api.v2.RouteDiscoveryService/StreamRoutes,服务定义参考rds.proto 如果在Envoy的HttpConnectionManager配置的rds段包含以下形式的配置,Envoy调用此端点:
|
REST端点包括:
- POST /v2/discovery:clusters
- POST /v2/discovery:endpoints
- POST /v2/discovery:endpoints
- POST /v2/discovery:routes
Envoy使用了最终一致性模型,这意味着存在配置不一致的时间窗口。ADS能够将Envoy节点绑定到单个管理服务器,并且串行化API更新推送。
监听器包含的属性如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# 监听器唯一的名称,如果不指定自动生成UUID。如果此监听器通过LDS发现,则必须手工通过唯一性名称 name: "..." # 监听定制 address: "{...}" # 过滤器链 filter_chains: # 如果连接是通过iptables重定向来的,则Envoy接收请求的端口,可能和原始的Dest地址的端口不同 # 默认情况下Envoy在virtual(绑定端口15001) # 这个监听器接收所有iptables重定向来的请求,原始目的端口 # 可能是任何值,booinfo的例子中,原始目的地一般是***:9080 # # 如果此属性设置为true,则当前监听器将重定向的来请求转发(这个转发不需要走网络栈,在Envoy内部就完成) # 给和原始Dest地址匹配的那个监听器处理,booinfo的例子中就是名为***:9080的那个监听器 # # 如果没有匹配的监听器,则接收请求(当前)监听器负责处理请求。如果当前监听器是virtual则由转发给BlackHoleCluster # # 此属性已经废弃,用监听器过滤器use_original代替 use_original_dst: "{...}" # 每个连接的读写缓冲的软限制 per_connection_buffer_limit_bytes: "{...}" # 监听器元数据 metadata: "{...}" # 在监听器级别的Draining操作如何进行 # DEFAULT # MODIFY_ONLY drain_type: "..." # 监听器过滤器列表,监听器过滤器可以操控、增强连接的元数据,这些元数据可能被连接过滤器链使用 listener_filters: # 监听器过滤器的超时,默认15秒,0表示永不超时 # 如果超时后,监听器过滤器链还没完成处理,则套接字被关闭,连接不会创建 listener_filters_timeout: "{...}" # 此监听器是否应该被设置为透明套接字 # 如果为true,连接可以通过iptables TPROXY 目标重定向到此监听器的端口, # 原始的Src/Dest地址会被保留在创建的连接上 # 此标记应该和监听器过滤器original_dst联用,以标记连接的本地地址为“restored” # 可用于将重定向到本监听器的连接转发给和原始Dest关联的其它监听器处理 # 直接连接到本监听器端口的连接,和通过TPROXY转发的连接是无法区分的,这意味着前者被当作后者处理 # 当此标记设置为false时,监听器的套接字被明确的重置为非透明的 # 设置此标记要求Envoy以CAP_NET_ADMIN权限运行 # 如果此标记为空(默认值)则套接字不被修改,其transparent选项不是set也不是reset transparent: "{...}" # 监听器是否设置套接字选项IP_FREEBIND。如果true,则监听器可以绑定到本机没有的IP地址上 # 如果不设置此字段,则套接字不会改变,而不是禁止FREEBIND freebind: "{...}" # 额外的套接字选项 socket_options: # 是否接受TCP Fast Open(TFO)连接,如果设置为大于0的值,则启用TCP_FASTOPEN套接字选项 # 在Linux上内核参数net.ipv4.tcp_fastopen必须包含标记0x2才能启用TCP_FASTOPEN tcp_fast_open_queue_length: "{...}" deprecated_v1: # 此监听器是否需要绑定到(监听)端口,默认true # 如果false,则监听器仅仅能够接受从其它use_original_dst=true的监听器重定向来的连接 bind_to_port: false |
包含的属性如下:
1 2 3 4 5 |
# 监听器名称 name: "..." # 监听器配置,config或者typed_config二选一 config: "{...}" typed_config: "{...}" |
过滤器名称envoy.listener.original_dst。当一个连接通过iptables REDIRECT(或TPROXY+监听器的transparent配置)重定向到监听器时,此过滤器读取套接字选项SO_ORIGINAL_DST。Envoy的后续处理过程中看到的连接本地地址(connection’s local,也就是服务器端地址)是原始的目的地址,而不是监听器正在监听的地址。
此外,基于原始目的地类型的上游集群,可以作为转发目标,也就是说Envoy直接把HTTP/TCP发送给原始的目的地址。
过滤器名称envoy.listener.original_src。
此过滤器能够复制下游连接的远程地址,并将上游连接的源地址设置为此原始的源地址,而不是默认的使用Envoy代理自身的地址。例如,如果下游连接到Envoy时使用的IP地址时10.1.2.3,则Envoy连接上游的时候,使用10.1.2.3,而非Envoy本机IP地址。
如果下游连接的源地址没有被代理或转换,可以简单的获得原始源地址。如果使用的代理协议,则原始源地址可以通过proxy_protocol过滤器获取。
此过滤器支持IPv4和IPv6。
下游的远程地址常常是全局可路由的,默认情况下,从上游返回的包不会经过Envoy路由,你必须进行网络配置,将任何Envoy复制了(作为自己的源地址发往上游)的那些下游IP的路由强制的定向给Envoy。
如果Envoy和上游在同一主机(Sidecar模式),则通过配置Iptables和路由策略即可保证正确行为。此过滤器包含配置项mark:
- 如果将其设置为X,则Envoy会将所有从当前监听器发起的对上游的流量打上标记X
- 如果将其设置为0,则不会打标记
假设mark=1986,则配合下面的规则即可正确的路由回来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# MARK标记用于将特定的数据包打上标签,供iptables配合TC做QOS流量限制或应用策略路由 # 匹配带有标记1986的数据包,并将标记存储到连接中 iptables -t mangle -I PREROUTING -m mark --mark 1986 -j CONNMARK --save-mark # 匹配连接被标记为1986的连接,并且将标记设置到数据包中 iptables -t mangle -I OUTPUT -m connmark --mark 1986 -j CONNMARK --restore-mark # 对于具有1986标记的数据报,到912表查路由 ip rule add fwmark 1986 lookup 912 # 对于这种数据报,直接路由给本地环回网卡 ip route add local 0.0.0.0/0 dev lo table 912 # 允许eth0转发目的或源地址为127.0.0.0/8的数据包,也就是来自或去往lo设备的数据包 # 这样,你就可以使用NAT强制将目的地址是127.0.0.1的封包 # iptables -t nat -A OUTPUT -d 127.0.0.1 -p tcp -m tcp --dport 22 # 的目的地址改为其它值 # -j SNAT --to-destination 16.186.74.32 echo 1 > /proc/sys/net/ipv4/conf/eth0/route_localnet |
Envoy配置示例:
1 2 3 4 5 6 7 8 9 10 |
listeners: - address: socket_address: address: 0.0.0.0 port_value: 8888 listener_filters: - name: envoy.listener.proxy_protocol - name: envoy.listener.original_src config: mark: 1986 |
回顾一下工作原理:
- Envoy接收到下游连接
- 得到下游连接的IP地址,然后以此地址作为源地址,标记封包为1986
- 由于封包具有1987标记,因此到912表查路由,结果发给lo
- lo网卡接收到上述封包,在PREROUTING时发现有标记1986,将当前连接标记为1986
- 路由给本地的上游服务进程
- 进程处理完毕请求后,在OUTPUT时,将封包标记为1986,注意这里的连接同一个,因此可以标记封包
- 由于封包具有1987标记,因此到912表查路由,还是发给lo
- 由于目的地址是最初下游连接的地址,因此封包发给Envoy
过滤器名称envoy.listener.proxy_protocol,用于支持HAProxy代理协议。
启用此过滤器后,连接被假设来自一个代理,此代理将原始的源地址(IP+PORT)存放在一个连接字符串中,Envoy会抽取此字符串,并将其设置为远程地址。
此协议自动识别HAProxy代理协议的v1/v2版本。
过滤器名称envoy.listener.tls_inspector,用于探测连接是基于TLS还是明文的。如果是基于TLS,则此过滤器会:
- 检测SNI(Server Name Indication,服务器名称指示)
- 检测应用层协议协商( Application-Layer Protocol Negotiation)
利用这些信息,可以FilterChainMatch的server_names、application_protocols来选择一个FilterChain
这里讨论除了HTTP连接管理器之外的网络过滤器。
过滤器名称envoy.client_ssl_auth,用于执行TLS身份验证。
此过滤器每个刷新间隔都调用REST API:GET /v1/certs/list/approved,以获取被许可的证书/主体列表。
包含的属性如下:
1 2 3 4 5 6 7 8 9 10 |
# 运行身份验证服务的集群名称 auth_api_cluster: "..." # 统计信息前缀 stat_prefix: "..." # Principal刷新间隔,没人60000,也就是60秒 refresh_delay: "{...}" # IP白名单列表 ip_white_list: - address_prefix: prefix_len: |
过滤器名称envoy.echo,仅仅用于示例网络过滤器API的用法。它会回响所有数据包给下游服务器。
过滤器名称envoy.ext_authz,调用外部的授权服务,验证入站请求是否有权限访问。如果无权访问则连接会被关闭。发送给外部服务的请求格式由CheckRequest提供。
建议把此过滤器配置在过滤器链的头部,这样非法请求不会执行其它过滤器。
包含的属性如下:
1 2 3 4 5 6 |
# 统计指标前缀 stat_prefix: "..." # 外部的gRPC授权服务配置,没人超时200ms grpc_service: "{...}" # 如果外部授权服务不能响应,是否允许请求授权通过,没人false failure_mode_allow: "..." |
配置示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
filters: - name: envoy.ext_authz stat_prefix: ext_authz grpc_service: envoy_grpc: cluster_name: ext-authz clusters: - name: ext-authz type: static http2_protocol_options: {} hosts: - socket_address: { address: 127.0.0.1, port_value: 10003 } |
过滤器名称envoy.mongo_proxy,支持嗅探MongoDB协议,支持故障注入。
包含的属性如下:
1 2 3 4 5 6 7 8 |
# 统计指标前缀 stat_prefix: "..." # 访问日志路径 access_log: "..." # 在代理一个Mongo操作之前,注入一个固定的延迟 delay: "{...}" # 是否产生动态元数据,没人false emit_dynamic_metadata: "..." |
过滤器名称envoy.ratelimit,用于支持全局性限速。
包含的属性如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
# 统计指标前缀 stat_prefix: "..." # 在限速服务请求中使用的限速域 domain: "..." # 限速描述符 descriptors: # 访问限速服务的超时,没人20ms timeout: "{...}" # 如果限速服务不能响应,是否拒绝请求,默认false failure_mode_deny: "..." # 外部限速服务提供者的配置信息 rate_limit_service: "{...}" |
此过滤器包含以下运行时设置:
设置 | 说明 |
ratelimit.tcp_filter_enabled | 多少百分比的连接会调用限速服务,默认100 |
ratelimit.tcp_filter_enforcing | 多少百分比的连接会调用限速服务并且强制限速策略,默认100 |
过滤器名称envoy.filters.network.local_ratelimit,实现本地限速。
使用令牌桶算法,每个入站连接会占用一个令牌,如果没有令牌可用,立即关闭下游连接。
过滤器名称envoy.redis_proxy。Envoy实现了一个Redis代理,可以基于哈希来将请求分发给上游Redis服务集群。
此过滤器包含的属性如下:
1 2 3 4 5 6 |
stat_prefix: "..." # 上游集群名称 cluster: "..." # 设置 settings: op_timeout: "{...}" |
过滤器名称envoy.tcp_proxy。
此代理使用的上游集群可以被其它网络过滤器动态的设置。具体做法时为每个连接设置在键envoy.tcp_proxy.cluster下设置一个状态对象。
此过滤器包含的属性如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
stat_prefix: "..." # 需要连接到的上游集群 cluster: "..." # 一个路由可以对应多个集群,这些集群按权重处理连接 weighted_clusters: clusters: - name: weight: # 元数据匹配,仅上游集群中的、匹配此规则的端点被考虑作为转发目标 metadata_match: "{...}" # 最大空闲时间,也就是上下游都没有收发数据的时间 idle_timeout: "{...}" # 访问日志存储位置 access_log: # 最大连接请求次数 max_connect_attempts: "{...}" |
过滤器名称envoy.filters.network.sni_cluster。此过滤器在TLS连接中,以SNI为上游集群的名称。
没有配置项,必须在tcp_proxy之前安装。
过滤器名称:envoy.filters.network.mysql_proxy。此过滤器处于开发阶段,2019年1月17日PR刚刚合并。
此过滤器能够解析MySQL协议,能解析出协议载荷中的SQL查询。解码后的信息作为作为动态元数据释放。这些动态元数据包括:
元数据 | 说明 |
<table.db> |
string,以table.db形式表示的资源名称,如果无法得知db,则为table 值是在资源上执行单操作的列表,操作包括insert/update/select/drop/delete/create/alter/show |
配置示例:
1 2 3 4 5 6 7 8 9 10 |
filter_chains: - filters: - name: envoy.filters.network.mysql_proxy config: stat_prefix: mysql # 应当和TCP代理联用 - name: envoy.tcp_proxy config: stat_prefix: tcp cluster: ... |
MySQL释放的动态元数据,可以和RBAC过滤器联用,实现对表的访问控制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
filter_chains: - filters: - name: envoy.filters.network.mysql_proxy config: stat_prefix: mysql - name: envoy.filters.network.rbac config: stat_prefix: rbac rules: # 拒绝对productdb库的catalog表进行update操作 action: DENY policies: "product-viewer": permissions: - metadata: filter: envoy.filters.network.mysql_proxy path: - key: catalog.productdb value: list_match: one_of: string_match: exact: update principals: - any: true - name: envoy.tcp_proxy config: stat_prefix: tcp cluster: mysql |
过滤器名称envoy.filters.network.rbac。该过滤器完成对下游服务器的授权(依据其principal)操作,可以明确管理到应用程序的调用,阻止非法访问。
此过滤器支持配置以下两者之一:
- 安全列表(ALLOW)
- 禁止列表(DENY)
列表基于连接属性(IP、端口、SSL subject)来设置访问策略。
访问策略可以工作在enforcement或shadown模式,后者不会实际影响用户,用于在上线前测试策略会产生的影响。
此过滤器包含的属性如下:
1 2 3 4 5 6 7 8 9 |
# 全局应用的RBAC规则 rules: "{...}" # 不会对流量产生实际影响,但是会产生统计信息、日志 shadow_rules: "{...}" # 统计指标前缀 stat_prefix: "..." # RBAC应用的策略,默认情况下,仅当下游服务第一个字节传输过来时应用ONE_TIME_ON_FIRST_BYTE # 如果设置为CONTINUOUS则在每个消息边界(依赖于对应协议的过滤器)都应用 enforcement_type: "..." |
此过滤器名称envoy.filters.network.dubbo_proxy。
解码Dubbo客户端和服务器之间的RPC协议,信息中抽取的信息转换为元数据,包括:请求ID、请求类型、串行化类型、请求服务名、方法名、参数名、参数值。
此过滤器会暴露很多统计信息。
支持配置dubbo_filters,你可以影响Dubbo路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
filter_chains: - filters: - name: envoy.filters.network.dubbo_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.dubbo_proxy.v3.DubboProxy stat_prefix: dubbo_incomming_stats # 指定通信协议和串行化协议 protocol_type: Dubbo serialization_type: Hessian2 # 路由配置 route_config: name: local_route interface: org.apache.dubbo.demo.DemoService routes: - match: method: name: exact: sayHello route: cluster: user_service_dubbo_server dubbo_filters: - name: envoy.filters.dubbo.testFilter typed_config: "@type": type.googleapis.com/google.protobuf.Struct value: name: test_service - name: envoy.filters.dubbo.router |
过滤器名称envoy.filters.network.kafka_broker。
该过滤器解码Kafka客户端协议,包括请求和应答。Kafka 2.4中的消息版本被支持。该过滤器尝试不去影响客户端和服务器之间的通信,因此无法解码的报文原样转发。
该过滤器必须和tcp_proxy联用,示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
listeners: - address: socket_address: address: 127.0.0.1 # 客户端连接的Kafka地址 port_value: 19092 # 客户端连接的Kafka端口 filter_chains: - filters: - name: envoy.filters.network.kafka_broker typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.kafka_broker.v3.KafkaBroker stat_prefix: exampleprefix - name: envoy.filters.network.tcp_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy stat_prefix: tcp # 穿透给localkafka集群 cluster: localkafka clusters: - name: localkafka connect_timeout: 0.25s type: strict_dns lb_policy: round_robin load_assignment: cluster_name: some_service endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.1 # Kafka broker的实际地址 port_value: 9092 # Kafka broker的实际端口 |
解码PostgresSQl客户端(下游)和服务器(上游)之间的wire协议,获取的信息用于生成Postgres级别的统计信息:会话、语句、执行的事务…… 目前不去解析SQL查询。
应当和tcp_proxy联用:
1 2 3 4 5 6 7 8 9 10 11 |
filter_chains: - filters: - name: envoy.filters.network.postgres_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.postgres_proxy.v3alpha.PostgresProxy stat_prefix: postgres - name: envoy.tcp_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy stat_prefix: tcp cluster: postgres_cluster |
使用TLS连接中的SNI来作为上游集群的名称。对于非TLS连接没有影响。
需要联用tls_inspector。基于SNI进行动态代理,下面是一个完整配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
admin: access_log_path: /tmp/admin_access.log address: socket_address: protocol: TCP address: 127.0.0.1 port_value: 9901 static_resources: listeners: - name: listener_0 address: socket_address: protocol: TCP address: 0.0.0.0 port_value: 10000 listener_filters: - name: envoy.filters.listener.tls_inspector filter_chains: - filters: - name: envoy.filters.network.sni_dynamic_forward_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.sni_dynamic_forward_proxy.v3alpha.FilterConfig port_value: 443 dns_cache_config: name: dynamic_forward_proxy_cache_config dns_lookup_family: V4_ONLY - name: envoy.tcp_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy stat_prefix: tcp cluster: dynamic_forward_proxy_cluster clusters: - name: dynamic_forward_proxy_cluster connect_timeout: 1s lb_policy: CLUSTER_PROVIDED cluster_type: name: envoy.clusters.dynamic_forward_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig dns_cache_config: name: dynamic_forward_proxy_cache_config dns_lookup_family: V4_ONLY |
对一个下游HTTP请求进行路由匹配的流程如下:
- HTTP请求的host或authority头匹配到一个虚拟主机(virtual host)
- 虚拟主机中的每个路由条目按顺序检查,如果找到匹配立即使用,不进行后续检查
- 虚拟主机中的每个虚拟集群(virtual cluster),如果找到立即使用
Envoy支持在虚拟主机的多个上游集群之间划分流量,具体有两种应用场景:
- 版本升级,流量逐步的从一个集群转移(Shifting)到另外一个
- A/B测试,多个版本同时被使用。下一节讨论
路由配置中的runtime对象决定选择虚拟主机的某个特定集群的概率。通过runtime_fraction配置,某个虚拟主机的流量可以逐步的转移,下面是个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
virtual_hosts: - name: www2 domains: - '*' # 此虚拟主机包含两个路由,分别转发给v1和v2集群 routes: - match: prefix: / runtime_fraction: default_value: # 分子,通过改变不断修改此值,实现流量转移 numerator: 50 # 分母 denominator: HUNDRED runtime_key: routing.traffic_shift.helloworld route: cluster: helloworld_v1 - match: prefix: / route: cluster: helloworld_v2 |
完成流量转移的步骤如下:
- 最初,将routing.traffic_shift.helloworld设置为100,所有请求路由给v1
- 逐步减小routing.traffic_shift.helloworld的值,导致越来越多的请求路由给v2
- routing.traffic_shift.helloworld设置为0,完成转移
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
virtual_hosts: - name: www2 domains: - '*' routes: - match: { prefix: / } route: weighted_clusters: clusters: - name: helloworld_v1 weight: 33 - name: helloworld_v2 weight: 33 - name: helloworld_v3 weight: 34 |
对于该例子,可以通过运行时变量routing.traffic_split.helloworld.helloworld_v1/2/3动态调整权重。
HTTP连接管理器在编码响应/解码请求时,都会操控一系列HTTP头:
头 | 说明 |
user-agent |
如果配置了add_user_agent选项,解码请求时会添加此头。如果头已经存在则不会修改 此头的值取决于--service-cluster命令行参数 |
server | 解码时设置为 server_name选项的值 |
x-client-trace-id |
如果外部客户端设置了此头,则Envoy将此ID和内部生成的x-request-id连接起来 如果此头被设置,其效果类似于x-envoy-force-trace,参考运行时配置tracing.client_enabled |
x-envoy-downstream-service-cluster |
用于提示上游集群,是哪个服务集群调用了它。其值由命令行选项--service-cluster决定 需要将user_agent选项设置为true以使用该头 |
x-envoy-downstream-service-node |
用于提示上游集群,是哪个服务实例调用了它。其值由命令行选项--service-node决定 |
x-envoy-external-address |
如果请求来自网格外部,则设置为客户端地址(trusted client address ) 如果请求来自内部,不会设置或修改此头。也就是说,此头可以在多个内部服务之间传递来传递去,不需要考虑XFF头那样的复杂性 |
x-envoy-force-trace |
如果某个内部服务设置了此头,则Envoy会修改自动生成的x-request-id并强制调用链跟踪 这也强制在响应头中返回x-request-id 如果此x-request-id传播到了其它主机,这些主机也会进行调用链收集,这会最终生成完整的调用链 参考运行时配置 tracing.global_enabled、tracing.random_sampling |
x-envoy-internal | 用于区分请求来自内部还是外部,Envoy根据XFF判断,并自动设置此头为true或false |
x-envoy-original-dst-host |
使用原始目的地这个负载均衡策略时,此头用于覆盖目的地地址 默认情况下此头被忽略,除非特定通过use_http_header启用 |
x-forwarded-client-cert | XFCC是一个代理头,用于指出请求穿过的客户端、所有中间代理的的证书信息 |
x-forwarded-for |
XFF是一个标准代理头,用于指示请求穿过的客户端、所有中间代理的IP地址。任何代理都应该用逗号分隔的方式,将直接客户端的地址附加到此头中 仅仅当HTTP连接管理器的use_remote_address选项为true且skip_xff_append=false,Envoy才会附加XFF。否则HTTP连接管理器运行在“透明”模式,导致上游服务不会感知它的存在 |
x-forwarded-proto | 用于指示原始的协议(HTTP还是HTTPS) |
x-request-id |
Envoy根据此头来唯一的识别请求,并进行稳定的访问日志和请求追踪 Envoy会为所有外部原始请求生成x-request-id(消毒),对于内部请求,如果没有此头,Envoy也会生成一个 经过网格中各节点时,此x-request-id应该被正确的传播,以便调用链可以被追踪。由于Envoy的进程外架构,它无法自动的转发此ID,你需要自己转发,要么手工编码,要么使用某种Agent或库 如果x-request-id正确的传播到所有主机上,则以下特性可用:
|
x-ot-span-context | 使用LightStep这一Tracer时,Envoy使用此头来建立Tracing Span之间的父子关系 |
x-b3-traceid | 使用Zipkin这一Tracer时,此头是一个8字节的字符串,表示整个Trace的标识 |
x-b3-spanid | 使用Zipkin这一Tracer时,此头是一个8字节的字符串,表示当前操作在Span树中的位置 |
x-b3-parentspanid | 使用Zipkin这一Tracer时,此头是一个8字节的字符串,表示当前操作的父操作在Span树中的位置 |
x-b3-sampled | 使用Zipkin这一Tracer时,如果设置为1或者不设置,则Span会被报告给Tracer,整个Trace中此值应该恒定 |
x-b3-flags | 使用Zipkin这一Tracer时,编码多个选项 |
b3 | 使用Zipkin这一Tracer时,更加复杂的头格式 |
x-datadog-trace-id | Datadog(一种Tracer)相关的头 |
x-datadog-parent-id | |
x-datadog-sampling-priority |
在权重集群、路由、虚拟主机、全局配置级别,都可以添加自定义请求/响应头。
以-开头的伪头可以通过自定义头来覆盖。而:path和:authority头则可以通过prefix_rewrite或host_rewrite机制修改。
头的优先级顺序(从高到低):权重集群、路由、虚拟主机、全局配置级别。
在自定义头中可以使用以下变量:
变量 | 说明 | ||
%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT% | 下游连接的地址,不带端口 | ||
%DOWNSTREAM_LOCAL_ADDRESS% |
下游连接的本地的地址,包含IP和端口 如果原始请求基于Iptables REDIRECT转发,此变量存储原始目的地址 如果原始请求基于Iptables TPROXY转发,且监听器的transparent为true,此变量存储原始目的地址和端口 |
||
%DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT% | 上面的,不包含端口 | ||
%PROTOCOL% | 原始的协议,和x-forwarded-proto头相同 | ||
%UPSTREAM_METADATA([“namespace”, “key”, …])% |
从被路由选择的上游主机的EDS端点元数据中获取的元数据 |
||
%PER_REQUEST_STATE(reverse.dns.data.name)% | |||
%START_TIME% |
请求处理开始时间,示例:
|
由于安全原因,Envoy会对收到的HTTP头进行消毒(sanitize) —— 要么添加、修改、删除。
HTTP连接管理器支持以下运行时设置:
设置 | 说明 |
tracing.client_enabled | 如果设置了x-client-trace-id头,多少比例的请求被追踪,默认100 |
tracing.global_enabled | 如果其它条件都满足,则多少比例的请求被追踪,默认100 |
tracing.random_sampling | 多少请求被采样式的追踪,取值0-10000之间,100表示百分之一 |
路有发现服务RDS是一个可选的API,Envoy可以调用此API以动态的抓取路由配置。每个HTTP连接管理器可以独立的抓取。
过滤器名称envoy.buffer。此过滤器用于停止过滤器迭代,然后等待完整的请求到达。用途是:
- 保护某些应用程序,使其不必处理不完整请求(partial request)
- 降低网络延迟
此过滤器包含的属性:
1 2 |
# 此过滤器最大能缓冲的请求大小,超过请求后HTTP连接管理器返回413错误 max_request_bytes: "{...}" |
在每个路由级别,buffer过滤器可以被覆盖配置或者禁用:
1 2 3 4 5 |
# 是否禁用 disabled: true # 覆盖的配置 buffer: max_request_bytes: ... |
过滤器名称envoy.cors。此过滤器基于路由/虚拟主机的配置来处理跨站资源共享(Cross-Origin Resource Sharing)请求。
此过滤器包含的属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 允许的源 allow_origin: [] # 允许的源的需要匹配的正则式 allow_origin_regex: [] # 指定access-control-allow-methods头的内容 allow_methods: "..." # 指定access-control-allow-headers头的内容 allow_headers: "..." # 指定access-control-expose-headers头的内容 expose_headers: "..." # 指定access-control-max-age头的内容 max_age: "..." # 是否allows credentials allow_credentials: "{...}" # 是否启用CORS enabled: "{...}" |
此过滤器支持以下运行时RuntimeFractionalPercent设置:
- filter_enabled:对于多少比例的请求启用此过滤器,默认100/HUNDRED
- shadow_enabled:多少比例的请求在shadow模式下运行此过滤器,默认0
过滤器名称envoy.ext_authz。此过滤器调用外部的gRPC/HTTP服务,以确认请求是否有权访问。如果验证失败会返回403/Forbidden应答。
从授权服务传递额外的元数据到上游、下游也是支持的,反方向也是。发送给授权服务的请求内容由CheckRequest规定。
此过滤器包含的属性:
1 2 3 4 5 6 |
# 外部gRPC授权服务配置 grpc_service: "{...}" # 外部HTTP授权服务配置 http_service: "{...}" # 如果授权服务不响应,是否允许请求通过 failure_mode_allow: "..." |
基于gRPC授权服务的配置示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
http_filters: - name: envoy.ext_authz config: grpc_service: envoy_grpc: cluster_name: ext-authz # 默认超时200ms timeout: 0.5s ... clusters: - name: ext-authz type: static http2_protocol_options: {} hosts: - socket_address: { address: 127.0.0.1, port_value: 10003 } # 最初握手的超时,不是请求超时 connect_timeout: 0.25s |
基于HTTP授权服务的配置示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
http_filters: - name: envoy.ext_authz config: http_service: server_uri: uri: 127.0.0.1:10003 cluster: ext-authz timeout: 0.25s failure_mode_allow: false ... clusters: - name: ext-authz connect_timeout: 0.25s type: logical_dns lb_policy: round_robin hosts: - socket_address: { address: 127.0.0.1, port_value: 10003 } |
在路由级别定制配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] per_filter_config: envoy.ext_authz: check_settings: context_extensions: virtual_host: local_service routes: - match: { prefix: "/static" } route: { cluster: some_service } # 为某个过滤器覆盖配置 per_filter_config: # 禁用ext_authz envoy.ext_authz: disabled: true - match: { prefix: "/" } route: { cluster: some_service } |
过滤器名称envoy.fault。故障注入过滤器可以用于测试微服务的顽强性(resiliency) ,它能够注入延迟(目前仅支持固定延迟),或者注入用户指定的错误码(仅仅支持HTTP状态码),从而模拟各种错误场景。这些场景包括:服务过载、高网络延迟、网络分区。
故障注入可以仅仅应用于特定的请求集,这些请求集以请求头、上游服集群等信息识别。
此过滤器的属性包括:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# 注入延迟 delay: # 目前仅仅支持FIXED type: FIXED fixed_delay: "{...}" percentage: "{...}" # 注入中断 abort: http_status: "..." percentage: "{...}" # 此过滤器匹配的上游集群 upstream_cluster: "..." # 此过滤器匹配的请求头 headers: - name: "..." exact_match: "..." regex_match: "..." range_match: "{...}" present_match: "..." prefix_match: "..." suffix_match: "..." invert_match: "..." # 故障为以下下游主机注入, 下游主机的名字从x-envoy-downstream-service-node头读取 downstream_nodes: [] |
此过滤器支持以下运行时配置:
配置 | 说明 |
fault.http.abort.abort_percent |
请求头匹配的请求,有多少百分比被注入abort 下游集群可以覆盖: fault.http.<downstream-cluster>.abort.abort_percent |
fault.http.abort.http_status |
注入什么状态码 下游集群可以覆盖: fault.http.<downstream-cluster>.abort.http_status |
fault.http.delay.fixed_delay_percent |
请求头匹配的请求,有多少百分比被注入延迟 下游集群可以覆盖: fault.http.<downstream-cluster>.delay.fixed_delay_percent |
fault.http.delay.fixed_duration_ms |
注入多久的延迟 下游集群可以覆盖: fault.http.<downstream-cluster>.delay.fixed_duration_ms |
这些运行时设置可以为每个下游集群覆盖,<downstream-cluster>取请求头x-envoy-downstream-service-cluster。
过滤器名称envoy.grpc_http1_bridge,用于桥接一个不支持响应报尾(response trailers)的HTTP/1.1客户端到gRPC服务器。其工作方式如下:
- 请求发送时,此过滤器检查连接是否基于HTTP/1.1,且MIME类型为application/grpc,如果是
- 当Envoy接收到应答时,过滤器会缓冲它,并等待trailers,检查grpc-status码
- 如果此码不为0,则过滤器将HTTP状态码设置为503,同时将grpc-status、grpc-message这两个tailers存放到响应头中
- 客户端必须发送包含以下伪头的HTTP/1.1请求:
- :method,取值POST
- :path,取值gRPC方法的名称
- content-type,取值application/grpc
- 请求体必须是串行化的gRPC体:
- 第一字节为非压缩的0
- 网络字节序的4字节,Proto消息的长度
- 串行化后的Proto消息
- 由于需要缓冲响应并寻找grpc-status报尾,因此仅仅支持一元gRPC API
过滤器名称envoy.grpc_http1_reverse_bridge。将gRPC请求转换为HTTP 1.1请求,并转发给不理解gRPC或HTTP/2的服务器处理。
过滤器名称envoy.grpc_json_transcoder。将RESTful JSON API请求转换为gRPC请求并转发给gRPC服务器。
过滤器名称envoy.grpc_web。实现gRPC-Web客户端到gRPC服务器的桥接。
过滤器名称envoy.gzip。实现压缩功能。
过滤器名称envoy.health_check。参考1.12 健康检查。
过滤器名称envoy.filters.http.header_to_metadata。此过滤器包含一系列规则,规则对请求/响应进行匹配。每个规则包含一个头,当请求/响应包含或不包含此头时,规则匹配,并添加动态元数据。
动态元数据可以用来决定如何负载均衡,或者被日志模块使用。最典型的用法是动态的匹配请求和负载均衡子集:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# 过滤器配置 http_filters: - name: envoy.filters.http.header_to_metadata config: request_rules: - header: x-version # 有x-version头的请求,使用和此头的值匹配的version的端点 on_header_present: metadata_namespace: envoy.lb key: version type: STRING # 没有x-version头的请求,使用默认端点 on_header_missing: metadata_namespace: envoy.lb key: default value: 'true' type: STRING remove: false # 上游集群配置 clusters: - name: versioned-cluster type: EDS lb_policy: ROUND_ROBIN lb_subset_config: fallback_policy: ANY_ENDPOINT # 负载均衡子集选择器 subset_selectors: - keys: - default - keys: - version |
过滤器名称envoy.lua。此过滤器运行你提供Lua脚本,处理请求/应答流。此过滤器适用于简单的场景,如果要实现复杂的、高性能的逻辑,请编写原生的C++过滤器。
注意:
- 所有Lua环境是每个工作线程独占的,也就是说没有真正全局性的数据。加载期间生成的全局变量,对于每个工作线程来说都是独立副本
- 所有脚本以协程的形式执行,也就是说它们以同步的方式运行,即使实际执行的是复杂的异步工作。网络/异步处理由Envoy执行,Envoy在适当的时机调用Lua脚本
- 绝不要在Lua中包含阻塞性操作
- 默认构建的Envoy没有导出符号,和以共享对象安装的Lua模块交互时可能需要这些符号
目前支持的特性:
- 查看报文头、报文体、报尾
- 修改报文头、报文尾
- 阻塞并等到完整的请求/应答,以进行完整的inspection
- 向上游主机发起异步的HTTP出站调用
- 直接提供响应,终止过滤器迭代
LuaJIT作为Lua的运行时,支持的版本是5.1,以及一些5.2特性。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
http_filters: - name: envoy.lua config: inline_code: | # 处理请求 function envoy_on_request(request_handle) request_handle:headers():add("foo", "bar") end # 处理应答 function envoy_on_response(response_handle) body_size = response_handle:body():length() response_handle:headers():add("response-body-size", tostring(body_size)) end |
流句柄(Stream handle)API,也就是envoy_on_request/envoy_on_response函数的入参handle的方法列表:
方法 | 说明 | ||
headers() | 返回请求/应答流的头,在发送到header chain中另一个过滤器链之前,这些头可以被修改。例如,你可以在httpCall()、body()调用之后进行头修改 | ||
body() | 返回流的体,此调用导致Envoy yield掉脚本,直到整个体被缓冲完毕 | ||
bodyChunks() |
返回一个迭代器,用于编译流体的所有片断。Envoy会在获取每个片断时yield脚本,但是不会进行缓冲操作。示例:
|
||
trailers() | 返回报文尾,如果没有可能返回nil。发送到下一个过滤器之间你可以修改trailers | ||
log*() |
记录Envoy应用程序日志:
|
||
httpCall() |
向上游主机发起HTTP调用:
Envoy会yield脚本,知道请求完成或者出错。cluster是已经配置好的集群名称,超时单位ms |
||
respond() |
不再继续过滤器迭代,立即进行应答。仅仅在请求流中可以调用:
|
||
metadata() |
返回当前路由条目元数据(route entry metadata),这些元数据必须声明在命名空间envoy.lua下面,例如:
|
||
streamInfo() | 返回和当前流有关的信息 | ||
connection() | 返回当前请求使用的底层连接对象 |
头对象的API:
方法 | 说明 | ||
add() | 添加一个头 | ||
get() | 获取一个头 | ||
__pairs() |
用于迭代所有头:
|
||
remove() | 移除头 | ||
replace() |
替换头 |
Buffer的API:
方法 | 说明 |
length() | 返回缓冲的长度 |
getBytes() | 从缓冲中读取字节: buffer:getBytes(index, length) |
Metadata对象的API:
方法 | 说明 |
get() | 获取一个元数据,值的类型可能是nil, boolean, number, string, table |
__pairs() | 用于迭代所有元数据 |
StreamInfo对象的API:
方法 | 说明 |
protocol() | 返回使用的协议,可能的值包括HTTP/1.0, HTTP/1.1, HTTP/2 |
dynamicMetadata() | 返回动态元数据 |
动态元数据的API:
方法 | 说明 | ||
get() |
获取动态元数据:
|
||
set() | 设置动态元数据: dynamicMetadata:set(filterName, key, value) | ||
__pairs() | 迭代所有元数据 |
API示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
-- 添加头 request_handle:headers():add("request_body_size", request_handle:body():length()) -- 删除头 response_handle:headers():remove("foo") -- 发起HTTP调用 local headers, body = request_handle:httpCall( "lua_cluster", { [":method"] = "POST", [":path"] = "/", [":authority"] = "lua_cluster" }, "hello world", 5000) -- 直接应答 request_handle:respond({[":status"] = "403", ["upstream_foo"] = headers["foo"]}, "nope") |
过滤器名称envoy.rate_limit。如果请求的路由/虚拟主机配置包含1-N个限速配置匹配过滤器的stage,则此过滤器会调用限速服务,将每个限速配置产生的描述符发送给限速服务。
如果限速服务返回的响应,提示任何一个描述符超限,则返回429响应给客户端。
如果调用限速服务失败,且failure_mode_deny配置为true,则返回500应答。
此过滤器包含的属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 调用限速服务时使用的限速域 domain: "..." # 应用的限速配置对应的Stage号,默认0 stage: "..." # 此过滤器应用到的请求类型。可选internal、external、both # 如果请求包含头x-envoy-internal=true则认为是内部请求 request_type: "..." # 调用限速服务的超时默认20ms timeout: "{...}" # 如果调用限速服务失败,是否拒绝请求 failure_mode_deny: "..." # 对于gRPC请求,是否返回RESOURCE_EXHAUSTED而非默认的UNAVAILABLE gRPC码 rate_limited_as_resource_exhausted: "..." # 外部限速服务的配置 rate_limit_service: grpc_service: "{...}" |
过滤器名称envoy.filters.http.rbac。此过滤器用于授权下游客户端的访问权限,用于明确管理谁能访问服务。可以根据连接属性(IP、端口、SSL主体)或者请求头来决定ALLOW/DENY访问请求。
过滤器名称envoy.router。此过滤器实现了HTTP forwarding,几乎会用在所有HTTP代理场景下。此过滤器的职责包括:
- 根据路由表配置进行转发
- 处理重试
- 处理统计信息收集
此过滤器包含的属性:
1 2 3 4 5 6 7 8 |
# 是否生成动态集群统计信息,默认true,在高性能场景下可以禁用 dynamic_stats: "{...}" # 是否为出口路由调用生成child span start_child_span: "..." # 上游调用日志的配置,这个日志类似于访问日志,但是每个条目表示一个对上游的请求,如果发生重试,一个访问日志可能对应多个上游日志 upstream_log: # 是否不添加额外的x-envoy-头 suppress_envoy_headers: "..." |
此过滤器会读取以下头:
头 | 说明 |
x-envoy-max-retries |
如果路由/虚拟主机的重试策略存在,默认情况下Envoy会重试一次,除非明确指定重试次数 重试次数可以在几个地方提供:
如果重试策略没有配置,且x-envoy-retry-on、x-envoy-retry-grpc-on头不存在,则Envoy不会进行重试 如果有多个配置,取其中最大的重试次数 关于Envoy重试的一些细节:
|
x-envoy-retry-on |
为出站请求设置该头,会导致在特定时机自动重试,多个时机用逗号分隔:
|
x-envoy-retry-grpc-on |
为出站请求设置该头,会导致在特定时机自动重试gRPC请求:
|
x-envoy-retriable-status-codes | 提示哪些状态码是“可重试”的 |
x-envoy-upstream-rq-timeout-ms | 覆盖路由配置中的超时,单位ms |
x-envoy-upstream-rq-per-try-timeout-ms | 每次重试的超时,单位ms |
x-envoy-immediate-health-check-fail | 如果上游主机返回此响应头,则Envoy立即认为上游主机主动检查失败(如果配置了主动检查的话),用于快速失败而不是等待下次健康检查 |
x-envoy-overloaded | 如果上游主机返回此响应头,则不进行重试 |
x-envoy-ratelimited | 如果上游主机返回此响应头,则不进行重试 |
此过滤器会设置以下头:
头 | 说明 |
x-envoy-attempt-count | 发送给上游主机,提示已经重试的次数,最初请求设置为1,每次重试+1 |
x-envoy-expected-rq-timeout-ms | 路由器期望请求在多长时间内完成。这个头发送给上游服务器,辅助其决策(例如超时后仍然在处理中,可以直接终止处理) |
x-envoy-upstream-service-time | 上游主机处理请求消耗的时间,客户端可以依次判断网络延迟的影响 |
x-envoy-original-path | 原始的路径,由于前缀重写,路径可能变化了,用于日志和调试 |
x-envoy-overloaded | 如果请求由于维护模式、上游断路而失败,在给下游的应答中设置此头 |
包含的属性:
1 2 3 4 5 6 7 8 9 10 11 12 |
# 本地集群(拥有运行此配置的Envoy的集群)名称。要使用Zone可知路由则必须设置,此属性和 --service-cluster 无关 local_cluster_name: "..." # 全局性异常检测设置 outlier_detection: event_log_path: "..." # 绑定新创建的上游连接时使用的配置 upstream_bind_config: source_address: "{...}" freebind: "{...}" socket_options: # load_stats_config: |
访问日志配置,是HTTP连接管理器或者TCP代理配置的一部分。具体参考官方文档。
过载管理器用于保护Envoy代理所在机器本身,过载管理器的配置,放在Bootstrap配置的overload_manager字段下。
TLS证书、密码等敏感信息可以在static_resource中配置,也可以从远程的SDS服务抓取。
SDS的价值主要是简化证书管理。如果没有此特性,在K8S场景下,证书只能以Secret的方式定义并挂载到代理容器中。如果证书过期,则需要重现挂载。使用SDS,过期后新证书自动被推送到Envoy代理。
监听器管理器在命名空间listener_manager.下暴露以下监控指标:
指标 | 说明 |
listener_added | Counter,总计添加的监听器数量(包括静态和LDS) |
listener_modified | Counter,通过LDS修改监听器的次数 |
listener_removed | Counter,通过LDS删除监听器的次数 |
listener_create_success | Counter,总计成功添加到Worker中的监听器对象数量 |
listener_create_failure | Counter,总计失败添加到Worker中的监听器对象数量 |
total_listeners_warming | Gauge,当前处于预热状态的监听器数量 |
total_listeners_active | Gauge,当前处于活动状态的监听器数量 |
total_listeners_draining | Gauge,当前处于draining状态的监听器数量 |
每个监听器都在命名空间 listener.<address>下暴露以下监控指标:
指标 | 说明 |
downstream_cx_total | Counter,连接总数 |
downstream_cx_destroy | Counter,销毁的连接总数 |
downstream_cx_active | Gauge,当前活动的连接总数 |
downstream_cx_length_ms | Histogram,连接持续时间ms |
downstream_pre_cx_timeout | Counter,在监听器过滤器处理期间,超时的套接字数量 |
downstream_pre_cx_active | Gauge,正在被监听器过滤器处理的连接请求数量 |
no_filter_chain_match | Counter,总计的不匹配任何过滤器链的连接数量 |
ssl.connection_error | Counter,总计的TLS连接错误数量,包括证书验证失败的情况 |
ssl.handshake | Counter,总计的TLS握手成功次数 |
ssl.session_reused | Counter,总计的TLS会话重用次数/home/alex/CPP/projects/clion/envoy |
ssl.no_certificate | Counter,总计的没有客户端证书的成功TLS连接次数 |
ssl.fail_verify_no_cert | Counter,总计的由于没有客户端证书而导致TLS连接失败的次数 |
ssl.fail_verify_error | Counter,总计的由于证书验证失败而导致TLS连接失败的次数 |
ssl.fail_verify_san | Counter,总计的由SAN验证失败而导致TLS连接失败的次数 |
ssl.fail_verify_cert_hash | Counter,总计的由证书哈希验证失败而导致TLS连接失败的次数 |
ssl.ciphers.<cipher> | Counter,使用cipher的成功TLS连接次数 |
ssl.curves.<curve> | Counter,使用curve的成功TLS连接次数 |
ssl.sigalgs.<sigalg> | Counter,使用签名算法sigalg的成功TLS连接次数 |
ssl.versions.<version> | Counter,使用协议版本version的成功TLS连接次数 |
此过滤器产生以下指标:
指标 | 说明 |
downstream_cx_proxy_proto_error | Counter,总计的代理协议错误数量 |
此过滤器在命名空间下暴露以下指标:
指标 | 说明 |
connection_closed | Counter,总计关闭的连接数量 |
client_hello_too_large | Counter,总计收到的客户端不合理的大尺寸Hello报文数量 |
read_error | Counter,总计读错误数量 |
tls_found | Counter,TLS被找到的数量(也就是说入站连接没有使用TLS协议) |
tls_not_found | Counter,TLS没被找到的数量 |
alpn_found | Counter,应用层协议协商找到的次数 |
alpn_not_found | Counter,应用层协议协商未找到的次数 |
sni_found | Counter,SNI找到的次数 |
sni_not_found | Counter,SNI未找到的次数 |
此过滤器在命名空间 auth.clientssl.<stat_prefix>.下暴露以下指标:
指标 | 说明 |
update_success | Counter,总计Principal更新成功次数 |
update_failure | Counter,总计Principal更新失败次数 |
auth_no_ssl | Counter,由于没有TLS而忽略的连接数量 |
auth_ip_white_list | Counter,由于IP白名单而允许的连接数量 |
auth_digest_match | Counter,由于证书匹配而允许的连接数量 |
auth_digest_no_match | Counter,由于证书不匹配而拒绝的连接数量 |
total_principals | Gauge,总计加载的Principal数量 |
此过滤器在命名空间config.ext_authz.下暴露以下指标:
指标 | 说明 |
total | Counter,此过滤器的响应总数 |
error | Counter,联系授权服务失败的次数 |
denied | Counter,无权访问的次数 |
failure_mode_allowed | Counter,验证不通过,但是由于failure_mode_allow设置为true而允许通过的请求数量 |
ok | Counter,授权服务允许连接通过的次数 |
cx_closed | Counter,被关闭的连接数 |
active | Gauge,当前活动的,正在被授权服务处理的请求 |
此过滤器在ratelimit.<stat_prefix>下暴露以下指标:
指标 | 说明 |
total | Gauge,向限速服务发送的总请求数 |
error | Gauge,访问限速服务出错的次数 |
over_limit | Gauge,限速服务提示超过限制的次数 |
ok | Gauge,限速服务提示没有超过限制的次数/home/alex/CPP/projects/clion/envoy |
cx_closed | Gauge,由于限速服务提示超限,而关闭的连接数量 |
active | Gauge,正在被限速服务处理的请求数量 |
failure_mode_allowed | Gauge,虽然超限,但是被允许的连接数 |
此过滤器在redis.<stat_prefix>下暴露如下指标:
指标 | 说明 |
downstream_cx_active | Gauge,活动连接数 |
downstream_cx_protocol_error | Counter,总的协议错误数量 |
downstream_cx_rx_bytes_buffered | Gauge,当前缓冲的接收字节数 |
downstream_cx_rx_bytes_total | Counter,总计接收字节数 |
downstream_cx_total | Counter,总计连接数 |
downstream_cx_tx_bytes_buffered | Gauge,当前缓冲的发送字节数 |
downstream_cx_tx_bytes_total | Counter,总计发送字节数 |
downstream_cx_drain_close | Counter,由于Draing导致关闭的连接数 |
downstream_rq_active | Gauge,活动的请求数 |
downstream_rq_total | Counter,总请求数 |
此过滤器在 redis.<stat_prefix>.splitter 下暴露了命令分流器(command splitter)相关的指标:
指标 | 说明 |
invalid_request | Counter,参数数量不正确的请求数 |
unsupported_command | Counter,命令分流器不能识别的命令数量 |
对于每个不同的Redis命令,此过滤器在 redis.<stat_prefix>.command.<command> 下暴露指标:
指标 | 说明 |
total | Counter,命令数量 |
success | Counter,成功数量 |
error | Counter,失败数量 |
此代理会在tcp.<stat_prefix>下产生下游、上游相关的统计指标:
指标 | 说明 |
downstream_cx_total | Counter,此过滤器处理的连接总数 |
downstream_cx_no_route | Counter,没有匹配路由、或者路由对应的集群不存在的连接数 |
downstream_cx_tx_bytes_total | Counter,写到下游的字节总数 |
downstream_cx_tx_bytes_buffered | Gauge,当前缓冲的准备发给下游集群的字节总数 |
downstream_cx_rx_bytes_total | Counter,从下游读取的字节总数 |
downstream_cx_rx_bytes_buffered | Gauge,从下游接收并正在缓冲中的字节总数 |
downstream_flow_control_paused_reading_total | Counter,流控制暂停从下游读取数据的次数 |
downstream_flow_control_resumed_reading_total | Counter,流控制恢复从下游读取数据的次数 |
idle_timeout | Counter,由于空闲而被关闭的连接数 |
upstream_flush_total | Counter,下游连接关闭后仍然向上游刷出数据的连接总数 |
upstream_flush_active | Gauge,下游连接关闭,正在向上游刷出数据的连接数 |
此过滤器在命名空间mysql.<stat_prefix>下暴露以下指标:
指标 | 说明 |
auth_switch_request | Counter,上游服务器要求客户端切换到其它身份验证模式的次数 |
decoder_errors | Counter,MySQL协议解码错误次数 |
login_attempts | Counter,登陆尝试次数 |
login_failures | Counter,登陆失败次数 |
protocol_errors | Counter,在会话中协议消息失序的次数 |
queries_parse_error | Counter,MySQL查询解析失败次数 |
queries_parsed | Counter,MySQL查询解析成功次数 |
sessions | Counter,从启动依赖创建的MySQL会话个数 |
upgraded_to_ssl | Counter,升级到SSL的会话/连接个数 |
此过滤器在命名空间<stat_prefix>.cors.* 下暴露以下指标:
指标 | 说明 |
origin_valid | Counter,有效源计数 |
origin_invalid | Counter,无效源计数 |
此过滤器在命名空间cluster.<route target cluster>.ext_authz.下暴露以下指标:
指标 | 说明 |
ok | Counter,从此过滤器返回的应答总数 |
error | Counter,连接外部授权服务失败的次数 |
denied | Counter,外部授权服务认为请求无权的次数 |
failure_mode_allowed | Counter,由于failure_mode_allow=true,尽管出错仍然允许通过的请求数 |
此过滤器在命名空间http.<stat_prefix>.fault. 下暴露以下指标:
指标 | 说明 |
delays_injected | Counter,总计注入延迟数量 |
aborts_injected | Counter,总计注入中断数量 |
<downstream-cluster>.delays_injected | Counter,某个特定下游集群总计注入延迟数量 |
<downstream-cluster>.aborts_injected | Counter,某个特定下游集群总计注入中断数量 |
此过滤器在命名空间 cluster.<route target cluster>.ratelimit 下暴露以下指标:
指标 | 说明 |
ok | Counter,未超限的请求数 |
error | Counter,调用限速服务错误的次数 |
over_limit | Counter,超限的请求数 |
failure_mode_allowed | Counter,请求限速服务失败,但是允许请求通过的次数 |
此过滤器在命名空间 http.<stat_prefix>下暴露以下指标,stat_prefix取决于所属的HTTP连接管理器:
指标 | 说明 |
no_route | Counter,由于没有路由而404的请求数 |
no_cluster | Counter,由于上游集群不存在而404的请求数 |
rq_redirect | Counter,导致重定向的请求数 |
rq_direct_response | Counter,导致直接应答的请求数 |
rq_total | Counter,总计被路由的请求数 |
虚拟集群的统计信息暴露在<virtual host name>.vcluster.<virtual cluster name>,包含以下指标:
指标 | 说明 |
upstream_rq_<*xx> | Counter,聚合状态码计数,例如5xx |
upstream_rq_<*> | Counter,具体状态码计数,例如500 |
upstream_rq_time | Histogram,请求耗时ms |
Envoy由一个JSON配置文件和一系列命令行选项一起驱动。命令行选项如下:
选项 | 说明 |
-c |
v2版本的JSON/YAML/Proto3格式的配置文件路径。如果此选项缺失,则必须提供--config-yaml 有效的扩展名包括.json, .yaml, .pb, .pb_text |
--config-yaml |
v2版本的YAML格式、JSON格式的Bootstrap配置 如果同时指定了--config-path,则当前选项的配置覆盖到--config-path指定的配置 |
--mode |
运行模式:
|
--admin-address-path | 管理地址/端口写入到什么文件中 |
--local-address-ip-version |
服务本地IP地址使用v4还是v6 |
--base-id |
共享内存区域的Base ID,Envoy在热重启时使用此区域 如果在一个节点上运行多个Envoy实例,则每个实例必须具有不同的Base ID |
--concurrency | 工作线程数量 |
-l | 日志级别 |
--component-log-level | 为每个组件定制日志级别 |
--log-path | 日志记录未知,默认stderr |
--log-format | 日志格式,默认[%Y-%m-%d %T.%e][%t][%l][%n] %v |
--service-cluster | 定义Envoy所在节点的服务集群的名称 |
--service-node | 定义Envoy所在节点的名称 |
--service-zone | 定义Envoy所在的区域,区域的定义取决于上下文,例如AWS的可用性区域 |
--file-flush-interval-msec | 文件(例如访问日志)刷出间隔ms,默认10秒 |
--drain-time-s |
热重启时,等待多久Envoy会Drain掉连接。默认600s 通常应该小于--parent-shutdown-time-s |
--parent-shutdown-time-s | 热重启时,等待多久关闭主进程 |
--max-obj-name-len | cluster/route_config/listener的名字最大长度 |
--disable-hot-restart | 禁用热重启 |
--allow-unknown-fields | 用于禁用protobuf未知字段校验 |
Envoy暴露了一个本地的管理接口,可以用于查询/修改Envoy的各个方面。端点列表:
端点 | 说明 | ||
GET / | 返回一个HTML页面,包含所有选项的链接 | ||
GET /help | 所有选项的文本表格 | ||
GET /certs | 列出加载的TLS证书 | ||
GET /clusters GET /clusters?format=json |
列出所有已经配置的集群管理器集群,包括每个集群的所有上游主机、每个主机的统计信息 | ||
GET /config_dump | 以JSON形式导出当前配置 | ||
GET /contention | 如果mutex争用跟踪被启用,导出Envoy mutex争用信息 | ||
POST /cpuprofiler | 启用或禁用CPU剖析,需要启用gperftools编译 | ||
POST /healthcheck/fail | 发起一个入站健康检查失败 | ||
POST /healthcheck/ok | 发起一个入站健康检查成功 | ||
POST /logging |
修改组件的日志级别
|
||
POST /memor | 显示内存、堆使用情况 | ||
POST /quitquitquit | 立即停止服务 | ||
POST /reset_counters | 重置所有计数器 | ||
GET /server_info | 获取服务器信息 | ||
GET /stats GET /stats?filter=regex GET /stats?format=json |
输出统计信息 | ||
GET /stats/prometheus | 以Prometheus v0.04格式输出统计信息,目前仅仅输出Counter、Gauge | ||
GET /runtime | 输出所有运行时设置 |
Envoy可以从文件系统或者一个/多个管理服务器发现各种动态资源。这些管理服务器提供的发现服务,以及发现服务的API,被称为xDS。
资源的通过订阅(subscription)的方式获得,订阅的初始化方式是 —— 指定需要监控的文件系统路径,初始化gRPC流,或者轮询REST-JSON URL。后两种方式需要发送DiscoveryRequest请求,资源则通过DiscoveryResponse返回。
本章主要讨论基于gRPC的流式订阅。
已经彻底废弃,基于REST-JSON的API。现在使用的是基于REST/gRPC的xDS API
不赞成使用,2020 EOL
推荐使用此版本。仍然是基于gRPC的REST。
对于每一个xDS API,你都可以指定一个ApiConfigSource:
1 2 3 4 5 6 7 8 9 |
{ "api_type": "...", // 管理服务器集群的名称 "cluster_names": [], "grpc_services": [], "refresh_delay": "{...}", "request_timeout": "{...}", "rate_limit_settings": "{...}" } |
这样,可以为每种xDS资源类型发起独立的(可以和不同服务器建立)双向gRPC流。
每种xDS API都操作某种类型的资源。xDS API和资源类型具有1:1对应关系:
- LDS: envoy.api.v2.Listener
- RDS: envoy.api.v2.RouteConfiguration
- CDS: envoy.api.v2.Cluster
- EDS: envoy.api.v2.ClusterLoadAssignment
- SDS: envoy.api.v2.Auth.Secret
Envoy发送的多种请求、管理服务器返回的多种响应中,都需要声明Type URL。这些xDS API的Type URL的形式均为:type.googleapis.com/资源类型,例如type.googleapis.com/envoy.api.v2.Listener。
每个gRPC流以来自Envoy的DiscoveryRequest请求开始,指定一系列需要订阅的资源、这些资源对应的Type URL、节点标识符、空白的version_info,示例:
1 2 3 4 5 6 7 |
version_info: node: { id: envoy } resource_names: - foo - bar type_url: type.googleapis.com/envoy.api.v2.ClusterLoadAssignment response_nonce: |
管理服务器可以立即应答,或者在请求的资源可用时进行应答,应答都封装在DiscoveryResponse中:
1 2 3 4 5 6 |
version_info: X resources: - foo ClusterLoadAssignment proto encoding - bar ClusterLoadAssignment proto encoding type_url: type.googleapis.com/envoy.api.v2.ClusterLoadAssignment nonce: A |
处理完DiscoveryResponse之后,Envoy会在gRPC流上发送新请求,提供最后一次成功应用的版本号以及管理服务器提供的nonce。在上面的例子中,如果DiscoveryResponse成功应用,则后续请求的version_info为X, response_nonce为A。
如果Envoy无法应用DiscoveryResponse,则会回复error_detail和上一个版本号给管理服务器。
每个gRPC流具有自己的版本号,不存在跨资源共享的版本号机制。当不使用ADS时,每种资源的版本不一样很正常。
仅仅DiscoveryResponse中的资源发生变化后,管理服务器才应该发送更新给Envoy。Envoy会在应用/拒绝DiscoveryResponse后,发送包含ACK/NACK的DiscoveryRequest给管理服务器。
在单个流中,后面的DiscoveryRequest代替先前的、相同资源类型的DiscoveryRequest。如果管理服务器接收到多个DiscoveryRequest,它可能只需要回复最后一个。
DiscoveryRequest中的resource_names字段是一个“提示”。
Cluster、Listener等资源类型的resource_name字段为空,管理服务器应该每次都提供完整的LDS/CDS资源列表。列表中不存在,但是Envoy中存在的资源会被删除。Listener/Cluster对应的EDS/RDS资源会自动被级联删除。
对于EDS/RDS,管理服务器不需要提供每个请求的资源,另外它也可以提供没有请求的资源。resource_names仅仅是个提示。管理服务器能够从DiscoveryRequest的node(节点标识)字段推断出哪些资源是需要的。
Envoy的xDS API是最终一致性的。在一次次DiscoveryResponse可能导致应用程序流量丢失。
考虑这样的一个场景,当前通过CDS/EDS仅仅获知了集群X的信息。一个引用X的RouteConfiguration,将X改为Y并更新,在Y集群的尚未通过CDS/EDS发现指前,流量都进入黑洞。
部分应用程序允许流量丢失,应用代码或者Sidecar中的重试可能隐藏这种流量丢失。另外一些应用则不能忍受流量丢失,这就要求管理服务器以一定的顺序推送更新。一般来说,避免流量丢失的推送时序如下:
- 如果存在CDS更新,则必须首先推送
- EDS更新必须在对应CDS更新之后推送
- LDS更新必须在对应的CDS/EDS更新之后推送
- 和新添加监听器有关的RDS必须在最后推送
如果没有新的集群/路由/监听器、或者可以容忍临时的流量丢失,则xDS更新可以独立的推送。
前面提到了最终一致性问题和解决流量丢失的手段。在分布式管理服务器场景下,保证更新的时序是很困难的。
ADS允许单个管理服务器通过单个gRPC流来推送所有的API更新。使用ADS,管理服务器可以小心编排推送时序,避免流量丢失。多个DiscoveryRequest/DiscoveryResponse序列可以基于Type URL进行gRPC流的多路复用,并且保证上节描述的推送时序
Incremental xDS是ADS/CDS/RDS的独立的xDS端点,允许:
- 增量式的更新被xDS客户端跟踪的资源列表。这允许Envoy按需/延迟的请求额外的资源。例如当一个针对位置集群的请求到达时,触发对此集群的更新
- 增强xDS可扩容性,避免单个集群信息变更就被迫推送100K的完整集群列表
Incremental xDS仅仅支持gRPC,增量xDS会话总是在单个gRPC双向流的上下文中发生,xDS服务器可以跟踪每个连接到它的xDS客户端的状态,以决定什么需要推送。
增量xDS使用nonce字段将 IncrementalDiscoveryResponse和IncrementalDiscoveryRequest的ACK/NACK进行配对。
受益匪浅,感谢博主提供此片博客,作为一个Java程序员在看Envoy的代码中,觉得 Envoy 的代码整体架构清晰,但是代码不知道为了性能优化还是如何,有大量的代码嵌套在不怎么能理解的地方,比如向下游转发的行为居然是放在 decodeHeader() 函数中,让人费解。
我想是因为某些行为在处理HTTP头时就可以决断下来,所有就立刻执行转发操作了吧