在微服务架构中,API 网关通常需要对请求和响应进行高级别的处理,如身份验证、数据转换和安全检查。Envoy 提供的 ext_proc
外部处理过滤器,是一个强大的工具,通过与 gRPC 服务交互,实现灵活的请求与响应处理。本文将深入解析该过滤器的功能、配置与性能优化策略,帮助开发人员和 DevOps 工程师高效应用该特性。
ext_proc
和 Envoy 中的其他 gRPC 接口过滤器(如 ext_authz
)在功能上有相似之处,但 ext_proc
提供了更强大的功能,支持完整的请求和响应处理。这使其特别适用于需要深度内容检查和修改的应用场景。
你可以通过下面的 Envoy 外部处理过滤器思维导图快速了解 ext_proc
。
这张思维导图展示了 Envoy ext_proc
外部处理过滤器的核心结构和功能模块。ext_proc
通过 gRPC 双向流协议与外部服务交互,可灵活处理 HTTP 请求和响应的各个阶段,并支持同步与异步处理。
ext_proc
是 Envoy 提供的 HTTP 过滤器,支持将请求和响应外包给 gRPC 服务进行处理,允许在外部服务中实现复杂的逻辑,灵活应对业务需求。例如,在安全场景中,ext_proc
可用于执行身份验证和授权检查;在数据转换场景中,可以实现数据格式转换与内容过滤。此外,还可用于记录审计日志、动态请求重写以及内容增强等功能,适用于各种企业应用环境中的深度流量管理。
ext_proc
使用双向 gRPC 流与外部服务通信,实现请求和响应处理的实时交互。这使得 Envoy 可以将复杂任务(如身份验证、数据转换和自定义 Header 操作)卸载到外部服务,从而提高灵活性和可扩展性。
Envoy 发送 ProcessingRequest
消息,外部服务返回 ProcessingResponse
消息。需要注意的是,每个 HTTP 请求流都会创建一个独立的 gRPC 流,而不会在多个请求之间共享。每个由 Envoy 处理的 HTTP 请求都会创建其专属的 gRPC 流,从而确保请求与响应的隔离和精确管理。
这种设计允许外部服务在请求与响应生命周期的不同阶段进行干预,甚至能够生成全新的响应内容。``
下图概述 Envoy 外部处理过滤器的处理过程:
以下是一个基本的 Envoy 配置示例:
static_resources:
listeners:
- name: listener_0
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 8080
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(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% \"%RESP(X-EXTPROC-HELLO)%\" \"%RESP(CONTENT-TYPE)%\" \"%RESP(CONTENT-LENGTH)%\" %DURATION% ms\n"
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: [ "*" ]
routes:
- match:
prefix: "/"
route:
host_rewrite_literal: www.envoyproxy.io
cluster: service_envoyproxy_io
http_filters:
- name: envoy.filters.http.ext_proc
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor
grpc_service:
envoy_grpc:
cluster_name: ext_proc_cluster
failure_mode_allow: true
processing_mode:
request_header_mode: SKIP
response_header_mode: SEND
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: ext_proc_cluster
connect_timeout: 0.25s
type: LOGICAL_DNS
lb_policy: ROUND_ROBIN
http2_protocol_options: {}
load_assignment:
cluster_name: ext_proc_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 9000
- name: service_envoyproxy_io
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
load_assignment:
cluster_name: service_envoyproxy_io
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: www.envoyproxy.io
port_value: 443
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: www.envoyproxy.io
为了理解 Envoy 配置与 gRPC 服务之间的关联,我们需要了解以下配置项如何影响流量处理:
ext_proc_cluster
。ext_proc
和 router
。详细的配置说明请参考 Envoy 文档。
Envoy 使用这些配置选项将请求和响应外包给 gRPC 服务,处理结果通过双向流协议返回,影响请求的转发行为。
以下是一个简单的 gRPC 外部处理服务器实现,演示如何通过 ext_proc
添加自定义响应头。该实现展示了核心方法的选择和设计决策,例如使用 Process
方法持续接收请求和发送响应,确保处理过程的连续性。此外,采用 HeaderMutation
配置修改 HTTP 响应头,展现了 gRPC 消息结构与 Envoy 配置的紧密集成,便于动态扩展和灵活管理。
package main
import (
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
configPb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
extProcPb "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
)
type extProcServer struct {
extProcPb.UnimplementedExternalProcessorServer
}
// Process handles external processing requests from Envoy.
// It listens for incoming requests, modifies response headers,
// and sends the updated response back to Envoy.
//
// When a request with response headers is received, it adds a custom header
// "x-extproc-hello" with the value "Hello from ext_proc" and returns the modified headers.
//
// Note: The `RawValue` field is used instead of `Value` because it supports
// setting the header value as a byte slice, allowing precise handling of binary data.
//
// This function is called once per HTTP request to process gRPC messages from Envoy.
// It exits when an error occurs while receiving or sending messages.
func (s *extProcServer) Process(
srv extProcPb.ExternalProcessor_ProcessServer,
) error {
for {
req, err := srv.Recv()
if err != nil {
return status.Errorf(codes.Unknown, "error receiving request: %v", err)
}
log.Printf("Received request: %+v\n", req)
// Prepare the response to be returned to Envoy.
resp := &extProcPb.ProcessingResponse{}
// Only process response headers, not request headers.
if respHeaders := req.GetResponseHeaders(); respHeaders != nil {
log.Println("Processing Response Headers...")
resp = &extProcPb.ProcessingResponse{
Response: &extProcPb.ProcessingResponse_ResponseHeaders{
ResponseHeaders: &extProcPb.HeadersResponse{
Response: &extProcPb.CommonResponse{
HeaderMutation: &extProcPb.HeaderMutation{
SetHeaders: []*configPb.HeaderValueOption{
{
Header: &configPb.HeaderValue{
Key: "x-extproc-hello",
RawValue: []byte("Hello from ext_proc"),
},
},
},
},
},
},
},
}
log.Printf("Sending response: %+v\n", resp)
// Send the response back to Envoy.
if err := srv.Send(resp); err != nil {
return status.Errorf(codes.Unknown, "error sending response: %v", err)
}
} else {
// If it is not a callback in the response header stage, do not make any modifications and continue processing the next event.
// For request_headers or other events, do not modify & ensure that Envoy will not be stuck.
// An empty processing can be returned for request_headers, or it can be skipped in envoy.yaml.
// Here, simply continue to wait for the next event.
continue
}
}
}
func main() {
lis, err := net.Listen("tcp", ":9000")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
// Register the ExternalProcessorServer with the gRPC server.
extProcPb.RegisterExternalProcessorServer(grpcServer, &extProcServer{})
log.Println("Starting gRPC server on :9000...")
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
预期结果:请求返回状态码 200,响应头中包含自定义头 x-extproc-hello: Hello from ext_proc
。如果缺少该头,检查以下内容:
:9000
。ext_proc
过滤器已启用,并且 ext_proc_cluster
配置无误。在本地运行 Envoy 和 gRPC 服务后,使用 curl
进行测试:
envoy -c envoy.yaml
go run main.go
curl -v http://localhost:8080
你将看到包含自定义头 x-extproc-hello: Hello from ext_proc
的响应。
你将看到如下图所示的结果。
在 curl 请求的响应中包含了我们自定义的 header x-extproc-hello: Hello from ext_proc
。
ext_proc
输出的统计信息位于 http.<stat_prefix>.ext_proc.
命名空间,其中 stat_prefix
是 HTTP 连接管理器的前缀。常用统计信息包括:
指标名称 | 类型 | 描述 |
---|---|---|
streams_started | Counter | 启动的 gRPC 流数量 |
streams_msgs_sent | Counter | 发送的消息数量 |
streams_msgs_received | Counter | 接收的消息数量 |
spurious_msgs_received | Counter | 接收的违反协议的意外消息数量 |
streams_closed | Counter | 成功关闭的流数量 |
streams_failed | Counter | 产生 gRPC 错误的流数量 |
failure_mode_allowed | Counter | 错误被忽略的次数(根据配置) |
message_timeouts | Counter | 配置超时内未收到响应的消息数量 |
rejected_header_mutations | Counter | 被拒绝的头部更改数量 |
clear_route_cache_ignored | Counter | 忽略的清理路由缓存请求数量 |
clear_route_cache_disabled | Counter | 被禁用的清理路由缓存请求数量 |
详见 Envoy 文档。
故障恢复与负载均衡:部署多实例 gRPC 服务,使用负载均衡自动转移请求。
消息超时与重试配置:
http_filters:
- name: envoy.filters.http.ext_proc
config:
grpc_service:
envoy_grpc:
cluster_name: ext_proc_server
processing_mode:
request_header_mode: SEND
response_header_mode: SEND
message_timeout: 500ms
max_message_timeout: 1000ms
failure_mode_allow: false
元数据选项与安全策略:
metadata_options:
forwarding_namespaces:
untyped: ["custom_namespace"]
Envoy 的 ext_proc
过滤器通过灵活的请求和响应处理能力,为微服务架构中的服务治理、数据转换和请求检查提供了强大的支持。正确配置和优化 ext_proc
可以显著提高系统的灵活性和可扩展性,满足多样化的业务需求。
最后更新于 2024/12/20