ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Higress 云原生网关实战:Envoy+Istio+Wasm 架构解析与性能调优

Higress 云原生网关实战:Envoy+Istio+Wasm 架构解析与性能调优

1. 从 Envoy 到 Higress:一个云原生网关的诞生逻辑

第一次接触 Higress 是在一个内部服务治理项目里,当时我们面临一个很典型的问题:K8s 集群里跑着几十个微服务,南北向流量用 Nginx Ingress,东西向用 Istio,两套体系各自为政,配置分散、排查困难。后来团队决定统一网关层,评估了几个方案之后选了 Higress。用了一段时间,我逐渐从“会用”进入到“想搞明白它怎么跑的”阶段,于是花了不少时间读源码、抓包、做压测,这篇文章就是这段时间的总结。

Higress 是阿里巴巴开源的一款云原生 API 网关,它同时具备流量网关、微服务网关和安全网关的能力。说人话就是:它想用一套东西,把 K8s 集群入口流量(Ingress)、服务间调用(微服务网关)、以及 API 管理(认证、限流、灰度)全部管起来。它的底层数据面基于 Envoy 构建,控制面则深度集成了 Istio 的 xDS 协议体系,同时通过 Wasm 插件机制实现了高度可扩展的业务逻辑注入。

这篇文章适合谁看?如果你正在做 K8s 服务治理、API 网关选型、或者对 Envoy 和 Wasm 的工程化落地感兴趣,那接下来的内容应该能帮你少走一些弯路。我会从架构设计、核心组件、Wasm 插件机制、实操部署、性能调优、问题排查几个维度,把 Higress 的原理和实战经验掰开揉碎讲清楚。

2. 架构拆解:Higress 为什么选择 Envoy + Istio + Wasm 这套组合

2.1 数据面为什么是 Envoy 而不是自研

很多人会问:既然阿里技术实力这么强,为什么不自己写一个数据面?答案其实很务实——Envoy 在云原生流量代理这个领域已经是事实标准,它的线程模型、连接池管理、负载均衡算法、可观测性都已经过大规模生产验证。自己从头写一个,光是 HTTP/2、gRPC 代理、连接复用这些细节就够折腾好几年,而且很难保证稳定性。

Higress 的做法是:数据面直接复用 Envoy 的能力,但在配置下发和扩展机制上做文章。Envoy 本身提供了 xDS 协议(一套动态配置发现协议),Higress 的控制面通过 xDS 把路由规则、集群信息、监听器配置推给 Envoy。这样数据面不需要重启就能动态更新配置,这是传统 Nginx reload 模式做不到的。

我实测过一个场景:在 1000 个路由规则的情况下,Higress 推送一次全量配置到 Envoy 生效,耗时在毫秒级,而 Nginx 做一次 reload 至少需要几百毫秒甚至秒级,而且会有连接抖动。这个差异在频繁变更的微服务环境里非常关键。

2.2 控制面为什么复用 Istio 的 xDS 体系

Higress 的控制面并没有完全从零构建,而是复用了 Istio 的 Pilot 组件(现在叫 Istiod)的 xDS 服务端能力。Istio 已经把 xDS 协议实现得很完整了,包括 CDS(集群发现)、EDS(端点发现)、LDS(监听器发现)、RDS(路由发现)等。Higress 在此基础上做了大量裁剪和增强,去掉了 Istio 中一些重量级的 sidecar 管理逻辑,专注于网关场景。

这样做的好处是:如果你已经在用 Istio 做服务网格,Higress 可以无缝接入现有的控制面体系,不需要额外维护一套配置管理。而且 Istio 的 CRD(自定义资源定义)体系,比如 VirtualService、Gateway、DestinationRule,Higress 都能兼容,学习成本低。

但这里有个坑要注意:Higress 虽然兼容 Istio 的 API,但它并不是 Istio 的替代品。Istio 的 sidecar 模式是每个 Pod 注入一个代理,而 Higress 是独立部署的网关集群。两者定位不同,不要混用。我在早期就犯过这个错误,试图用 Higress 的 CRD 去管理 sidecar,结果配置冲突,排查了很久。

2.3 Wasm 插件机制解决了什么问题

传统网关扩展一般有两种方式:一是改源码重新编译,二是用 Lua 脚本(比如 OpenResty)。改源码的问题很明显,升级维护成本高;Lua 脚本虽然灵活,但性能和类型安全都不够理想,而且生态相对封闭。

Higress 选择了 Wasm(WebAssembly)作为插件运行时。Wasm 的好处是:语言无关(可以用 Go、Rust、C++ 写插件)、沙箱隔离(插件崩溃不会影响主进程)、热加载(不需要重启网关)。这三点在网关场景里太重要了。想象一下,你写了一个自定义认证插件,如果它因为某个边界条件 panic 了,整个网关挂掉,那影响面就大了。Wasm 沙箱能保证插件出问题只影响自己。

而且 Wasm 的性能比 Lua 好不少。我做过一个简单的 benchmark:同样的 JWT 认证逻辑,用 Lua 写平均延迟增加 0.3ms,用 Go 编译成 Wasm 后平均延迟增加 0.15ms 左右。虽然绝对值都不大,但在高并发场景下差距会被放大。

3. 核心组件与请求流转全链路解析

3.1 Higress 的四大核心组件

Higress 的部署架构里主要有四个角色:

  • Higress Controller:负责监听 K8s 的 Ingress、Gateway、HttpRoute 等资源变化,转换成 Istio 的配置模型,推送给 Istiod。
  • Istiod:作为 xDS 服务端,把配置翻译成 Envoy 能理解的 Cluster、Listener、Route 等资源,通过 gRPC 流式推送给数据面。
  • Higress Gateway:基于 Envoy 构建的数据面 Pod,实际处理流量。通常以 Deployment 或 DaemonSet 形式部署。
  • Higress Console:可选的 Web 管理界面,用于配置路由、插件、证书等。

这四个组件的交互关系是:Controller 监听 K8s API Server,Istiod 监听 Controller 生成的配置,Gateway 通过 xDS 从 Istiod 拉取配置。整个链路是单向的,配置变更从 K8s 资源开始,最终生效在 Gateway 上。

3.2 一个 HTTP 请求的完整生命周期

假设你在 K8s 里部署了一个服务my-app,通过 Higress 暴露出去。当一个外部请求进来时,会经历以下步骤:

  1. DNS 解析:请求先到达 Higress Gateway 的 Service(通常是 LoadBalancer 或 NodePort)。
  2. TCP 连接建立:Envoy 的监听器(Listener)接受连接,根据配置决定是否启用 TLS。
  3. HTTP 协议解析:Envoy 的 HTTP 连接管理器解析请求头、方法、路径。
  4. 路由匹配:根据 RDS 下发的路由配置,匹配到对应的 VirtualHost 和 Route。匹配规则包括域名、路径前缀、Header 等。
  5. Wasm 插件执行:如果路由上绑定了 Wasm 插件,会按顺序执行插件链。比如先执行认证插件,再执行限流插件,最后执行日志插件。
  6. 集群选择与负载均衡:根据路由配置的 Destination,选择一个后端 Cluster,然后从 Cluster 的 Endpoint 列表里按负载均衡算法选一个 Pod。
  7. 请求转发:Envoy 把请求转发给选中的后端 Pod,等待响应。
  8. 响应处理:后端返回响应后,Envoy 可能再次执行 Wasm 插件(比如响应头修改),然后返回给客户端。

这个链路里,最耗时的通常是第 7 步的网络转发,而第 4、5、6 步都是内存操作,延迟在微秒级。我抓包分析过,在局域网环境下,Higress 自身引入的额外延迟大约在 0.5ms 到 1ms 之间,主要花在 TLS 握手和 Wasm 插件执行上。

3.3 配置下发的 xDS 协议细节

xDS 是 Higress 动态能力的核心。它其实是一组协议的总称,包括:

协议全称作用
CDSCluster Discovery Service下发后端集群信息
EDSEndpoint Discovery Service下发集群内的端点(Pod IP)
LDSListener Discovery Service下发监听器配置
RDSRoute Discovery Service下发路由规则
SDSSecret Discovery Service下发 TLS 证书

Higress 的 Istiod 会维护这些资源的版本号,当配置变更时,通过 gRPC 流式推送给 Envoy。Envoy 收到更新后,会做增量更新(EDS 支持增量,CDS/LDS 通常是全量)。这里有个细节:Envoy 在收到新配置后,不会立即丢弃旧配置,而是等新配置初始化完成后再切换,这叫“热重启”机制,保证了配置更新期间流量不中断。

我在调试时遇到过一个典型问题:配置推送成功但路由不生效。后来发现是因为 Envoy 的监听器有“预热”过程,新配置需要等所有 Cluster 都初始化完成才会生效。如果某个 Cluster 的后端不可达,会导致整个配置卡住。解决办法是在 Cluster 配置里设置合理的connect_timeout和circuit_breakers,避免单个后端拖垮全局。

4. Wasm 插件开发实战:从零写一个自定义认证插件

4.1 为什么选择 Go 来写 Wasm 插件

Higress 的 Wasm 插件支持多种语言,但官方推荐用 Go 或 Rust。Rust 的性能最好,但学习曲线陡峭;Go 的性能稍逊,但开发效率高,而且 Higress 提供了完善的 Go SDK(higress-wasm-go-sdk),封装了大部分底层细节。

我选 Go 的原因很简单:团队里 Go 是主力语言,维护成本低。而且实测下来,Go 编译的 Wasm 插件在网关场景下性能完全够用。一个简单的 Header 修改插件,单次执行耗时在 50 微秒左右,相比网络转发的毫秒级延迟,可以忽略不计。

4.2 插件开发环境搭建

首先需要安装 TinyGo,因为标准 Go 编译器生成的 Wasm 体积太大,TinyGo 专门为嵌入式场景优化过。安装步骤:

# 安装 TinyGo(以 macOS 为例) brew tap tinygo-org/tools brew install tinygo # 验证安装 tinygo version

然后创建一个插件项目:

mkdir my-auth-plugin && cd my-auth-plugin go mod init my-auth-plugin go get github.com/alibaba/higress/plugins/wasm-go

4.3 插件核心代码结构

一个典型的 Higress Wasm 插件包含三个核心函数:

package main import ( "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" ) func main() { wrapper.SetCtx( "my-auth-plugin", wrapper.ParseConfigBy(parseConfig), wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders), ) } type MyConfig struct { TokenHeader string ValidToken string } func parseConfig(json gjson.Result, config *MyConfig) error { config.TokenHeader = json.Get("token_header").String() config.ValidToken = json.Get("valid_token").String() return nil } func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action { token, _ := proxywasm.GetHttpRequestHeader(config.TokenHeader) if token != config.ValidToken { proxywasm.SendHttpResponse(401, nil, []byte("Unauthorized"), -1) return types.ActionPause } return types.ActionContinue }

这段代码的逻辑是:从请求头里取一个 Token,跟配置里的合法 Token 比对,不匹配就返回 401。虽然简单,但涵盖了 Wasm 插件的核心要素:配置解析、请求头处理、响应返回。

4.4 编译与部署

编译命令:

tinygo build -o main.wasm -scheduler=none -target=wasi ./main.go

编译出来的main.wasm文件通常在几百 KB 到 1MB 之间。然后需要把它打包成 OCI 镜像,推送到镜像仓库,再在 Higress 里配置插件。这个过程稍微繁琐,Higress 提供了higressctl工具来简化:

higressctl plugin build --name my-auth-plugin --wasm main.wasm higressctl plugin push --name my-auth-plugin --tag v1.0.0

部署时,在 Higress Console 里创建一个 WasmPlugin 资源,指定镜像地址和配置参数即可。插件生效是热加载的,不需要重启网关。

注意:Wasm 插件在 Envoy 里是每个 Worker 线程独立实例化的,所以插件里的全局变量不共享。如果你需要跨请求共享状态(比如缓存),得用 Redis 之类的共享存储。

4.5 插件性能优化经验

我踩过几个性能相关的坑,这里分享出来:

第一,避免在插件里做同步网络调用。Wasm 插件运行在 Envoy 的 Worker 线程里,如果做同步 HTTP 请求,会阻塞整个线程。Higress SDK 提供了异步 HTTP 调用接口,必须用异步方式。

第二,控制插件数量。每个插件都会增加请求处理链路,我建议单个路由上绑定的插件不超过 5 个。如果逻辑复杂,尽量合并到一个插件里。

第三,注意内存分配。Wasm 的线性内存模型跟原生 Go 不同,频繁的大内存分配会导致性能下降。尽量复用 buffer,避免在热路径上创建大对象。

5. 生产环境部署与性能调优实录

5.1 部署模式选择:Deployment 还是 DaemonSet

Higress Gateway 支持两种部署模式:

  • Deployment:网关作为独立集群部署,前面挂一个 LoadBalancer Service。适合大多数场景,扩缩容方便。
  • DaemonSet:每个节点跑一个网关 Pod,通过 HostNetwork 暴露。适合对延迟极度敏感的场景,可以减少一跳网络转发。

我一开始用的是 Deployment,后来因为业务对延迟要求高,切换到了 DaemonSet。实测下来,DaemonSet 模式能降低约 0.3ms 的延迟,但运维复杂度上升,因为每个节点都需要开放端口,而且扩缩容不如 Deployment 灵活。

5.2 关键性能参数调优

Envoy 的默认配置偏向保守,生产环境需要调整一些参数。以下是我总结的调优清单:

参数默认值建议值说明
concurrency1CPU 核数Worker 线程数,建议等于 CPU 核数
connection_buffer_limit_bytes32KB1MB连接缓冲区大小,大文件传输场景需调大
max_requests_per_connection0(无限)1000单连接最大请求数,避免长连接内存泄漏
stream_idle_timeout5分钟30秒流空闲超时,及时释放资源
upstream_connection_pool_size100500后端连接池大小,高并发场景需调大

这些参数可以通过 Higress 的 ConfigMap 或者 EnvoyFilter 来配置。我建议先在测试环境压测,找到瓶颈后再调整,不要盲目照搬。

5.3 压测数据与瓶颈分析

我用 wrk 做过一组压测,环境是 4 核 8G 的虚拟机,Higress 和 backend 各一台。结果如下:

场景QPSP99 延迟CPU 使用率
纯转发(无插件)280003.2ms75%
带 JWT 认证插件220004.8ms85%
带限流插件250003.9ms80%
带日志插件(异步)270003.5ms78%

从数据可以看出,Wasm 插件对性能的影响在 10% 到 20% 之间,其中认证类插件因为涉及加密计算,影响最大。日志插件如果做成异步的,影响很小。

瓶颈主要在 CPU 上,当 QPS 超过 25000 后,CPU 打满,延迟急剧上升。解决办法是水平扩容,增加 Gateway Pod 数量。Higress 的 Gateway 是无状态的,可以随意扩容。

5.4 可观测性配置

生产环境必须配好监控。Higress 支持 Prometheus 指标暴露,默认端口是 15020。关键指标包括:

  • envoy_http_downstream_rq_total:总请求数
  • envoy_http_downstream_rq_time:请求延迟分布
  • envoy_cluster_upstream_rq_total:后端请求数
  • envoy_cluster_upstream_rq_time:后端延迟分布
  • envoy_wasm_vm_execution_time:Wasm 插件执行时间

我建议把 Wasm 插件执行时间单独监控,如果某个插件耗时突然上升,能快速定位。另外,Envoy 的访问日志建议开启,但要注意日志量,高并发下日志本身会成为瓶颈。可以用异步日志或者采样日志。

6. 常见问题排查与避坑指南

6.1 路由不生效的排查思路

这是最常见的问题。我的排查顺序是:

  1. 检查 K8s 资源是否正确创建:kubectl get ingress/gateway/httproute -A
  2. 检查 Higress Controller 日志:kubectl logs -n higress-system deploy/higress-controller
  3. 检查 Istiod 是否收到配置:kubectl logs -n higress-system deploy/istiod
  4. 检查 Envoy 是否收到配置:进入 Gateway Pod,curl localhost:15000/config_dump查看路由配置
  5. 检查域名和路径匹配规则是否正确

我遇到过一次路由不生效,最后发现是 Ingress 的pathType写错了。Prefix和Exact的匹配行为完全不同,Prefix是前缀匹配,Exact是精确匹配。如果写成Exact但请求路径多了个斜杠,就匹配不上。

6.2 Wasm 插件加载失败的常见原因

Wasm 插件加载失败通常有几个原因:

  • 镜像拉取失败:检查镜像仓库地址和凭证
  • Wasm 文件格式错误:确保用 TinyGo 编译,标准 Go 编译的 Wasm 可能不兼容
  • ABI 版本不匹配:Higress 的 Wasm ABI 版本要和 SDK 版本对应
  • 内存超限:Wasm 插件默认内存限制是 20MB,如果插件内存占用过大,会被 kill

排查时可以先看 Envoy 日志,搜索wasm关键字。如果日志里有Failed to load wasm module,基本就是上述原因之一。

6.3 性能突然下降的排查清单

如果网关性能突然下降,按以下顺序排查:

排查项检查方法常见原因
CPU 使用率kubectl top pod插件逻辑复杂、QPS 突增
内存使用率kubectl top pod连接泄漏、插件内存泄漏
后端延迟查看 Envoy 指标后端服务变慢
连接数ss -s连接池耗尽
配置版本config_dump配置频繁变更导致 Envoy 不断重建

我有一次遇到性能下降,最后发现是某个 Wasm 插件里做了同步 DNS 解析,每次请求都阻塞。改成异步后,性能恢复正常。这个坑很隐蔽,因为插件逻辑本身没问题,但同步调用在 Envoy 的线程模型里是致命的。

6.4 证书管理的注意事项

Higress 支持自动证书管理(通过 cert-manager 或内置的 ACME 客户端)。但生产环境我建议还是手动管理证书,原因有二:一是自动续期失败时排查困难,二是证书私钥的安全管理需要额外考虑。

配置 TLS 时,注意 SDS(Secret Discovery Service)的更新机制。证书更新后,Envoy 不会立即使用新证书,而是等下一次 TLS 握手时才加载。如果客户端保持长连接,可能很长时间都用不到新证书。解决办法是设置合理的session_ticket_key轮换周期,或者主动断开旧连接。

7. 一些个人体会和后续扩展方向

Higress 这套架构最让我欣赏的地方是它的“务实”。它没有追求完全自研,而是站在 Envoy 和 Istio 的肩膀上,把精力集中在网关场景的优化和 Wasm 插件生态的建设上。这种取舍在工程上是非常明智的。

我在实际使用中最大的体会是:Wasm 插件机制虽然强大,但不要滥用。我见过有团队把业务逻辑大量塞进 Wasm 插件,结果网关变成了一个“分布式应用服务器”,性能和维护性都成了问题。网关的职责应该是流量治理,业务逻辑尽量放在后端服务里。

后续如果继续深入,我打算研究两个方向:一是 Wasm 插件的 A/B 测试能力,看看能不能在网关层做灰度发布;二是 Higress 和 Service Mesh 的协同,比如用 Higress 做南北向网关,Istio 做东西向治理,两者共享一套控制面。这两个方向都有实际需求,也有不少坑要踩。

最后分享一个小技巧:Higress 的 Console 虽然方便,但生产环境建议用 GitOps 方式管理配置,把所有路由和插件配置写成 YAML 文件,通过 CI/CD 流水线部署。这样配置变更有记录、可回滚,比在界面上点来点去靠谱得多。

返回列表