
做K8s网关这几年身边团队在Ingress选型上踩过的坑五花八门。有人从Nginx Ingress起步后面发现限流、认证、灰度全得靠写Lua脚本或者干脆再叠一层网关有人直接上Istio结果被复杂的Sidecar体系和一大堆CRD劝退。Higress这个项目我前后研究了不少时间它是阿里开源、后来进入CNCF的云原生网关数据面用Envoy控制面从Istio演进而来目标是做“K8s Ingress 微服务网关”二合一。这篇我会顺着Higress的运转链路把它的控制面、数据面、Wasm插件机制和高可用原理一层层拆开尽量讲清楚每个设计背后的取舍而不是只停留在“怎么装、怎么配”的层面。1. 先弄明白 Higress 到底在解决什么问题1.1 一个云原生网关要同时扛起的职责在传统架构里流量入口往往被拆成两个东西机房最前面的Nginx负责转发和负载均衡业务系统内部再挂一个API网关负责鉴权、限流、路由和协议转换。到了K8s环境Ingress Controller接过了“流量入口”的角色但它本质上只是把Service暴露出去治理能力非常弱。Kong、APISIX这类独立API网关倒是能补上治理能力可它们又脱离了K8s原生的资源模型得单独维护一套配置体系。Higress的思路就是把这些事合并到一个入口既要能像标准Ingress一样直接用K8s资源描述路由规则也要能像微服务网关一样提供认证、限流、熔断、灰度、观测这些能力。这个定位决定了Higress在架构上的两个基本判断第一不能用OpenResty那套“Nginx Lua”的路线因为Nginx的模块生态和并发模型在云原生环境里扩展起来太别扭第二也不能直接把完整Istio拿过来当网关因为Istio的重心是Sidecar和网格流量管理对网关场景来说太重了。于是Higress选择了一条务实路径数据面直接用Envoy控制面复用Istio的istiod核心代码并按网关场景裁剪自己再写一个轻量Controller来对接K8s Ingress和Gateway API。1.2 Higress 的整体架构轮廓Higress整体上是标准的控制面加数据面两层结构但和Kong这类“自己写一套控制面”的网关不同它把控制面内部又拆成了两个有明确分工的组件。组件来源职责Higress Controller自研监听Ingress、Gateway、McpBridge、WasmPlugin等K8s资源转换成Istio的VirtualService、Gateway、DestinationRule等CRDHigress Pilot基于istiod改造监听Istio CRD生成Envoy所需的xDS配置并推送给数据面Higress GatewayEnvoy真正处理用户流量的数据面执行路由、负载均衡、限流、Wasm插件等逻辑Higress Agent部分场景自研负责插件运行时、证书管理等辅助能力理解这个分工特别重要。Higress Controller做的是“对上层资源负责”它让用户可以用最熟悉的K8s Ingress语法写路由规则也可以直接用Istio的VirtualService表达更复杂的匹配逻辑Pilot做的是“对底层Envoy负责”它不关心你的规则是从哪儿来的只负责把VirtualService、DestinationRule变成Envoy能识别的监听器、路由和集群配置。这套转换链路虽然比普通Ingress Controller多了一环但换来的是标准化的内部数据模型这正是后面所有治理能力和插件机制能跑起来的基础。1.3 为什么基于 Envoy 和 Istio 而不是从头写很多网关项目喜欢自己造轮子但网络基础设施最怕的就是轮子不够圆。Envoy在数据面已经把HTTP/2、gRPC、TLS、熔断、重试、流量镜像这些能力做得足够成熟而且它的xDS协议本身就是为动态配置设计的Higress没必要重新发明一个数据面。选择基于Istio而不是完全自研控制面理由也很直接istiod已经解决了“把K8s集群状态映射成Envoy配置”这个最难的问题包括服务发现、端点管理、证书下发、配置一致性校验这些都是经过大规模生产环境验证的。Higress要做的不是从零到一而是做减法把Sidecar注入、网格策略这类网关用不到的部分裁掉再补上网关特有的能力比如Higress自定义的McpBridge对接Nacos等注册中心和WasmPlugin插件体系。2. 控制面原理一条配置从 Ingress 到 Envoy 的完整链路2.1 第一跳Higress Controller 翻译标准 K8s 资源先看最常用的一条链路用户在K8s里创建一个标准Ingress资源比如apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: demo namespace: default annotations: higress.io/route-match: true spec: ingressClassName: higress rules: - host: demo.example.com http: paths: - path: /api pathType: Prefix backend: service: name: demo-svc port: number: 8080Higress Controller Watch到这个Ingress后会把它转换成一条Istio VirtualService规则。为什么不能直接把Ingress给Pilot用因为Ingress的表达能力太有限了它只有host、path、backend这三个字段而网关场景里常见的“按Header分流”“重写路径”“超时设置”在Ingress里只能通过Annotation表达。Istio的VirtualService天然支持这些语义所以Higress的做法是把所有来源统一翻译成VirtualServiceIngress进来的会翻译Gateway API进来的也会翻译甚至用Istio CRD直接编写的规则也一样被处理。这种“多源归一”的设计让数据面只需要面对一套内部模型复杂度被封锁在Controller这一层。翻译完成后VirtualService、DestinationRule会被写入K8s的CRD存储中Pilot会Watch到这些新的Istio资源。这里有个容易被忽略的细节Higress Controller和Pilot是同一个进程中两个独立控制器二者通过K8s API解耦而不是直接进程内调用。这意味着即使Pilot异常重启Controller创建好的CRD依然存在数据面的配置不会因为控制面进程重启而丢失恢复后会重新推送给Envoy。生产环境里这种解耦的价值非常大。2.2 第二跳Pilot 把 Istio CRD 变成 xDS 配置Pilot的核心工作可以概括成一句话把集群状态转化成一组Envoy可以消费的xDS协议数据。xDS里的字母分别代表着不同类型的配置LDS是监听器配置RDS是路由配置CDS是集群配置EDS是集群端点配置SDS是证书配置。Envoy启动时只带着一个最小的Bootstrap配置里面指向Pilot的接入地址之后所有的监听器、路由、上游集群都是动态下发进来的。以刚才那个Ingress为例Pilot最终会生成类似这样的逻辑结构// LDS: 监听器 { name: 0.0.0.0_443, 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, route_config_name: http.443, http_filters: [] } } ] } ] } // RDS: 路由表 { name: http.443, virtual_hosts: [ { domains: [demo.example.com], routes: [ { match: {prefix: /api}, route: { cluster: outbound|8080||demo-svc.default.svc.cluster.local } } ] } ] }这里面能看出Envoy设计上非常优雅的一点把“监听什么端口”和“收到请求后怎么转发”拆成两个级别的配置。LDS决定流量进入的入口RDS则根据Host和Path决定具体动作。Pilot在推送时也会把CDS和EDS单独管理因为上游服务的Pod数量是频繁变化的只有把“有哪些实例”单独放一层配置才能在Pod扩缩容时只推送变更的端点数据而不是把整份配置重新推一遍。2.3 关键机制xDS、ACK/NACK 与增量推送Envoy对xDS的处理机制值得单独讲一下因为这里藏着“配置如何保证一致”的核心逻辑。Pilot每次感知到集群状态变化后会生成一个新版本的配置通过gRPC双向流推送给Envoy。Envoy收到后会做配置校验如果配置合法就应用并返回ACK告诉Pilot“我这边的配置已经是version X了”如果配置非法比如路由指向了一个不存在的集群Envoy会返回NACK并带上详细的错误信息。Pilot收到NACK后会在日志里记录但不会无限重试推送同一份错误配置因为Envoy会继续使用上一次有效的配置继续服务。把这个机制和生活里的事情类比就很好理解就像你更新手机App下载了新版本但装不上系统会告诉你失败原因但手机上的旧版本还能继续正常用。这个特性对网关来说极其重要意味着即使控制面生成了错误配置数据面也不会因此中断流量。早期Istio用的是一次性全量推送集群规模一大每次配置变化都会产生巨大的推送压力。Higress继承并优化了后续版本的增量推送机制只有受影响的监听器或路由才会被重新推送配合控制面的缓存机制整个控制面在几百个服务的情况下也能保持很低的资源占用。我自己的经验是控制面CPU和内存的峰值通常出现在批量创建服务的瞬间日常运行非常平稳。2.4 多租户与配置隔离是怎么做到的Higress支持把多个业务团队共用一个网关这个能力背后依赖的是Istio天然的多租户隔离机制。在Envoy的配置模型里每个VirtualHost是独立的路由域Higress通过Namespace来划分边界某个Namespace下的VirtualService只能引用同一个Namespace下的Service作为上游。这样设计的好处非常直观团队A修改自己的路由规则时不可能把团队B的服务地址暴露在路由里也不可能误改团队B的域名配置。但这里有个关键的实现细节隔离不是简单地在代码里判断“服务名相同就在一起”而是借助K8s RBAC和Istio的授权策略再加上Pilot侧对CRD命名空间的校验共同完成的。Higress默认只会监听和管理它自己所在Namespace及显式声明关联的Namespace里的Istio CRD避免多个Higress实例同时管理同一条规则产生冲突。我们团队在落地时还额外加了一层约定禁止业务方直接创建Istio CRD所有路由变更统一走Ingress或Gateway APIIstio CRD只由平台团队使用。这样既保留了高级能力的入口又不会把一个原本清晰的管理模型搅乱。3. 数据面原理Envoy 之上如何承载网关能力3.1 路由匹配一个请求是怎么被命中并转发的当用户请求到达Higress的数据面时Envoy的HTTP连接管理器会先把TLS终止掉然后进入HTTP/2或HTTP/1.1的协议解析流程接着把请求交给路由表处理。路由匹配的核心是VirtualHost它按Host域来划分每个VirtualHost里有一组Route规则。Higress生成的VirtualHost匹配顺序遵循“精确域名优先、通配域名其次”的规则域名内部的Route规则则按“精确路径优先、前缀路径次之、正则最后”的顺序匹配。实际排查问题的时候我建议先抓住一个原则Envoy不是把所有路由规则从头到尾都试一遍而是按优先级取第一个命中的规则。所以如果你同时配置了/api的前缀路由和/api/health的精确路由请求/api/health会命中后者因为精确匹配的优先级更高。Higress对这些匹配逻辑做了封装底层依然是Envoy的Route匹配规则链。命中Route以后请求会进入一个过滤器链。Envoy的过滤器机制是它扩展性的灵魂认证、熔断、限流、Wasm插件都是以过滤器形式挂在链上的。过滤器按在YAML里声明的顺序依次执行比如最常见的顺序是WAF插件、认证插件、限流插件、路由转发。这个顺序在Higress平台侧是固定的因为一旦调整顺序插件的语义就可能改变。例如限流如果放在认证前面未认证的请求也会占用量配额这往往不是想要的结果。3.2 服务发现K8s、Nacos 和 DNS 如何被统一编排网关除了要接K8s集群内部的Service通常还得接外部系统比如老架构里用Nacos注册的微服务。Higress的McpBridge CRD就是为这种场景设计的。看一个示例apiVersion: networking.higress.io/v1 kind: McpBridge metadata: name: default namespace: higress-system spec: registries: - type: nacos name: nacos-registry domain: nacos-service.nacos.svc.cluster.local port: 8848 nacosNamespaceId: public - type: dns name: dns-registry domain: *.example.com这段配置会让Higress去指定的Nacos实例拉取服务列表并把所有实例映射成K8s Service的形式随后这些服务就能像普通K8s Service一样出现在DestinationRule的引用范围里。对我个人而言McpBridge是把Higress从“K8s专用网关”变成“企业混合架构网关”的关键组件因为它补上了Service Registry之间的鸿沟。落地时有一个点必须提前想清楚外部注册中心的服务实例和K8s Service的Pod一样会被实时同步到EDS里服务实例上下线的延迟通常取决于注册中心本身的推送机制。Nacos默认的推送配置下感知一个实例下线可能有一到几秒的延迟这在K8s环境下的表现和原生EndpointSlice的秒级感知并不完全一样。如果业务对故障转移时间特别敏感要在负载均衡层面额外配置主动健康检查利用Envoy的主动健康检查去探测不健康实例不要只依赖注册中心的被动通知。3.3 治理能力背后的算法拆解限流、熔断、灰度限流是网关最常用的治理能力。Higress的限流插件在本地限流模式下会利用Envoy的令牌桶算法每个节点独立计数配置简单但无法做到集群维度精确控制。分布式限流模式则会引入Redis作为中心计数存储用滑动窗口算法统计单位时间内的请求数。滑动窗口的核心思想是把一个时间窗口切成多个小格子每个格子记录请求数窗口不断滑动统计值比固定窗口更平滑也能规避窗口边界上的突发流量问题。熔断机制层面Envoy的Circuit Breaker主要分两类一类是按上游集群的并发连接数、并发请求数做阈值限制另一类是针对异常比例做熔断。Higress在DestinationRule里开放了这些配置你可以设置maxRequestsPerConnection、maxConnections和pendingRequests等参数。团队实际测试时我发现很多人会把maxRequestsPerConnection理解成“每秒请求数”其实它是“每个连接上最多允许多少个请求”代表的是连接复用策略不是吞吐量限制千万别混淆。灰度发布则是利用Envoy路由规则中的Weighted Cluster实现的。Higress可以把流量按权重分配到一个Service的多个版本上也可以根据Header、Cookie做精准引流。底层实现并不神秘就是Envoy在为某个请求选中目标Cluster时按照权重算出随机数落在哪个区间里。线上灰度我建议配合Higress的观测面板一起使用灰度流量的链路追踪能直接看出新旧版本的实际表现而不只是看增加了多少请求量。4. Wasm 插件机制Higress 最核心的扩展原理4.1 为什么是 Wasm 而不是 Lua网关的扩展能力长期被“脚本语言嵌入式运行时”这套模式主导最典型的是OpenResty里的Lua。但Lua生态最大的问题是语言本身和宿主强绑定写出来的插件很难复用而且沙箱能力弱一个插件写崩了可能直接带崩整个Nginx Worker。Higress选择WasmWebAssembly有非常现实的理由Wasm天生是沙箱模型插件运行在独立的虚拟机里内存越界、无限循环这些问题不会影响主进程沙箱内插件崩溃Envoy还能继续处理其他请求只是该插件链路上的关键能力会失效。加上Wasm的ABI标准由proxy-wasm规范定义理论上同一个插件可以跨网关复用这对构建插件生态是决定性的。Wasm插件的另一个优势是可以多语言开发。Higress对Go的支持尤其值得一提插件可以用Go编写通过TinyGo编译成wasm文件这大大降低了从Java或Go团队转型写网关插件的门槛。我们团队之前维护过一个基于OpenResty的WAF插件Lua代码和Nginx版本强绑定升级Nginx就得回归测一遍插件现在用Go重构后编译成wasm直接挂到Higress上控制面升级完全不影响插件逻辑。4.2 插件从编译到生效的完整生命周期Higress的插件不是简单扔一个wasm文件到Pod里就能用的它的完整生命周期分四步开发编译、镜像打包、配置下发、运行时加载。Higress开发套件会生成一个带骨架的Go项目插件通过SDK暴露的接口来处理请求比如ParseConfig负责解析配置OnHttpRequestHead负责在请求头阶段介入。编译完成后插件会打包成OCI镜像格式推送到镜像仓库镜像里包含了wasm文件和相关元数据。运行时环境里Higress的Agent或Pilot侧的逻辑会根据WasmPlugin资源决定是否拉取镜像。这里有个设计上的亮点插件镜像不是每个Envoy实例单独去拉而是通过一个共享的插件运行时机制只用把wasm文件放到Healthy Pod的共享目录里Envoy按需加载。加载后插件会注册成Envoy的一个Filter出现在HTTP Filter链上。插件按作用范围分成全局插件和路由级插件。全局插件作用在所有流量上比如全局WAF路由级插件只对特定域名或特定路由生效。这些配置都定义在WasmPlugin CRD里示例apiVersion: extensions.higress.io/v1alpha1 kind: WasmPlugin metadata: name: key-auth namespace: higress-system spec: defaultConfig: key: x-api-key hide_credentials: false matchRules: - ingress: - demo.example.com config: key: x-api-key hide_credentials: true这个CRD同时体现了“默认配置”和“按规则覆盖”的设计全局配置兜底命中匹配规则的请求用更具体的配置。配置的粒度可以精确到Ingress级别这是Higress插件体系比原生Envoy Wasm配置好用很多的地方原生Wasm配置需要手动往EnvoyFilter的typed_config里塞数据修改一次都要更新整个Filter排查问题极其痛苦。4.3 插件配置热更新与多级别生效插件配置的更新不需要重启Envoy也不需要一个一个Pod滚动发布。当WasmPlugin CRD变更时Pilot会感知到并立即给受影响的Envoy实例推送新的配置数据。插件的ParseConfig方法会在新配置到达时被调用插件内部可以重新初始化状态。我们生产环境中实际这样操作过修改限流阈值、切换认证模式时下游完全无感知。但热更新不等于所有问题都能原地解决。插件如果持有连接池、缓存等长期状态配置变更时这些状态不会自动重建需要插件开发者自行在ParseConfig里处理。比如一个插件维护了指向内部服务的连接池配置变更后旧连接池可能继续存在导致新配置引用的服务地址没有立即生效。这个坑我踩过一次解决方案是在解析新配置时先释放旧连接池再重新初始化。多级别生效机制的实现也值得展开Higress会在生成的Envoy配置里利用Per-Route配置和Extension Config机制把不同的配置绑定到不同路由上。对外表现是“我只需要在这个Ingress上配一个认证参数”对内则是Envoy路由表里每个Route都带了一个独立的插件配置块。理解这一点后排查问题时你会更快比如一个路由级插件不生效先检查该Ingress是否真的被转换成了独立的Route再检查插件匹配规则里写的域名路径和实际路径是否一致。4.4 性能与安全边界Wasm沙箱天然有性能开销这不是Higress独有的问题。从我们的压测数据来看启用一个简单的Wasm插件比如只做Header检查和日志记录在中等配置的Pod上对P99延迟的影响通常在10%以内但如果插件里做了base64解码、正则匹配、复杂对象序列化这类CPU密集操作开销会明显上升。所以写插件时有一条铁律把能提前算的配置都放到ParseConfig阶段请求处理路径上只做轻量操作。另一个值得注意的安全点是Wasm模块和宿主机之间的数据通信边界。proxy-wasm规范提供了调用宿主API的能力但HTTP调用、共享内存这类操作都有明确的ABI限制插件不能直接访问Envoy进程的内存空间。这层隔离既是安全优势也是开发时的限制如果你想在插件里记录访问日志到本地文件会发现宿主API并不提供文件写入能力必须通过发送日志给宿主导出。这不是设计缺陷而是刻意为之为了不让单个插件的异常行为影响整个网关稳定性。5. 高可用部署与实战踩坑记录5.1 控制面高可用Higress的控制面组件虽然是关键路径但它的高可用模型更多体现在“故障自愈”而不是“双活热切”。Controller和Pilot都支持多副本部署多个副本之间通过K8s的Leader Election机制竞争只有Leader执行配置生成和推送Follower保持待命状态。这里有一个经常被问到的点为什么不是所有副本同时工作根本原因是Envoy不能同时从多个Pilot节点接收同一份配置否则版本号容易冲突Envoy也不知道该以谁为准。控制面故障时数据面依然可以继续转发流量因为Envoy已经把最新配置加载到本地了。这意味着控制面重启几十秒不会断流量但期间无法发布新路由、新插件配置。我们团队的实际建议是把控制面Pod数固定在2个或3个并设置合理的资源请求更重要的是给Pilot开启持续的健康检查让K8s能快速摘除异常Pod因为Leader重新选举通常只需要几秒到十几秒远快于人工介入。5.2 数据面扩缩容与性能调优数据面本质上是一组无状态的Envoy Pod水平扩缩容非常直接。但在扩容前要先理解Envoy的线程模型每个Worker线程独占一个事件循环多个Worker之间通过SO_REUSEPORT共享同一组监听端口内核负责把新连接负载均衡到不同Worker上。Higress默认会根据Pod的CPU核数自动调整Worker数量。如果发现单Pod吞吐上不去先看CPU核数和连接数分布再看是否因为禁用了SO_REUSEPORT导致新连接全部堆在一个Worker上。另一个影响性能的隐藏参数是连接池的全局限制。Envoy的HTTP连接池默认对每个上游集群有并发和连接数上限当业务压测时出现大量的upstream_cx_overflow指标时不是Envoy不行而是连接池阈值太低。Higress在DestinationRule里暴露了这些调优参数我之前调过一次把http2MaxRequests从默认值调高单个Pod的吞吐直接涨了一截。调优时别只看吞吐还要看延迟分布连接池开得太大反而可能导致上游被打爆。5.3 证书体系与自动 HTTPSHTTPS证书管理是网关运维最繁琐的一环。Higress通过SDS动态下发证书证书的签发、轮换和下发全部走xDS链路。和传统Nginx Ingress“修改证书文件后reload进程”的方式完全不同SDS是Envoy主动向控制面拉取证书的协议证书轮换时不会中断现有连接因为旧证书会维持到导出前的最后一刻。初始接入时Higress支持自动签发证书背后对接的是Lets Encrypt或云厂商的证书服务通过DNS-01或HTTP-01方式完成域名验证。我们生产经验里有一点特别重要自动签发的证书依赖域名解析的正确性如果域名解析已经切换到了其他环境自动续期很可能失败。因为DNS-01校验需要Lets Encrypt能访问到你的DNS记录而我们已经不止一次因为DNS分裂导致证书续期前一个月才告警。建议给证书到期时间设一个提前两周的监控不要等到最后几天才发现。5.4 常见坑和排查思路先说配置不生效这个最常见的问题。如果改了Ingress或WasmPlugin但流量没有任何变化先别急着怀疑Higress按“Ingress → VirtualService → xDS配置”这条链路一层层缩小范围。先看Higress Controller日志里有没有成功转换这条规则再看Pilot有没有生成对应的VirtualHost最后看Envoy管理页面上的RDS配置。大多数问题都出在最上层比如Ingress匹配规则里的pathType写错导致Higress生成的Route优先级低于预期的另一条规则。再说一个NACK相关的坑。刚接手Higress时我们试过在DestinationRule里配置一个并不存在的上游ServicePilot日志立刻出现NACK与此同时Envoy还在用旧配置继续转发。排查NACK时找到Envoy返回的具体错误文本很关键Pilot日志里会带上再把对应的CLusterName和当前Service列表对比基本能定位是命名空间写错了还是服务名不对。还有一个插件相关的高发问题Wasm插件在控制台显示已生效但请求日志里看不到插件输出。这种情况八成是插件没有真正挂在流量的Filter链上。排查方式是登录到数据面Pod看Envoy的HTTP Filter链里是否包含该插件对应的extension配置。如果Filter链里没有要么是插件的匹配规则没命中要么是插件镜像拉取失败导致Envoy回退到无插件状态。镜像仓库的网络连通性一定要提前验证特别是私有化的插件市场场景。最后我要特别提一下配置变更时的灰度意识。网关是全局性质的组件路由规则或插件配置的一丁点错误都会影响所有流量。团队内部我会强制要求任何涉及认证或限流的插件变更先在测试环境按路由灰度验证再推向生产。Higress本身提供了完善的插件配置分环境能力你可以用同一个WasmPlugin CRD的不同matchRules实现不同环境的差异化配置这是比在代码里维护多套配置更优雅的方案。用了一段时间之后我最深的体会是Higress最大的价值不在于某个单项技术指标而在于它用一套完整且自洽的架构把K8s Ingress和微服务网关两套本来割裂的体系融在了一起。它的学习曲线不算陡但如果你只是想“装个Ingress替代品”可能会忽略掉Wasm插件和McpBridge这些真正拉开体验差距的能力。选型建议上我会说先带着团队把配置链路亲手走一遍再评估要不要把它作为默认流量入口。毕竟网关本身的架构设计再漂亮最终还是要能在你的业务场景里稳定跑起来才算数。