首页 / 资讯中心 / 文章详情

Higress云原生API网关深度拆解:从Envoy控制面到Wasm插件原理

Higress云原生API网关深度拆解:从Envoy控制面到Wasm插件原理 ★ FEATURED ARTICLE
其实最早看到 Higress 这个名字我第一反应是又是哪个团队造了个 Kubernetes Ingress 的轮子后来在 KubeCon 上听阿里的同学分享又回去翻了源码和文档才发现这个“云原生 API 网关”跟传统的 Nginx Ingress 完全不是一个物种。Higress 最核心的差异不在于又多了一个 CRD 或者一个控制器而在于它把网关的控制面和数据面彻底拆开然后基于 Envoy 和 Istio 的生态重新定义了配置生成、插件扩展和流量治理的方式。这篇文章不打算念文档我想从原理层面拆一下 Higress 到底是怎么工作的它为什么能兼容多种入口资源为什么敢用 Wasm 插件替代传统 Lua 脚本以及在排障的时候那些绕不过去的坑。适合正在调研网关选型、准备从 Nginx Ingress 迁移或者想搞清楚 Envoy 控制面机制的人。1. 从 Ingress 到 Higress网关演进背后的深层动机1.1 Nginx Ingress 时代我们真正缺的是什么很多团队用 Nginx Ingress 好几年了功能上好像也没少什么路由、Rewrite、TLS 终止、灰度发布都能做。但一旦流量规模上来、业务线变多痛点会非常具体。首先是配置模型的表达能力有限Nginx Ingress 的 Annotation 体系是开放的每个功能都靠不同的注解去开关组合多了以后语义重叠、优先级混乱几乎没法在代码里做静态校验。其次是插件扩展机制很别扭传统 Nginx 网关要么改配置模板、要么用 Lua 脚本嵌入而这套做法在容器环境里极度不友好。改配置模板意味着每次升级都要重新合并Lua 脚本则因为 Nginx 的进程模型导致只能绑定在特定阶段里执行隔离性和弹性都很差。更深一层的问题是控制面太弱。Nginx Ingress 控制器本质上只是一个 watch Kubernetes 资源并生成 nginx.conf 的进程生成配置后靠 reload 触发生效而 reload 会新建 worker、重放连接高峰期一个 reload 就可能带来几分钟的延迟毛刺和长连接断开。这个架构决定了它无法做到精细的动态路由调整也不可能在千级路由的维度上维持毫秒级收敛。Higress 之所以值得写深度原理不是因为它的 UI 好看也不是因为它是阿里的开源项目而是它把 Nginx Ingress 丢失的两样东西补了回来一个是自身控制面的可编程性和扩展能力一个是基于 Envoy 的动态配置模型。有了这两样网关才能真正演变成平台基础设施而不再是“一个吃配置的转发器”。1.2 Higress 的设计定位把“网关”当控制面问题来解决Higress 的定位是云原生 API 网关但它兼容的东西非常多。你依然可以用 Kubernetes 原生的 Ingress 资源接入流量也可以用 Gateway API甚至直接用 Higress 自己的 McpBridge、IngressAnnotation 等 CRD 做更复杂的路由和服务发现。这种兼容不是简单的功能堆叠而是控制面对不同输入源做了统一抽象再转换成 Envoy 能理解的数据模型。这一点是理解 Higress 的钥匙它把网关的所有功能都抽象成“控制面配置生成 数据面执行”两个阶段。你对网关表达的所有期望无论是路由规则、限流策略、认证逻辑还是负载均衡算法最终都会变成 Envoy 的 xDS 配置。而数据面只剩下一件事高效、稳定地执行这些配置。换句话说Higress 的聪明之处在于它不去重新发明一个高性能转发内核而是站在 Envoy 肩膀上把重心放在控制面的表达能力和生态兼容上。2. 控制面与数据面Higress 的运行时骨架2.1 数据面为什么选了 Envoy 而不是 Nginx选择 Envoy 作为数据面不是偶然。Envoy 天生就是为动态配置设计的它的 xDS APIListener Discovery Service、Route Discovery Service、Cluster Discovery Service、Endpoint Discovery Service提供了一套完整的订阅机制配置变更不需要 reload而是通过 ADS 通道增量推送。管控面的策略调整能在秒级内传递到每个数据面实例这对大规模分布式环境来说是刚需。Nginx 的进程模型是“master 读配置、worker 转发流量”配置变更必须重读文件即便采用动态 reload也无法细粒度地只更新某一条路由。Envoy 则不同它的 worker 线程通过共享内存和线程安全的配置对象工作xDS 推送过来的新配置会被热替换连接不断、请求不丢。从底层机制看Envoy 解决的是配置面与数据面的生命周期解耦Higress 选择它相当于直接拿到了一个可以编程的、具备热更新能力的七层转发内核。要注意的是这里的“选择”并不代表 Envoyer 比 Nginx 在所有场景都强。纯静态配置的小规模网关Nginx 反而内存占用更低。但在微服务和多租户场景下Envoy 的动态能力、插件隔离能力和可观测性设计明显更占优。Higress 团队做这个技术选型时显然是站在“平台化”和“规模化”的视角来权衡的。2.2 控制面的配置生成链路全解析Higress 控制面的核心是经过改造的 Istiod如果你了解 Istio 架构这里会很有亲切感。控制面中运行着多个控制器分别负责监听不同类型的输入源Kubernetes Ingress 资源解析 host、path、annotations生成域名路由和对应的流量策略。Gateway API 资源处理 HTTPRoute、TLSRoute、TCPRoute 等标准资源。注册中心服务通过 Nacos、Consul、Eureka 等对接获取服务端点和权重信息。微服务框架的 ServiceEntry将 Dubbo、Spring Cloud 等治理模型转换成 Envoy Cluster。这些输入源会被统一转换成一个内部的抽象配置模型也就是 Higress 的虚拟服务模型。这个模型有点像 Istio 的 VirtualService DestinationRule 的组合但它额外吸收了 Ingress 的语义。之后控制面会把这个内部模型编译为 Envoy 的 xDS 资源并通过 ADS 通道推送到数据面。整个链路是一个典型的“声明式配置 事件驱动”的流程任何输入源发生变化控制器都会将其视为一个事件触发一次重新计算。计算过程中会合并全局路由优先级处理冲突项然后把最终结果编码成 Envoy 的 protobuf 配置。这个模型的好处是上层多种配置可以共存底层又保持唯一事实源不会出现多个控制器各自生成配置导致的覆盖问题。2.3 配置热更新的两个关键路径理解配置热更新可以重点看两条路径上游端点变更和路由规则变更。上游端点变更对应的是 Envoy 的 EDS。Kubernetes 里 Pod 的增删、注册中心里服务实例的上线下线都会被 Higress 控制面捕获。控制面会把变更后的端点列表做一次健康检查过滤再以 EDS 资源推给 Envoy。Envoy 收到新端点后在其负载均衡池里替换旧端点不需要断开任何已有连接新的请求自然会被转发到新的 Pod 上。这里有个细节Envoy 对上游连接采用连接池复用端点变更时空闲连接会被优先复用失效连接会被 lazy 断开这个机制保证了上下游之间的连接不会因服务实例变化而出现断崖式错误。路由规则变更走的是 RDS。当新加一条域名或路径路由时控制面会把对应 VirtualHost 重新编码下发。Envoy 的 RouteConfiguration 是按需加载的Higress 会为每个域名甚至每个 listener 生成独立的 RouteConfiguration这样局部路由变更只需要推送对应配置块不会污染全局。从请求视角来看路由变更的生效粒度是“下一次连接建立时”还是“下一次请求时”取决于 Envoy 的快照机制实际上几乎是无感的。3. 核心机制拆解路由模型、服务发现与可扩展插件3.1 路由规则的三层抽象Ingress 到 Envoy Route 的映射这条路从用户配置到 Envoy 实际执行中间跨了三个层级。理解这三层基本就理解了 Higress 路由原理的 70%。第一层是用户入口也就是 Ingress 或 Gateway API 资源。你写了一条spec.rules[].http.paths[]里面定义了 path、backend service 和 port。第二层是 Higress 的内部抽象。它会为每个 Ingress 资源生成一个VirtualHost概念域名和 TLS 配置挂在 VirtualHost 上路径规则折叠成内部的HttpRoute对象同时注入来自 annotation 的重写、重定向、跨域、认证等能力。第三层是 Envoy 配置内部 HttpRoute 最终被编码成 Envoy 的 RouteConfiguration、VirtualHost、Route、Cluster 等资源。映射过程中最容易踩坑的是 path 匹配的语义。Nginx Ingress 的路径前缀匹配和 Envoy 的 prefix match 虽然都是前缀但边界处理不一样。Higress 做了语义转换把 Ingress 里的路径转换成 Envoy 的 safe_regex 或者 prefix 匹配但要命的是各类 Ingress Controller 对 pathType 的实现有细微差别。我在实践中遇到过一个案例一条/api的前缀路由在 Nginx Ingress 下能匹配到/api和/api/迁移到 Higress 后如果不做处理/api裸路径会被 404。原因是 Envoy 的 prefix match 默认要求字符级前缀Higress 默认配置下对 path 的规范化策略与 Nginx 不同。后来通过调整路径归一化设置并显式配置重定向才解决。这类细节在“深度原理”里往往比大而全的架构图更重要。3.2 多注册中心服务发现的工作原理Higress 一个很突出的设计是 McpBridge 和 Nacos 等注册中心的整合。传统 Ingress 只能把后端 Service 作为转发目标但微服务架构里很多服务的实例信息并不完全由 Kubernetes Service 暴露。比如 Dubbo 服务走的是 NacosSpring Cloud 服务走的是 Eureka这些实例有自己独立的端口和健康检查方式K8s Service 只能看到同命名空间的 Pod。Higress 的控制面里有一个服务发现控制器它负责从多个注册中心拉取服务实例并把它们映射成 Envoy 的 Cluster 和 Endpoint。McpBridge 这个名字拆开看是“Mesh Control Plane Bridge”意思是把外部注册中心的数据桥接到控制面的服务模型中。每出现一个新的服务实例Higress 都会创建一个对应的 Envoy Endpoint并参与负载均衡。这个机制带来的优势是你可以用一条 Ingress 把流量转发到注册中心里的服务名而不是只能转发 K8s Service。比如在 Nacos 里注册了一个名为order-service的微服务Higress 可以像访问本地 Service 一样直接路由到它同时还能利用注册中心上报的健康状态做主动健康检查。底层原理其实不复杂就是控制面把注册中心的实例列表转换成了 Envoy 的 EDS 数据但难的是多注册中心之间的命名冲突处理、元信息同步和权限认证。Higress 选了一条“以注册中心为事实源、以 K8s Service 为辅”的折中路线对国内存量微服务架构非常友好。3.3 Wasm 插件机制为什么能摆脱 Lua 的枷锁Wasm 插件是 Higress 最吸引我的一点。传统网关如 Kong 或 APISIX插件体系是 Lua Nginx 模块好处是启动快、性能好坏处是语言绑定、依赖具体版本而且插件之间无法真正隔离。一个插件崩了可能拖垮整个 worker。Higress 选择 WebAssembly 作为插件运行时核心动机有三个。第一个是语言自由。Wasm 插件可以用 Go、Rust、C 等语言编译。对团队来说不需要为了写网关插件专门去学 Lua直接用 Go 写基于 Envoy Proxy Wasm ABI 的代码就可以。第二个是安全隔离。Wasm 模块运行在独立沙箱内默认拿不到宿主机文件系统和危险 syscall即便插件里有恶意代码或缺陷最坏情况也只是影响当前请求。第三个是热加载。Envoy 原生支持 Wasm 模块的热加载Higress 通过这个能力实现了插件发布不重启这在网关场景里是刚需。不过 Wasm 也不是银弹。它给 Envoy 数据面引入了额外的运行时开销特别是使用 Go 编译的 Wasm执行效率比原生 C 低不少。Higress 团队在官方性能报告中提到纯 Wasm 限流插件在高并发下会有轻微损耗但这与其带来的是动态扩展能力和稳定隔离性相比是值得的。对于高 QPS 的轻量逻辑我个人的建议是能写进 Envoy 原生过滤器就尽量别用 WasmWasm 适合那些需要灵活迭代、逻辑复杂的灰度策略、认证鉴权、报文改写类插件。关于 Wasm 插件要补充一个底层原理Envoy 对 Wasm 模块的调用分同步和异步两种方式。同步方式直接调用模块内的 handler适合请求头处理异步方式用于涉及网络调用或共享存储的场景。Higress 在插件框架里封装了请求阶段与异步接口让插件开发者不需要直接面对 Envoy ABI 的细节但了解这层机制对排查“插件为什么阻塞了请求”这类问题会很有帮助。3.4 可观测性与链路追踪打通网关做得好不好很大程度取决于你能不能在两分钟内定位到一个异常请求。Higress 在这块的数据链路设计得很完整。它的数据面 Envoy 本身就具备 OpenTelemetry 埋点能力配合 Higress 的插件系统可以把 Wasm 插件执行的耗时、状态码、匹配到的路由规则全部作为 span 输出。控制面还会记录配置变更的版本号和下发时间方便确认某条配置是不是真的推到了 Envoy。链路追踪上Higress 支持将 Trace 信息注入到上游请求头并与 SkyWalking、Jaeger、Zipkin 等主流追踪系统对接。原理上Envoy 的 tracing 机制会生成 span并在对外发出请求时注入x-b3-traceid等上下文头上游服务只要接入同一套追踪系统就能把完整链路串起来。排查问题时最常见的使用方式是根据 TraceId 反查网关日志确认是网关层失败还是上游失败然后由网关侧的 span 查看 Wasm 插件耗时、重试次数和负载均衡结果。4. 手把手实践从部署到第一个自定义插件4.1 最小化部署与环境准备讲原理不落实践总觉得飘着。我先说一下最小化环境。Higress 官方推荐使用 Helm 安装到 Kubernetes 集群我这里以一套标准的 K8s 1.26 集群为例。不需要特殊硬件但建议至少预留 1 核 2G 给控制面数据面 Envoy 实例按需扩缩容。安装之前先确认集群里有PV能力因为 Higress 默认会把一些配置和插件包保存在 PVC 里。在我自己的测试集群上直接用本地路径的 PV 也没问题但生产环境建议用高性能存储。还需要确认DNS和证书管理策略。Higress 自带了基于 cert-manager 的证书签发能力如果你已经在集群里跑了 cert-manager可以直接复用。插件市场中有些插件依赖外部服务比如限流插件依赖 Redis这类组件建议在安装网关前就准备好连接串免得后续配置时反复改。4.2 用 Helm 安装 Higress安装命令很简单但有几个容易忽略的参数。基础安装如下helm repo add higress.io https://higress.io/helm-charts helm repo update helm install higress -n higress-system --create-namespace higress.io/higress如果你需要控制面暴露 Dashboard或者想让数据面网关使用LoadBalancer类型 Service可以追加参数。比较常见的配置是helm install higress -n higress-system --create-namespace \ --set higress-core.gateway.service.typeLoadBalancer \ --set higress-console.enabledtrue \ higress.io/higress这里多说一句我在生产环境踩过坑Helm 安装时如果没有指定global.ingressClass安装完成后不会立刻接管集群里已有的 Ingress。Higress 默认使用自己的IngressClass你需要在原有 Ingress 资源上增加ingressClassName: higress才会被接管。如果你希望 Higress 成为默认网关那就要显式配置--set global.ingressClassnginx去兼容老的 IngressClass 命名。这种兼容命名看起来很细但会让很多人误以为“装完就接管了”结果流量完全没变化。4.3 配置一条从域名到服务的完整路由安装完成后可以用 YAML 配置一个最基础的路由。下面是兼容 Kubernetes Ingress 的写法apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: demo namespace: default annotations: higress.io/domain: demo.example.com higress.io/backend-service: demo-service:8080 spec: ingressClassName: higress rules: - host: demo.example.com http: paths: - path: / pathType: Prefix backend: service: name: demo-service port: number: 8080Higress 对 Ingress 的解析流程中会同时读取spec和metadata.annotations。标注里的higress.io/backend-service是它兼容老版本的一种写法如果你使用标准的spec.rules[].backend也是可以的。看到这里可能有人问为什么既要书面的标准写法又要注解因为 Higress 早期的版本为了兼容原本基于 Nginx 的迁移场景保留了一部分注解式配置能力。但从原理上Higress 的控制面最终都会把它们归一到同一条内部路由不存在“注解优先”与“资源优先”的差异。配置完成后用kubectl apply -f应用正常情况下半秒内 Higress 控制面就会把这条路由推送到 Envoy。验证方式可以直接 curl 网关地址也可以在 Higress 控制台上看路由表和 Envoy 配置快照。4.4 编写并加载一个 Wasm 请求头改写插件要真正体会 Higress 的插件原理强烈建议手动跑一次 Wasm 插件。我这里不贴完整代码只说关键流程因为官方仓库的示例更权威。第一步是用 Go 写一个符合 Envoy Proxy Wasm ABI 的函数主要实现on_request_headers在这个回调里通过headers接口给请求添加自定义头。第二步是使用 TinyGo 编译成plugin.wasm。第三步通过 Higress 控制台上传插件的 Wasm 包或者使用 Helm/CRD 方式把 Wasm 文件以 ConfigMap 形式挂载。第四步绑定插件到一条路由或域名生效范围可以是全局也可以是某个路由。背后原理是Higress 控制面会把插件模块的下载地址、SHA256 校验值、执行阶段和路由匹配条件等封装进 Envoy 的EnvoyFilter或Wasm扩展配置里随同路由配置一起推给数据面。Envoy 的 Wasm 扩展会在收到配置后下载模块并实例化后续每个匹配请求都能调用到这个插件。这个过程中比较容易出错的是插件 ABI 版本和 Envoy 版本的对齐官方的 Higress Wasm 插件 SDK 已经封装好了版本兼容层直接用 SDK 就好。5. 常见问题与排查技巧实录5.1 插件不生效的典型原因插件不生效在我这边出现频率最高的是“作用域绑定错误”。你可能在控制台上全局启用了插件但路由上的插件配置为空也可能是路由上绑定插件时选择了“域名级别”而不是“路由级别”导致预期绑定的路径没有生效。原理上Higress 对插件的作用域有严格的三层全局、域名、路由。匹配规则是路由级别优先若路由未绑定再看域名级最后看全局。如果发现插件没生效优先顺着这个优先级倒查。第二个常见原因是 Wasm 模块加载失败。Envoy 加载 Wasm 模块前会先做 SHA256 校验如果上传的模块在控制面上显示“已就绪”但数据面实例的网络不通控制面的对象存储或者拉包超时插件就会静默失败。这种情况看 Envoy 日志最直接会发现failed to fetch wasm module的关键字。解决方式是确保数据面 Pod 可以访问插件仓库地址或者把 Wasm 包放到 ConfigMap 中随 Pod 挂载这样不依赖网络。5.2 503 与连接重置类问题的排查思路503 是网关排障里最常见的告警。用 Higress 时503 的语义要区分几种上游没有健康端点、路由未匹配到 Cluster、Envoy 连接池耗尽后拒绝新请求。其中最容易迷惑的是“路由未匹配”。如果你通过域名访问一个不存在的路径返回 404 不奇怪但如果域名对应对应了一个 Cluster 且所有端点都异常Envoy 会返回 503。需要看清楚 Higress 控制面上的“服务健康状态”如果显示envoy/cluster中 upstream 健康率为 0那问题就出在上游而不是网关本身。连接重置类问题往往与协议和超时设置有关。Envoy 默认对上游空闲连接有idle_timeout如果业务一边使用长连接一边间隔很久才发一次心跳空闲连接会被回收回收瞬间的请求会因为连接重置而报错。Higress 支持在路由或服务资源配置idle_timeout和max_requests_per_connection原理是直接映射到 Envoy 的CircuitBreakers和Cluster配置上。遇到偶发连接重置先别急着加重试先看是不是长连接超时抖动。5.3 控制面与数据面配置不一致的处理有时候控制面显示路由已经生效但实测流量还是走老策略这种“看起来生效了”的问题非常骗人。先确认 Envoy 实际的配置快照是否更新。可以通过 Envoy admin 接口获取Higress 的数据面默认暴露了 admin 端口在 Pod 里执行curl localhost:15000/config_dump?resourceROUTES就能看到当前生效的 RouteConfiguration。如果配置 dump 里没有新路由说明控制面的推送链路出了问题如果配置 dump 已经更新但行为不对那问题在数据面执行层。我遇到过一种情况Higress 控制面升级后旧的配置缓存未失效导致同一份 Ingress 生成了两套 VirtualHost新规则被旧规则覆盖。解决方法不复杂重启控制面或者触发一次配置全量重推必要时可以删除网关的 ConfigMap 缓存并滚动重启。这个坑对于从旧版本迁移的人来说尤其要留意升级后记得核对控制面日志中的reconcile完成状态。6. 关于 Higress 的一些长期观察聊完原理和排障最后分享一点我个人在长期使用过程中形成的观点。Higress 之所以能在 API 网关领域里站稳脚跟不仅仅因为它是阿里的开源项目更因为它把“兼容存量”和“面向未来”这两件事同时落到了设计里。它兼容 Nginx Ingress 的存量资源让老用户可以平滑迁移它拥抱 Wasm 和 Gateway API让新用户可以在不换网关的情况下获得插件化和标准化的能力。但从实际运维角度来看Higress 的控制面复杂度确实比 Nginx Ingress 高一个数量级。Istiod 是一个完整的服务网格控制面运行时的资源消耗、日志量、配置事件频率都远超普通 Ingress Controller。团队如果只是管理两三个服务用 Higress 属于大炮打蚊子但如果服务数量上百、且有大量路由策略和灰度规则需要动态调整Higress 的优势就会非常明显。如果你正在做技术选型我给的建议是先列出你未来一年内最头痛的 10 个网关问题看 Higress 能解决几个。如果超过五个那值得深挖如果主要还是“改一条路由就 reload 一次”那先解决自己的运维流程可能比换网关更重要。网关本质上是一个控制面与数据面协同的系统工程不能只看转发性能还要看配置管理、插件生态、可观测性和团队学习成本。Higress 在这几个维度上给出了一个相对均衡的答案至于这个答案适不适合你最终还是要拿真实流量去验证。
阅读完成 · 觉得有帮助?
咨询建站