ARTICLE DETAIL

资讯详情

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

Higress 云原生网关深度解析:架构、xDS 配置流转与 Wasm 插件机制

Higress 云原生网关深度解析:架构、xDS 配置流转与 Wasm 插件机制 1. 从一次网关选型聊起为什么我要啃 Higress 的源码去年底我接手了一个挺棘手的活儿把公司内部跑了三年多的 Nginx Lua 自研网关整体换掉。原因不复杂业务侧微服务数量从最初的十几个膨胀到两百多个原来那套基于 OpenResty 手写的路由、鉴权、限流逻辑已经彻底失控——配置文件八千多行改一个路由要三个人 review灰度发布全靠人肉切流量出问题排查得翻半天日志。团队一开始想直接上 Istio 的 Ingress Gateway毕竟社区热度摆在那儿但真拉了个测试环境跑下来发现两个绕不过去的坎一是 Envoy 的配置模型对业务同学太不友好一个简单的 header 改写要写一堆 YAML二是 Istio 整套控制面太重我们只想解决南北向流量不想把服务网格那一摊子全背上。后来在几个技术群里看到有人提 Higress说是阿里内部孵化、2022 年开源的云原生网关底层同样是 Envoy但控制面做了大量简化还兼容 Nginx Ingress 的注解。我抱着试试看的心态搭了个 demo结果两天就跑通了核心链路。但真正让我决定深入研究的是某次线上一个诡异的路由不生效问题——表面看配置全对实际流量就是走不到后端。那次排查逼着我把 Higress 的配置下发链路从 Console 一路追到 Envoy 的 xDS 接口才算真正摸清了它的工作原理。这篇内容就是那次深度折腾的产物。我不打算写成官方文档的复读机而是想从一个真正在生产环境踩过坑的从业者视角把 Higress 的核心架构、配置流转、Wasm 插件机制、和 Envoy/Istio 的关系这几件事讲透。如果你正在做网关选型或者已经用了 Higress 但对其内部机制一知半解再或者你是个想理解云原生网关设计思路的后端/运维同学这篇应该都能给你一些参考。全文会涉及不少源码级别的细节但我会尽量用生活化的类比把复杂概念讲明白保证你看完能自己动手复现关键实验。2. Higress 的整体架构控制面与数据面到底怎么分工2.1 一张图看懂三个核心组件的关系很多人第一次接触 Higress 会被它的组件命名搞晕higress-controller、higress-console、higress-gateway再加上底层的 Envoy到底谁管谁我用一个类比来解释把整个网关系统想象成一家餐厅。higress-console 是前台点餐系统你在这里下单配置路由、插件higress-controller 是后厨的调度员它把前台的订单翻译成厨师能看懂的做法把配置转成 Envoy 能识别的 xDS 协议higress-gateway 就是真正炒菜的厨师Envoy 进程它只负责按指令干活不关心订单从哪来。具体到技术层面Higress 的架构可以拆成这么几层数据面一个基于 Envoy 的进程监听 80/443 等端口处理所有实际流量。Envoy 本身是 C 写的性能强悍支持 HTTP/1.1、HTTP/2、gRPC、WebSocket 等协议。控制面higress-controller 是核心它本质上是一个实现了 Kubernetes Controller 模式的组件监听 Ingress、McpBridge、WasmPlugin 等 CRD 资源的变化然后通过 xDS 协议把配置推给 Envoy。管理面higress-console 提供 Web UI底层调用 controller 的 API 来增删改配置。它其实是个可选组件你完全可以只用 YAML 操作。这里有个关键点容易被忽略Higress 的 controller 和 gateway 是分离部署的。controller 通常以 Deployment 形式跑在 K8s 里gateway 可以是 Deployment 也可以是 DaemonSet。它们之间通过 gRPC 长连接通信controller 主动推送配置gateway 被动接收。这种设计的好处是控制面挂了不影响数据面继续工作——Envoy 会把最后一次收到的配置缓存在内存里即使 controller 重启现有流量也不受影响。2.2 为什么选 Envoy 而不是自研数据面这个问题我被问过很多次。自研网关用 OpenResty 不是挺好吗为什么 Higress 要套一层 Envoy我研究下来的结论是Envoy 解决的是通用流量处理能力的问题而自研解决的是特定业务逻辑的问题。Envoy 有几个自研很难短期追上的优势。第一是协议支持广度HTTP/2、gRPC、Dubbo、Redis 这些协议的代理它原生就支持你自研的话每个协议都得写一遍解析器。第二是可观测性Envoy 内置了丰富的 metrics、access log、tracing 能力和 Prometheus、Jaeger 这些生态无缝对接。第三是动态配置能力xDS 协议让配置热更新成为可能不用 reload 进程。但 Envoy 的短板也很明显配置模型极其复杂一个 Listener 下面挂 FilterChainFilterChain 里又有 NetworkFilter 和 HttpFilter层层嵌套。直接用它做业务网关运维成本太高。Higress 的价值就在于在 Envoy 之上做了一层符合 K8s 习惯的抽象让你用 Ingress 注解和 CRD 就能完成大部分配置同时保留了 Envoy 的全部底层能力。2.3 和 Istio 的关系是替代还是互补这是选型时绕不开的问题。我的理解是Higress 和 Istio 在南北向流量场景下是竞争关系在东西向场景下可以互补。Istio 的 Ingress Gateway 本质上也是 Envoy但它的配置模型完全围绕 Istio 的 VirtualService、Gateway 这些 CRD 设计学习曲线陡峭。而且 Istio 的控制面istiod功能太重包含了服务发现、证书管理、策略执行等一大堆东西如果你只想做个 API 网关这些大部分用不上。Higress 则轻量得多它的 controller 只做网关配置下发这一件事。同时它兼容 Nginx Ingress 的注解意味着你从 Nginx 迁移过来几乎零成本。我实测过一个中等规模的配置约 50 条路由、10 个插件Higress 的 controller 内存占用在 200MB 左右而 istiod 动辄 1GB 起步。不过 Higress 也能和 Istio 共存。你可以用 Istio 管理东西向的服务网格用 Higress 管理南北向的入口流量两者共享同一套 Envoy 数据面通过配置不同的 Listener。这种混合架构在一些大型企业里已经有落地案例。3. 配置从 YAML 到 Envoy 的完整旅程3.1 一次路由配置下发的全链路追踪理解 Higress 工作原理最好的方式就是跟着一条配置走完全程。假设我在 Console 上新建了一条路由域名api.example.com路径/user/*转发到后端服务user-service。这条配置到底经历了什么第一步Console 调用 controller 的 REST API把配置写入 K8s 的 Ingress 资源或者 Higress 自己的 McpBridge CRD。第二步controller 里的 Informer 监听到 Ingress 变化事件触发 reconcile 逻辑。第三步reconcile 过程中controller 把 Ingress 转换成 Envoy 的 Cluster、Listener、RouteConfiguration 等内部对象。第四步这些对象被序列化成 xDS 协议规定的 protobuf 格式通过 gRPC 流推送给 gateway。第五步Envoy 收到 xDS 更新后动态重建对应的 Listener 和 Route整个过程不断连接、不 reload。我实际抓过这个链路的日志从 Console 点击保存到 Envoy 生效正常情况下在 200ms 到 500ms 之间。如果超过 1 秒还没生效基本可以断定是 controller 的 reconcile 卡住了常见原因是配置有语法错误导致转换失败或者 K8s API Server 响应慢。3.2 xDS 协议里到底传了什么xDS 是一组协议的统称包括 LDSListener Discovery Service、RDSRoute Discovery Service、CDSCluster Discovery Service、EDSEndpoint Discovery Service等。Higress 主要用到这四个。用餐厅类比CDS 告诉 Envoy 后厨有哪些灶台后端服务集群EDS 告诉 Envoy 每个灶台上有哪些厨师具体的 Pod IPRDS 告诉 Envoy 什么订单该送到哪个灶台路由规则LDS 告诉 Envoy 餐厅开哪个门迎客监听端口和域名。这里有个细节值得注意Higress 默认用的是Delta xDS而不是 SotWState of the WorldxDS。区别在于SotW 每次更新都推送全量配置Delta 只推变化的部分。在配置规模大的场景下Delta 能显著降低 controller 和 gateway 之间的网络开销。我实测过一个 500 条路由的配置全量推送一次约 2MB而增量推送通常只有几 KB。3.3 配置不生效时的排查思路回到我开头提到的那个诡异问题配置全对但流量走不到后端。我当时的排查路径是这样的确认 Console 配置已保存查 K8s 里对应的 Ingress 资源是否存在kubectl get ingress -n higress-system。确认 controller 已 reconcile看 controller 日志有没有报错重点搜 reconcile 和 error 关键字。确认 Envoy 已收到配置通过 Envoy 的 admin 接口curl localhost:15000/config_dump导出当前配置搜索目标域名。确认路由匹配规则这一步最容易出问题。Envoy 的路由匹配是有优先级的精确匹配 前缀匹配 正则匹配。我当时的问题就是一条更宽泛的前缀路由把精确路由覆盖了。提示Envoy 的 admin 接口默认监听 15000 端口但 Higress 出于安全考虑可能没对外暴露。你可以在 gateway 的 Pod 里用kubectl exec进去本地 curl 访问。这个排查链路我后来整理成了一张检查清单每次配置不生效就按顺序过一遍基本五分钟内能定位问题。4. Wasm 插件机制Higress 最被低估的能力4.1 为什么插件要用 Wasm 而不是 LuaNginx 时代大家写插件都用 Lua简单直接。但 Lua 有几个硬伤一是性能Lua 是解释执行高频调用下开销明显二是隔离性Lua 脚本跑在同一个进程里一个脚本死循环能把整个网关拖垮三是语言生态Lua 的库太少了想调个 JWT 解析都得自己造轮子。WasmWebAssembly恰好解决了这三个问题。它是编译执行的性能接近原生它运行在沙箱里天然隔离它支持多种语言编译Rust、Go、C、AssemblyScript你可以用熟悉的语言写插件。Higress 是国内比较早把 Wasm 插件机制做成熟的网关项目。它的做法是Envoy 通过 Wasm 运行时早期是 V8后来支持 Wasmtime加载插件插件在请求处理的各个阶段onRequestHeaders、onRequestBody、onResponseHeaders 等插入自定义逻辑。4.2 一个鉴权插件的完整实现拆解我拿一个最常用的 JWT 鉴权插件来演示。假设需求是所有请求必须带合法的 JWT token否则返回 401。用 Go 写的话核心逻辑大概是这样func onHttpRequestHeaders(ctx plugin.HttpContext, req plugin.HttpRequest) types.Action { token : req.Header().Get(Authorization) if token { ctx.SendHttpResponse(401, nil, []byte(missing token)) return types.ActionPause } // 解析并校验 JWT claims, err : parseJWT(token) if err ! nil { ctx.SendHttpResponse(401, nil, []byte(invalid token)) return types.ActionPause } // 把用户信息透传给后端 req.Header().Set(X-User-Id, claims.UserId) return types.ActionContinue }这段代码编译成 Wasm 后通过 WasmPlugin CRD 挂载到指定路由上。关键点是ActionPause和ActionContinue这两个返回值前者表示中断请求链直接返回响应后者表示放行继续走后续的 Filter。我实测下来一个这样复杂度的 Wasm 插件单次请求的处理耗时在 50 微秒左右相比 Lua 实现快了大概 3 到 5 倍。当然具体数字跟插件复杂度和机器配置有关但量级上的优势是确定的。4.3 插件热更新的坑与正确姿势Wasm 插件最爽的一点是支持热更新——不用重启 Envoy 就能换插件版本。但这里有个坑我踩过插件更新不是原子的。具体表现是当你更新 WasmPlugin 的镜像地址后Envoy 会异步去拉取新的 Wasm 模块。在拉取完成之前旧版本还在处理请求。如果新模块拉取失败比如镜像地址写错、网络不通Envoy 会继续用旧版本但 controller 那边可能已经认为更新成功了。这就导致 Console 上显示的是新版本实际跑的是旧版本。正确的做法是更新后一定要通过 Envoy 的 admin 接口确认 Wasm 模块的加载状态curl localhost:15000/wasm能看到当前加载的模块列表和版本。另外建议在 CI 流程里加一步更新后自动发一个测试请求验证插件行为是否符合预期。注意Wasm 插件虽然隔离性好但也不是绝对安全。恶意插件仍然可以通过大量内存分配拖垮 Envoy。生产环境建议只加载自己编译的插件不要随意使用来源不明的第三方 Wasm 模块。5. 生产环境落地时那些文档没写的事5.1 性能调优从默认配置到压测数据Higress 的默认配置是面向通用场景的直接上生产往往需要调优。我做过一轮比较系统的压测环境是 4 核 8G 的虚拟机后端是一个简单的 echo 服务。默认配置下单实例 QPS 大概在 8000 左右P99 延迟 12ms。这个数字不算差但离 Envoy 的理论上限还有距离。我调整了几个参数后有明显提升参数默认值调优值效果worker 线程数自动绑定 CPU 核数QPS 15%连接池大小100500高并发下 P99 降低 30%access log 采样全量1%CPU 占用降低 8%Wasm 运行时V8Wasmtime内存占用降低 40%其中 access log 采样这个点特别值得说。默认全量打日志在高 QPS 下非常吃 CPU我建议生产环境至少调到 10% 采样或者干脆只记录错误请求。Wasm 运行时从 V8 换成 Wasmtime 是 Higress 较新版本支持的特性Wasmtime 启动更快、内存更省但某些依赖 V8 特有 API 的插件可能不兼容切换前要测试。5.2 灰度发布与多集群流量管理Higress 在流量管理上有个很实用的能力基于权重的灰度发布。你可以给同一个后端服务配置两个版本一个权重 90一个权重 10然后逐步调整比例。配置方式是在 Ingress 注解里加higress.io/weight相关字段或者用 McpBridge 定义多集群后端。我实际用下来权重调整是秒级生效的比 Nginx 的 reload 体验好太多。多集群场景下Higress 支持通过 McpBridge 把多个 K8s 集群的服务注册到同一个网关。这个能力在混合云场景下很有用——你可以把部分流量导到云上集群部分留在自建机房。不过要注意网络延迟跨集群调用的 RT 会比同集群高不少建议只对延迟不敏感的业务用。5.3 监控告警体系怎么搭网关是流量的入口监控做不好等于裸奔。Higress 暴露的 metrics 非常丰富我建议至少监控这几类流量指标QPS、请求总数、响应码分布。响应码里 5xx 突增是最重要的告警项。延迟指标P50、P95、P99。P99 超过阈值通常意味着后端有问题或者网关本身过载。资源指标CPU、内存、连接数。Envoy 的连接数是有上限的打满后会拒绝新连接。插件指标Wasm 插件的执行耗时和错误率。插件写不好会拖慢整个请求链。我用 Prometheus Grafana 搭了一套看板告警规则里最关键的一条是5xx 比例连续 1 分钟超过 1% 就触发告警。这条规则帮我提前发现过好几次后端服务的异常。6. 关于 Higress 原理我踩过之后才明白的几件事第一个体会是不要试图绕过 controller 直接改 Envoy 配置。我早期图省事直接通过 Envoy 的 admin 接口改过路由当时确实生效了但下一次 controller 推送配置时就被覆盖了而且覆盖得悄无声息。Higress 的设计哲学是 controller 是唯一配置源所有变更都必须走它。第二个体会是Wasm 插件的调试比想象中麻烦。因为插件跑在沙箱里你不能像普通程序那样打断点。我的做法是在插件里加详细的日志输出通过 Envoy 的日志级别控制来观察。另外 Higress 提供了一个本地调试工具可以在不部署到 K8s 的情况下测试插件逻辑这个工具在开发阶段能省很多时间。第三个体会是理解 xDS 的推送机制对排查问题至关重要。很多配置不生效的问题根源都在于 xDS 推送失败或者 Envoy 拒绝接受配置。学会看 Envoy 的 config_dump 和 controller 的 xDS 日志能解决 80% 的配置类问题。最后分享一个我常用的调试技巧在 gateway 的 Pod 里起一个 tcpdump抓 controller 和 gateway 之间的 gRPC 流量能看到 xDS 推送的原始内容。虽然 protobuf 是二进制的不太好读但至少能确认推送有没有发生、频率如何。这个技巧在我排查一次推送风暴controller 频繁推送导致 Envoy CPU 飙高时起了关键作用。
返回列表