使用Envoy json_to_metadata扩展基于请求体路由失败排查求助
需求
针对POST /v2/meal接口,请求体包含"food":"fruit"时路由至Service A,包含"food":"soup"时路由至Service B。
实现配置
Envoy过滤器配置
添加json_to_metadata过滤器解析请求体并写入元数据,同时配置访问日志记录元数据:
resources: - name: listener_0 "@type": type.googleapis.com/envoy.config.listener.v3.Listener traffic_direction: INBOUND address: socket_address: 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.typed_json typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: "/dev/stdout" typed_json_format: message: "requestLog" # 省略其他字段 meta_data: "%DYNAMIC_METADATA(envoy.lb)%" # 元数据字段始终为null http_filters: - name: envoy.filters.http.json_to_metadata typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.json_to_metadata.v3.JsonToMetadata request_rules: rules: - selectors: - key: food on_present: metadata_namespace: envoy.lb key: food on_missing: metadata_namespace: envoy.lb key: default value: "foodMissing" preserve_existing_metadata_value: true on_error: metadata_namespace: envoy.lb key: default value: "foodError" preserve_existing_metadata_value: true - name: envoy.filters.http.router # 必须是HTTP过滤器链的最后一个 typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
Python动态路由配置
使用envoy_data_plane构建路由,通过MetadataMatcher匹配元数据中的food值:
# 根据请求体中的food字段路由 metadata_matchers = [] if route_data.get("food"): food = route_data.get("food") metadata_matchers.append(MetadataMatcher( filter="envoy.lb", path=[ MetadataMatcherPathSegment("food") ], value=ValueMatcher( string_match=StringMatcher( exact=food, ignore_case=True ) ) )) route_match = RouteMatch( dynamic_metadata=metadata_matchers, # 匹配元数据 prefix=request_path_prefix, case_sensitive=False, headers=request_headers, query_parameters=query_parameters )
路由规则定义:
ROUTES = [ { "prefix": "/v2/meal", "food": "fruit", "config": SERVICE_A }, { "prefix": "/v2/meal", "food": "soup", "config": SERVICE_B }, ]
问题现象
使用命令curl -X POST localhost:8080/v2/meal --data-raw '{"food":"fruit"}'测试时,返回404错误,请求被路由至默认集群Service C(该集群无/v2/meal接口),且访问日志中meta_data字段为null:
{"response_code":404,"request_method":"POST","trace_id":null,"user_agent":"curl/8.4.0","message":"requestLog","protocol":"HTTP/1.1","authority":"localhost:8080","full_path":"/v2/meal","bytes_sent":0,"timestamp":"2024-02-12T22:14:36.573Z","request_id":"fe632ad6-f9a7-4349-a6bc-22fd09e76b5d","duration":285,"cluster":"SERVICE_C","meta_data":null,"upstream_service_time":null,"bytes_received":164,"response_flags":"-"}
排查与修复建议
添加Content-Type请求头
json_to_metadata过滤器默认仅处理Content-Type: application/json的请求,测试命令需补充该头:curl -X POST localhost:8080/v2/meal -H "Content-Type: application/json" --data-raw '{"food":"fruit"}'若需要支持其他Content-Type,可在过滤器配置中添加
match_content_type字段指定额外类型。验证元数据写入与日志输出
修改访问日志格式,单独输出food元数据字段,确认过滤器是否正常写入:typed_json_format: # 其他字段省略 food_meta: "%DYNAMIC_METADATA(envoy.lb:food)%" default_meta: "%DYNAMIC_METADATA(envoy.lb:default)%"若输出
foodError,说明请求体JSON解析失败;输出foodMissing说明未匹配到food字段;若仍为null,检查过滤器是否被正确加载(可通过Envoy admin接口/config_dump查看过滤器配置)。调整路由匹配顺序
Envoy路由按配置顺序匹配,确保带有元数据匹配的路由(Service A、Service B)排在默认路由(Service C)之前,避免默认路由先被匹配。确认元数据匹配配置正确性
检查MetadataMatcher的参数:filter字段需与过滤器配置的metadata_namespace一致(此处为envoy.lb)path需正确指向元数据中的字段路径(此处顶级字段food,路径配置正确)- 可通过Envoy admin接口
/routes查看生成的路由配置,确认元数据匹配规则已正确生成。
版本兼容性检查
确认使用的Envoy版本支持json_to_metadata过滤器(v1.18及以上版本支持),且envoy_data_plane生成的配置符合对应Envoy版本的v3 API规范。
内容的提问来源于stack exchange,提问作者Kai W

