目录
原文链接
https://onedayxyy.cn/docs/EnvoyFilter-API
本节实战
实战名称 |
---|
🚩 实战:EnvoyFilter API-全局范围-2023.12.18(测试成功) |
🚩 实战:EnvoyFilter API-配置优先级-2023.12.18(测试成功) |
🚩 实战:EnvoyFilter API-添加 Lua 脚本-2023.12.18(测试成功) |
EnvoyFilter API
前面我们介绍了可以使用 EnvoyFilter
对象来部署 Istio Wasm 插件,此外 EnvoyFilter
还可以实现很多其他的功能。EnvoyFilter
提供了一种机制来自定义 Istio Pilot 生成的 Envoy 配置,使用 EnvoyFilter 可以修改某些字段的值、添加特定过滤器,甚至添加全新的侦听器、集群等,当然我们必须谨慎使用此功能,因为不正确的配置可能会破坏整个网格的稳定性。
需要注意当多个 EnvoyFilter
绑定到指定命名空间中的相同工作负载时,所有补丁将按照创建时间的顺序依次处理,如果多个 EnvoyFilter
配置相互冲突,则会无效。要将 EnvoyFilter
资源应用于系统中的所有工作负载(sidecar 和网关),则根命名空间中定义该资源,而不使用工作负载选择器即可。
Patch 操作
在 EnvoyFilter
对象中有一个 configPatches
字段,这个字段属于核心字段,用于指定要对 Envoy 进行什么样的操作,对各种配置对象进行的更改,该字段下面主要包括三个字段:
applyTo
:指定应在 Envoy 配置中的哪个位置应用补丁。根据 applyTo 的不同,匹配条件应选择相应的对象。例如,带有HTTP_FILTER
的 applyTo 应该在监听器上有一个匹配条件,网络过滤器选择envoy.filters.network.http_connection_manager
,并在相对于插入应执行的 HTTP 过滤器上有一个子过滤器选择。类似地,对于CLUSTER
的 applyTo,如果提供了匹配条件,应该在集群上匹配,而不是在监听器上。match
:在监听器/路由配置/集群上的匹配。patch
:要应用的补丁以及操作。
其中的 applyTo
指定了在 Envoy 配置中给定的补丁应该被应用的位置,这个字段的值可以使用的值如下所示:
名称 | 描述 |
---|---|
INVALID | |
LISTENER | 将补丁应用于监听器。 |
FILTER_CHAIN | 将补丁应用于过滤器链。 |
NETWORK_FILTER | 将补丁应用于网络过滤器链,以修改现有过滤器或添加新过滤器。 |
HTTP_FILTER | 将补丁应用于 HTTP 连接管理器中的 HTTP 过滤器链,以修改现有过滤器或添加新过滤器。 |
ROUTE_CONFIGURATION | 将补丁应用于 HTTP 连接管理器内的路由配置(rds 输出)。这不适用于虚拟主机。目前,仅允许在路由配置对象上进行MERGE 操作。 |
VIRTUAL_HOST | 将补丁应用于路由配置中的虚拟主机。 |
HTTP_ROUTE | 将补丁应用于路由配置中匹配的虚拟主机内的路由对象。 |
CLUSTER | 将补丁应用于 CDS 输出中的集群。也用于添加新集群。 |
EXTENSION_CONFIG | 将补丁应用于或在 ECDS 输出中添加扩展配置。注意,ECDS 仅由 HTTP 过滤器支持。 |
BOOTSTRAP | 将补丁应用于引导配置。 |
LISTENER_FILTER | 将补丁应用于监听器过滤器。 |
根据我们的需求选择不同的 applyTo
值,然后在 match
字段中指定匹配条件,在将补丁应用于给定代理的生成配置之前,必须满足一个或多个匹配条件,match
下面可以配置的字段如下所示:
-
context
:匹配特定的配置的生成上下文。Istio Pilot 在网关的上下文中、sidecar 的入站流量和出站流量中生成 envoy 配置。可以配置的包括:ANY
:Sidecar 和网关中的所有侦听器/路由/集群。SIDECAR_INBOUND
:Sidecar 中的入站侦听器/路由/集群。SIDECAR_OUTBOUND
:Sidecar 中的出站侦听器/路由/集群。GATEWAY
:网关中的侦听器/路由/集群。
-
proxy
:匹配与代理关联的属性。 -
listener
:匹配 envoy 监听器属性。 -
routeConfiguration
:匹配 envoy HTTP 路由配置属性。 -
cluster
:匹配 envoy 集群属性。
最后的 patch
字段用于指定要应用的补丁以及操作,这个字段的值可以使用的值如下所示:
-
operation
:指定 path 应该如何被应用,可以配置的值包括:INVALID
:无效的操作。MERGE
:将补丁与现有配置合并,如果要指定整个配置,请使用用REPLACE
。ADD
:将提供的配置添加到现有列表中(侦听器、集群、虚拟主机、网络过滤器或 HTTP 过滤器)。当applyTo
设置为ROUTE_CONFIGURATION
或HTTP_ROUTE
时,此操作将被忽略。REMOVE
:从列表(侦听器、集群、虚拟主机、网络过滤器、路由或 http 过滤器)中删除选定的对象,不需要指定value
。当applyTo
设置为ROUTE_CONFIGURATION
或HTTP_ROUTE
时,此操作将被忽略。INSERT_BEFORE
:在指定的对象之前插入提供的配置。此操作通常仅在过滤器或路由的上下文中有用,其中元素的顺序很重要。路由应根据最具体的匹配条件进行排序,因为会选择第一个匹配的元素。对于集群和虚拟主机,数组中的元素的顺序并不重要。在选择的过滤器或子过滤器之前插入。如果未选择任何过滤器,则指定的过滤器将插入到列表的最前面。INSERT_AFTER
:在指定的对象之后插入提供的配置。INSERT_FIRST
:根据所选的过滤器的存在与否,在列表中首先进行插入。当您希望根据 Match 子句中指定的匹配条件将您的过滤器排在列表的第一位时,这特别有用。REPLACE
:用新内容替换过滤器的内容。REPLACE
操作仅适用于HTTP_FILTER
和NETWORK_FILTER
。
-
value
:指定要应用的补丁,被修补的对象的 JSON 配置。将使用 proto 合并语义与路径中现有的 proto 进行合并。 -
filterClass
:确定过滤器插入顺序,它与ADD
操作结合使用,如果您的过滤器依赖于或影响过滤器链中另一个过滤器的功能,则过滤器排序非常重要。在过滤器类中,过滤器按照处理顺序插入。可以配置的值包括:UNSPECIFIED
:控制平面决定在哪里插入过滤器,如果过滤器独立于其他过滤器,则不要指定FilterClass
。AUTHN
:在 Istio 身份验证过滤器之后插入过滤器。AUTHZ
:在 Istio 授权过滤器之后插入过滤器。STATS
:在 Istio 统计过滤器之前插入过滤器。
接下来我们就用几个例子再来熟悉下 EnvoyFilter
的使用。
全局范围
🚩 实战:EnvoyFilter API-全局范围-2023.12.18(测试成功)
- 测试环境
k8s v1.27.6(containerd://1.6.20)(cni:flannel:v0.22.2)
istio v1.19.3(--set profile=demo)
tinygo version 0.30.0
实验软件:
链接:https://pan.baidu.com/s/1pMnJxgL63oTlGFlhrfnXsA?pwd=7yqb
提取码:7yqb
2023.11.5-实战:BookInfo 示例应用-2023.11.5(测试成功)
- 比如我们创建一个如下所示的
EnvoyFilter
资源对象,这个资源对象是在根命名空间中定义的,并且没有使用工作负载选择器,这样就会应用到系统中的所有工作负载(sidecar 和网关):
# access-log-filter.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: access-log
namespace: istio-system
spec:
configPatches:
- applyTo: NETWORK_FILTER
match:
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: MERGE
value:
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
access_log:
- name: envoy.access_loggers.file
typed_config:
"@type": "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog"
path: /dev/stdout
log_format:
text_format: "[%START_TIME%] \" %RESPONSE_CODE% \n"
在上面的这个资源对象中我们重新定义了 Envoy 的的访问日志,将日志输出到标准输出中,并重新定义了日志的格式。
- 先看下默认时应用的访问日志
[root@master1 ~]#kubectl logs -f productpage-v1-564d4686f-7vhks
……
INFO:werkzeug:::ffff:10.244.2.18 - - [18/Dec/2023 11:48:32] "GET /metrics HTTP/1.1" 200 -
INFO:werkzeug:::ffff:10.244.2.18 - - [18/Dec/2023 11:48:47] "GET /metrics HTTP/1.1" 200 -
INFO:werkzeug:::ffff:10.244.2.18 - - [18/Dec/2023 11:49:02] "GET /metrics HTTP/1.1" 200 -
INFO:werkzeug:::ffff:10.244.2.18 - - [18/Dec/2023 11:49:17] "GET /metrics HTTP/1.1" 200 -
INFO:werkzeug:::ffff:10.244.2.18 - - [18/Dec/2023 11:49:32] "GET /metrics HTTP/1.1" 200 -
- 直接应用上面的资源对象即可:
kubectl apply -f access-log-filter.yaml
- 然后我们可以重新访问下 Bookinfo 应用,看下是否能够看到新的日志:
export GATEWAY_URL=$(kubectl get po -l istio=ingressgateway -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc istio-ingressgateway -n istio-system -o 'jsonpath={.spec.ports[?(@.name=="http2")].nodePort}')
curl -v http://$GATEWAY_URL/productpage
- 访问后我们可以查看下 productpage 的日志,正常情况下应该能够看到如下所示的日志:
$ kubectl logs -f $(kubectl get po -l app=productpage -o 'jsonpath={.items[0].metadata.name}') -c istio-proxy
# ......
[2023-12-11T06:39:06.955Z] " 200
[2023-12-11T06:39:06.945Z] " 200
从上面的结果可以看到,我们通过 EnvoyFilter 自定义的日志已经生效了。
- 同样也可以查看下其他服务的日志,比如 details 服务,正常也能看到如下所示的日志:
$ kubectl logs -f $(kubectl get po -l app=details -o 'jsonpath={.items[0].metadata.name}') -c istio-proxy
[2023-12-11T06:39:06.948Z] " 200
- 其它
如果设置一个其他的命名空间,比如
default
,那么这个EnvoyFilter
就只会应用到default
命名空间中的工作负载,而不会应用到其他命名空间中的工作负载。同样再次加上工作负载选择器,那么这个EnvoyFilter
就只会应用到default
命名空间中特定的工作负载了,而不会应用到工作负载。
测试结束。😘
配置优先级
🚩 实战:EnvoyFilter API-配置优先级-2023.12.18(测试成功)
- 测试环境
k8s v1.27.6(containerd://1.6.20)(cni:flannel:v0.22.2)
istio v1.19.3(--set profile=demo)
tinygo version 0.30.0
实验软件:
链接:https://pan.baidu.com/s/1pMnJxgL63oTlGFlhrfnXsA?pwd=7yqb
提取码:7yqb
2023.11.5-实战:BookInfo 示例应用-2023.11.5(测试成功)
在 EnvoyFilter
对象中有一个 priority
字段,该字段用于指定 EnvoyFilter
的优先级,优先级低的 EnvoyFilter
会先于优先级高的 EnvoyFilter
应用。如果两个 EnvoyFilter
的优先级相同,那么就会按照创建时间的顺序依次处理。
- 比如我们创建一个如下所示的
EnvoyFilter
资源对象:
# priority-20-filter.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: priority-20
spec:
workloadSelector:
labels:
app: productpage
priority: 20
configPatches:
- applyTo: HTTP_FILTER
match:
context: ANY
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.fault"
patch:
operation: MERGE
value:
name: envoy.filters.http.fault
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
abort:
http_status: 503
percentage:
numerator: 100
denominator: HUNDRED
在上面的这个资源对象中我们定义了一个 EnvoyFilter
,这个 EnvoyFilter
的优先级为 20,我们将会在 Productpage 服务中应用这个 EnvoyFilter
,这个 EnvoyFilter
的作用是在 Productpage 服务中添加一个故障注入的过滤器,当我们访问 Productpage 服务时,会全部返回 503 错误。
- 然后我们再创建一个如下所示的
EnvoyFilter
资源对象:
# priority-10-filter.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: priority-10
spec:
workloadSelector:
labels:
app: productpage
priority: 10
configPatches:
- applyTo: HTTP_FILTER
match:
context: ANY
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.fault"
patch:
operation: MERGE
value:
name: envoy.filters.http.fault
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
abort:
http_status: 500
percentage:
numerator: 50
denominator: HUNDRED
这个资源对象和前面的资源对象基本一样,只是将优先级设置为 10,另外将故障注入的概率都设置为 50%, 并且中断的状态码为 500。
- 接下来我们同时应用上面的两个资源对象:
kubectl apply -f priority-10-filter.yaml
kubectl apply -f priority-20-filter.yaml
- 应用后我们再多次访问 Productpage 服务,看下会得怎样的结果:
$ curl -v http://$GATEWAY_URL/productpage
> GET /productpage HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 192.168.0.20:31896
> Accept: */*
>
< HTTP/1.1 503 Service Unavailable
< content-length: 18
< content-type: text/plain
< date: Mon, 11 Dec 2023 07:08:33 GMT
< server: istio-envoy
< x-envoy-upstream-service-time: 58
<
从结果看每次请求都会返回 503 错误,这是因为优先级为 10 的 EnvoyFilter
先于优先级为 20 的 EnvoyFilter
应用,所以优先级为 20 的 EnvoyFilter
覆盖了优先级为 10 的 EnvoyFilter
,这也是符合我们的预期的。
- 记得回收掉刚才创建的资源
kubectl delete -f priority-10-filter.yaml
kubectl delete -f priority-20-filter.yaml
测试结束。😘
添加 Lua 脚本
🚩 实战:EnvoyFilter API-添加 Lua 脚本-2023.12.18(测试成功)
- 测试环境
k8s v1.27.6(containerd://1.6.20)(cni:flannel:v0.22.2)
istio v1.19.3(--set profile=demo)
tinygo version 0.30.0
实验软件:
链接:https://pan.baidu.com/s/1pMnJxgL63oTlGFlhrfnXsA?pwd=7yqb
提取码:7yqb
2023.11.5-实战:BookInfo 示例应用-2023.11.5(测试成功)
比如现在我们想要在 Productpage 服务中添加一个 Lua 脚本,当我们收到 Productpage 服务的请求后,会发起一个 HTTP 请求到 baidu.com
,并将 baidu.com
的搜索结果作为原始请求的响应。
首先我们需要在 EnvoyFilter
中添加一个 Lua 过滤器,然后再添加一个 lua_cluster
集群,用来发起 HTTP 请求到 baidu.com,用来实现我们的逻辑。
- 创建一个如下所示的
EnvoyFilter
资源对象:
# productpage-lua-filter.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: productpage-lua
namespace: default
spec:
workloadSelector:
labels:
app: productpage
configPatches:
# 第一个 patch 将 lua 过滤器添加到监听器/HTTP连接管理器中。
- applyTo: HTTP_FILTER # HTTP_FILTER 用于 HTTP 连接管理器中的 HTTP 过滤器链
match:
context: SIDECAR_INBOUND
listener:
portNumber: 9080
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value: # lua filter specification
name: envoy.filters.http.lua
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
defaultSourceCode:
inlineString: |
function envoy_on_response(response_handle)
response_handle:headers():add("lua-filter", "true")
response_handle:headers():add("website", "youdianzhishi.com")
end
function envoy_on_request(request_handle)
-- 发起 HTTP 调用到 lua_cluster (即 baidu.com),并附加搜索词
local response_headers, response_body = request_handle:httpCall(
"lua_cluster",
{
[":method"] = "GET",
[":path"] = "/s?wd=优点知识",
[":authority"] = "baidu.com"
},
"",
5000
)
-- 使用 baidu.com 的搜索结果作为原始请求的响应
request_handle:respond(response_headers, response_body)
end
# 第二个 path 需要添加上面 lua 脚本里面指定的 lua_cluster 集群
- applyTo: CLUSTER
match:
context: SIDECAR_OUTBOUND
patch:
operation: ADD
value: # cluster specification
name: "lua_cluster"
type: STRICT_DNS
connect_timeout: 0.5s
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: lua_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
protocol: TCP
address: "baidu.com"
port_value: 80
上面的资源对象中我们定义了两个 patch,第一个用于将 lua 过滤器添加到监听器/HTTP 连接管理器中,并添加上 Lua 代码,第二个补丁用于添加 lua_cluster
集群。
envoy_on_request
这是一个定义在 Envoy 的 Lua 环境中的函数,它在请求被处理时被调用。这个函数名是 Envoy 约定的特定名称,Envoy 会在处理请求时自动调用这个函数。
- 直接应用上面的资源对象即可:
kubectl apply -f productpage-lua-filter.yaml
- 应用后,当我们再次请求 Productpage 服务就会发现请求会被重定向到
baidu.com
,并且响应头中会添加我们定义的 Header:
- 同样我们可以使用
istioctl proxy-config
命令来查看下 Envoy 的配置, 比如查看 Productpage 服务的监听器配置:
istioctl proxy-config listener productpage-v1-564d4686f-sgzkw --port 15006 -oyaml
在上面的结果中可以看到我们添加的 Lua 过滤器:
- 同样查看 Endpoint 的配置正常可以看到我们添加的
lua_cluster
集群:
istioctl proxy-config endpoint productpage-v1-564d4686f-sgzkw -oyaml
正常可以看到如下所示的结果:
除此之外还有非常多的使用场景,总之,只要能够通过 Envoy 的配置来实现的,都可以通过 EnvoyFilter
来实现。
测试结束。😘
作业
假设有一个需求是有好几万的租户在使用一个云服务,这个云服务会自动为所有的租户分配一个二级域名,配置 HTTPS 证书,这个场景我们直接通过 Gateway API 里面配置一个通配符的域名和对应的证书是不是就可以了。
现在的需求是有很多用户有自定义域名的一个需求,也需要支持这些用户配置证书,由于现在的 443 端口已经被通用的 Gateway 对象占用了,所以不能为每一个租户添加一个 Gateway API,那么应该如何使用同一个 Gateway API 来支持通配符的证书以及用户自定义的域名和证书的配置呢?
直接在 Gateway API 里面是不是加上这些自定义的域名和证书就可以了?确实可以,但是这个功能是开放给用户去使用的,这样的话是不是就会对其他用户可能产生影响,肯定是不希望用户能够去更新 Gateway API 对象的,那么这个时候应该如何实现呢?
不希望修改 Gateway API 对象,就能够支持很多用户的自定义域名和证书的需求(使用同一个 443 端口)?
使用我们今天讲的 EnvoyFilter 是不是就可以可以了?一个租户去自定义域名或者证书的时候单独就创建一个 EnvoyFilter 是不是就可以了(针对 Gateway)。
关于我
我的博客主旨:
- 排版美观,语言精炼;
- 文档即手册,步骤明细,拒绝埋坑,提供源码;
- 本人实战文档都是亲测成功的,各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人帮您解决问题,让我们一起进步!
🍀 微信二维码
x2675263825 (舍得), qq:2675263825。
🍀 微信公众号
《云原生架构师实战》
🍀 个人博客站点
http://onedayxyy.cn/
🍀 语雀
https://www.yuque.com/xyy-onlyone
🍀 csdn
https://blog.csdn.net/weixin_39246554?spm=1010.2135.3001.5421
🍀 知乎
https://www.zhihu.com/people/foryouone
最后
好了,关于本次就到这里了,感谢大家阅读,最后祝大家生活快乐,每天都过的有意义哦,我们下期见!