
1. 为什么在K8s生态里还要再造一个网关说到K8s网关大多数人第一反应是nginx-ingress其次是apisix这类基于OpenResty的网关再新一点是emissary、traefik。Higress从2023年开源到现在很多人看它的第一眼会觉得“又是一个包装了Envoy的ingress controller”但真去拆原理会发现它和传统“把Nginx配置翻译成YAML”的思路差别非常大。Higress本质上是一个云原生网关不只是K8s Ingress的实现它的核心定位是以Gateway API为入口、以Istio控制面为骨架、以Envoy为数据面的下一代网关。说人话就是它不自己写一套路由引擎而是站在Istio和Envoy的肩膀上把整个服务网格里已经被验证过的数据面能力直接拿来做南北向流量管理。那为什么还要“再造”一遍原因很简单Istio的入口网关istio-ingressgateway虽然底层很强大但配置模型完全面向服务网格普通用户根本玩不转。你想只接一个HTTP域名转发到后端Service需要同时搞Gateway、VirtualService、DestinationRule三个CRD还涉及端口命名、Sidecar注入等一堆细节。Higress要解决的就是这个体验断层——它用K8s社区最熟悉的Ingress/Gateway API语义做用户入口把复杂的Istio模型藏到内部同时把阿里内部多年沉淀的网关插件能力限流、熔断、鉴权、WAF等以WASM插件的形态开放出来。所以这篇分析我不会只讲“怎么用”而是把Higress从控制面到数据面的几条关键链路拆开配置怎么进的、路由怎么定的、插件怎么跑的、挂了怎么办。适合两类人看一类是在生产环境里已经用上Higress、想搞清楚一条请求到底经过了哪些环节的后台开发者另一类是做网关选型、正在对比Kong和APISIX的架构师。2. 配置流转从K8s API到Envoy Listener之间发生了什么Higress对外暴露的配置入口有三种Ingress资源、Gateway API资源和Istio的VirtualService等CRD。但不管走哪个入口最终都要落到Envoy真正执行的配置上。这条链路是最值得先讲透的因为后面所有路由、插件、灰度逻辑都建立在这条链路上。2.1 控制器如何“翻译”配置Higress的控制器以Pod形式跑在集群里它做的事情可以理解成一个编译器监听K8s API Server上的资源变化把用户提交的Ingress、Gateway、HttpRoute等声明式配置翻译成Istio层能识别的配置再交给Istio的控制面去生成最终下发到Envoy的xDS配置。这里有一个容易误解的点Higress并不是直接生成Envoy的Listener/Route/Cluster而是先生成Istio的VirtualService、Gateway、DestinationRule。为什么多一层因为Higress的架构里控制面核心是Istio本身它复用了Istio的协议栈、服务发现、配置校验和安全能力。如果不经过Istio这层等于要自己实现一套从服务注册中心到数据面的同步逻辑那工作量完全不是一个量级。举个实际例子你提交一个Ingress规则apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: demo annotations: higress.io/rate-limit: on spec: ingressClassName: higress rules: - host: demo.example.com http: paths: - path: /api pathType: Prefix backend: service: name: demo-service port: number: 8080Higress controller收到这个变更后会做几件事校验域名、路径、后端服务是否存在以及正则表达式是否合法。根据IngressClassName过滤出自己负责的资源避免和nginx-ingress抢活。将规则转换成Istio的VirtualService其中host是demo.example.comhttp路由的match是prefix /apiroute目标指向demo-service:8080对应的Envoy cluster。将注解中的限流配置单独提取出来转换成插件配置而不是混入VirtualService。这个“翻译”过程在内存中维护了一个全量模型每次事件变更都会做一次diff只有真正变化的配置才会触发推送。这一点对生产环境非常重要否则一次Deployment滚动更新就会导致整个网关全量reload。2.2 xDS协议只做最后一步Istio控制面生成的配置最终通过xDS尤其是LDS、RDS、CDS、EDS协议下发给Envoy。xDS的核心是一套gRPC流式推送机制Envoy启动后主动建立与PilotIstio的discovery server的连接订阅自己关注的所有配置类型。Higress用Istio这套协议有一个直接的性能红利Envoy对xDS的支持是原生内置的增量推送Delta xDS可以只发送变化的部分而不是把整份配置重新推一遍。一个拥有几千条路由规则的集群改一条路径的权重时数据面收到的可能只是几个字节的增量更新而不是几MB的全量JSON。我在实际压测中观察过这个增量推送能保证配置变更的P99生效时间在毫秒级到秒级之间。它在控制平面的CPU负载上也表现良好——因为Istio的PushContext会预先编译好全量配置的索引推送时只序列化目标Envoy需要的那一部分。2.3 为什么能秒级生效K8s Ingress ControllerNginx版的配置变更通常要经过“写模板→reload Nginx master进程→worker平滑重启”的过程。即便reload做得再平滑也会带来连接重新分配、内存短暂飙升的问题碰到高并发时偶尔还能看到502。Higress完全绕开了reload这个动作。Envoy本身就是面向动态配置设计的Listeners和Routes都是通过API动态更新的不存在“重启进程”的概念。控制面推送一份新的Listener配置后Envoy会在同一个进程里完成Listener的热替换旧Listener排空已有连接后退出新Listener开始接受新连接。这个机制使得高流量场景下的配置变更肉眼无感。但这里也有一个需要留意的点Listener的热替换不是瞬间的。Envoy默认的drain timeout是几秒如果旧的Listener上还有长连接在排队这些连接会继续走旧路由直到超时。所以在做流量切换类的变更时最好预估一下长连接的排空时间不要把drain timeout调到0否则高峰期会看到瞬时连接断开。3. 路由引擎拆解虚拟服务、域名匹配与灰度发布的真实机制路由是网关最基础也最关键的能力。Higress的路由模型不是简单的一张“域名路径→Service”映射表而是分层匹配的理解这层对排查问题尤其重要。3.1 路由匹配的优先级并不是“谁先声明谁先来”Higress内部是基于Istio的VirtualService来实现路由的。当我们通过Ingress或者共用Ingress注解提交路由时每条路由都必须经过三层匹配域名匹配authority/ Host header路径匹配exact / prefix / regex条件匹配Header、Query参数、Method等优先级顺序是精确匹配域名 通配符域名 任意域名路径则是精确(exact)优先前缀(prefix)次之正则(regex)最后。如果两条路由的域名和路径都完全相同则按创建时间的先后顺序决定谁在前谁先命中。这里有一个生产环境常见的坑如果同一个域名下同时定义了 /api 的前缀路由和 /api/v1/order 的精确路由请求 /api/v1/order 时Envoy按精确优先原则会命中后者而不是按“更长的前缀优先”。如果你沿用了Nginx的location匹配逻辑到这里很容易判断错。Higress在路由控制台里会把Ingress中的每一条path翻译成一个VirtualService的http route项并在路由项里显式标注匹配优先级。这就意味着控制台上显示的“优先级”数字本质上是Istio VirtualService里route列表的下标顺序而不是一个独立的优先级字段。3.2 灰度发布和后端权重调整的实现原理我们用Higress做灰度发布的时候最常见的方式是在Ingress注解里配置权重或者直接编辑VirtualService的weight字段。它的底层实现是Envoy的weighted_clusters。比如给服务A分配80%流量、服务B分配20%流量http: - match: - uri: prefix: /api route: - destination: host: demo-service-v1 weight: 80 - destination: host: demo-service-v2 weight: 20Envoy在收到这个路由后会为demo-service-v1和demo-service-v2各维护一个locality aware的权重表。实际的请求分发发生在worker线程内部按随机数落在哪个区间去选择对应的cluster这个过程不会经过额外的组件性能损耗几乎可以忽略。有一类灰度需求会要求“同一个用户只走新版本或旧版本”——这叫会话保持不是简单的权重分配能解决的。Higress支持在路由级别配置基于Header或Cookie的一致性哈希它映射到Envoy的hash_policy。如果你看到控制台里有“灰度标签”功能本质上是按header的version值做hash保证相同header值的请求始终走到同一版本。3.3 HTTP/gRPC/Dubbo多协议支持的处理路径Higress支持Dubbo协议这一点是它和其它K8s Ingress Controller拉开差异的重要卖点。常规Ingress Controller只认HTTP遇到Dubbo这类TCP协议要么裸转发、要么干脆不支持。Higress的做法是给Envoy挂载了一个专门的WASM协议插件在Listener层做协议识别。一个Listener上同时开启了HTTP和Dubbo的filter chainEnvoy根据端口和报文头部的magic number判断该走哪条链。Dubbo流量进入后插件会把Dubbo的请求体解析出来提取出interface、method、version等信息映射到注册到Higress上的服务模型上然后做流量分发。这个分发的目标是后端的Dubbo Provider整个过程不需要业务应用改任何代码也不用单独部署Dubbo代理。这个设计让我想到了一个问题协议扩展能力是否够强Higress的答案是把协议解析做成WASM插件而不是固化在网关主进程里。如果你有自研的RPC协议按照WASM插件规范写一个协议解析器就能接入Higress这比修改Envoy C代码、重新编译整个网关的维护成本低很多。4. WASM插件机制Higress最有代表性的设计取舍如果说架构复用Istio还在“用了别人的轮子”的范畴内那Higress把WASM插件做成核心扩展机制值得单独拿出来说说。网关类产品的扩展能力通常是选型的关键从Nginx的C模块/Lua到APISIX的Lua插件再到Envoy的WASM走了一条从强耦合到弱耦合的路线。4.1 为什么不用传统C插件或Lua插件Nginx的C模块性能最好但写了就不能热加载编译环境要和Nginx主体保持一致升级Nginx意味着所有模块重新编译。Lua方案OpenResty系开发效率高但运行时里带的Lua状态、依赖库、调试能力都会成为线上排障的变量而且很难做到多租户隔离。WASMWebAssembly的思路是把可执行代码编成字节码由Envoy内置的WASM虚拟机在沙箱里执行。这个选择有几个直接的好处插件可以独立于网关主进程升级不会引入reload。插件的运行环境被沙箱隔离插件崩溃不会带崩整个Envoy进程。插件可以用Go、Rust、C等语言编写团队不用为了写网关插件去学Lua或C。每个插件都有独立的资源配额能限制内存和CPU占用做多租户相对安全。代价是WASM的执行效率比原生C和LuaJIT略低网络密集型的场景下会有一点影响。但从Higress的压测报告和我自己的测试结果看通用场景损失在可接受范围内换成灵活性和迭代效率是非常划算的。4.2 插件运行和生命周期管理Higress的插件在Envoy里以HttpFilter的形态挂在路由链路上。一条请求从Listener进来会先走内置的CORS检查、限流、鉴权等逻辑再走到路由转发。每个插件的执行阶段分为请求进来时的on_request_header做鉴权、参数改写、路由重定向等。请求体处理阶段的on_request_body做报文体校验、加解密。转发后处理响应的on_response_header/on_response_body做响应头注入、响应体改写。插件状态在Envoy的worker线程间是共享的同一时刻可能有几十个worker线程在调用同一个WASM插件的instance。Envoy的WASM运行时通过shared queue把插件的调用请求串行化保证没有数据竞争。这也是为什么WASM插件里不能写阻塞式的网络调用——你阻塞整个worker线程的共享队列就等于阻塞了这台网关机一半的流量。Higress给插件开了几个常用的SDK接口拿请求头、设响应头、读写动态元数据、发起异步HTTP调用、访问Redis等。其中异步Redis访问是重点全局限流的计数就是靠它实现的。4.3 内置插件体系与Redis全局限流的联动Higress开箱即用内置了几十个插件涵盖流量治理限流、熔断、降级、安全防护WAF、IP黑白名单、JWT鉴权、Key Auth、HMAC、可观测性日志、Metrics、Tracing三大类。全局限流是一个比较典型的设计。它与本地限流不同本地限流的计数存在单个Envoy实例的共享内存里每个副本只限制自己收到的流量集群整体限流值需要把总配额除以Pod副本数一旦某个Pod重启计数就清零。全局限流则把所有计数放在Redis中每个Envoy实例在处理请求时通过WASM插件向Redis发一个原子的计数指令超过阈值就直接返回限流响应。这里有个细节值得注意为了降低对Redis的访问压力Higress的全局限流插件支持“令牌桶预取”模式Envoy实例会每隔一个窗口期从Redis拉取一批配额本地消耗避免每个请求都打一次Redis。这个窗口期是可配置的窗口越小精度越高、Redis压力越大窗口越大性能越好、突发流量控制精度越差。生产环境一般把burst配到正常QPS的2到3倍把窗口期设为1秒既平滑又能挡住瞬时尖峰。5. 高可用视角控制面挂了流量会不会断网关是流量路径上的单点用户对它的关注必然包含“网关自己挂了怎么办”。Higress的高可用设计分成控制面和数据面两层分别讨论。5.1 控制面和数据面的解耦Higress的数据面是多个Envoy副本组成的Deployment控制面是Higress Controller和Istio Pilot组成的另一个Deployment。两者通过xDS通信但它们的生命周期并不强绑定。Envoy启动时会从控制面拉取全量配置拿到之后即使控制面进程全部宕机Envoy仍然能按最后一份配置持续转发流量只是无法接收新的配置变更。也就是说控制面宕机影响的是“变更能力”而不是“转发能力”。这一点对线上稳定性很关键日常发布、弹性伸缩、域名新增这类操作会失败或延迟但存量业务不受影响。控制面自身的高可用由K8s的多副本保证。Istio Pilot采用主备模式多个副本通过Lease选举出一个leader只有leader会处理配置推送其他副本处于standby。leader宕机后standby会在几十秒内完成选举并接管推送任务。这么短的中断对数据面来说只是断开了xDS流重连后会全量重推一次配置完成同步。5.2 多副本、反亲和性与优雅排空数据面多副本部署时我强烈建议在Deployment上配置PodAntiAffinity让不同副本尽量调度到不同节点。否则一旦某个物理节点故障所有网关副本同时失效哪怕K8s能快速重建Pod也需要一个拉取镜像和注册这里的注册是指创建Pod、完成Ready检查、加入Service Endpoints的完整过程。对流量中断的恢复来讲这几分钟太长了。配了反亲和性之后单节点故障最多损失1/N的副本剩下副本能扛住流量。另一个容易忽略的是优雅排空。当网关Pod因为发布或节点驱逐被销毁时K8s默认只会等terminationGracePeriodSeconds也就是30秒的宽限期。如果网关Pod里还活着大量长连接Pod进程强杀后这一批既有连接全部断掉。网关对外暴露的是Service但K8s Service的Endpoints摘除不是即时的——Pod进入Terminating状态后Endpoint还会短暂保留一段时间新连接依然会打过来。解决方案是给Higress网关Pod设置preStop钩子在进程退出前主动让Envoy进入drain模式排空存量连接同时把terminationGracePeriodSeconds调整到长连接平均时长的2倍以上。我在生产里通常配60到120秒这个值太低容易断连太高又会拖慢整个发布流程需要根据业务长连接占比来权衡。6. 调优与避坑自己在生产环境里处理过的问题原理讲完聊几个我在实际使用Higress时踩过的坑和调优经验。这些不一定写在官方文档显眼位置但遇到了真的会卡你半天。6.1 内存消耗和副本数规划Envoy本身的内存大头是线程数乘以每个线程的buffer池再加上Listener和Route配置的缓存。Higress默认会按宿主机的CPU核数开worker线程一台16核的节点光线程内存就有不小的基数。如果只跑几百条路由Pod的内存limit给的特别激进比如1G可能在流量高峰时直接被OOM Kill。我的经验值是单副本最低给2G内存路由条目在1000条以上、且开启了全局限流和WAF插件时内存给到4G以上比较稳。CPU的request不用太高0.5核就够了但limit至少要给到2核否则高并发下worker线程虽然创建了却拿不到调度时间延迟会明显上升。网关副本数的规划也不能只看QPS还要考虑单副本故障时剩余副本能否扛住整体流量。一般建议至少3副本起步核心集群5副本再配合HPA按CPU和连接数双指标扩缩。6.2 用日志和监控定位配置生效异常如果你发现某个路由变更没有生效先别怀疑控制面Bug。通常问题出在Ingress资源没有指定正确的ingressClassName或者Higress controller因为RBAC权限问题没收到事件。排查链路是查看Higress controller的日志确认它有没有监听到Ingress的add/update事件。查看对应VirtualService是否生成成功kubectl get virtualservice -n higress-system。如果VirtualService存在但Envoy行为不符进入网关Pod执行sidecar-cli或者看Admin接口的config_dump确认Listener和Route是否已经加载了预期规则。Higress提供了Admin接口但默认不会直接暴露到集群外。你可以通过kubectl exec进入Pod然后访问localhost:15000来查看Envoy运行时配置。6.3 插件热更新过程中的一个小坑WASM插件更新时Higress会通过Istio的WasmPlugin资源推送新的wasm二进制内容Envoy会加载新的插件实例。但插件实例的切换不是无状态的旧实例上还在执行的请求会继续跑完新请求走到新实例上两个实例会短暂并存。如果你的插件里缓存了某些状态比如缓存了Token校验结果更新后新实例的缓存是空的突然的冷缓存会让依赖后端的鉴权服务瞬间被打满。解决方式是在灰度更新插件时先给鉴权后端预留足够的冗余或者把插件的缓存预热逻辑做在启动阶段而不是等第一个请求进来再逐一填充。另一个经验是wasm镜像tag尽量不要用latest因为WasmPlugin拉取远程OCI镜像后会在网关本地缓存一份。如果镜像仓库里的layers变了而tag没变由于本地缓存的存在Envoy可能加载的还是旧版本。这样你在控制台上改了插件配置、显示“已更新”实际运行逻辑还是旧的排查起来非常崩溃。6.4 性能压测时的意外发现我在一次压测里发现Higress在开启多个WASM插件后QPS损失除了插件自身的CPU开销还有一部分来自线程间的数据拷贝。Envoy的WASM插件默认在独立的虚拟机中运行请求数据从Envoy C管线传到WASM沙箱存在一次内存拷贝。插件越多、处理的body越大这个开销越明显。如果你的接口是上传下载大文件类最好不要让请求体经过不必要的WASM插件。Higress里有配置项可以把插件只绑定到特定路由或者特定域名尽量缩小插件的覆盖范围让大流量路径上少挂插件性能会明显回升。7. 从维护者的视角看Higress的未来走向开头说的是“为什么再造一个网关”写到这里我觉得结论比较清晰了Higress不是去重复Nginx Ingress的工作而是在一种更贴近云原生的模型上重新组织和暴露网关能力。它的几个关键选择——控制面复用Istio、数据面用Envoy、扩展用WASM——互相之间是自洽的并非把几个开源组件拼在一起。在实际维护中我的感受是Higress的调试门槛确实比Nginx Ingress高因为链路里多出了Istio这层出了问题要在K8s资源、VirtualService、xDS推送、Envoy config_dump之间来回定位。但它换来的收益也很直接不需要维护一套自研网关的控制面不需要为高可用做特殊订制插件用Go/Rust就能写团队里做Java或Go的后端工程师都能上手做网关插件。如果你现在的集群规模在几十个服务到几千个服务之间并且有明显的南北向流量治理需求——限流、灰度、多协议、WAF——把Higress放进选型池里对比一下不会有错。但要记得一件事网关这个层选型不是选功能最多或性能纸面数据最高的而是选你和你的团队最好维护的。对大多数团队来说能像写普通业务代码一样去写网关插件这件事本身就是最大的护城河。