EnvoyFilter API

EnvoyFilter API_第1张图片

目录

EnvoyFilter API_第2张图片

原文链接

https://onedayxyy.cn/docs/EnvoyFilter-API

EnvoyFilter API_第3张图片

本节实战

实战名称
实战: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_CONFIGURATIONHTTP_ROUTE 时,此操作将被忽略。
    • REMOVE:从列表(侦听器、集群、虚拟主机、网络过滤器、路由或 http 过滤器)中删除选定的对象,不需要指定 value。当 applyTo 设置为 ROUTE_CONFIGURATIONHTTP_ROUTE 时,此操作将被忽略。
    • INSERT_BEFORE:在指定的对象之前插入提供的配置。此操作通常仅在过滤器或路由的上下文中有用,其中元素的顺序很重要。路由应根据最具体的匹配条件进行排序,因为会选择第一个匹配的元素。对于集群和虚拟主机,数组中的元素的顺序并不重要。在选择的过滤器或子过滤器之前插入。如果未选择任何过滤器,则指定的过滤器将插入到列表的最前面。
    • INSERT_AFTER:在指定的对象之后插入提供的配置。
    • INSERT_FIRST:根据所选的过滤器的存在与否,在列表中首先进行插入。当您希望根据 Match 子句中指定的匹配条件将您的过滤器排在列表的第一位时,这特别有用。
    • REPLACE:用新内容替换过滤器的内容。REPLACE 操作仅适用于 HTTP_FILTERNETWORK_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(测试成功)

image-20231105111842627

  • 比如我们创建一个如下所示的 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(测试成功)

image-20231105111842627

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(测试成功)

image-20231105111842627

比如现在我们想要在 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:

EnvoyFilter API_第4张图片

  • 同样我们可以使用 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

正常可以看到如下所示的结果:

EnvoyFilter API_第5张图片

除此之外还有非常多的使用场景,总之,只要能够通过 Envoy 的配置来实现的,都可以通过 EnvoyFilter 来实现。

测试结束。

作业

假设有一个需求是有好几万的租户在使用一个云服务,这个云服务会自动为所有的租户分配一个二级域名,配置 HTTPS 证书,这个场景我们直接通过 Gateway API 里面配置一个通配符的域名和对应的证书是不是就可以了。

现在的需求是有很多用户有自定义域名的一个需求,也需要支持这些用户配置证书,由于现在的 443 端口已经被通用的 Gateway 对象占用了,所以不能为每一个租户添加一个 Gateway API,那么应该如何使用同一个 Gateway API 来支持通配符的证书以及用户自定义的域名和证书的配置呢?

直接在 Gateway API 里面是不是加上这些自定义的域名和证书就可以了?确实可以,但是这个功能是开放给用户去使用的,这样的话是不是就会对其他用户可能产生影响,肯定是不希望用户能够去更新 Gateway API 对象的,那么这个时候应该如何实现呢?

不希望修改 Gateway API 对象,就能够支持很多用户的自定义域名和证书的需求(使用同一个 443 端口)?

使用我们今天讲的 EnvoyFilter 是不是就可以可以了?一个租户去自定义域名或者证书的时候单独就创建一个 EnvoyFilter 是不是就可以了(针对 Gateway)。

关于我

我的博客主旨:

  • 排版美观,语言精炼;
  • 文档即手册,步骤明细,拒绝埋坑,提供源码;
  • 本人实战文档都是亲测成功的,各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人帮您解决问题,让我们一起进步!

微信二维码
x2675263825 (舍得), qq:2675263825。

EnvoyFilter API_第6张图片

微信公众号
《云原生架构师实战》

EnvoyFilter API_第7张图片

个人博客站点

http://onedayxyy.cn/

EnvoyFilter API_第8张图片

EnvoyFilter API_第9张图片

语雀

https://www.yuque.com/xyy-onlyone

EnvoyFilter API_第10张图片

csdn

https://blog.csdn.net/weixin_39246554?spm=1010.2135.3001.5421

EnvoyFilter API_第11张图片

知乎

https://www.zhihu.com/people/foryouone

EnvoyFilter API_第12张图片

最后

好了,关于本次就到这里了,感谢大家阅读,最后祝大家生活快乐,每天都过的有意义哦,我们下期见!

EnvoyFilter API_第13张图片

你可能感兴趣的:(istio)