Higress 是基于阿里内部的 Envoy Gateway 实践沉淀、以开源 Istio + Envoy 为核心构建的云原生 API 网关,实现了流量网关 + 微服务网关 + 安全网关三合一的高集成能力,深度集成 Dubbo、Nacos、Sentinel 等微服务技术栈,能够帮助用户极大的降低网关的部署及运维成本;在标准上全面支持 Ingress 与 Gateway API,积极拥抱云原生下的标准 API 规范;同时,Higress Controller 也支持 Nginx Ingress 平滑迁移,帮助用户零成本快速迁移到 Higress。
行业中通常把网关分为两个大类:流量网关与业务网关,流量网关主要提供全局性的、与后端业务无关的策略配置,例如阿里内部的的统一接入网关 Tengine 就是典型的流量网关;业务网关顾名思义主要提供独立业务域级别的、与后端业务紧耦合策略配置,随着应用架构模式从单体演进到现在的分布式微服务,业务网关也有了新的叫法 - 微服务网关。
在虚拟化时期的微服务架构下,业务通常采用流量网关 + 微服务网关的两层架构,流量网关负责南北向流量调度和安全防护,微服务网关负责东西向流量调度和服务治理,而在容器和 K8s 主导的云原生时代,Ingress 成为 K8s 生态的网关标准,赋予了网关新的使命,使得流量网关 + 微服务网关合二为一成为可能。
作为面向南北向的公网网关,使用 Waf 防护异常流量是很常规的需求,而且随着互联网环境变得越来越复杂,用户对防护的诉求是持续增强的,常规做法是将流量先接入 Waf 安全网关,过滤后再将流量转发给流量网关,最后到达微服务网关;Higress 希望通过内置 Waf 模块,使得用户的请求链接只经过 Higress 就可以同时完成 Waf 防护、流量分发、微服务治理,既可以降低链路 RT,也可以降低网关的运维复杂度。因此 Higress 实现了流量网关 + 微服务网关 + 安全网关三合一的高集成能力。
使用场景
Higress 支持多种功能特性,适用于多种场景:
- Kubernetes Ingress 网关:
Higress 可以作为 K8s 集群的 Ingress 入口网关, 并且兼容了大量 K8s Nginx Ingress 的注解,可以从 K8s Nginx Ingress 快速平滑迁移到 Higress。
支持 Gateway API 标准,支持用户从 Ingress API 平滑迁移到 Gateway API。 - 微服务网关:
Higress 可以作为微服务网关, 能够对接多种类型的注册中心发现服务配置路由,例如 Nacos, ZooKeeper, Consul, Eureka 等。
并且深度集成了 Dubbo, Nacos, Sentinel 等微服务技术栈,基于 Envoy C++ 网关内核的出色性能,相比传统 Java 类微服务网关,可以显著降低资源使用率,减少成本。 - 安全防护网关:
Higress 可以作为安全防护网关, 提供 WAF 的能力,并且支持多种认证鉴权策略,例如 key-auth, hmac-auth, jwt-auth, basic-auth, oidc 等。
核心优势
- 生产等级
脱胎于阿里巴巴 2 年多生产验证的内部产品,支持每秒请求量达数十万级的大规模场景。
彻底摆脱 reload 引起的流量抖动,配置变更毫秒级生效且业务无感。 - 平滑演进
支持 Nacos/Zookeeper/Eureka 等多种注册中心,可以不依赖 K8s Service 进行服务发现,支持非容器架构平滑演进到云原生架构。
支持从 Nginx Ingress Controller 平滑迁移,支持平滑过渡到 Gateway API,支持业务架构平滑演进到 ServiceMesh。 - 兼容性强
兼容 Nginx Ingress Annotation 80%+ 的使用场景,且提供功能更丰富的 Higress Annotation 注解。
兼容 Ingress API/Gateway API/Istio API,可以组合多种 CRD 实现流量精细化管理。 - 便于扩展
提供 Wasm、Lua、进程外三种插件扩展机制,支持多语言编写插件,生效粒度支持全局级、域名级,路由级。
插件支持热更新,变更插件逻辑和配置都对流量无损。
架构
整体上 Higress 网关由控制面组件 higress-controller 和数据面组件 higress-gateway 组成。higress-gateway 负责承载数据流量,higress-controller 负责管理配置下发。
Higress
数据面组件 higress-gateway 是基于 Envoy 开发的网关组件,负责接收和处理流量,支持 HTTP/1.1、HTTP/2、gRPC 等协议,支持 TLS、mTLS、WAF、限流、熔断、重试、负载均衡、路由、转发、重定向、跨域等功能,也就是说真正的流量处理都是在 higress-gateway 中完成的。
控制面组件 higress-controller 负责管理配置下发,支持 Ingress API、Gateway API、Istio API,支持多种注册中心,支持多种认证鉴权策略,支持多种插件扩展机制,支持多种 CRD 实现流量精细化管理,也就是说所有的配置都是通过 higress-controller 下发到 higress-gateway 中的。
安装
Higress 的安装非常简单,只需要通过 Helm 安装即可,如果想要根据自己的需求进行定制,可以通过修改 Helm 的参数来实现,完整参数介绍可以查看运维参数说明,常用的一些可定制参数如下所示:
参数名 | 参数说明 | 默认值 |
全局参数 | ||
global.local | 如果要安装至本地 K8s 集群(如 Kind、Rancher Desktop 等),请设置为 true | false |
global.ingressClass | 用于过滤被 Higress Controller 监听的 Ingress 资源的 IngressClass。 在集群内部署了多个网关时,可以使用这一参数来区分每个网关的职责范围。 IngressClass 有一些特殊的取值: 1. 如果设置为“nginx”,Higress Controller 将监听 Ingress 为 nginx 或为空的 Ingress 资源。 2. 如果设为空,Higress Controller 将监听 K8s 集群内的全部 Ingress 资源。 | higress |
global.watchNamespace | 如果值不为空,Higress Controller 将只会监听指定命名空间下的资源。 当基于 K8s 命名空间进行业务系统隔离时,若需要对每个命名空间部署一套独立的网关,可以通过这一参数来限制 Higress 监听指定命名空间内的 Ingress。 | "" |
global.disableAlpnH2 | 是否在 ALPN 中禁用 HTTP/2 协议 | true |
global.enableStatus | 若为true, Higress Controller 将会更新 Ingress 资源的 status 字段。 为避免从 Nginx Ingress 迁移过程中,覆盖 Ingress 对象的 status 字段,可以将这一参数设置为false,这样 Higress 默认就不会将入口 IP 写入 Ingress 的 status 字段。 | true |
global.enableIstioAPI | 若为true,Higress Controller 将同时监听 istio 资源 | false |
global.enableGatewayAPI | 若为true,Higress Controller 将同时监听 Gateway API 资源 | false |
global.onlyPushRouteCluster | 若为true,Higress Controller 将会只推送被路由关联的服务 | true |
核心组件参数 | ||
higress-core.gateway.replicas | Higress Gateway 的 pod 数量 | 2 |
higress-core.controller.replicas | Higress Controller 的 pod 数量 | 1 |
控制台参数 | ||
higress-console.replicaCount | Higress Console 的 pod 数量 | 1 |
higress-console.service.type | Higress Console 所使用的 K8s Service 类型 | ClusterIP |
higress-console.web.login.prompt | 登录页面上显示的提示信息 | "" |
higress-console.o11y.enabled | 若为 true,将同时安装可观测性套件(Grafana + Promethues) | false |
higress-console.pvc.rwxSupported | 标识目标 K8s 集群是否支持 PersistentVolumeClaim 的 ReadWriteMany 操作方式。 | true |
默认情况下 ingress-gateway 会通过一个 LoadBalancer Service 暴露出来,如果你的集群不支持 LoadBalancer 类型的 Service,可以通过修改 higress-core.gateway.service.type 参数来修改 Service 类型,或者通过修改 higress-core.gateway.hostNetwork 参数来直接使用宿主机网络,比如我们这里就使用 hostNetwork 来直接使用宿主机网络:
helm repo add higress.io https://higress.io/helm-charts
helm upgrade --install higress -n higress-system higress.io/higress --set higress-core.gateway.hostNetwork=true,higress-core.gateway.service.type=ClusterIP --create-namespace
另外 Higress 也支持 Istio CRD(可选),但是集群里需要提前安装好 Istio 的 CRD,如果不希望安装 Istio,也可以只安装 Istio 的 CRD:
helm repo add istio https://istio-release.storage.googleapis.com/charts
helm install istio-base istio/base -n istio-system --create-namespace
这种模式下,需要更新 Higress 的部署参数:
helm upgrade higress -n higress-system --set global.enableIstioAPI=true higress.io/higress --reuse-values
同样 Higress 也支持 Gateway API CRD(可选),一样我们也需要提前安装好 Gateway API 的 CRD:https://github.com/kubernetes-sigs/gateway-api/releases,比如我们这里安装 1.0.0 版本的 CRD,具体版本可以根据实际情况进行选择:
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/experimental-install.yaml
这种模式下,需要更新 Higress 的部署参数:
helm upgrade higress -n higress-system --set global.enableGatewayAPI=true higress.io/higress --reuse-values
安装完成后我们可以查看 Higress 的部署情况:
$ kubectl get pods -n higress-system
NAME READY STATUS RESTARTS AGE
higress-console-796544b9df-9v5hn 1/1 Running 0 4m57s
higress-controller-8d4bb7456-4nx6l 2/2 Running 0 4m57s
higress-gateway-596f4f6d9-d46wg 1/1 Running 0 4m57s
higress-gateway-596f4f6d9-mbhp9 1/1 Running 0 4m57s
$ kubectl get svc -n higress-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
higress-console ClusterIP 10.96.2.141 <none> 8080/TCP 9m35s
higress-controller ClusterIP 10.96.0.98 <none> 8888/TCP,15051/TCP,15010/TCP,15012/TCP,443/TCP,15014/TCP 9m35s
higress-gateway ClusterIP 10.96.3.17 <none> 80/TCP,443/TCP 9m35s
接下来我们可以创建一个如下所示的 Ingress 对象来暴露 higress-console
服务:
# higress-console.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: higress-console
namespace: higress-system
spec:
ingressClassName: higress
rules:
- host: higress.k8s.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: higress-console
port:
number: 8080
直接应用上面的 Ingress 对象即可:
$ kubectl apply -f higress-console.yaml
$ kubectl get ingress -n higress-system
NAME CLASS HOSTS ADDRESS PORTS AGE
higress-console higress higress.k8s.local 80 15s
由于我们这里将 higress-gateway 设置成了 hostNetwork 模式,所以我们只需要将 higress.k8s.local 解析到 higress-gateway 所在的任一节点即可,这样我们就可以通过 higress.k8s.local 来访问 higress-console 服务了。
初始化管理员
基本使用
当我们第一次访问 higress-console 的时候需要初始化管理员账号,初始化完成后我们就可以通过管理员账号登录到 higress-console 控制台了。
higress console
然后接下来就可以通过 higress-console 控制台来配置我们的网关了,包括创建路由规则、服务、证书等等。
比如现在在 default 命名空间下有如下所示的一个 foo 服务:
# foo.yaml
kind: Pod
apiVersion: v1
metadata:
name: foo-app
labels:
app: foo
spec:
containers:
- name: foo-app
image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/http-echo:0.2.4-alpine
args:
- "-text=foo"
---
kind: Service
apiVersion: v1
metadata:
name: foo-service
spec:
selector:
app: foo
ports:
# Default port used by the image
- port: 5678
现在我们希望创建一个对应 http://foo.bar.com/foo 的路由来指向该服务。这里我们有两种方法可以实现,一种是通过 higress-console 控制台来创建,另一种是通过 YAML 文件来创建。
通过控制台创建
首先点击左侧域名管理导航栏,然后点击页面右侧的创建域名按钮,按照下图所示内容填写表单并点击确定按钮。
创建域名
然后点击左侧路由管理导航栏,然后点击页面右侧的创建路由按钮,按照下图片所示内容填写表单并点击确定按钮(要注意选择目标服务)。
创建路由
接下来同样我们只需要将域名 foo.bar.com 解析到 higress-gateway 所在的任一节点即可,这样我们就可以通过 foo.bar.com 来访问 foo-service 服务了。
$ curl http://foo.bar.com/foo
foo
通过 YAML 文件创建
同样我们也可以通过 YAML 文件来创建路由(其实就是一个 Ingress 对象),首先我们需要创建一个 foo-route.yaml
文件,然后使用下方 YAML 来创建我们需要的路由配置。
# foo-route.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: foo
spec:
ingressClassName: higress
rules:
- host: foo.bar.com
http:
paths:
- pathType: Prefix
path: "/foo"
backend:
service:
name: foo-service
port:
number: 5678
然后我们只需要应用上面的 YAML 文件即可(记得先将控制台中添加的域名和路由删除):
$ kubectl apply -f foo-route.yaml
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
foo higress foo.bar.com 80 4s
同样现在我们也可以通过 foo.bar.com 来访问 foo-service 服务了。
$ curl foo.bar.com/foo
foo
但是需要注意的是通过 YAML 文件管理的路由配置并不会同步到控制台中去,所以在管理路由的时候需要注意。
高级用法
标准的 K8s Ingress 资源只能处理简单场景下的 HTTP(S)流量路由,无法处理流量切分,超时重试,Header 控制和跨域等问题。因此,不同的 Ingress Controller 利用自定义的 Ingress Annotation 增强 Ingress 能力。常见的 Nginx Ingress Controller 引入了 100 多个 Annotation 对 Ingress 在流量治理和安全防护上进行了扩展实现。目前,Higress 已经全面兼容了大部分 Nginx Ingress Annotation,方便用户从 Nginx Ingress 无缝迁移至 Higress,所以我们可以继续使用 Nginx Ingress 的 Annotation 来配置 Higress。
我们可以根据使用习惯继续使用 Nginx Ingress 的 Annotation 前缀 nginx.ingress.kubernetes.io ,或者使用 H igress Ingress 的 Annotation 前缀 higress.io,两者是等价的,下面我们就通过几个简单的例子来演示如何使用 Annotation 来配置 Higress。
跨域
跨域资源共享 CORS 是指允许 Web 应用服务器进行跨域访问控制,从而实现跨域数据安全传输,我们可以通过下面的 Annotation 来配置 Higress 的跨域策略:
- higress.io/enable-cors:true 或者 false,用于开启或关闭跨域。
- higress.io/cors-allow-origin:允许的第三方站点,支持泛域名,逗号分隔;支持通配符。默认值为 *,即允许所有第三方站点。
- higress.io/cors-allow-methods:允许的请求方法,如 GET、POST,逗号分隔;支持通配符 *。默认值为 GET, PUT, POST, DELETE, PATCH, OPTIONS。
- higress.io/cors-allow-headers:允许的请求头部,逗号分隔;支持通配符 *。默认值为 DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization。
- higress.io/cors-expose-headers:允许的响应头部,逗号分隔。
- higress.io/cors-allow-credentials:true 或者 false。是否允许携带凭证信息。默认允许。
- higress.io/cors-max-age:预检结果的最大缓存时间,单位为秒;默认值为 1728000。
比如我们现在有一需求是跨域请求被限制为只能来自 example.com 域的请求,并且 HTTP 方法只能是 GET 和 POST,允许的请求头部为 X-Foo-Bar,不允许携带凭证信息,那么我们可以通过下面的 Annotation 来配置 Higress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/enable-cors: "true"
higress.io/cors-allow-origin: "example.com"
higress.io/cors-allow-methods: "GET,POST"
higress.io/cors-allow-headers: "X-Foo-Bar"
higress.io/cors-allow-credentials: "false"
name: demo
spec:
ingressClassName: higress
rules:
- http:
paths:
- backend:
service:
name: demo-service
port:
number: 80
path: /hello
pathType: Exact
Rewrite
在请求转发给目标后端服务之前,Rewrite 重写可以修改原始请求的路径(Path)和主机域(Host),我们可以通过下面的 Annotation 来配置 Higress 的 Rewrite 重写策略:
- higress.io/rewrite-target:重写 Path,支持捕获组(Capture Group)。
- higress.io/upstream-vhost:重写 Host。
Rewrite 重写 Path
将请求 example.com/test 在转发至后端服务之前,重写为 example.com/dev:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/rewrite-target: "/dev"
name: demo
spec:
ingressClassName: higress
rules:
- host: example.com
http:
paths:
- backend:
service:
name: demo-service
port:
number: 80
path: /test
pathType: Exact
将请求 example.com/v1/app 在转发至后端服务之前,去掉 Path 前缀 /v1:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/rewrite-target: "/$2"
name: demo
spec:
ingressClassName: higress
rules:
- host: example.com
http:
paths:
- backend:
service:
name: demo-service
port:
number: 80
path: /v1(/|$)(.*)
pathType: ImplementationSpecific
将请求 example.com/v1/app 在转发至后端服务之前,将 Path 前缀 /v1 更改为 /v2:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/rewrite-target: "/v2/$2"
name: demo
spec:
ingressClassName: higress
rules:
- host: example.com
http:
paths:
- backend:
service:
name: demo-service
port:
number: 80
path: /v1(/|$)(.*)
pathType: ImplementationSpecific
Rewrite 重写 Host
将请求 example.com/test 在转发至后端服务之前,重写为 test.com/test:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/upstream-vhost: "test.com"
name: demo
spec:
ingressClassName: higress
rules:
- host: example.com
http:
paths:
- backend:
service:
name: demo-service
port:
number: 80
path: /test
pathType: Exact
重定向
通过重定向可以将原始客户端请求更改为目标请求。
配置 HTTP 重定向至 HTTPS
我们可以使用下面的 Annotation 来配置 Higress 将 HTTP 请求重定向至 HTTPS:
- higress.io/ssl-redirect:HTTP 重定向到 HTTPS
- higress.io/force-ssl-redirect: HTTP 重定向到 HTTPS
Higress 对于以上两个注解不区分对待,都是强制将 HTTP 重定向到 HTTPS。
比如我们需要将请求 http://example.com/test 重定向为 https://example.com/test,我们可以通过下面的 Annotation 来配置 Higress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/ssl-redirect: "true"
name: demo
spec:
ingressClassName: higress
rules:
- host: example.com
http:
paths:
- backend:
service:
name: demo-service
port:
number: 80
path: /test
pathType: Exact
永久重定向
- higress.io/permanent-redirect:永久重定向的目标 url,必须包含 scheme(http or https)。
- higress.io/permanent-redirect-code:永久重定向的 HTTP 状态码,默认为 301。
比如将请求 http://example.com/test 永久重定向为 http://example.com/app,我们可以通过下面的 Annotation 来配置 Higress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/permanent-redirect: "http://example.com/app"
name: demo
spec:
ingressClassName: higress
rules:
- host: example.com
http:
paths:
- backend:
service:
name: demo-service
port:
number: 80
path: /test
pathType: Exact
临时重定向
- higress.io/temporal-redirect:临时重定向的目标 url,必须包含 scheme(http or https)。
需要将请求 http://example.com/test 临时重定向为 http://example.com/app,我们可以通过下面的 Annotation 来配置 Higress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/temporal-redirect: "http://example.com/app"
name: demo
spec:
ingressClassName: higress
rules:
- host: example.com
http:
paths:
- backend:
service:
name: demo-service
port:
number: 80
path: /test
pathType: Exact
配置后端服务协议
Higress 默认使用 HTTP 协议转发请求到后端业务容器。当您的业务容器为 HTTPS 协议时,可以通过使用注解 higress.io/backend-protocol: "HTTPS";当您的业务容器为 GRPC 服务时,可以通过使用注解 higress.io/backend-protocol: "GRPC"。
相比 Nginx Ingress 的优势,如果您的后端服务所属的 K8s Service 资源中关于 Port Name 的定义为 grpc 或 http2,您无需配置注解 higress.io/backend-protocol: "GRPC",Higress 会自动使用 GRPC 或者 HTTP2。
比如我们需要请求 example/test 转发至后端服务使用 HTTPS 协议,我们可以通过下面的 Annotation 来配置 Higress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/backend-protocol: "HTTPS"
name: demo
spec:
ingressClassName: higress
rules:
- host: example.com
http:
paths:
- backend:
service:
name: demo-service
port:
number: 80
path: /
pathType: Exact
如果需要请求 example/test 转发至后端服务使用 GRPC 协议,第一种做法就是通过注解,如下所示:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/backend-protocol: "GRPC"
name: demo
spec:
ingressClassName: higress
rules:
- host: example.com
http:
paths:
- backend:
service:
name: demo-service
port:
number: 80
path: /test
pathType: Exact
第二种做法是通过指定 Service Port Name 为 grpc即可,如下所示:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo
spec:
ingressClassName: higress
rules:
- host: example.com
http:
paths:
- backend:
service:
name: demo-service
port:
number: 80
path: /order
pathType: Exact
---
apiVersion: v1
kind: Service
metadata:
name: demo-service
spec:
ports:
- name: grpc # 指定 Service Port Name 为 grpc,Higress 会自动使用 GRPC 协议
port: 80
protocol: TCP
selector:
app: demo-service
超时
Higress 提供路由级别的超时设置,与 nginx ingress 不同,没有区分连接/读写超时,而是面向的接口处理总延时进行配置,在未进行配置时默认不限制,例如后端未返回应答,网关将无限等待。
设置超时时间为 5s:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/timeout: "5"
name: demo
spec:
ingressClassName: higress
rules:
- host: example.com
http:
paths:
- backend:
service:
name: demo-service
port:
number: 80
path: /test
pathType: Exact
灰度发布
Higress 提供复杂的路由处理能力,和 Ingress Nginx 类似,Higress 也支持基于 Header、Cookie 以及权重的灰度发布功能。灰度发布功能可以通过设置注解来实现,为了启用灰度发布功能,需要设置注解 higress.io/canary: "true"。通过不同注解可以实现不同的灰度发布功能。
当多种方式同时配置时,灰度方式选择优先级为:基于 Header > 基于 Cookie > 基于权重(从高到低)。
下面我们通过一个示例应用来对灰度发布功能进行说明。
第一步. 部署 Production 应用
首先创建一个 production 环境的应用资源清单:
# production.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: production
labels:
app: production
spec:
selector:
matchLabels:
app: production
template:
metadata:
labels:
app: production
spec:
containers:
- name: production
image: mirrorgooglecontainers/echoserver:1.10
ports:
- containerPort: 8080
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
---
apiVersion: v1
kind: Service
metadata:
name: production
labels:
app: production
spec:
ports:
- port: 80
targetPort: 8080
name: http
selector:
app: production
然后创建一个用于 production 环境访问的 Ingress 资源对象:
# production-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: production
spec:
ingressClassName: higress
rules:
- host: echo.k8s.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: production
port:
number: 80
直接创建上面的几个资源对象:
☸ ➜ kubectl apply -f production.yaml
☸ ➜ kubectl apply -f production-ingress.yaml
☸ ➜ kubectl get pods -l app=production
NAME READY STATUS RESTARTS AGE
production-64cfc46b65-j6krb 1/1 Running 0 56s
☸ ➜ kubectl get ingress production
NAME CLASS HOSTS ADDRESS PORTS AGE
production higress echo.k8s.local 80 9s
应用部署成功后即可正常访问应用:
☸ ➜ curl http://echo.k8s.local
Hostname: production-64cfc46b65-j6krb
Pod Information:
node name: node1
pod name: production-64cfc46b65-j6krb
pod namespace: default
pod IP: 10.0.1.249
Server values:
server_versinotallow=nginx: 1.13.3 - lua: 10008
Request Information:
client_address=10.0.1.162
method=GET
real path=/
query=
request_versinotallow=1.1
request_scheme=http
request_uri=http://echo.k8s.local:8080/
Request Headers:
accept=*/*
host=echo.k8s.local
original-host=echo.k8s.local
req-start-time=1713508680651
user-agent=curl/7.87.0
x-b3-sampled=0
x-b3-spanid=5ec284bec822750c
x-b3-traceid=5c48c3cc4192ddc85ec284bec822750c
x-envoy-attempt-count=1
x-envoy-decorator-operatinotallow=production.default.svc.cluster.local:80/*
x-envoy-internal=true
x-forwarded-for=192.168.0.112
x-forwarded-proto=http
x-request-id=d42c8455-5b2e-471d-811c-65f80210ccec
Request Body:
-no body in request-
第二步. 创建 Canary 版本
参考将上述 Production 版本的 production.yaml 文件,再创建一个 Canary 版本的应用。
# canary.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: canary
labels:
app: canary
spec:
selector:
matchLabels:
app: canary
template:
metadata:
labels:
app: canary
spec:
containers:
- name: canary
image: mirrorgooglecontainers/echoserver:1.10
ports:
- containerPort: 8080
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
---
apiVersion: v1
kind: Service
metadata:
name: canary
labels:
app: canary
spec:
ports:
- port: 80
targetPort: 8080
name: http
selector:
app: canary
接下来就可以通过配置 Annotation 规则进行流量切分了。
基于 Header 灰度发布
当我们需要通过 Header 来进行灰度发布时,我们可以通过下面的 Annotation 来配置 Higress 的灰度发布策略:
- 只配置 higress.io/canary-by-header:基于 Request Header 的名称进行流量切分。当请求包含该 Header 并其值为 always 时,请求流量会被分配到灰度服务入口;其他情况时,请求流量不会分配到灰度服务。
- 同时配置 higress.io/canary-by-header 和 higress.io/canary-by-header-value:基于 Request Header 的名称和值进行流量切分。当请求中的 header 的名称和 header 的值与该配置匹配时,请求流量会被分配到灰度服务;其他情况时,请求流量不会分配到灰度服务。
比如现在当请求 Header 为 higress:always 时将访问灰度服务 canary;其他情况将访问正式服务 production,那么我们可以创建如下的 Ingress 对象:
# canary-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/canary: "true"
higress.io/canary-by-header: "higress"
name: canary
spec:
ingressClassName: higress
rules:
- host: echo.k8s.local
http:
paths:
- backend:
service:
name: canary
port:
number: 80
path: /
pathType: Prefix
直接应用上面的 YAML 文件即可:
☸ ➜ kubectl apply -f canary.yaml
☸ ➜ kubectl apply -f canary-ingress.yaml
☸ ➜ kubectl get pods -l app=canary
NAME READY STATUS RESTARTS AGE
canary-7d97679b67-sh2r8 1/1 Running 0 12m
☸ ➜ kubectl get ingress canary
NAME CLASS HOSTS ADDRESS PORTS AGE
canary higress echo.k8s.local 80 37s
更新上面的 Ingress 资源对象后,我们在请求中加入不同的 Header 值,再次访问应用的域名来查看效果。
☸ ➜ for i in $(seq 1 10); do curl -s echo.k8s.local | grep "Hostname"; done
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
这里我们在请求的时候没有配置任何 Header,所以请求没有发送到 Canary 应用中去,如果设置为其他值呢:
☸ ➜ for i in $(seq 1 10); do curl -s -H "higress: always" echo.k8s.local | grep "Hostname"; done
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
由于我们请求设置的 Header 值为 higress: always,所以请求全都路由到了 canary 的服务,如果是其他的值则不会路由到 canary 的服务。
这个时候我们可以在上一个 annotation (即 canary-by-header)的基础上添加一条 higress.io/canary-by-header-value: user-value 这样的规则,就可以将指定 Header 值的请求路由到 Canary Ingress 中指定的服务了。
annotations:
higress.io/canary: "true" # 要开启灰度发布机制,首先需要启用 Canary
higress.io/canary-by-header: "higress" # 基于header的流量切分
higress.io/canary-by-header-value: "user-value"
同样更新 Ingress 对象后,重新访问应用,当 Request Header 满足 higress: user-value时,所有请求就会被路由到 Canary 版本:
☸ ➜ for i in $(seq 1 10); do curl -s -H "higress: user-value" echo.k8s.local | grep "Hostname"; done
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
基于 Cookie 灰度发布
与基于 Request Header 的 annotation 用法规则类似。例如在 A/B 测试场景下,需要让地域为北京的用户访问 Canary 版本。那么当 cookie 的 annotation 设置为 higress.io/canary-by-cookie: "users_from_Beijing",此时后台可对登录的用户请求进行检查,如果该用户访问源来自北京则设置 cookie users_from_Beijing 的值为 always,这样就可以确保北京的用户仅访问 Canary 版本。
基于 Cookie 的灰度发布不支持自定义设置 Key 对应的值,只能是 always。
比如我们现在请求的 Cookie 为 users_from_Beijing=always 时将访问灰度版本服务 canary;其他情况将访问正式服务 production,则我们可以创建如下的 Ingress 对象:
# canary-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/canary: "true"
higress.io/canary-by-cookie: "users_from_Beijing"
name: canary
spec:
ingressClassName: higress
rules:
- host: echo.k8s.local
http:
paths:
- backend:
service:
name: canary
port:
number: 80
path: /
pathType: Prefix
更新上面的 Ingress 资源对象后,我们在请求中设置一个 users_from_Beijing=always 的 Cookie 值,再次访问应用的域名。
☸ ➜ for i in $(seq 1 10); do curl -s -b "users_from_Beijing=always" echo.k8s.local | grep "Hostname"; done
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
Hostname: canary-7d97679b67-sh2r8
我们可以看到应用都被路由到了 Canary 版本的应用中去了,如果我们将这个 Cookie 值设置为其他值,则不会路由到 Canary 应用中。
基于权重灰度发布
基于权重的流量切分的典型应用场景就是蓝绿部署,可通过将权重设置为 0 或 100 来实现。例如,可将 Green 版本设置为主要部分,并将 Blue 版本的入口配置为 Canary。最初,将权重设置为 0,因此不会将流量代理到 Blue 版本。一旦新版本测试和验证都成功后,即可将 Blue 版本的权重设置为 100,即所有流量从 Green 版本转向 Blue。
在 Higress 中,我们可以通过下面的 Annotation 来配置基于权重的灰度发布策略:
- higress.io/canary-weight:设置请求到指定服务的百分比(值为 0~100 的整数)
- higress.io/canary-weight-total:设置权重总和,默认为 100
比如我们配置灰度服务 canary 的权重为 20%,配置正式服务 production 的权重为 80%,则我们可以创建如下的 Ingress 对象:
# canary-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/canary: "true"
higress.io/canary-weight: "20" # 分配20%流量到当前Canary版本
name: canary
spec:
ingressClassName: higress
rules:
- host: echo.k8s.local
http:
paths:
- backend:
service:
name: canary
port:
number: 80
path: /
pathType: Prefix
更新上面的 Ingress 对象,接下来我们在命令行终端中来不断访问这个应用,观察 Hostname 变化:
☸ ➜ for i in $(seq 1 10); do curl -s echo.k8s.local | grep "Hostname"; done
Hostname: production-64cfc46b65-j6krb
Hostname: canary-7d97679b67-sh2r8
Hostname: production-64cfc46b65-j6krb
Hostname: canary-7d97679b67-sh2r8
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
Hostname: production-64cfc46b65-j6krb
由于我们给 Canary 版本应用分配了 20% 左右权重的流量,所以上面我们访问 10 次有 3 次(不是一定)访问到了 Canary 版本的应用,符合我们的预期。
到这里我们就通过 Higress 的灰度发布功能实现了流量的切分,可以根据不同的场景选择不同的灰度发布方式。
插件
Higress 提供 Wasm、Lua、进程外三种插件扩展机制,可以通过插件机制实现自定义的功能扩展。支持多语言编写插件,生效粒度支持全局级、域名级,路由级。插件支持热更新,变更插件逻辑和配置都对流量无损。
要为 Higress 启用插件,一共有两种方法,一是通过 Higress 控制台进行配置,另外还可以通过 Higress WasmPlugin CRD 进行配置。
通过 Higress 控制台进行配置
Higress 控制台提供了 3 个入口进行插件配置:
- 全局配置:插件市场 -> 选择插件进行配置
- 域名级配置:域名管理 -> 选择域名 -> 点击策略-> 选择插件进行配置
- 路由级配置: 路由配置 -> 选择路由 -> 点击策略 -> 选择插件进行配置
这三个配置的生效优先级是: 路由级 > 域名级 > 全局,也就是对于没有匹配到具体路由或域名的请求才会生效全局配置。
对于一般的插件,包括自定义插件在内,路由/域名级的配置字段和全局配置字段是完全一样的;对于认证类插件(Key 认证、HMAC 认证、Basic 认证、JWT 认证等)则不同,全局配置仅做 Consumer 凭证配置,以及是否开启全局认证,而在路由/域名级通过 allow 字段配置允许访问的 Consumer 列表。
比如我们要给前面的 foo.bar.com 服务配置一个 Basic Auth 插件,首先需要在全局配置启用 Basic Auth 插件,然后在域名级(或者路由级)配置 Basic Auth 插件。
在控制台中切换到插件市场,找到 Basic Auth 插件,点击配置,然后在数据编辑器中通过 consumers
字段配置用户凭证,如下所示:
global_auth: false
consumers: # 配置两个用户凭证
- name: consumer1
credential: admin:123456
- name: consumer2
credential: guest:abc
要记得选择开启状态,然后点击保存。
配置 Basic 认证
接下来切换到域名管理页面,添加一个域名 foo.bar.com,然后点击策略,选择 Basic Auth 插件进行配置,。
在数据编辑器中通过 allow 字段配置允许访问的用户,如下所示:
allow:
- consumer1
要记得选择开启状态,然后点击保存。这里我们配置的是只允许 consumer1 用户访问 foo.bar.com 服务,其他调用者不允许访问。
现在我们可以访问下 foo.bar.com 服务进行验证:
$ curl -v foo.bar.com/foo
* Trying 192.168.0.116:80...
* Connected to foo.bar.com (192.168.0.116) port 80 (#0)
> GET /foo HTTP/1.1
> Host: foo.bar.com
> User-Agent: curl/7.87.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< www-authenticate: Basic realm=MSE Gateway
< date: Sat, 20 Apr 2024 06:05:15 GMT
< server: istio-envoy
< content-length: 0
<
* Connection #0 to host foo.bar.com left intact
可以看到返回了 401 Unauthorized,因为我们没有提供用户凭证,接下来我们提供用户凭证进行访问:
$ curl -v -u admin:123456 foo.bar.com/foo
* Trying 192.168.0.116:80...
* Connected to foo.bar.com (192.168.0.116) port 80 (#0)
* Server auth using Basic with user 'admin'
> GET /foo HTTP/1.1
> Host: foo.bar.com
> Authorization: Basic YWRtaW46MTIzNDU2
> User-Agent: curl/7.87.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< x-app-name: http-echo
< x-app-version: 0.2.4
< date: Sat, 20 Apr 2024 06:06:10 GMT
< content-length: 4
< content-type: text/plain; charset=utf-8
< req-cost-time: 1
< req-arrive-time: 1713593170671
< resp-start-time: 1713593170673
< x-envoy-upstream-service-time: 1
< server: istio-envoy
<
foo
* Connection #0 to host foo.bar.com left intact
可以看到我们提供了用户凭证后成功访问了服务,如果我们提供的用户凭证不在允许访问的用户列表中,则会返回 403 Forbidden 错误。
$ curl -v -u guest:abc foo.bar.com/foo
* Trying 192.168.0.116:80...
* Connected to foo.bar.com (192.168.0.116) port 80 (#0)
* Server auth using Basic with user 'guest'
> GET /foo HTTP/1.1
> Host: foo.bar.com
> Authorization: Basic Z3Vlc3Q6YWJj
> User-Agent: curl/7.87.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< www-authenticate: Basic realm=MSE Gateway
< date: Sat, 20 Apr 2024 06:06:54 GMT
< server: istio-envoy
< content-length: 0
<
* Connection #0 to host foo.bar.com left intact
通过 Higress WasmPlugin 进行配置
除了通过控制台可以配置插件,还可以通过 Higress WasmPlugin CRD 进行配置,Higress WasmPlugin CRD 是 Istio WasmPlugin CRD 的扩展,新增了一些配置字段。
字段名称 | 数据类型 | 填写要求 | 描述 |
defaultConfig | object | 选填 | 插件默认配置,全局生效于没有匹配具体域名和路由配置的请求 |
matchRules | array of object | 选填 | 匹配域名或路由生效的配置 |
matchRules中每一项的配置字段说明:
字段名称 | 数据类型 | 填写要求 | 配置示例 | 描述 |
ingress | array of string | ingress和domain中必填一项 | ["default/foo","default/bar"] | 匹配 ingress 资源对象,匹配格式为: 命名空间/ingress名称 |
domain | array of string | ingress和domain中必填一项 | ["example.com","*.test.com"] | 匹配域名,支持泛域名 |
config | object | 选填 | - | 匹配后生效的插件配置 |
同样如果要通过 WasmPlugin 来配置实现 Basic Auth 插件,首先需要创建一个 WasmPlugin CRD 对象,如下所示:
# basic-auth-plugin.yaml
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: basic-auth
namespace: higress-system
spec:
url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/basic-auth:1.0.0 # 插件镜像地址
defaultConfig:
consumers:
- name: consumer1
credential: admin:123456
- name: consumer2
credential: guest:abc
matchRules:
- domain:
- foo.bar.com
config:
allow:
- consumer2
将上面通过控制台配置的 Basic Auth 插件配置重置还原,不启用 Basic Auth 插件。
在上面的 WasmPlugin 对象中我们通过 defaultConfig 字段配置了用户凭证,这部分其实就对应于控制台中的全局配置,然后通过 matchRules 字段配置了域名级的配置,这部分对应于控制台中的域名级配置,当然除了 domain 之外还可以配置 ingress 字段,这部分对应于控制台中的路由级配置。比如上面的配置表示只允许 consumer2 用户访问 foo.bar.com 服务。
直接创建上面的 WasmPlugin 对象:
☸ ➜ kubectl apply -f basic-auth-plugin.yaml
然后再次访问 foo.bar.com 服务进行验证:
☸ ➜ curl -v foo.bar.com/foo
* Trying 192.168.0.116:80...
* Connected to foo.bar.com (192.168.0.116) port 80 (#0)
> GET /foo HTTP/1.1
> Host: foo.bar.com
> User-Agent: curl/7.87.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< www-authenticate: Basic realm=MSE Gateway
< date: Sat, 20 Apr 2024 06:23:48 GMT
< server: istio-envoy
< content-length: 0
<
* Connection #0 to host foo.bar.com left intact
可以看到返回了 401 Unauthorized,因为我们没有提供用户凭证,接下来我们提供用户凭证进行访问:
☸ ➜ curl -v -u guest:abc foo.bar.com/foo
* Trying 192.168.0.116:80...
* Connected to foo.bar.com (192.168.0.116) port 80 (#0)
* Server auth using Basic with user 'guest'
> GET /foo HTTP/1.1
> Host: foo.bar.com
> Authorization: Basic Z3Vlc3Q6YWJj
> User-Agent: curl/7.87.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< x-app-name: http-echo
< x-app-version: 0.2.4
< date: Sat, 20 Apr 2024 06:24:49 GMT
< content-length: 4
< content-type: text/plain; charset=utf-8
< req-cost-time: 1
< req-arrive-time: 1713594289845
< resp-start-time: 1713594289847
< x-envoy-upstream-service-time: 0
< server: istio-envoy
<
foo
* Connection #0 to host foo.bar.com left intact
上面我们在 WasmPlugin 中配置了只允许 consumer2 用户访问 foo.bar.com 服务,所以只有提供了 consumer2 用户凭证的请求才能访问服务,所以这里我们提供的 guest:abc 用户凭证成功访问了服务,如果提供的用户凭证不在允许访问的用户列表中,则会返回 403 Forbidden 错误。
☸ ➜ curl -v -u admin:123456 foo.bar.com/foo
* Trying 192.168.0.116:80...
* Connected to foo.bar.com (192.168.0.116) port 80 (#0)
* Server auth using Basic with user 'admin'
> GET /foo HTTP/1.1
> Host: foo.bar.com
> Authorization: Basic YWRtaW46MTIzNDU2
> User-Agent: curl/7.87.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< www-authenticate: Basic realm=MSE Gateway
< date: Sat, 20 Apr 2024 06:26:00 GMT
< server: istio-envoy
< content-length: 0
<
* Connection #0 to host foo.bar.com left intact
这里我们就实现了通过 Higress WasmPlugin CRD 对象配置 Basic Auth 插件,实现了用户凭证认证功能。除了 Basic Auth 插件,Higress 还提供了很多其他插件,比如 Key Auth 插件、HMAC Auth 插件、JWT Auth 插件等,可以根据实际需求选择不同的插件进行配置,每种插件的配置属性可以参考控制台中的插件市场的配置说明。
自定义插件
除了 Higress 提供的内置插件外,有时候我们可能会有一些特殊的需求,这时候我们可以通过自定义插件来实现自定义的功能扩展。我们可以使用多种编程语言来编写插件,生效粒度支持全局级、域名级,路由级,此外插件还支持热更新,变更插件逻辑和配置都对流量无损。下面我们就用一个简单的示例来说明下如何编写一个自定义插件。
首先需要安装 Golang 和 TinyGo 两个程序。Golang 要求 1.18 版本以上,官方指引链接:https://go.dev/doc/install;TinyGo 要求 0.28.1 版本以上,官方指引链接:https://tinygo.org/getting-started/install/。
☸ ➜ go version
go version go1.20.14 darwin/arm64
☸ ➜ tinygo version
tinygo version 0.30.0 darwin/amd64 (using go version go1.20.14 and LLVM version 16.0.1)
接下来首先我们初始化工程目录,新建一个名为 wasm-demo-go
的目录,在所建目录下执行以下命令,进行 Go 工程初始化
go mod init wasm-demo-go
国内环境需要设置下载依赖包的代理
go env -w GOPROXY=https://proxy.golang.com.cn,direct
然后下载构建插件的依赖:
go get github.com/higress-group/proxy-wasm-go-sdk
go get github.com/alibaba/higress/plugins/wasm-go@main
go get github.com/tidwall/gjson
接下来我们来编写一个简单的插件,实现一个简单的功能,当插件配置中有 mockEnable: true 时直接返回 hello world 应答;未做插件配置,或者设置 mockEnable: false 时给原始请求添加 hello: world 请求 Header 头。
package main
import (
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
)
func main() {
wrapper.SetCtx(
// 插件名称
"my-plugin",
// 为解析插件配置,设置自定义函数
wrapper.ParseConfigBy(parseConfig),
// 为处理请求头,设置自定义函数
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
// 自定义插件配置
type MyConfig struct {
mockEnable bool
}
// 在控制台插件配置中填写的yaml配置会自动转换为json,此处直接从json这个参数里解析配置即可
func parseConfig(json gjson.Result, config *MyConfig, log wrapper.Log) error {
// 解析出配置,更新到config中
config.mockEnable = json.Get("mockEnable").Bool()
return nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig, log wrapper.Log) types.Action {
proxywasm.AddHttpRequestHeader("hello", "world")
if config.mockEnable {
proxywasm.SendHttpResponse(200, nil, []byte("hello world"), -1)
}
return types.ActionContinue
}
在网关控制台中的插件配置为 yaml 格式,下发给插件时将自动转换为 json 格式,所以例子中的 parseConfig 可以直接从 json 中解析配置。
上面代码中通过 wrapper.ProcessRequestHeadersBy 将自定义函数 onHttpRequestHeaders 用于 HTTP 请求头处理阶段处理请求,除此之外,还可以通过下面方式,设置其他阶段的自定义处理函数。
HTTP 处理阶段 | 触发时机 | 挂载方法 |
HTTP 请求头处理阶段 | 网关接收到客户端发送来的请求头数据时 | wrapper.ProcessRequestHeadersBy |
HTTP 请求 Body 处理阶段 | 网关接收到客户端发送来的请求 Body 数据时 | wrapper.ProcessRequestBodyBy |
HTTP 应答头处理阶段 | 网关接收到后端服务响应的应答头数据时 | wrapper.ProcessResponseHeadersBy |
HTTP 应答 Body 处理阶段 | 网关接收到后端服务响应的应答 Body 数据时 | wrapper.ProcessResponseBodyBy |
上面示例代码中的 proxywasm.AddHttpRequestHeader 和 proxywasm.SendHttpResponse 是插件 SDK 提供的两个工具方法,主要的工具方法见下表:
分类 | 方法名称 | 用途 | 可以生效的 HTTP 处理阶段 |
请求头处理 | GetHttpRequestHeaders | 获取客户端请求的全部请求头 | HTTP 请求头处理阶段 |
ReplaceHttpRequestHeaders | 替换客户端请求的全部请求头 | HTTP 请求头处理阶段 | |
GetHttpRequestHeader | 获取客户端请求的指定请求头 | HTTP 请求头处理阶段 | |
RemoveHttpRequestHeader | 移除客户端请求的指定请求头 | HTTP 请求头处理阶段 | |
ReplaceHttpRequestHeader | 替换客户端请求的指定请求头 | HTTP 请求头处理阶段 | |
AddHttpRequestHeader | 新增一个客户端请求头 | HTTP 请求头处理阶段 | |
请求 Body 处理 | GetHttpRequestBody | 获取客户端请求 Body | HTTP 请求 Body 处理阶段 |
AppendHttpRequestBody | 将指定的字节串附加到客户端请求 Body 末尾 | HTTP 请求 Body 处理阶段 | |
PrependHttpRequestBody | 将指定的字节串附加到客户端请求 Body 的开头 | HTTP 请求 Body 处理阶段 | |
ReplaceHttpRequestBody | 替换客户端请求 Body | HTTP 请求 Body 处理阶段 | |
应答头处理 | GetHttpResponseHeaders | 获取后端响应的全部应答头 | HTTP 应答头处理阶段 |
ReplaceHttpResponseHeaders | 替换后端响应的全部应答头 | HTTP 应答头处理阶段 | |
GetHttpResponseHeader | 获取后端响应的指定应答头 | HTTP 应答头处理阶段 | |
RemoveHttpResponseHeader | 移除后端响应的指定应答头 | HTTP 应答头处理阶段 | |
ReplaceHttpResponseHeader | 替换后端响应的指定应答头 | HTTP 应答头处理阶段 | |
AddHttpResponseHeader | 新增一个后端响应头 | HTTP 应答头处理阶段 | |
应答 Body 处理 | GetHttpResponseBody | 获取客户端请求 Body | HTTP 应答 Body 处理阶段 |
AppendHttpResponseBody | 将指定的字节串附加到后端响应 Body 末尾 | HTTP 应答 Body 处理阶段 | |
PrependHttpResponseBody | 将指定的字节串附加到后端响应 Body 的开头 | HTTP 应答 Body 处理阶段 | |
ReplaceHttpResponseBody | 替换后端响应 Body | HTTP 应答 Body 处理阶段 | |
HTTP 调用 | DispatchHttpCall | 发送一个 HTTP 请求 | - |
GetHttpCallResponseHeaders | 获取 DispatchHttpCall 请求响应的应答头 | - | |
GetHttpCallResponseBody | 获取 DispatchHttpCall 请求响应的应答 Body | - | |
GetHttpCallResponseTrailers | 获取 DispatchHttpCall 请求响应的应答 Trailer | - | |
直接响应 | SendHttpResponse | 直接返回一个特定的 HTTP 应答 | - |
流程恢复 | ResumeHttpRequest | 恢复先前被暂停的请求处理流程 | - |
ResumeHttpResponse | 恢复先前被暂停的应答处理流程 | - |
接下来我们需要编译生成 WASM 文件:
go mod tidy
tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags="custommalloc nottinygc_finalizer" ./main.go
编译成功会在当前目录下生成文件 main.wasm,要在 Higress 中配合 Wasmplugin CRD 或者 Console 的 UI 交互配置该插件,需要将该 wasm 文件打包成 oci 或者 docker 镜像。
在项目根目录下面创建一个 Dockerfile 文件,内容如下:
FROM scratch
COPY main.wasm plugin.wasm
然后执行以下命令构建并推送镜像:
docker build -t cnych/higress-plugin-demo:1.0.0 .
docker push cnych/higress-plugin-demo:1.0.0
这样我们就将自定义插件打包成了一个 Docker 镜像。然后在网关控制台的插件市场中创建一个自定义插件,填入上面构建出的 Wasm 镜像地址即可。
添加插件
创建完成后,点击插件卡片的配置按钮,填入插件的配置 mockEnable: true,打开开启开关就生效了。如果插件逻辑发生了变更,可以构建一个新的镜像,并使用不同的镜像 tag,点插件卡片右上方菜单中的编辑按钮,将 Wasm 镜像地址修改为新版本的地址即可。
不过需要注意的是开启后会对所有请求生效,所以在生产环境中需要谨慎使用,可以通过域名级或者路由级配置插件来控制插件的生效范围。
除了通过控制台配置插件,还可以通过 Higress WasmPlugin CRD 对象配置插件,这样可以更灵活的控制插件的生效范围。
# wasm-plugin-demo.yaml
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: plugin-demo
namespace: higress-system
spec:
url: oci://docker.io/cnych/higress-plugin-demo:1.0.0
defaultConfig:
mockEnable: false
matchRules:
- domain:
- foo.bar.com
config:
mockEnable: true
直接应用上面的 WasmPlugin 对象:
☸ ➜ kubectl apply -f wasm-plugin-demo.yaml
然后我们再次访问 foo.bar.com 服务进行验证:
☸ ➜ curl foo.bar.com/foo
hello world
可以看到我们提供了插件配置 mockEnable: true 后直接返回了 hello world 应答,如果我们将插件配置设置为 mockEnable: false,则会给原始请求添加 hello: world 请求 Header 头。
这样我们就通过 Higress WasmPlugin CRD 对象配置了自定义插件,实现了自定义的功能扩展。
这里对插件的生效机制简单做个说明:
- 用户将代码编译成 wasm 文件
- 用户将 wasm 文件构建成 docker 镜像
- 用户将 docker 镜像推送至镜像仓库
- 用户创建 WasmPlugin 资源
- Istio watch 到 WasmPlugin 资源的变化
- Higress Gateway 中的 xDS proxy 进程从 Istio 获取到配置,发现插件的镜像地址
- xDS proxy 从镜像仓库拉取镜像
- xDS proxy 从镜像中提取出 wasm 文件
- Higress Gateway 中的 envoy 进程从 xDS proxy 获取到配置,发现 wasm 文件的本地路径
- Envoy 从本地文件中加载 wasm 文件
插件生效
这里 envoy 获取配置并加载 wasm 文件使用到了 ECDS (Extension Config Discovery Service)的机制,实现了 wasm 文件更新,直接热加载,不会导致任何连接中断,业务流量完全无损。
除了本文介绍的 Higress 功能之外,还有很多其他功能和最佳实践方案,更多内容可以参考 Higress 官方文档。
参考文档:https://higress.io