该系列学习笔记是作者为记录云原生基础架构学习过程而写。若想要详细学习 envoy,大家可以去 tetrate 官网和官方文档中学习。
如果没有任何接触云原生的经验,可以在学习相关概念前,去了解以下概念:
- kubernetes(这个是云原生的基础)
- 微服务架构是什么(微服务,RPC,服务发现等等)
- 服务网格(Service Mesh)是什么
0. 什么是 Envoy?
如果是云原生相关的 devops 工程师应该多多少少都会了解 envoy 这个东西。
在行业大量使用 微服务架构 和 云原生解决方案 的今天,一切向"云"靠拢,那么对于云上的流量治理 与网络调试就是一件复杂但必要的事情。
各个微服务可能使用不一致的流量跟踪和记录机制,对于开发人员来说,这方面调试的复杂性大大增加。对于这种类似多对多映射的状况,我们就很难确定问题发生在哪里,以及如何解决。如果你是一个业务开发,而调试网络问题不是你所擅长的话,那就更是如此。
将网络问题从应用程序堆栈中抽离出来,由另一个组件来处理网络部分,让调试网络问题变得更容易。这就是 Envoy 所做的事情。
也就是说,你写的微服务不用再关注一系列网络与流量问题相关的程序逻辑(如接口速率限制,鉴权验证,超时重试等等)这些全部抽离出来,在envoy中配置好帮你完成。同时,envoy 会做好流量跟踪记录相关的工作,在多个envoy联合工作时,就可以通过一个统一的控制面去收集这些流量信息,治理效果就很显著了。
1. Envoy 的两种模式
envoy 是 云原生时代 代理服务器应用的一种,和nginx类似,但它比 nginx 这种传统代理服务器更胜一筹。
它有两种部署方式,一种是很符合它本质工作的部署方式:边缘代理,这种情况下,envoy部署用于构建 API 网关,和正常网关的功能差不多。
另一种部署方式就是:边车部署(sidecar) ,这种情况下就是前文我们说的,将网络层面的工作从应用程序中"抽离"出来,单独运行。sidecar方式部署时,envoy是和应用服务一起部署的。Envoy 和应用程序形成一个原子实体,但仍然是独立的进程。应用程序处理业务逻辑,而 Envoy 则处理网络问题。(最常见的例子在 k8s 里面,就是一个pod里面放两个容器,一个跑业务代码,另一个就是envoy,在pod内部业务代码容器的流量都会先经过envoy容器,再由envoy传到pod外)
sidecar部署的方式用来组织当前云原生中的服务治理大杀器------服务网格。但当前不会提及,后面学习也是先学习边缘代理模式。
2. Envoy的高级特性
2.1 进程外架构
这里要说的是 envoy 的sidecar部署方式的好处。tetrate 学习资料中对这部分介绍写得比较清晰,我放一下原文(有些模糊的地方我会做注释,用斜体标出):
Envoy 是一个独立的进程,旨在与每个应用程序一起运行 ------ 也就是我们前面提到的 Sidecar 部署模式。集中配置的 Envoy 的集合形成了一个透明的服务网格。
路由和其他网络功能的责任被推给了 Envoy。应用程序**(进行DNS服务发现时)** 向一个虚拟地址(localhost)而不是真实地址(如公共 IP 地址或主机名)发送请求,不知道网络拓扑结构。应用程序不再承担路由的责任,因为该任务被委托给一个外部进程。(应用程序的流量会先到达envoy监听的端口。原理是envoy会在 L4 层修改 iptables 规则,进行流量劫持,将出站和入站流量重定向到 Envoy 监听的端口)
与其让应用程序管理其网络配置,不如在 Envoy 层面上独立于应用程序管理网络配置。在一个组织中,这可以使应用程序开发人员解放出来,专注于应用程序的业务逻辑。
Envoy 适用于任何编程语言。你可以用 Go、Java、C++ 或其他任何语言编写你的应用程序,而 Envoy 可以在它们之间架起桥梁。Envoy 的行为是相同的,无论应用程序的编程语言或它们运行的操作系统是什么。
Envoy 还可以在整个基础设施中透明地进行部署和升级。这与为每个单独的应用程序部署库升级相比,后者可能是非常痛苦和耗时的。
进程外架构是有益的,因为它使我们在不同的编程语言 / 应用堆栈中保持一致,我们可以免费获得独立的应用生命周期和所有的 Envoy 网络功能,而不必在每个应用中单独解决这些问题。
"集中配置的 Envoy 的集合形成了一个透明的服务网格"。服务网格的核心目标是解决微服务架构中服务间通信的复杂性,而多语言环境下的互通问题正是其重要应用场景之一。envoy很好地契合了这一任务。
2.2 L3/L4 过滤器结构
Envoy 是一个 L3/L4 网络代理,根据 IP 地址和 TCP 或 UDP 端口进行决策。它具有一个可插拔的过滤器链(就像linux上的管道),可以编写你的过滤器来执行不同的 TCP/UDP 任务。
Envoy 可以通过堆叠所需的过滤器来构建逻辑和行为,形成一个过滤器链。当前已有很多做好了的过滤器,支持诸如原始 TCP 代理、UDP 代理、HTTP 代理、TLS 客户端认证等任务。Envoy 也是可扩展的,我们可以编写我们的过滤器。(特别是当下结合 wasm 技术)
2.3 L7 过滤器结构
Envoy 支持一个额外的 HTTP L7 过滤器层。我们可以在 **HTTP 连接管理子系统(HCM)**中插入 HTTP 过滤器,执行不同的任务,如 cache、速率限制、路由 / 转发等等。
2.4 HTTP 相关支持
HTTP2 支持
Envoy 同时支持 HTTP/1.1 和 HTTP/2,并且可以作为一个透明的 HTTP/1.1 到 HTTP/2 的双向代理进行操作。这意味着任何 HTTP/1.1 和 HTTP/2 客户端和目标服务器的组合都可以被桥接起来。即使你的传统应用没有通过 HTTP/2 进行通信,如果你把它们部署在 Envoy 代理旁边,它们最终也会通过 HTTP/2 进行通信。
推荐在所有的服务间配置的 Envoy 使用 HTTP/2,以创建一个持久连接的网格,请求和响应可以在上面复用。
HTTP 路由
当以 HTTP 模式操作并使用 REST 时,Envoy 支持路由子系统HCM,能够根据 路径、权限、内容类型 和 运行时间值 等规则来路由和重定向请求。
在将 Envoy 作为构建 API 网关的前台 / 边缘代理时,这一功能非常有用,在构建服务网格(sidecar 部署模式)时,也可以利用这一功能。
2.5 gRPC 支持
Envoy 支持作为 gRPC 请求和响应的路由和负载均衡底层所需的所有 HTTP/2 功能。
gRPC 是一个开源的远程过程调用(RPC)系统,它使用 HTTP/2 进行传输,并将协议缓冲区作为接口描述语言(IDL),它提供的功能包括认证、双向流和流量控制、阻塞 / 非阻塞绑定,以及取消和超时。
2.6 服务发现和动态配置
我们可以使用静态配置文件来配置 Envoy,这些配置文件描述了服务间通信方式,后面我们会有相关学习。
对于静态配置 Envoy 不现实的高级场景,Envoy 支持动态配置,在运行时自动重新加载配置。
一组名为 xDS 的发现服务可以用来通过网络动态配置 Envoy,并为 Envoy 提供关于主机、集群 HTTP 路由、监听套接字和加密信息。
但这个是sidecar部署以及服务网格相关的内容了,简单来说,就是有一个控制平面 动态收集当前微服务集群中的信息,然后将这些信息转换成各种各样的 config.yaml ,再统一下发到各个 sidecar envoy 中。
2.7 健康检查
负载均衡器(LB)有一个特点,那就是只将流量路由到健康和可用的上游服务。
Envoy 支持健康检查子系统(Health Checking),对上游服务集群进行主动健康检查。健康检查包括很多种类型,有HTTP,TCP,gRPC等,采用类似于定期发送心跳包的方式。
(但需要后端实现对应心跳包响应逻辑,像HTTP主动心跳检查,后端应该实现一个 /health 的接口来响应200状态。)
(但如果 Envoy 完全依赖 服务网格 EDS机制(通过监听 Kubernetes 的 Endpoints 状态),并且 Kubernetes 的 readinessProbe 已能准确反映应用的健康状态,那么 Envoy 基本无需额外配置主动健康检查。后面会继续学习。)
然后,Envoy 使用 服务发现 和 健康检查信息 的组合来确定健康的负载均衡目标。
Envoy 还可以通过 异常点检测子系统(Outlier Detection) 支持被动健康检查.
有一个简单的总结表,暂时看不懂也没关系:
场景 | 是否需要 Envoy 主动检查 | 理由 |
---|---|---|
纯 Kubernetes + EDS | 否 | Kubernetes 探针已足够,Envoy 依赖 EDS 状态路由 |
需要深度健康检查 | 是 | 叠加 Envoy 检查以补充 Kubernetes 探针的不足 |
服务网格(如 Istio) | 否 | 依赖 Kubernetes 探针 + 被动异常检测,控制平面保障 EDS 数据一致性 |
混合环境(K8s + 非 K8s 服务) | 部分需要 | 非 Kubernetes 服务需通过 Envoy 主动检查 |
2.8 高级负载均衡
Envoy 支持自动重试、断路、全局速率限制(使用外部速率限制服务)、shadow请求(或流量镜像)、异常点检测和请求对冲。
2.10 TLS 终止
应用程序和代理的解耦使网格部署模型中所有服务之间的 TLS 终止(双向 TLS)成为可能。
Envoy 可以作为代理,负责处理 TLS 加密连接的建立、解密和验证,而无需后端应用程序直接参与 TLS 握手或加密通信。这样业务代码只需要处理明文流量就好,也无需业务开发在应用中管理证书和密钥。
在服务网格(如 Istio)中,Envoy 作为 Sidecar 代理,集中管理 TLS 加密和身份认证。
2.11 可观测性
为了便于观察,Envoy 会生成日志、指标和追踪。Envoy 目前支持 statsd(和兼容的提供者)作为所有子系统的统计。得益于可扩展性,我们也可以在需要时插入不同的统计提供商。
在服务网格如istio中,envoy收集的信息通过经典的 Prometheus + Grafana 的组合进行可视化。
2.12 HTTP/3 (Alpha)
Envoy 1.19.0 支持 HTTP/3 的上行和下行,并在 HTTP/1.1、HTTP/2 和 HTTP/3 之间进行双向转义。
3. Envoy 架构图
下面给出大致的 envoy 架构(偏向 sidecar 部署,其中 控制平面 是服务网格的东西)
具体对过滤器结构的介绍见下一章的【请求流作用模块】。
flowchart TB subgraph "Envoy Proxy" direction TB admin["Admin Interface"] subgraph "数据平面(Data Plane)" listeners["Listeners"] subgraph "过滤器结构" fc["过滤器链(Filter Chain)"] nf["Network Filters"] hcm["HTTP Connection Manager"] hf["HTTP Filters"] router["Router Filter"] end clusters["Clusters"] endpoints["Endpoints"] lb["Load Balancers"] hc["Health Checkers"] end subgraph "可观测性(Observability)" stats["统计(Statistics)"] logging["日志(Logging)"] tracing["追踪(Tracing)"] end end subgraph "控制平面(Control Plane)" xds["xDS API (Dynamic Configuration)"] end client[("客户端(Client)")] --> listeners listeners --> fc fc --> nf nf --> hcm hcm --> hf hf --> router router --> clusters clusters --> lb --> endpoints clusters --> hc --> endpoints endpoints --> backend[("后端服务(Backend)")] xds <-.-> admin xds <-.-> listeners xds <-.-> clusters xds <-.-> endpoints nf <-.-> stats & logging & tracing hf <-.-> stats & logging & tracing clusters <-.-> stats & logging & tracing
4. Envoy 的构建模块
在tetrate官网上, Envoy 的基本构建模块包括四部分:
graph TD; A[Listeners] --> B[Routes] B --> C[Clusters] C --> D[Endpoints]
但这个构建模块太过抽象,不太好理解,我将更加具体的三层四区抽象放在下面:
flowchart TB client(("客户端")) --> L subgraph "1. 监听与接收层" L["监听器 (Listener)"] LF["监听器过滤器"] FC["过滤器链 (Filter Chain)"] NF["网络过滤器 (Network Filters)"] end subgraph "2. 处理与路由层" HCM["HTTP 连接管理器(HCM)"] HF["HTTP 过滤器链"] RF["路由过滤器 (Router)"] end subgraph "3. 上游服务层" CL["集群 (Cluster)"] LB["负载均衡器"] EP["端点 (Endpoint)"] end subgraph "4. 控制与观测区" XDS["动态配置 (xDS API)"] OBS["可观测性组件"] end L --> LF --> FC --> NF --> | HCM 是一种 NetworkFilter | HCM HCM --> HF --> |RouterFilter 是一种 HTTPFilter, 也是最后一个| RF --> CL CL --> LB --> EP --> backend(("后端服务")) XDS -.-> L & HCM & CL & EP OBS -.-> NF & HF & CL & LB
以一个典型的 HTTP 请求为例:
- 客户端发起 HTTP 请求到 Envoy 监听的端口
- 监听与接收层:监听器接受连接并选择过滤器链;网络过滤器识别 HTTP 协议
- 处理与路由层:HTTP连接管理器(HCM)解析请求,HTTP过滤器链处理请求(认证、限流等),路由过滤器确定目标集群
- 上游服务层:集群中的负载均衡器选择一个健康的端点,请求被转发到该端点
- 后端服务处理请求并返回响应,响应沿原路返回客户端
- 控制与观测区:整个过程中,配置可能动态更新,请求被记录、统计和追踪
我们主要关注三个层,主要关注静态资源,在后面再介绍学习如何配置动态资源。
那么现在我们先从 Listener 开始介绍各个构建模块,思路还是跟着官网的学习路线走,对三层四区的抽象只是更加方便理解。
4.1 Listener 监听器
Envoy 暴露的监听器是命名的网络位置,可以是一个 IP 地址和一个端口,也可以是一个 Unix 域套接字路径。Envoy 通过监听器接收连接和请求。考虑一下下面的 Envoy 配置。
yaml
static_resources:
listeners:
- name: listener_0
address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains: [{}]
通过上面的 Envoy 配置,我们在 0.0.0.0
地址的 10000
端口上声明了一个名为 listener_0
的监听器。这意味着 Envoy 正在监听 0.0.0.0:10000
的传入请求。
每个监听器都有不同的部分需要配置。然而,唯一需要的设置是地址。上述配置是有效的,你可以用它来运行 Envoy------ 尽管它没有用,因为所有的连接都会被关闭。因为我们让网络过滤器链 filter_chains
字段为空,因为在接收数据包后不需要额外的操作。
为了进入下一个构件(路由),我们需要创建一个或多个filter_chains
,至少要有一个过滤器。
4.2 FilterChain 过滤器链机制
每个通过监听器进来的请求可以流经多个过滤器。我们可以写一个配置,根据传入的请求或连接属性选择不同的过滤器链。过滤器链可以对进入和离开 Envoy 的请求和响应进行处理(注意,有来有回,过滤器链不只是处理请求)。
我们先来全面介绍一下 Envoy 中的监听器类别,记得结合三层四区图来看!
Envoy 定义了三类过滤器:监听器过滤器、网络过滤器 和 HTTP 过滤器。注意,三类过滤器构成了一个层次化的请求处理管道,而不是平行组件。
- 监听器过滤器 在收到数据包后立即启动,通常对数据包的头信息进行操作。监听器过滤器包括:
- 代理监听器过滤器(提取 PROXY 协议头)
- TLS 检查器监听器过滤器(检查流量是否为 TLS,如果是,则从 TLS 握手中提取数据)。
- 网络过滤器(NetworkFilter) 通常对数据包的有效载荷进行操作,查看有效载荷并对其进行解析。例如,PostgreSQL 网络过滤器解析数据包的主体,检查数据库操作的种类或其携带的结果。
- 一个特殊的、内置的网络过滤器被称为 HTTP 连接管理器 过滤器(HTTP Connection Manager Filter)或 HCM。HCM 过滤器能够将原始字节转换为 HTTP 级别的消息。它可以处理访问日志,生成请求 ID,操作头信息,管理路由表,并收集统计数据。我们将在以后的学习中对 HCM 进行更详细的介绍。
- HTTP过滤器 ,就像我们可以为每个监听器定义多个网络过滤器(其中一个是 HCM)一样,Envoy 也支持在 HCM 过滤器中定义多个 HTTP 级过滤器。我们可以在名为
http_filters
的字段下定义这些 HTTP 过滤器。
总的来说是下面这样:
- Listener 有自己的监听器过滤器,除此之外,一个 Listener 包含一个或多个 FilterChain 。
- 一个FilterChain 包含一个或多个 NetworkFilter 。
- HCM 是其中一种 NetworkFilter ,HTTPFilter 组成了 HCM 中的 HTTPFilterChain
- RouterFilter 是其中一种 HTTPFilter,放在 HTTPFilterChain 的最后执行。
注意,我们在 4.1 中的配置文件中,只定义了一个监听器(
listener_0
) 和 一个空的过滤器链filter_chains: [{}]
,但这是网络过滤器链(Network Filter Chain)的部分,而不是监听器过滤器。监听器过滤器需要在配置中使用listener_filters
字段定义。
4.3 Router
HTTP 过滤器链中的最后一个过滤器必须是路由器过滤器(envoy.filters.HTTP.router
)。路由器过滤器负责执行路由任务。这最终把我们带到了第二个构件 ------ 路由。
域名匹配
我们在 HCM 过滤器 的 route_config
字段下定义路由配置。在路由配置中,我们可以通过查看元数据(URI、Header 等)来匹配传入的请求,并在此基础上,定义流量的发送位置。
路由配置中的顶级元素是虚拟主机 virtual_hosts
。每个虚拟主机元素都有一个属于自己的name
,在发布统计数据时使用(不用于路由),还有一组被路由到它的域名domain
。
让我们考虑下面的路由配置和域名的集合。
yaml
route_config:
name: my_route_config
virtual_hosts:
- name: cnblogs_hosts
domains: ["cnblogs.com"]
routes:
...
- name: test_hosts
domains: ["test.cnblogs.com", "qa.cnblogs.com"]
routes:
...
如果传入请求的目的地是 cnblogs.com
(即HTTP请求中 Host/Authority
标头被设置为其中一个值),则该请求会走向cnblogs_hosts
虚拟主机中定义的路由。
同样,如果 Host/Authority
标头包含 test.cnblogs.com
或 qa.cnblogs.com
,则该请求会走向test_hosts
虚拟主机下的路由。这样我们就可以用一个监听器(0.0.0.0:10000
)来处理多个顶级域名。
如果你在数组中指定多个域,搜索顺序如下:
- 精确的域名(例如:
cnblogs.com
)。 - 后缀域名通配符(如
*.cnblogs.com)
。 - 前缀域名通配符(例如:
cnblogs.*
)。 - 匹配任何域的特殊通配符(
*
)。
路由匹配
在 Envoy 匹配域名后,是时候处理所选虚拟主机中的 routes
字段了。我们用该字段,指定如何匹配一个请求,以及接下来如何处理该请求(例如,重定向、转发、重写、发送直接响应等)。
我们来看看一个例子。
yaml
static_resources:
listeners:
- name: listener_0
address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: hello_world_service
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: my_first_route
virtual_hosts:
- name: direct_response_service
domains: ["*"]
routes:
- match:
prefix: "/"
direct_response:
status: 200
body:
inline_string: "envoy yay"
配置的顶部部分与我们之前看到的一样。我们已经添加了 HCM 过滤器、统计前缀(hello_world_service
)、单个 HTTP 过滤器(路由器)和路由配置。
我们把route_config
的部分拿出来:
yaml
route_config:
name: my_first_route
virtual_hosts:
- name: direct_response_service
domains: ["*"]
routes:
- match:
prefix: "/"
direct_response:
status: 200
body:
inline_string: "envoy yay"
在虚拟主机内,我们要匹配任何域名(通配符 *
指定)。在 routes
下,我们匹配前缀(/
),然后我们可以发送一个响应。
当涉及到匹配请求时,我们有多种选择。
路由匹配 | 描述 | 示例 |
---|---|---|
prefix |
前缀必须与:path 头的开头相符。 |
/hello 与 hello.com/hello 、hello.com/helloworld 和 hello.com/hello/v1 匹配。 |
path |
路径必须与:path 头完全匹配。 |
/hello 匹配 hello.com/hello ,但不匹配 hello.com/helloworld 或 hello.com/hello/v1 |
safe_regex |
所提供的正则表达式必须与:path 头匹配。 |
/\{3} 匹配任何以 / 开头的三位数。例如,与 hello.com/123 匹配,但不能匹配 hello.com/hello 或 hello.com/54321。 |
connect_matcher |
匹配器只匹配 CONNECT 请求。 |
一旦 Envoy 将请求与路由相匹配,我们就可以对其进行路由、重定向或返回一个直接响应。在这个例子中,我们通过 direct_response
配置字段使用直接响应。
在tetrate的教学过程中,他们使用了一个名为 func-e 的命令行工具。func-e 允许我们选择和使用不同的 Envoy 版本。
安装 func-e CLI 的方式见func-e官网。
当然,也可以直接下载envoy的bin文件来跑。
更加折腾的方式是自己下载envoy源码进行编译。但是编译的环境要求非常高(建议用编译环境镜像做这个事情),并且编译时间会非常非常非常长......
现在把上述配置保存到 envoy-direct-response.yaml
中后,我们可以用这个配置运行 Envoy。
sh
func-e run -c envoy-direct-response.yaml
如果是自己拉下来的二进制 envoy,则:
sh
envoy-1.27.0-linux-x86_64 -c conf.yaml
一旦 Envoy 启动,我们就可以向 localhost:10000
发送一个请求,以获得我们配置的直接响应。
$ curl localhost:10000
envoy yay
同样,如果我们添加一个不同的主机头(例如 -H "Host: hello.com"
)将得到相同的响应,因为 hello.com
主机与虚拟主机中定义的域相匹配。
4.4 Cluster 和 Endpoint
在大多数情况下,从配置中直接发送响应是一个很好的功能,但我们会有一组端点或主机,我们将流量路由到这些端点或主机。在 Envoy 中做到这一点的方法是通过定义集群。
集群(Cluster)是一组 接受流量的上游类似主机。这可以是你的服务所监听的主机或 IP 地址的列表(类似 k8s 中 Service 与 EndpointSlice 的关系)
例如,假设我们有一个 hello world 服务监听 127.0.0.0:8000
。然后,我们可以用一个单一的端点创建一个集群,像这样:
static_resources:
clusters:
- name: hello_world_service
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
hosts:
- socket_address:
address: 127.0.0.1
port_value: 8000
集群的定义与监听器的定义在同一级别 ,使用 clusters
字段。我们在路由配置中引用集群时,以及在导出统计数据时,都会使用集群这个字段部分。该集群名称在所有集群中必须是唯一的。
我们还可以定义集群内部端点的负载均衡:
clusters:
- name: hello_world_service
load_assignment:
cluster_name: hello_world_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8000
在 load_assignment
字段下,我们可以定义要进行负载均衡的端点列表,以及负载均衡策略设置。每个 lb_endpoints
包含一个或多个端点,对于 endpoint 部分见 4.5 介绍。
Envoy 支持多种负载均衡算法(round-robin、Maglev、least-request、random),这些算法是通过一系列设定共同配置的,这些相关设定有:静态引导配置、DNS、动态 xDS(CDS 和 EDS 服务)以及 主动 / 被动健康检查。如果我们没有通过 lb_policy
字段明确地设置负载均衡算法,它默认为 round-robin。
(当然这里的envoy负载均衡只是一个了解,后面会有单独的笔记深入学习。)
我们可以先来了解一下 endpoint 部分负载均衡支持的一些特性。通常情况下,负载均衡器对所有端点一视同仁,但集群定义允许在端点内建立一个层次结构。
例如,端点可以有一个 权重(weight) 属性,这将指示负载均衡器与其他端点相比,向这些端点发送更多 / 更少的流量。
另一种层次结构类型是基于**地域(locality)**属性,通常用于定义故障转移架构,在容灾处理和平常的业务优化都很有用。这种层次结构允许我们定义地理上比较接近的 "首选" 端点,以及在 "首选" 端点变得不健康的情况下应该使用的 "备份" 端点。
我们还可以在 Cluster 中配置以下可选功能:
- 主动健康检查(
health_checks
) - 断路器 (
circuit_breakers
) - 异常点检测(
outlier_detection
) - 在处理上游的 HTTP 请求时有额外的协议选项
- 一组可选的网络过滤器,应用于所有出站连接等
和监听器的地址一样,端点地址可以是一个套接字地址,也可以是一个 Unix 域套接字。
4.5 示例结合 yaml
让我们看看这些配置是如何结合起来的。
static_resources:
listeners:
- name: listener_0
address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: hello_world_service
http_filters:
- name: envoy.filters.http.router
route_config:
name: my_first_route
virtual_hosts:
- name: direct_response_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: hello_world_service
clusters:
- name: hello_world_service
connect_timeout: 5s
load_assignment:
cluster_name: hello_world_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8000
与前面的yaml不同,我们添加了集群配置,没有使用 direct_response
,而是使用 routes
字段并指定集群名称。
我们在本地跑一个 golang 的 hello-world 服务来验证这个配置,监听 8000 端口:
golang
package main
import (
"fmt"
"net/http"
)
func helloWorld(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", helloWorld)
fmt.Println("Starting server at port 8000")
if err := http.ListenAndServe(":8000", nil); err != nil {
fmt.Println(err)
}
}
然后我们可以向 127.0.0.1:8000
发送一个请求,以检查我们是否得到 "Hello World" 的响应。
接下来,让我们把上述保存了的 Envoy 配置送到 Envoy 启动。
envoy-1.27.0-linux-x86_64 -c envoy-cluster.yaml
当 Envoy 代理启动时,向 0.0.0.0:10000
发送一个请求,让 Envoy 代理请求到 hello world 端点。
sh
$ curl -v 0.0.0.0:10000
* Trying 0.0.0.0:10000...
* Connected to 0.0.0.0 (0.0.0.0) port 10000
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 0.0.0.0:10000
> User-Agent: curl/8.12.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< date: Tue, 01 Apr 2025 12:23:00 GMT
< content-length: 13
< content-type: text/plain; charset=utf-8
< x-envoy-upstream-service-time: 0
< server: envoy
<
* Connection #0 to host 0.0.0.0 left intact
Hello, World!
从冗长的输出中,我们会注意到由 Envoy 代理设置的响应头 x-envoy-upstream-service-time
和 server: envoy
。
5. 一些学习需要用到的工具
- 轻量级压测工具 hey:https://github.com/rakyll/hey
- docker 和 python3
- 关于 GLIBC:我的envoy基于1.27版本,需要 2.29及以上的 GLIBC,像腾讯云的centos服务器一般只有 2.28,这个要注意。
- 前期了解envoy特性为目标的话,尽量不要浪费时间在手动编译上。直接去下载二进制文件,我的1.27版本:https://github.com/envoyproxy/envoy/releases/tag/v1.27.0,当前最新是1.33