HTTP
5 分钟阅读
简要概述
Envoy 具有一个内置的网络层过滤器,称为 HTTP 连接管理器,它实现了大量的 HTTP 特定功能,接口定义在文件 http_connection_manager.proto 中。
因为是网络层过滤器,所以使用上是配置在 “filter_chains” 且类型名称为:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
示例配置
static_resources:
listeners:
- name: listener_0
address:
...
# 监听器过滤器
listener_filters:
...
# 网络层过滤器
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
...
clusters:
...
功能特点
- HTTP 协议原生支持:HTTP/1.1, HTTP/2 and HTTP/3
- HTTP 头清理:为安全原因,会自动修正一些请求头
- HTTP 路由表配置:支持静态或者动态 RDS 接口
- HTTP 重试插件配置:多种充实机制可供选择
功能定义在:
github.com/envoyproxy/envoy/api/envoy/extensions/retry
- HTTP 内部重定向
- HTTP 超时处理
HTTP 过滤器
过滤器类型
类似网络层过滤器链(filter_chains),在 HTTP 连接管理器内,还支持专门处理该层协议的过滤器链配置(http_filters)。
HTTP 级别过滤器的API允许过滤器在不了解底层协议的情况下运行。
与网络级别过滤器一样,HTTP过滤器可以停止并继续迭代到后续过滤器。这允许更复杂的场景,如处理健康检查、调用速率限制服务、缓冲、路由、为应用程序流量生成DynamoDB等的统计信息。
编写过滤器无需过多底层协议知识,主要有三种类型:
- Decoder(解码器)
当连接管理器解码请求流的部分(请求头、请求体和尾部)时,将调用解码器过滤器。
- Encoder(编码器)
当连接管理器即将对部分响应流(响应头、响应体和尾部)进行编码时,将调用编码器过滤器。
- Decoder/Encoder (解码器/编码器)
当连接管理器解码请求流的部分以及当连接管理器即将编码响应流的部分时,将同时调用解码器/编码器过滤器。
过滤器执行顺序
假设以下三个过滤器均是 “Decoder/Encoder” 类型:
http_filters:
- name: example.filters.a
...
- name: example.filters.b
...
- name: example.filters.c
...
当请求进入后,连接管理器按照以下顺序调用解码器过滤器:
example.filters.a
->
example.filters.b
->
example.filters.c
响应用户时按照相反的顺序调用编码器过滤器:
example.filters.c
->
example.filters.b
->
example.filters.a
HTTP 路由规则
基础结构
基础路由规则一般适合应用在处理边缘流量(传统的反向代理请求处理)以及服务端至服务端间组成的网格,它的配置在 “route_config” 部分:
static_resources:
listeners:
- name: listener_0
address:
...
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
# 路由配置
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
注意 “scoped_routes” 与 “route_config” 只能二选一,见以下 proto 定义文件。
虚拟主机和集群
将匹配以下规则的请求转发至上游集群 “oneops-syncds-v1”:
- 匹配下游请求的所有域名,域名支持正则,配置 “domains” 为 “*”
- 匹配下游请求的所有 url 地址,配置 “routes.match[0].prefix” 为 “/”
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: oneops-syncds-v1
上游集群 “oneops-syncds-v1”:
clusters:
- name: oneops-syncds-v1
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
load_assignment:
cluster_name: oneops-syncds-v1
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 192.168.0.1
port_value: 8080
客户端请求重定向
将匹配以下规则的请求转发重定向特定站点:
- 匹配下游请求的所有域名,域名支持正则,配置 “domains” 为 “*”
- 匹配下游请求的 url 前缀为 “/baidu”,配置 “routes.match[0].prefix” 为 “/baidu”
- 重定向站点配置 “host_redirect” 为 “www.baidu.com”
- 重定向站点使用 https 配置 “https_redirect” 为 true
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
# 支持 "NONE" "EXTERNAL_ONLY" "ALL"
require_tls: NONE
routes:
- match:
prefix: "/baidu"
redirect:
https_redirect: true
host_redirect: www.baidu.com
path_redirect: /
服务端响应 301 重定向:
* Trying 192.168.0.2:80...
* Connected to 192.168.0.2 (192.168.0.2) port 80 (#0)
> GET /baidu HTTP/1.1
> Host: 192.168.0.2
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< location: https://192.168.0.2/baidu
< date: Mon, 22 Jan 2024 08:45:29 GMT
< server: envoy
< content-length: 0
<
* Connection #0 to host 192.168.0.2 left intact
路径前缀与请求匹配
将匹配以下规则的请求转发至上游集群 “oneops-syncds-v1”:
- 匹配下游请求的所有域名,域名支持正则,配置 “domains” 为 “*”
- 匹配下游请求的 url 为 “/",配置 “routes[0].match.prefix” 为 “/”
- 匹配下游请求的 url 不区分大小写,配置 “routes[0].match.case_sensitive” 为 “true”
- 匹配下游请求头中 “user-agent” 值为 “SpecialClient”
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
case_sensitive: true
headers:
- name: user-agent
exact_match: "SpecialClient"
route:
cluster: oneops-syncds-v1
curl -vvv -H "user-agent: SpecialClient" http://192.168.0.2:80
路径前缀与主机重写
将匹配以下规则的请求转发至上游集群 “oneops-syncds-v1”:
- 匹配下游请求为特定域名,配置 “domains” 为 “test.example.com”
- 匹配下游请求的 url 为 “/",配置 “routes[0].match.prefix” 为 “/”
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["test.example.com"]
routes:
- match:
prefix: "/"
route:
cluster: oneops-syncds-v1
prefix_rewrite: "/ds/"
host_rewrite_literal: "app.example.com"
同时转发上游集群时重写:
- 参数 “prefix_rewrite” 重写原始请求前缀为 “/ds/”
- 参数 “host_rewrite_literal” 重写原始请求域名为 “app.example.com”
请求超时重试与对冲
TODO;
未成功实现
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: oneops-syncds-v1
retry_policy:
retry_on: connect-failure,refused-stream,retriable-status-codes
num_retries: 5
retriable_status_codes:
- "500"
- "502"
- "503"
请求流量分流与转移
根据权重负载到多个上游集群
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
weighted_clusters:
clusters:
- name: oneops-syncds-v1
weight: 2
- name: oneops-syncds-v2
weight: 8
在两个上游集群进行流量转移
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
runtime_fraction:
default_value:
numerator: 20
route:
cluster: oneops-syncds-v1
- match:
prefix: "/"
route:
cluster: oneops-syncds-v2
支持基于策略的路由
TODO;
支持直接响应客户端
通过指令 “direct_response” 配置响应内容,这样无需从上游集群获取数据。
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
direct_response:
status: 200
body:
inline_string: "hello world"
非 TLS 请求绝对转发
将匹配以下规则的请求转发至上游集群 “oneops-syncds-v1”:
- 匹配下游请求的所有域名,配置 “domains” 为 “*”
- 匹配下游请求的所有 url 地址,配置 “routes.match[0].prefix” 为 “/”
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: oneops-syncds-v1
通用匹配树指定路由表
使用 matcher
为通用匹配树,相比 routes
更具表达力的匹配引擎,允许在任意头部执行线性匹配,它提供了更大的灵活性,使得可以根据请求的各种头部信息进行更复杂的路由决策。
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
matcher:
matcher_tree:
input:
name: request-headers
typed_config:
"@type": type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput
header_name: :path
exact_match_map:
map:
"/ds/v1":
action:
name: route_ds_v1
typed_config:
"@type": type.googleapis.com/envoy.config.route.v3.Route
match:
prefix: /
route:
cluster: oneops-syncds-v1
"/ds/v2":
action:
name: route_ds_v1
typed_config:
"@type": type.googleapis.com/envoy.config.route.v3.Route
match:
prefix: /
route:
cluster: oneops-syncds-v2
详细使用见官方文档 Generic Matching。
HTTP 路由作用域
基础结构
路由作用域(Route Scope)用于将特定路由规则与某个作用域(scope)相关联,每个请求都会计算一个作用域键(scope key),并根据这个键来选择相应的路由规则,涉及以下两个概念:
- Scope Key(作用域键): 对于每个请求,HTTP 连接管理器会动态计算一个作用域键,这个键用于决定选择哪个路由表。作用域键的计算可能基于请求的某些属性或上下文信息。
- Route Table(路由表): 路由表是一组与特定作用域键相关联的路由规则。每个路由表定义了一组路由规则,当请求的作用域键与路由表相关联时,Envoy 将使用这个路由表来确定请求的最终路由。
static_resources:
listeners:
- name: listener_0
address:
...
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
# 路由作用域
scoped_routes:
name: scope_by_addr
scope_key_builder:
......
scoped_route_configurations_list:
scoped_route_configurations:
......
注意 “scoped_routes” 与 “route_config” 只能二选一,在 “http_connection_manager.proto” 文件中定义的 “oneof route_specifier {}” 类型。
特定请求头
scoped_routes:
name: scope_by_addr
scope_key_builder:
fragments:
- header_value_extractor:
name: Addr
element_separator: ";"
element:
key: x-foo-key
separator: "="
scoped_route_configurations_list:
scoped_route_configurations:
- on_demand: true
name: scoped_route_0
key:
fragments:
- string_key: bar
route_configuration:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: oneops-syncds-v2
prefix_rewrite: "/ds/"
内置键中缺少片段(视为 NULL)会使请求无法匹配任何范围,即找不到该请求的路由条目。
HTTP 动态正向代理
下游使用明文访问
static_resources:
listeners:
- name: listener_0
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 80
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
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: dynamic_forward_proxy_cluster
http_filters:
- name: envoy.filters.http.dynamic_forward_proxy
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig
dns_cache_config:
name: dynamic_forward_proxy_cache_config
dns_lookup_family: V4_ONLY
typed_dns_resolver_config:
name: envoy.network.dns_resolver.cares
typed_config:
"@type": type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: dynamic_forward_proxy_cluster
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
typed_dns_resolver_config:
name: envoy.network.dns_resolver.cares
typed_config:
"@type": type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
validation_context:
trusted_ca:
filename: /etc/ssl/certs/ca-certificates.crt
下游使用 TLS 加密访问
static_resources:
listeners:
- name: listener_0
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 443
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
......
http_filters:
- name: envoy.filters.http.dynamic_forward_proxy
......
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
tls_certificates:
# The following self-signed certificate pair is generated using:
# $ openssl req -x509 -newkey rsa:2048 -keyout a/front-proxy-key.pem -out a/front-proxy-crt.pem -days 3650 -nodes -subj '/CN=front-envoy'
#
# Instead of feeding it as an inline_string, certificate pair can also be fed to Envoy
# via filename. Reference: https://envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#config-core-v3-datasource.
#
# Or in a dynamic configuration scenario, certificate pair can be fetched remotely via
# Secret Discovery Service (SDS). Reference: https://envoyproxy.io/docs/envoy/latest/configuration/security/secret.
- certificate_chain:
inline_string: |
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
private_key:
inline_string: |
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----
clusters:
- name: dynamic_forward_proxy_cluster
......