十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Envoy HTTP 过滤器(HTTP Filters)架构深度解析:解码/编码链、路由变更与路由级过滤链

Envoy HTTP 过滤器(HTTP Filters)架构深度解析:解码/编码链、路由变更与路由级过滤链 Envoy HTTP 过滤器HTTP Filters架构深度解析解码/编码链、路由变更与路由级过滤链【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy本文以 Envoy 官方架构概览文档 http_filters.rst 为主体系统讲解 HTTP 连接管理器HTTP Connection Manager, HCM内 HTTP 过滤器栈的核心机制Decoder/Encoder 过滤器分类、过滤器执行顺序、条件式过滤器配置、路由变更setRoute/clearRouteCache/refreshRouteCluster、按路由分发过滤器配置per-filter config以及按路由启停过滤器route based filter chain。读者读完后将能够正确编排 HTTP 过滤器链、理解路由缓存变更的安全边界并掌握在路由/虚拟主机/路由表粒度上定制过滤器行为的实战配置方法。HTTP 过滤器栈位于连接管理器内部的应用层扩展点与 Envoy 在监听器listener层面提供的 :ref:网络层过滤器栈类似HTTP 连接管理器HCM内部也维护了一个HTTP 层过滤器栈。两者的关键区别在于抽象层级网络层过滤器工作在原始字节流 / 连接层面处理的是 TCP/UDP 连接上的数据。HTTP 层过滤器工作在 HTTP 消息层面操作的是请求/响应的 header、body、trailer完全不需要感知底层物理协议HTTP/1.1、HTTP/2、HTTP/3 等以及多路复用multiplexing能力。这种协议无关性是 Envoy HTTP 过滤器 API 的设计目标之一。同一个过滤器例如限流、鉴权、缓冲过滤器可以不加修改地应用于任何底层 HTTP 协议使得 Envoy 在 HTTP/1.1 与 HTTP/2 并存、协议升级等场景下保持一致的过滤语义。按部署位置HTTP 过滤器又分为两类类型关联对象触发时机Downstream HTTP filters某个 listener监听器对每一条下游请求在路由routing之前进行流处理Upstream HTTP filters某个 cluster集群在router 过滤器之后对每一条上游请求进行一次流处理下游过滤器负责入口流量侧的加工鉴权、限流、改写等上游过滤器则负责出口流量侧的处理例如针对特定上游集群的请求改写。三种 HTTP 过滤器类型Decoder、Encoder 与 Decoder/Encoder从流方向上HTTP 层过滤器分为三种Decoder解码过滤器在连接管理器解码请求流时被调用处理的对象是请求的 header、body 与 trailer。Encoder编码过滤器在连接管理器即将编码响应流时被调用处理的对象是响应的 header、body 与 trailer。Decoder/Encoder 过滤器上述两个阶段都会被调用既能处理请求流也能处理响应流。大多数常用过滤器router、buffer、lua 等都属于这一类。需要说明的是解码与编码的命名以连接管理器的视角为准请求从客户端进入 Envoy 时被解码响应从上游返回即将发往客户端时被编码。过滤器 API 保证无论底层是 HTTP/1.1 还是 HTTP/2过滤器都以相同的调用约定工作。与网络层过滤器一样HTTP 过滤器可以停止stop并继续continue迭代到后续过滤器。这一能力支撑了大量复杂场景例如健康检查处理调用限流服务rate limiting service后决定是否放行请求体缓冲buffering路由决策为应用流量如 DynamoDB生成统计信息等。此外HTTP 层过滤器之间可以在同一条请求流的上下文内共享状态静态与动态状态具体机制可参考 Envoy 文档中 :ref:过滤器间数据共享 arch_overview_data_sharing_between_filters一节。关于过滤器配置的完整参考可查阅HTTP 过滤器的 :ref:配置参考 config_http_filtersHttpConnectionManager.http_filters字段的 :ref:protobuf 定义 envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.http_filters见 http_connection_manager.proto已内置过滤器的完整清单见扩展分类 :ref:envoy.filters.http extension_category_envoy.filters.http。HttpFilter 消息结构过滤器链的最小单元在HttpConnectionManager.http_filters列表中每一项都是一个 HttpFilter 消息其核心字段包括name必填min_len: 1过滤器配置名同时也是 ExtensionConfigDS 中的资源名typed_config该过滤器专属的强类型配置Any类型支持通过ExtensionWithMatcher附带匹配树config_discovery通过扩展配置发现服务ExtensionConfigDS动态下发配置当获取失败且无默认配置时监听器会以 500 响应is_optional为true时不识别该过滤器的客户端可以忽略它而接受配置为false默认时不识别该过滤器的客户端必须拒绝整个配置disabled为true时该过滤器默认禁用需要依赖路由配置中的 per-filter config 显式启用详见下文按路由启停过滤器。终端过滤器如envoy.filters.http.router不允许被标记为disabled。过滤器执行顺序解码正序、编码逆序http_filters列表中的顺序是有意义的。假设按如下顺序配置了三个过滤器且三者均为 decoder/encoder 类型http_filters: - A - B # 最后一个过滤器必须是终端过滤器terminal filter由 # NamedHttpFilterConfigFactory::isTerminalFilterByProto(config, context) 判定 # 通常是 router 过滤器。 - C连接管理器对过滤器的调用规则为Decoder 方向请求流按配置顺序A → B → C依次调用Encoder 方向响应流按配置逆序 C → B → A依次调用。也就是说配置列表中越靠前的过滤器越靠近客户端一侧越靠后的过滤器越靠近 router/上游一侧。请求从 A 进入、依次经过 B、C 后进入路由与上游响应则从上游返回后先经过 C再经 B最后经 A 离开 Envoy。关于最后一个过滤器必须是终端过滤器的约束源码层面由NamedHttpFilterConfigFactory::isTerminalFilterByProto()方法判定定义见 filter_config.h。该虚方法默认返回false而 router 等终端过滤器会重写它并返回true。连接管理器在加载配置时会校验过滤器链的最后一个过滤器是否满足终端条件若不满足则拒绝配置。因此配置 HTTP 过滤器链时务必把envoy.filters.http.router或其他终端过滤器放在最后一位。条件式过滤器配置按请求动态解析过滤器配置Envoy 还支持让过滤器配置根据入站请求而变化。具体做法是使用 :ref:composite 过滤器 config_http_filters_composite它允许配置一棵匹配树match tree根据请求的属性header、路径、动态元数据等在运行时解析出当前请求应该使用哪一份过滤器配置。这为同一条监听器上、不同请求走不同过滤逻辑提供了声明式的配置手段避免了为每种场景重复部署监听器。过滤器路由变更setRoute、clearRouteCache 与 refreshRouteCluster缓存路由cached route的产生在下游 HTTP 过滤器链的处理过程中当某个过滤器调用decodeHeaders()时连接管理器会执行路由解析route resolution并把解析结果缓存在一个cached route缓存路由中指向某个上游集群。此后过滤器链中的后续阶段以及 router 过滤器会基于这份缓存路由完成最终的上游转发决策。setRoute 与 DelegatingRoute 机制下游 HTTP 过滤器拥有在路由解析之后直接修改这份缓存路由的能力入口即setRoute回调配合 :repo:DelegatingRoute source/common/router/delegating_route_impl.h机制实现。DelegatingRoute是一个对Router::Route的包装类默认将所有方法调用委托给其包裹的基础路由对象。过滤器的典型用法是派生一个DelegatingRoute或其子类DelegatingRouteEntry用于覆盖routeEntry()相关方法的子类在子类中只覆盖需要变更的方法——例如路由的超时值timeout()/idleTimeout()或路由条目的集群名clusterName()其余属性与行为全部保留为被包裹的基础路由的行为调用setRoute(...)把缓存路由手动设置为这个DelegatingRoute实例。从源码看delegating_route_impl.h 提供了三个层次的类DelegatingRouteBaseInterface模板基类统一代理directResponseEntry()、routeEntry()、decorator()、tracingConfig()、perFilterConfigs()、metadata()、typedMetadata()、filterDisabled()、routeName()、virtualHost()等全部Router::Route接口DelegatingRouteEntry额外代理Router::RouteEntry的全部方法clusterName()、timeout()、idleTimeout()、retryPolicy()、hashPolicy()、rateLimitPolicy()等并允许子类直接覆盖这些方法DynamicRouteEntryDelegatingRouteEntry的一个具体子类仅覆盖clusterName()集群名由创建它的过滤器决定。测试代码中提供了一个可直接参照的派生类示例——ExampleDerivedDelegatingRouteEntry见 test/test_common/delegating_route_utility.h它演示了如何同时覆盖clusterName()与idleTimeout()并对未显式覆盖的idleTimeout()回退到基础路由的实现。该测试工具类同时被 HCM 单元测试与集成测试使用是学习如何实现自定义路由变更过滤器的最佳起点。clearRouteCache重新触发路由解析过滤器链中常见的另一个操作是clearRouteCache()。它会使连接管理器丢弃已缓存的 route 选择从而在后续阶段重新执行路由匹配可能因为请求属性在之前的过滤器中被修改需要基于最新属性重新选路。连接管理器对这两个回调的实现分别位于ActiveStream::setRoute与ActiveStream::clearRouteCache见 conn_manager_impl.cc 与 conn_manager_impl.cc。需要特别注意setRoute设置的路由选择不会在clearRouteCache()之后存活。如果链中其他过滤器在setRoute之后又调用了clearRouteCache()缓存路由会被重置之前手动设置的路由将失效。因此若只想修正集群选择而不希望触发整条路由的重新匹配应优先考虑下面的refreshRouteCluster()。refreshRouteCluster仅刷新集群选择除了setRoute()与clearRouteCache()下游 HTTP 过滤器还可以通过refreshRouteCluster()回调刷新集群——前提是当前路由的集群说明符cluster specifier支持该回调。截至目前仅以下两类集群说明符支持refreshRouteCluster():ref:matcher based cluster specifier config_http_cluster_specifier_matcher基于匹配树的集群选择:ref:dynamic modules cluster specifier config_http_cluster_specifier_dynamic_modules动态模块集群选择。该回调不会更新缓存路由只刷新集群选择结果。官方文档明确建议如果你只想基于过滤器更新后的最新请求属性确定目标集群、又不想在路由表中配置大量相似路由那么应当用refreshRouteCluster()取代clearRouteCache()——前者代价更小、语义更精确不会引发整条路由的重新匹配。从源码看refreshRouteCluster()在 delegating_route_impl.h 中声明为RouteEntry的一个虚方法并在路由配置实现如 config_impl.h与 router 主实现router.cc中被调用。安全注意事项路由缓存清理与路由相关鉴权清空路由缓存可能导致 Envoy 在更早的 HTTP 过滤器已经处理完请求之后重新计算路由匹配结果这在先执行路由相关鉴权的过滤器、后执行会变更路由匹配输入的过滤器的场景下存在安全隐患官方文档明确将其标注为attention级注意事项路由匹配可能依赖请求头、动态元数据dynamic metadata、过滤器状态filter state、请求路径或其他请求属性如果某个较晚的过滤器修改了上述输入之一并清空路由缓存那么后续过滤器与 router 观察到的路由可能与更早过滤器看到的路由不一致从而绕过基于原路由做出的鉴权决策。已知可能清空路由缓存的过滤器包括:ref:Lua 过滤器 config_http_filters_lua—— 通过clearRouteCache()方法:ref:ext_proc 过滤器 config_http_filters_ext_proc—— 配置了CLEAR路由缓存动作或响应中包含clear_route_cache指令时:ref:Golang 过滤器 config_http_filters_golang—— 通过clearRouteCache()API:ref:Language 过滤器 config_http_filters_language—— 启用了clear_route_cache时:ref:JSON to metadata 过滤器 config_http_filters_json_to_metadata—— 启用了clear_route_cache时:ref:IP tagging 过滤器 config_http_filters_ip_tagging—— 清理 header 时其他在 decoder callbacks 中调用clearRouteCache()的自定义过滤器。给运维与开发者的实操建议在使用基于路由的鉴权过滤器如 :ref:RBAC config_http_filters_rbac、:ref:ExtAuthZ config_http_filters_ext_authz、:ref:JWT config_http_filters_jwt_authn时仔细审查 HTTP 过滤器顺序避免为不可信的变更来源开启路由缓存清理能力在条件允许时将路由相关鉴权过滤器放在会变更路由匹配输入的过滤器之后执行确保鉴权基于与最终路由一致的请求属性。路由特定配置Per-Filter ConfigHTTP 过滤器可以通过per filter config map获得路由route/ 虚拟主机virtual host/ 路由表route configuration粒度的专属配置。对应的三个字段分别为:ref:Route.typed_per_filter_config envoy_v3_api_field_config.route.v3.Route.typed_per_filter_config:ref:VirtualHost.typed_per_filter_config envoy_v3_api_field_config.route.v3.VirtualHost.typed_per_filter_config:ref:RouteConfiguration.typed_per_filter_config envoy_v3_api_field_config.route.v3.RouteConfiguration.typed_per_filter_configper filter config map 的 key 必须与过滤器的配置名filter config name完全一致。注意这里的配置名既可以是自定义名也可以是规范名canonical name取决于HttpFilter.name字段的实际取值。官方文档给出如下示例http_filters: - name: custom-filter-name-for-lua # 自定义名作为 filter config name typed_config: { ... } - name: envoy.filters.http.buffer # 规范名作为 filter config name typed_config: { ... }此时custom-filter-name-for-lua与envoy.filters.http.buffer将分别作为 key用于在路由配置中查找对应的 per-filter config。需要强调是否使用、如何使用 per filter config map 完全由过滤器自身决定filter-specific。不同过滤器对 per-filter config 的利用方式差异很大具体请查阅各过滤器的专属文档:ref:HTTP filter 文档 config_http_filters。例如 Lua 过滤器使用LuaPerRoute提供路由级脚本JWT 过滤器使用 per-filter config 提供路由级鉴权要求等。按路由启停过滤器Route Based Filter ChainEnvoy 支持为不同路由提供不同的过滤器链具体有两种模式在特定路由上禁用过滤器链中的某个过滤器过滤器默认禁用在特定路由上显式启用。默认情况下所有路由共享同一过滤器链且所有过滤器均为启用状态。要实现路由级启停核心依赖FilterConfig消息的 :ref:disabled 字段 envoy_v3_api_field_config.route.v3.FilterConfig.disabled其 protobuf 定义位于 route_components.proto。模式一默认启用按路由禁用给定如下 HTTP 过滤器配置buffer与lua默认全部启用http_filters: - name: buffer typed_config: { ... } - name: lua typed_config: { ... }若希望在某条特定路由上禁用buffer过滤器只需在该路由的 per filter config map 中写入typed_per_filter_config: buffer: type: type.googleapis.com/envoy.config.route.v3.FilterConfig disabled: true该配置可置于Route、VirtualHost或RouteConfiguration中按字段位置决定生效粒度。当路由、虚拟主机、路由表三层同时存在配置时Envoy 会按最具体优先most specific的语义进行合并与覆盖。模式二默认禁用按路由启用反过来也可以让过滤器默认禁用再为特定路由开启。方式是在HttpFilter配置中设置disabled: true字段该字段定义见 http_connection_manager.protohttp_filters: - name: buffer typed_config: { ... } disabled: true - name: lua typed_config: { ... } disabled: true此时buffer与lua默认都不生效。若想在某条路由上启用lua在该路由的 per filter config map 中提供lua 过滤器合法的路由级专属配置即可typed_per_filter_config: lua: type: type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute name: my_lua_script官方文档特别指出像上面lua过滤器这样合法的路由级专属配置legitimate route-specific configuration就是启用该过滤器的有效方式。换言之只要某个过滤器的 per-filter config 是它认得的类型这条路由上就会激活该过滤器反之若过滤器处于disabled: true且路由上没有任何对应的 per-filter config则该过滤器对该路由不生效。与 is_optional / 终端过滤器的关系被标记为disabled: true的过滤器不能是终端过滤器router 等因为终端过滤器必须始终存在于链尾protobuf 注释中已明确此约束is_optional与disabled是相互独立的两个维度前者解决客户端不支持该过滤器时能否容忍配置后者解决过滤器默认是否参与执行。小结与最佳实践围绕 Envoy HTTP 过滤器栈本文梳理出的核心结论可总结为一张速查表关注点结论执行方向Decoder 按配置顺序正序执行Encoder 按配置逆序执行链尾约束最后一个过滤器必须是终端过滤器通常为envoy.filters.http.router由isTerminalFilterByProto()判定路由缓存decodeHeaders()触发路由解析并缓存路由setRoute可手动覆盖缓存路由配合DelegatingRoute子类缓存失效clearRouteCache()会丢弃缓存路由并触发重新匹配且会令setRoute失效轻量换集群仅需换集群时优先使用refreshRouteCluster()matcher / dynamic modules 集群说明符支持安全清空路由缓存可能绕过基于路由的鉴权需审查过滤器顺序鉴权过滤器尽量后置路由级配置per filter config map 的 key 必须与HttpFilter.name一致支持 route / vhost / route config 三级路由级启停FilterConfig.disabled按路由禁用的同时支持HttpFilter.disabled默认禁用 路由级合法 per-filter config 启用在实际生产配置中建议遵循以下实践始终将 router 过滤器置于链尾且不要把它标记为disabled把会修改请求属性header、路径、metadata的过滤器放在前面把基于路由做鉴权的过滤器放在后面避免路由缓存清理导致的鉴权绕过需要按路由差异化行为时优先利用 per filter config map 与 route based filter chain而不是部署多套监听器自定义过滤器若想覆盖路由的集群或超时参考DelegatingRouteEntry与测试类ExampleDerivedDelegatingRouteEntry的写法delegating_route_impl.h、delegating_route_utility.h只覆盖必要方法、其余委托给基础路由以最小化对路由语义的破坏。以上全部行为均有对应源码支撑过滤器链调用逻辑位于连接管理器实现conn_manager_impl.cc路由变更机制位于 delegating_route_impl.h终端过滤器判定位于 filter_config.h协议定义分别位于 http_connection_manager.proto 与 route_components.proto读者可以按图索骥深入研读。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表