Envoy是一个大规模面向服务架构设计的7层代理和通信总线,它的信条是 —— 网络应该对应用程序透明,当出现问题时,应该很容易定位到源头在网络还是应用。
Envoy的高层特性包括:
此图来自https://jimmysong.io。
Envoy可以作为服务网格的数据平面。
Envoy提出的资源发现协议集 —— xDS(包括CDS、EDS、LDS、RDS,以及后续补充的密钥发现服务/SDS、聚合发现服务/ADS、健康发现服务/HDS、限速服务/RLS等),已经被多个服务网格项目支持,其中包括著名的Istio。这些项目主要实现服务网格的控制平面。
xDS有时候也用于代指实现xDS API的那些发现服务。Envoy通过订阅(subscription)方式获取资源的更新,具体有三种:
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过滤器分为三种:
Envoy包含了一个HTTP Router Filter,用于执行高级的路由任务,不管是处理边缘流量还是网格内部流量,都可以用到此过滤器。
Envoy具有作为正向代理的能力,网格的客户端需要将其HTTP代理设置为一个Envoy实例,以使用代理。
此过滤器实现的特性包括:
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>映射,用于存储任意类型的状态。
负责管理上游集群,可以配置任意数量的上游集群。此管理器支持两种方式获得集群:
上游集群从网络/HTTP过滤器栈中剥离出来,以便被不同的代理任务重用。集群管理器向过滤器暴露了API,允许它们:
过滤器根据需要来获取L3/L4连接或者新的HTTP流,集群管理器负责处理其它的复杂性,包括:
不管是通过静态配置,还是通过CDS加载,只有当集群经过以下处理后,才是warmed状态,才可以使用:
讨论集群预热(cluster warming)时,所谓集群可用,是指:
服务发现是Envoy确定集群成员的过程,支持的服务发现类型包括:
很多RPC系统的服务发现是一个完全一致性过程,它们使用一致性的领导选举算法,例如ZK、Etcd、Consul。大规模使用完全一致性会存在性能问题,因此Envoy使用最终一致性的方式。
可以为每个上游集群配置主动健康检查,对于EDS服务发现来说,主动健康检查是和服务发现过程一同进行的。其它类型的服务发现也可以联用主动健康检查。
Envoy目前支持三种健康检查:
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健康检查过滤器,此过滤器允许以下操作模式:
太多的穿透可能引发大量的健康检查流量。
对于HTTP流量来说,Envoy支持连接池。HTTP/1.1和HTTP/2连接池的行为有所不同:
如果针对上游主机的主动/被动健康检查失败,则此主机的连接池会被关闭;反之,如果上游主机恢复健康,则连接池会重新创建。
Envoy根据知晓的上游集群成员信息,自己决定应该把负载分发给哪个端点。
一个全局性的权威负责决定负载如何在主机之间分发。对于Envoy来说,则可以通过控制平面达成。控制平面可以调整优先级、权重、端点健康状况来满足复杂的负载均衡需求。
| 算法 | 说明 |
| 加权轮询 | Weighted round robin,轮询上游主机,但是主机可以设置权重 |
| 加权最少请求 | Weighted least request,使用 O(1) 算法选择两个随机健康主机,并选择主动请求较少的主机 |
| 环哈希 |
Ring hash,针对上游主机实现一致性哈希,此算法将所有主机映射到一个环上,这样添加/删除一个主机,仅仅1/N的请求受到影响 也叫做ketama hashing 目前不支持权重 |
| 磁悬浮 | Maglev,类似针对上游主机实现一致性哈希,但具有快得多的查表编译时间以及主机选择时间(当使用 256K 条目的大环时大约分别为 10 倍和 5 倍),缺点是不如环哈希稳定,主机移除时更多请求需要移动(以达到再平衡) |
| 随机 | Random,随机选择一个健康主机 |
异常检测属于被动健康检查,是被动的(观测业务流量,而非主动发起流量)检查上游集群,并发现其中的不正常主机,然后将其剔除出负载均衡池的过程。
异常检测通常和主动健康检查联合使用。
根据异常检测的类型,驱除行为要么定期触发,要么在连续的异常时间发生后触发。算法工作方式如下:
| 检测类型 | 说明 |
| 连续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请求,被断路后,路由过滤器会设置响应头x-envoy-overloaded。
分布式系统中,断路器对吞吐量的控制一般非常有效。但是某些场景下,可能需要引入全局的限速机制。典型的场景例如数据库,来自大量主机的请求被转发给少量的服务器,很难在所有下游主机上配置断路器来防止级联性失败(雪崩)。
Envoy集成了全局的gRPC限速服务,Lyft提供该服务的参考实现,此外任何实现了RPC/IDL协议的程序都可以作为服务实现。
限速服务的功能包括:
对于监听器,Envoy支持TLS Termination。向上游发起请求时,Envoy支持TLS Origination。Envoy的SSL支持由BoringSSL库提供。
Envoy的一个目标是使网格可理解(观察),为此它可以收集大量的统计信息,统计信息可以分为三类:
在v1版本的API中,Envoy仅仅支持Statd作为输出格式。在v2版本的API中,可以使用自定义,可拔插的统计信息接收器。
统计信息都使用规范化的字符串来标识,字符串的动态部分被抽取成为标签(Tag)。
Envoy支持Counter、Gauge、Histogram三种指标类型。
在大型分布式架构中,分布式追踪让调用过程能够被可视化的展示,在了解串并行执行、延迟分析时非常重要。
Envoy包含三个和分布式追踪相关的特性:
初始化追踪的方式有多种:
路由过滤器可以使用start_child_span为出口调用创建下一级Span。
Envoy提供了根据网格中服务之间通信来报告追踪信息的能力,但是要想把一个调用流中各个代理产生的追踪片断关联起来,服务必须在入口、出口请求之间传递一些上下文信息。
不管使用哪种Tracer,所有服务都必须传播x-request-id头,服务实现者可能需要在代码层次上保证这一点(技术上无法实现通用的自动传播x-request-id的机制)。
Tracer还会需要额外的上下文信息,确保Span之间的父子关系能够被理解。
可以在服务中直接调用LightStep(OpenTracing API)或Zipkin来抽取入站请求的上下文信息,并注入到出站请求中。使用这种方式,服务也可以创建额外的Span。
另外一种方式是由服务手动的传播追踪信息:
一个端到端的Trace包含1-N个Span。一个Span代表了一个逻辑的工作单元。工作单元包括开始时间,持续时间,以及一系列的元数据:
Envoy自动将Span发送给跟踪收集器(Tracing collector),Span根据一些公共信息(例如x-request-id)组合在一起,产生单个Trace。
TCP代理过滤器可以实现下游 - 上游的基本的1:1代理功能。
TCP代理过滤器受到上游集群全局资源管理器的连接限制的约束。
HTTP 连接管理器与 TCP 代理支持可扩展的访问记录,并拥有以下特性:
Envoy实现了网络层的MongoDB嗅探过滤器,特性包括:
Envoy可以作为Redis代理,在Redis实例之间进行命令分区(partitioning commands)。Envoy的目标是维持可用性、分区容忍性(partition tolerance),它实现一个尽最大努力的缓存,而不会尝试维护数据一致性或者Redis集群成员的一致性。这和Redis Cluster不同。
Envoy Redis的特性包括:
未来计划添加的特性包括:
Redis上游集群应该以哈希环负载均衡的方式配置(ring hash load balancing)。
此代理支持Redis流水线,但是不支持MULTI。此外,不是所有Redis命令被支持,仅仅那些可以被可靠的哈希到一个服务器的操作被支持。
一个特殊命令是PING,Envoy会直接答复PONG,为PING指定参数是不允许的。
gRPC是Google开源的RPC框架,它使用Protocol Buffer作为串行化格式。在传输层,它使用HTTP/2进行请求/应答的多路复用。
Envoy对gRPC在传输层、应用层都做了支持:
Envoy支持两种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的所有方面。只有非常少见的配置更改(管理员、追踪驱动程序等)或二进制更新时才需要热重启。
任何监听器启动监听并接收新连接前,会发生如下事件序列:
关闭(Draining)发生在以下时机:
每个监听器都具有drain_type配置,决定何时发生Draining:
HTTP过滤器支持使用Lua脚本。
这是一个可扩展组件,用于保护Envoy本身(和断路器不同),防止过载导致各种系统资源(内存、CPU、文件描述符)不足。和断路器用于保护上游服务的目的不同,过载管理器是为了保护Envoy代理所在机器本身。
| 名词 | 说明 |
| 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实现的边缘代理。并在边缘代理实现:
尝试Envoy功能最轻松的途径是使用官方的Docker镜像:
docker pull envoyproxy/envoy:latest
此镜像包含了一个默认的配置,将入站请求路由给某个外部网站:
# 管理端口配置
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:
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端口。可以在容器中
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++标准:
为了保持对已经链接到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参数传递:
修改编译模式的例子:
bazel build -c opt //source/exe:envoy-static
下面的可选特性可通过Bazel命令行参数禁用:
下面的可选特性可通过Bazel命令行参数启用:
Envoy使用模块化的构建风格,不需要的扩展可以被移除。所有可以被禁用的扩展列在文件中extensions_build_config.bzl中。
使用下面的步骤来定制你需要的扩展集:
要想让你的本地构建使用修改后的extensions_build_config.bzl,可以使用下面两种方法之一:
workspace(name = "envoy")
local_repository(
name = "envoy_build_config",
# Relative paths are also supported.
path = "/somewhere/on/filesystem/envoy_build_config",
)
Envoy把构建环境固化在Docker镜像中,可以执行下面的命令在容器中构建Envoy:
./ci/run_envoy_docker.sh './ci/do_ci.sh bazel.dev'
bazel --bazelrc=/dev/null build -c opt //source/exe:envoy-static.stripped
为了便于开发者,Envoy提供了一个WORKSPACE、构建最近版本的依赖的规则集,这仅仅适用于开发和测试,因为依赖可能不是最新的而包含安全缺陷。
构建步骤如下:
echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main" | sudo tee /etc/apt/sources.list.d/llvm.list sudo 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/test sudo apt-get update sudo apt-get install gcc-5 g++-5 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 1 --slave /usr/bin/g++ g++ /usr/bin/g++-5
go get github.com/golang/protobuf/proto go get github.com/bazelbuild/buildtools/buildifier
bazel build --copt "-DENVOY_IGNORE_GLIBCXX_USE_CXX11_ABI_ERROR=1" //source/exe:envoy-static
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:
bazel-cmakelists --targets //source/exe:envoy-static
Istio为Envoy添加了mixer、authn等多个过滤器,这些过滤器代码是独立在Istio Proxy项目中维护的。
Istio Proxy项目以Bazel外部依赖的形式引入Envoy的源码
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二进制文件:
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/...
运行下面的命令,执行所有测试:
bazel test //test/...
如果报错ERROR: circular symlinks detected,说明存在符号链接错误。存在错误的路径会打印在控制台,手工修改一下即可。
运行下面的命令,仅仅执行的单元测试test/common/http:async_client_impl_test:
bazel test //test/common/http:async_client_impl_test
要查看更多的测试日志输出,使用下面的选项:
bazel test --test_output=streamed
要为测试用例传递额外的命令行参数,使用下面的选项:
# 传参 bazel test --test_output=streamed //test/common/http:async_client_impl_test --test_arg="-l trace"
默认情况下,用例同时针对IPv4和IPv6测试,在仅仅支持IPv4或IPv6的环境下,可以使用下面的环境变量:
# 禁用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或者修改模式:
# 禁用 bazel test //test/... --test_env=HEAPCHECK= # 使用minimal模式 bazel test //test/... --test_env=HEAPCHECK=minimal
内存泄漏的偏移量需要通过addr2line来解释,可以利用--config=clang-asan选项自动化的进行地址到代码行的转换。
默认情况下,已经通过的测试结果会被缓存,不再测试。如果要强制重新测试,使用选项:
bazel test //test/common/http:async_client_impl_test --cache_test_results=no
默认情况下,所有测试在沙盒中运行,不能访问本地文件系统。 使用下面的选项可以禁用沙盒:
--strategy=TestRunner=standalone
某些测试要求特殊权限,例如CAP_NET_ADMIN。在物理机上运行测试时,你可以用sudo提权。
如果在容器环境下测试且需要特权,可以利用tools/bazel-test-docker.sh脚本,此脚本可以在本地或者远程Docker引擎上以--privileged运行测试。此脚本的调用格式如下:
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
此脚本读取两个环境变量:
你可以启用GCC的ASAN(address sanitizer,地址消毒剂,检测内存访问错误)、UBSAN(undefined behavior sanitizer,未定义行为消毒剂),来构建和测试Envoy:
bazel test -c dbg --config=asan //test/...
包含行号的ASAN错误栈会打印出来,如果看不见符号,尝试将环境变量ASAN_SYMBOLIZER_PATH设置到 llvm-symbolizer所在位置,后者将 llvm-symbolizer放入$PATH。
如果使用clang-5.0或更高版本构建,可以启用额外的sanitizer:
bazel test -c dbg --config=clang-asan //test/... bazel test -c dbg --config=clang-tsan //test/...
Envoy能够根据需要生成堆栈追踪(backtraces),也可以由断言或其它失败(例如段错误)触发栈追踪。
在不支持absl::Symbolization的系统上,打印到日志或者标准错误的栈追踪包含地址而非符号。脚本tools/stack_decode.py可以来将这些地址转换为符号+行号。用法示例:
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测试:
# Bazel的-c dbg选项让二进制文件包含符号信息 tools/bazel-test-gdb //test/common/http:async_client_impl_test -c dbg
下面的命令示意了如何测试符号表正常:
cd ~/CPP/projects/clion/envoy gdbtui bazel-bin/source/exe/envoy-static
如果调试符号表可用,则可以看到envoy-static的main.cc的源码。
你可能需要设置souce path,否则GDB找不到源码而进行反汇编:
# 这些外部依赖的源码路径,可能总是需要添加 (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支持:
旧版本的Envoy v1 API已经废弃,但是其功能尚未完全迁移到v2中,有些情况下,需要使用deprecated_v1字段使用v1 API。
Envoy的配置API文档比较零散,本节使用从一个Istio proxy中dump出的Envoy完整配置来总览的了解Envoy配置规格:
kubectl exec -it ubuntu -c istio-proxy -- curl http://127.0.0.1:15000/config_dump > /tmp/config
转换为yaml格式后,内容如下:
# 配置主要分为以下几个段落
# 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
static_resources:
listeners:
name: listener_0
address:
socket_address:
address: 127.0.0.1
port_value: 15001
filter_chains:
- filters:
- name: echo
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请求):
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传入:
./envoy -c <path to config>.{json,yaml,pb,pb_text}
配置的根是一个Bootstrap消息,该消息的一个核心特点是将静态资源(static_resources)和动态资源分离(dynamic_resources)
第一个例子就是最简单的全静态配置。
下面的示例,在clusters配置中使用了EDS,以动态的发现上游集群包含哪些端点:
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:
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发现:
# 动态资源
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请求:
version_info: "0" resources: - "@type": type.googleapis.com/envoy.api.v2.Listener name: listener_0 address: ... filter_chains: ...
这样应答一个RDS请求:
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请求:
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请求:
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调用此端点: cds_config:
api_config_source:
api_type: GRPC
grpc_services:
envoy_grpc:
cluster_name: some_xds_cluster
|
|
POST /envoy.api.v2.EndpointDiscoveryService/StreamEndpoints ,服务定义参考eds.proto 如果在Envoy的Cluster配置的eds_cluster_config段包含以下形式的配置,Envoy调用此端点: eds_config:
api_config_source:
api_type: GRPC
grpc_services:
envoy_grpc:
cluster_name: some_xds_cluster
|
|
POST /envoy.api.v2.ListenerDiscoveryService/StreamListeners,服务定义参考lds.proto 如果在Envoy的自举配置的dynamic_resources段包含以下形式的配置,Envoy调用此端点: lds_config:
api_config_source:
api_type: GRPC
grpc_services:
envoy_grpc:
cluster_name: some_xds_cluster
|
|
POST /envoy.api.v2.RouteDiscoveryService/StreamRoutes,服务定义参考rds.proto 如果在Envoy的HttpConnectionManager配置的rds段包含以下形式的配置,Envoy调用此端点: route_config_name: some_route_name
config_source:
api_config_source:
api_type: GRPC
grpc_services:
envoy_grpc:
cluster_name: some_xds_cluster
|
REST端点包括:
Envoy使用了最终一致性模型,这意味着存在配置不一致的时间窗口。ADS能够将Envoy节点绑定到单个管理服务器,并且串行化API更新推送。
监听器包含的属性如下:
# 监听器唯一的名称,如果不指定自动生成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
包含的属性如下:
# 监听器名称
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:
假设mark=1986,则配合下面的规则即可正确的路由回来:
# 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配置示例:
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.listener.proxy_protocol,用于支持HAProxy代理协议。
启用此过滤器后,连接被假设来自一个代理,此代理将原始的源地址(IP+PORT)存放在一个连接字符串中,Envoy会抽取此字符串,并将其设置为远程地址。
此协议自动识别HAProxy代理协议的v1/v2版本。
过滤器名称envoy.listener.tls_inspector,用于探测连接是基于TLS还是明文的。如果是基于TLS,则此过滤器会:
利用这些信息,可以FilterChainMatch的server_names、application_protocols来选择一个FilterChain
这里讨论除了HTTP连接管理器之外的网络过滤器。
过滤器名称envoy.client_ssl_auth,用于执行TLS身份验证。
此过滤器每个刷新间隔都调用REST API:GET /v1/certs/list/approved,以获取被许可的证书/主体列表。
包含的属性如下:
# 运行身份验证服务的集群名称
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提供。
建议把此过滤器配置在过滤器链的头部,这样非法请求不会执行其它过滤器。
包含的属性如下:
# 统计指标前缀
stat_prefix: "..."
# 外部的gRPC授权服务配置,没人超时200ms
grpc_service: "{...}"
# 如果外部授权服务不能响应,是否允许请求授权通过,没人false
failure_mode_allow: "..."
配置示例:
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协议,支持故障注入。
包含的属性如下:
# 统计指标前缀
stat_prefix: "..."
# 访问日志路径
access_log: "..."
# 在代理一个Mongo操作之前,注入一个固定的延迟
delay: "{...}"
# 是否产生动态元数据,没人false
emit_dynamic_metadata: "..."
过滤器名称envoy.ratelimit,用于支持全局性限速。
包含的属性如下:
# 统计指标前缀
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服务集群。
此过滤器包含的属性如下:
stat_prefix: "..."
# 上游集群名称
cluster: "..."
# 设置
settings:
op_timeout: "{...}"
过滤器名称envoy.tcp_proxy。
此代理使用的上游集群可以被其它网络过滤器动态的设置。具体做法时为每个连接设置在键envoy.tcp_proxy.cluster下设置一个状态对象。
此过滤器包含的属性如下:
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 |
配置示例:
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过滤器联用,实现对表的访问控制:
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)操作,可以明确管理到应用程序的调用,阻止非法访问。
此过滤器支持配置以下两者之一:
列表基于连接属性(IP、端口、SSL subject)来设置访问策略。
访问策略可以工作在enforcement或shadown模式,后者不会实际影响用户,用于在上线前测试策略会产生的影响。
此过滤器包含的属性如下:
# 全局应用的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路由:
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联用,示例:
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联用:
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进行动态代理,下面是一个完整配置:
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请求进行路由匹配的流程如下:
Envoy支持在虚拟主机的多个上游集群之间划分流量,具体有两种应用场景:
路由配置中的runtime对象决定选择虚拟主机的某个特定集群的概率。通过runtime_fraction配置,某个虚拟主机的流量可以逐步的转移,下面是个例子:
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
完成流量转移的步骤如下:
示例:
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% |
请求处理开始时间,示例: route:
cluster: www
request_headers_to_add:
- header:
key: "x-request-start"
# 当前时间,以秒表示,精确到ms
value: "%START_TIME(%s.%3f)%"
append: true
|
由于安全原因,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。此过滤器用于停止过滤器迭代,然后等待完整的请求到达。用途是:
此过滤器包含的属性:
# 此过滤器最大能缓冲的请求大小,超过请求后HTTP连接管理器返回413错误
max_request_bytes: "{...}"
在每个路由级别,buffer过滤器可以被覆盖配置或者禁用:
# 是否禁用 disabled: true # 覆盖的配置 buffer: max_request_bytes: ...
过滤器名称envoy.cors。此过滤器基于路由/虚拟主机的配置来处理跨站资源共享(Cross-Origin Resource Sharing)请求。
此过滤器包含的属性:
# 允许的源
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设置:
过滤器名称envoy.ext_authz。此过滤器调用外部的gRPC/HTTP服务,以确认请求是否有权访问。如果验证失败会返回403/Forbidden应答。
从授权服务传递额外的元数据到上游、下游也是支持的,反方向也是。发送给授权服务的请求内容由CheckRequest规定。
此过滤器包含的属性:
# 外部gRPC授权服务配置
grpc_service: "{...}"
# 外部HTTP授权服务配置
http_service: "{...}"
# 如果授权服务不响应,是否允许请求通过
failure_mode_allow: "..."
基于gRPC授权服务的配置示例:
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授权服务的配置示例:
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 }
在路由级别定制配置:
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状态码),从而模拟各种错误场景。这些场景包括:服务过载、高网络延迟、网络分区。
故障注入可以仅仅应用于特定的请求集,这些请求集以请求头、上游服集群等信息识别。
此过滤器的属性包括:
# 注入延迟
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服务器。其工作方式如下:
过滤器名称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。此过滤器包含一系列规则,规则对请求/响应进行匹配。每个规则包含一个头,当请求/响应包含或不包含此头时,规则匹配,并添加动态元数据。
动态元数据可以用来决定如何负载均衡,或者被日志模块使用。最典型的用法是动态的匹配请求和负载均衡子集:
# 过滤器配置
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++过滤器。
注意:
目前支持的特性:
LuaJIT作为Lua的运行时,支持的版本是5.1,以及一些5.2特性。
示例:
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脚本,但是不会进行缓冲操作。示例: for chunk in request_handle:bodyChunks() do request_handle:log(0, chunk:length()) end |
| trailers() | 返回报文尾,如果没有可能返回nil。发送到下一个过滤器之间你可以修改trailers |
| log*() |
记录Envoy应用程序日志: handle:logTrace(message) handle:logDebug(message) handle:logInfo(message) handle:logWarn(message) handle:logErr(message) handle:logCritical(message) |
| httpCall() |
向上游主机发起HTTP调用: headers, body = handle:httpCall(cluster, headers, body, timeout) Envoy会yield脚本,知道请求完成或者出错。cluster是已经配置好的集群名称,超时单位ms |
| respond() |
不再继续过滤器迭代,立即进行应答。仅仅在请求流中可以调用: handle:respond(headers, body) |
| metadata() |
返回当前路由条目元数据(route entry metadata),这些元数据必须声明在命名空间envoy.lua下面,例如: metadata:
filter_metadata:
envoy.lua:
foo: bar
baz:
- bad
- baz
|
| streamInfo() | 返回和当前流有关的信息 |
| connection() | 返回当前请求使用的底层连接对象 |
头对象的API:
| 方法 | 说明 |
| add() | 添加一个头 |
| get() | 获取一个头 |
| __pairs() |
用于迭代所有头: for key, value in pairs(headers) do end |
| 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() |
获取动态元数据: dynamicMetadata:get(filterName) dynamicMetadata:get(filterName)[key] |
| set() | 设置动态元数据:dynamicMetadata:set(filterName, key, value) |
| __pairs() | 迭代所有元数据 |
API示例:
-- 添加头
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应答。
此过滤器包含的属性:
# 调用限速服务时使用的限速域
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代理场景下。此过滤器的职责包括:
此过滤器包含的属性:
# 是否生成动态集群统计信息,默认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 | 如果请求由于维护模式、上游断路而失败,在给下游的应答中设置此头 |
包含的属性:
# 本地集群(拥有运行此配置的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 |
修改组件的日志级别 # 修改所有级别 curl -X POST localhost:15000/logging?level=trace # 修改特定组件级别 curl -X POST localhost:15000/logging?main=trace |
| 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:
{
"api_type": "...",
// 管理服务器集群的名称
"cluster_names": [],
"grpc_services": [],
"refresh_delay": "{...}",
"request_timeout": "{...}",
"rate_limit_settings": "{...}"
}
这样,可以为每种xDS资源类型发起独立的(可以和不同服务器建立)双向gRPC流。
每种xDS API都操作某种类型的资源。xDS API和资源类型具有1:1对应关系:
Envoy发送的多种请求、管理服务器返回的多种响应中,都需要声明Type URL。这些xDS API的Type URL的形式均为:type.googleapis.com/资源类型,例如type.googleapis.com/envoy.api.v2.Listener。
每个gRPC流以来自Envoy的DiscoveryRequest请求开始,指定一系列需要订阅的资源、这些资源对应的Type URL、节点标识符、空白的version_info,示例:
version_info:
node: { id: envoy }
resource_names:
- foo
- bar
type_url: type.googleapis.com/envoy.api.v2.ClusterLoadAssignment
response_nonce:
管理服务器可以立即应答,或者在请求的资源可用时进行应答,应答都封装在DiscoveryResponse中:
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中的重试可能隐藏这种流量丢失。另外一些应用则不能忍受流量丢失,这就要求管理服务器以一定的顺序推送更新。一般来说,避免流量丢失的推送时序如下:
如果没有新的集群/路由/监听器、或者可以容忍临时的流量丢失,则xDS更新可以独立的推送。
前面提到了最终一致性问题和解决流量丢失的手段。在分布式管理服务器场景下,保证更新的时序是很困难的。
ADS允许单个管理服务器通过单个gRPC流来推送所有的API更新。使用ADS,管理服务器可以小心编排推送时序,避免流量丢失。多个DiscoveryRequest/DiscoveryResponse序列可以基于Type URL进行gRPC流的多路复用,并且保证上节描述的推送时序
Incremental xDS是ADS/CDS/RDS的独立的xDS端点,允许:
Incremental xDS仅仅支持gRPC,增量xDS会话总是在单个gRPC双向流的上下文中发生,xDS服务器可以跟踪每个连接到它的xDS客户端的状态,以决定什么需要推送。
增量xDS使用nonce字段将 IncrementalDiscoveryResponse和IncrementalDiscoveryRequest的ACK/NACK进行配对。
受益匪浅,感谢博主提供此片博客,作为一个Java程序员在看Envoy的代码中,觉得 Envoy 的代码整体架构清晰,但是代码不知道为了性能优化还是如何,有大量的代码嵌套在不怎么能理解的地方,比如向下游转发的行为居然是放在 decodeHeader() 函数中,让人费解。
我想是因为某些行为在处理HTTP头时就可以决断下来,所有就立刻执行转发操作了吧