在 Envoy Proxy 中,HTTP 路由是通过一个 HTTP 路由过滤器(Router Filter)来实现的,该过滤器可以执行高级路由任务。HTTP 路由机制允许 Envoy 将传入的 HTTP 请求映射到上游集群(服务),从而充当反向代理或构建服务网格(通常通过在 Host/Authority
HTTP 头上进行路由以到达特定的上游服务集群)。
HTTP 路由的结构
-
Router Filter(路由过滤器):Envoy 的 HTTP 路由是通过一个名为
Router Filter
的过滤器实现的,该过滤器可以在 HTTP 连接管理器中配置,以便在请求到达上游服务之前进行高级路由决策。 -
虚拟主机(Virtual Hosts)和集群(Clusters):虚拟主机将域名(Domain)或授权(Authority)映射到一组路由规则。虚拟主机的配置可以包含正则表达式匹配,用于生成集群级别的额外统计信息。
-
路径、前缀和头匹配(Path, Prefix, and Header Matching):支持基于前缀或精确路径的匹配(区分大小写和不区分大小写),以及更复杂的正则表达式路径匹配。可以根据任意请求头匹配路由。
-
路径和主机重写(Path and Host Rewriting):支持重写请求的路径或主机信息,允许进行自动主机重写(基于所选上游主机的 DNS 名称)或显式主机重写。
-
请求重定向(Request Redirection):支持基于路径或主机的请求重定向,以及虚拟主机级别的 TLS 重定向。
-
请求超时、重试和对冲(Request Timeouts, Retries, and Hedging):支持在路由配置或 HTTP 请求头级别配置请求重试和超时。还可以配置请求对冲,在请求超时时触发多个上游请求,并返回第一个符合条件的响应。
-
流量转移与拆分(Traffic Shifting and Splitting):支持通过运行时值在不同的上游集群之间转移流量,或通过基于权重/百分比的路由在多个上游集群之间拆分流量。
-
策略路由(Policy-Based Routing):支持基于优先级或哈希策略的路由,以便进行高级流量管理。
-
直接响应(Direct Responses):可以发送非代理的 HTTP 响应(直接响应),不需要将请求代理到上游服务器。
-
路由作用域(Route Scope):支持限定路由搜索空间,通过
Scoped Route Configuration
限制搜索空间以优化性能。
路由匹配过程
在 Envoy 中进行 HTTP 路由匹配时,主要依赖于一系列规则和步骤来决定一个请求应该被转发到哪一个后端。
下图展示了具体的匹配过程。
要点:
- Envoy 首先根据
host
或:authority
头来匹配虚拟主机。 - 在虚拟主机中,路由条目按顺序检查,直到找到一个匹配的路由。
- 如果路由较多,可以通过路由匹配器来提高匹配效率。
- 虚拟集群用于更细粒度的流量监控。
1. 基于主机(Host)或 :authority
头的匹配
Envoy 首先会检查 HTTP 请求中的 host
或 :authority
头,然后将它与虚拟主机(virtual host)进行匹配。虚拟主机定义了一组路由规则,用于处理特定的域名或主机名。
2. 路由条目的匹配
接下来,Envoy 会根据匹配的虚拟主机,按顺序检查其中定义的路由条目(route entry)。如果找到匹配的路由,Envoy 将直接使用该路由来处理请求,而不会再继续检查其他路由。
3. 路由匹配器的使用
在一些场景下,如果有大量路由条目,逐个匹配会显得效率较低。这时,虚拟主机可以使用路由匹配器(matcher entry)。通过构建高效的匹配树结构,Envoy 可以快速找到合适的路由。
4. 虚拟集群(Virtual Cluster)独立匹配
独立于路由条目的匹配,Envoy 还可以根据虚拟集群(virtual cluster)的配置来匹配请求。这些虚拟集群通常用于监控或统计,可以帮助你更好地观察不同路径或请求模式的流量。
路由规则示例
以下是一个简单的路由匹配示例配置:
virtual_hosts:
- name: backend
domains:
- "example.com"
routes:
- match:
prefix: "/"
route:
cluster: service_backend
在这个配置中:
- domains:
example.com
代表虚拟主机将匹配所有请求的Host
为example.com
的流量。 - routes: 定义了请求路径前缀为
/
的路由规则,将该请求路由到service_backend
集群。
HTTP 路由的工作原理
-
接收请求:Envoy 监听传入的 HTTP 请求。
-
路由匹配:根据请求头(如
Host
或Path
)匹配相应的路由规则。通过虚拟主机和路径匹配等规则选择目标上游集群。 -
连接池获取:匹配到合适的上游集群后,Envoy 获取到该集群中某个主机的连接池。
-
请求转发:将请求转发到所选的上游集群中的一个主机。
-
响应处理:上游服务处理请求后,返回响应,Envoy 通过已建立的连接将响应返回给客户端。
下图展示的是 HTTP 路由结构:
- HTTP Connection Manager (HCM):管理 HTTP 连接的组件,负责调用路由过滤器。
- Router Filter:执行路由决策的过滤器,基于请求特征选择目标上游集群。
- Match Virtual Host and Route:根据虚拟主机和路由规则匹配请求路径和头。
- Path, Prefix, Header Matching:执行路径、前缀、头匹配,找到最佳路由。
- Route Action:确定路由操作(如重定向、超时、重试等)。
- Acquire Connection Pool:获取到上游集群中某个主机的连接池以建立连接。
- Forward Request to Upstream Cluster:将请求转发到选定的上游集群中的一个主机。
- Receive Response from Upstream:接收来自上游服务的响应。
- Return Response to Client:将响应返回给客户端。
- Direct Responses, Request Redirection, Traffic Shifting/Splitting, Request Timeout and Retries:不同的路由操作,如直接响应、请求重定向、流量拆分、请求重试等。
HTTP 路由示例
为了更好地理解 Envoy 中的 HTTP 路由机制,下面我们通过一个简单的配置示例,展示如何设置基本的 HTTP 路由规则。该示例将演示如何根据请求的路径将流量路由到不同的上游集群。
配置示例
以下是一个基本的 Envoy 配置文件,展示了如何根据请求路径将流量路由到不同的服务集群:
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: ingress_http
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
log_format:
text_format: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(:PATH)% %PROTOCOL%\" %RESPONSE_CODE% \"%RESPONSE_FLAGS%\"\n"
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: local_route
virtual_hosts:
- name: service_virtual_host
domains: ["*"]
routes:
- match:
prefix: "/serviceA"
route:
cluster: service_A_cluster
prefix_rewrite: "/"
- match:
prefix: "/serviceB"
route:
cluster: service_B_cluster
prefix_rewrite: "/"
- match:
prefix: "/"
route:
cluster: default_service_cluster
clusters:
- name: service_A_cluster
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
load_assignment:
cluster_name: service_A_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: serviceA.example.com
port_value: 80
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: serviceA.example.com
- name: service_B_cluster
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
load_assignment:
cluster_name: service_B_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: serviceB.example.com
port_value: 80
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: serviceB.example.com
- name: default_service_cluster
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
load_assignment:
cluster_name: default_service_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: default.example.com
port_value: 80
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: default.example.com
admin:
address:
socket_address:
address: 127.0.0.1
port_value: 9901
解释
监听器(Listener)
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
...
- address: Envoy 在所有网络接口(
0.0.0.0
)的 10000 端口上监听传入的 HTTP 请求。 - filter_chains: 使用
http_connection_manager
过滤器来管理 HTTP 连接。
HTTP 连接管理器(HttpConnectionManager)
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
access_log:
- name: envoy.access_loggers.stdout
...
http_filters:
- name: envoy.filters.http.router
...
route_config:
name: local_route
virtual_hosts:
- name: service_virtual_host
domains: ["*"]
routes:
- match:
prefix: "/serviceA"
route:
cluster: service_A_cluster
prefix_rewrite: "/"
- match:
prefix: "/serviceB"
route:
cluster: service_B_cluster
prefix_rewrite: "/"
- match:
prefix: "/"
route:
cluster: default_service_cluster
- access_log: 将访问日志输出到标准输出(stdout),日志格式包含请求方法、路径、协议、响应状态码等信息。
- http_filters: 使用
router
过滤器来执行路由决策。 - route_config: 定义路由配置,包含一个虚拟主机和多个路由规则。
虚拟主机(Virtual Host)和路由规则(Routes)
virtual_hosts:
- name: service_virtual_host
domains: ["*"]
routes:
- match:
prefix: "/serviceA"
route:
cluster: service_A_cluster
prefix_rewrite: "/"
- match:
prefix: "/serviceB"
route:
cluster: service_B_cluster
prefix_rewrite: "/"
- match:
prefix: "/"
route:
cluster: default_service_cluster
- domains: 匹配所有域名(
"*"
)。 - routes: 定义了三个路由规则:
-
第一个路由:
- match.prefix: 匹配以
/serviceA
开头的路径。 - route.cluster: 将匹配的请求路由到
service_A_cluster
集群。 - prefix_rewrite: 将请求路径重写为
/
,即移除/serviceA
前缀。
- match.prefix: 匹配以
-
第二个路由:
- match.prefix: 匹配以
/serviceB
开头的路径。 - route.cluster: 将匹配的请求路由到
service_B_cluster
集群。 - prefix_rewrite: 将请求路径重写为
/
,即移除/serviceB
前缀。
- match.prefix: 匹配以
-
第三个路由:
- match.prefix: 匹配所有以
/
开头的路径(即默认路由)。 - route.cluster: 将匹配的请求路由到
default_service_cluster
集群。
- match.prefix: 匹配所有以
-
集群(Clusters)
clusters:
- name: service_A_cluster
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
load_assignment:
cluster_name: service_A_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: serviceA.example.com
port_value: 80
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: serviceA.example.com
- name: service_B_cluster
...
- name: default_service_cluster
...
-
service_A_cluster:
- type: 使用逻辑 DNS 方式发现上游服务。
- dns_lookup_family: 仅使用 IPv4 地址进行 DNS 解析。
- load_assignment: 定义上游服务的地址和端口,此处为
serviceA.example.com:80
。 - transport_socket: 配置 TLS 传输套接字,使用 SNI 指定服务器名称为
serviceA.example.com
。
-
service_B_cluster 和 default_service_cluster:
- 配置方式与
service_A_cluster
类似,分别指向不同的上游服务地址。
- 配置方式与
管理接口(Admin Interface)
admin:
address:
socket_address:
address: 127.0.0.1
port_value: 9901
- address: 管理接口仅绑定到本地地址
127.0.0.1
的 9901 端口,确保只能本地访问,提升安全性。
工作流程
-
接收请求:
- Envoy 在
0.0.0.0:10000
端口监听传入的 HTTP 请求。
- Envoy 在
-
路由匹配:
- 根据请求的路径前缀
/serviceA
或/serviceB
,Envoy 选择对应的集群service_A_cluster
或service_B_cluster
。 - 如果请求路径不匹配上述前缀,则使用默认路由将请求转发到
default_service_cluster
。
- 根据请求的路径前缀
-
路径重写:
- 在将请求转发到上游集群之前,Envoy 会将请求路径中的前缀
/serviceA
或/serviceB
重写为/
,确保上游服务接收到的路径是预期的。
- 在将请求转发到上游集群之前,Envoy 会将请求路径中的前缀
-
请求转发:
- Envoy 通过配置的集群信息,将请求转发到对应的上游服务地址
serviceA.example.com
、serviceB.example.com
或default.example.com
。
- Envoy 通过配置的集群信息,将请求转发到对应的上游服务地址
-
响应处理:
- 上游服务处理请求后,响应通过 Envoy 返回给客户端。
示例场景
假设有以下上游服务:
- Service A:运行在
serviceA.example.com:80
,提供/
路径下的功能。 - Service B:运行在
serviceB.example.com:80
,提供/
路径下的功能。 - Default Service:运行在
default.example.com:80
,作为默认服务处理所有其他请求。
通过上述配置:
- 当客户端请求
http://envoy_proxy:10000/serviceA/api
时,Envoy 会将请求路由到serviceA.example.com:80
,并将路径重写为/api
。 - 当客户端请求
http://envoy_proxy:10000/serviceB/home
时,Envoy 会将请求路由到serviceB.example.com:80
,并将路径重写为/home
。 - 当客户端请求
http://envoy_proxy:10000/about
时,Envoy 会将请求路由到default.example.com:80
,路径保持为/about
。
总结
通过这个简单的示例,我们展示了如何在 Envoy 中配置基本的 HTTP 路由规则,根据请求路径将流量路由到不同的上游服务集群。关键点包括:
- 路由匹配:基于请求路径前缀进行匹配,选择合适的集群。
- 路径重写:在转发请求前,重写请求路径以符合上游服务的预期。
- 集群配置:定义上游服务的地址、端口和传输配置,确保与上游服务的安全通信。