Istio EnvoyFilter+Lua 简单实现动态路由转发

公众号: 尹安灿 欢迎关注交流

因为种种原因,我们需要实现一个将服务名和subset携带在http请求的header里面,根据这个来实现将服务转发去特定的istio的服务的subset。

比如以下example:

  • 携带 service: msg-group tag: gray 的话,将其路由去msg-group 的gray subset。

    该版本返回数据: Call Read from msg-group,By New version!!!!

  • 携带 service: msg-group tag: default 的话,将其路由去msg-group 的default subset。

    改版本返回数据: Call Read from msg-group

用istio的Virtual Service来实现

大概如下:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: onroute-com
  namespace: develop
spec:
  gateways:
  - msggroup-gateway # 这个gateway - '*' 
  hosts:
  - onroute.com
  http:
  - match:
    - headers:
        service:
          exact: msg-group
        tag:
          exact: gray
    route:
    - destination:
        host: msg-group.develop.svc.cluster.local
        subset: gray
      headers:
        response:
          set:
            test: This value added By static route to gray
  - match:
    - headers:
        service:
          exact: msg-group
    route:
    - destination:
        host: msg-group.develop.svc.cluster.local
        subset: default
      headers:
        response:
          set:
            test: This value added By static route default

请求构造及结果如下:

注:10.99.20.74 是istio-ingressgateway的IP

请求 msg-group 的 gray 版本

# curl -v -H "Host: onroute.com" -H "service: msg-group" -H "tag: gray" 10.99.20.74/read
*   Trying 10.99.20.74:80...
* Connected to 10.99.20.74 (10.99.20.74) port 80 (#0)
> GET /read HTTP/1.1
> Host: onroute.com
> User-Agent: curl/7.77.0
> Accept: */*
> service: msg-group
> tag: gray
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: text/plain;charset=UTF-8
< content-length: 43
< date: Fri, 16 Sep 2022 07:36:35 GMT
< x-envoy-upstream-service-time: 7
< server: istio-envoy
< test: This value added By static route to gray
< 
* Connection #0 to host 10.99.20.74 left intact
Call Read from msg-group,By New version!!!!% 

请求 msg-group 的 default 版本

curl -v -H "Host: onroute.com" -H "service: msg-group" -H "tag: default" 10.99.20.74/read
*   Trying 10.99.20.74:80...
* Connected to 10.99.20.74 (10.99.20.74) port 80 (#0)
> GET /read HTTP/1.1
> Host: onroute.com
> User-Agent: curl/7.77.0
> Accept: */*
> service: msg-group
> tag: default
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: text/plain;charset=UTF-8
< content-length: 24
< date: Fri, 16 Sep 2022 07:38:41 GMT
< x-envoy-upstream-service-time: 12
< server: istio-envoy
< test: This value added By static route default
< 
* Connection #0 to host 10.99.20.74 left intact
Call Read from msg-group%

能实现问题,但是有几个小问题

  1. 配置不够灵活,如果面对几百个服务,配置是个巨大的工作量。
  2. 不同的namespace要配置不同的规则。

用Envoy Filter + Lua来实现

针对上面提到的问题,如果用EnvoyFiltter来实现动态的路由的话,就可以解决这个问题。

解决思路都是围绕config.route.v3.RouteAction cluster_header 这个字段来的

(string) Envoy will determine the cluster to route to by reading the value of the HTTP header named by cluster_header from the request headers. If the header is not found or the referenced cluster does not exist, Envoy will return a 404 response.

  1. 设置route的cluster_header字段为 x-service
  2. 从header读取service和tag的值组合出upstream cluster格式,并将x-service头替换为它
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: onroute-routing
  namespace: istio-system
spec:
  workloadSelector:
    labels:
      istio: ingressgateway
  configPatches:
  - applyTo: VIRTUAL_HOST
    match:
      context: GATEWAY
    patch:
      operation: ADD
      value:
        name: dynamic_routing
        domains:
          - 'onroute.com'
          - 'onroute.com:*'
        routes:
          - name: "path-matching"
            match:
              prefix: "/"
            route:
              cluster_header: "x-service"
  - applyTo: HTTP_FILTER
    match:
      context: GATEWAY
      listener:
        filterChain:
          filter:
            name: "envoy.http_connection_manager"
            subFilter:
              name: "envoy.router"
    patch:
      operation: INSERT_BEFORE
      value:
       name: envoy.service-helper
       typed_config:
         "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
         inlineCode: |
          function envoy_on_request(request_handle)
            local headers = request_handle:headers()
            local mesh_service = headers:get("service")
            local tag = headers:get("tag")
            if mesh_service ~= nil then
              if tag ~= nil then
                request_handle:headers():replace("x-service", "outbound|80|" .. tag .. "|" .. mesh_service .. ".svc.cluster.local")
              end
            end
          end

          function envoy_on_response(response_handle)
            response_handle:headers():add("test", "from envoy filter response")
          end

下面是测试结果:

请求 msg-group的 gray 版本

curl -v -H "Host: onroute.com" -H "service: msg-group.develop" -H "tag: gray" 10.99.20.74/read
*   Trying 10.99.20.74:80...
* Connected to 10.99.20.74 (10.99.20.74) port 80 (#0)
> GET /read HTTP/1.1
> Host: onroute.com
> User-Agent: curl/7.77.0
> Accept: */*
> service: msg-group.develop
> tag: gray
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: text/plain;charset=UTF-8
< content-length: 43
< date: Fri, 16 Sep 2022 08:07:29 GMT
< x-envoy-upstream-service-time: 16
< server: istio-envoy
< test: from envoy filter response
< 
* Connection #0 to host 10.99.20.74 left intact
Call Read from msg-group,By New version!!!!%

请求 msg-group的 default 版本

curl -v -H "Host: onroute.com" -H "service: msg-group.develop" -H "tag: default" 10.99.20.74/read
*   Trying 10.99.20.74:80...
* Connected to 10.99.20.74 (10.99.20.74) port 80 (#0)
> GET /read HTTP/1.1
> Host: onroute.com
> User-Agent: curl/7.77.0
> Accept: */*
> service: msg-group.develop
> tag: default
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: text/plain;charset=UTF-8
< content-length: 24
< date: Fri, 16 Sep 2022 08:12:40 GMT
< x-envoy-upstream-service-time: 14
< server: istio-envoy
< test: from envoy filter response
< 
* Connection #0 to host 10.99.20.74 left intact
Call Read from msg-group%

参考资料

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现分布式限流可以使用 Redis 的 Lua 脚本来完成。以下是可能的实现方案: 1. 使用 Redis 的 SETNX 命令来实现基于令牌桶算法的限流 令牌桶算法是一种常见的限流算法,它可以通过令牌的放置和消耗来控制流量。在 Redis 中,我们可以使用 SETNX 命令来实现令牌桶算法。 具体实现步骤如下: - 在 Redis 中创建一个有序集合,用于存储令牌桶的令牌数量和时间戳。 - 每当一个请求到达时,我们首先获取当前令牌桶中的令牌数量和时间戳。 - 如果当前时间戳与最后一次请求的时间戳之差大于等于令牌桶中每个令牌的发放时间间隔,则将当前时间戳更新为最后一次请求的时间戳,并且将令牌桶中的令牌数量增加相应的数量,同时不超过最大容量。 - 如果当前令牌桶中的令牌数量大于等于请求需要的令牌数量,则返回 true 表示通过限流,将令牌桶中的令牌数量减去请求需要的令牌数量。 - 如果令牌桶中的令牌数量不足,则返回 false 表示未通过限流。 下面是使用 Redis 的 Lua 脚本实现令牌桶算法的示例代码: ```lua -- 限流的 key local key = KEYS[1] -- 令牌桶的容量 local capacity = tonumber(ARGV[1]) -- 令牌的发放速率 local rate = tonumber(ARGV[2]) -- 请求需要的令牌数量 local tokens = tonumber(ARGV[3]) -- 当前时间戳 local now = redis.call('TIME')[1] -- 获取当前令牌桶中的令牌数量和时间戳 local bucket = redis.call('ZREVRANGEBYSCORE', key, now, 0, 'WITHSCORES', 'LIMIT', 0, 1) -- 如果令牌桶为空,则初始化令牌桶 if not bucket[1] then redis.call('ZADD', key, now, capacity - tokens) return 1 end -- 计算当前令牌桶中的令牌数量和时间戳 local last = tonumber(bucket[2]) local tokensInBucket = tonumber(bucket[1]) -- 计算时间间隔和新的令牌数量 local timePassed = now - last local newTokens = math.floor(timePassed * rate) -- 更新令牌桶 if newTokens > 0 then tokensInBucket = math.min(tokensInBucket + newTokens, capacity) redis.call('ZADD', key, now, tokensInBucket) end -- 检查令牌数量是否足够 if tokensInBucket >= tokens then redis.call('ZREM', key, bucket[1]) return 1 else return 0 end ``` 2. 使用 Redis 的 Lua 脚本来实现基于漏桶算法的限流 漏桶算法是另一种常见的限流算法,它可以通过漏桶的容量和漏水速度来控制流量。在 Redis 中,我们可以使用 Lua 脚本来实现漏桶算法。 具体实现步骤如下: - 在 Redis 中创建一个键值对,用于存储漏桶的容量和最后一次请求的时间戳。 - 每当一个请求到达时,我们首先获取当前漏桶的容量和最后一次请求的时间戳。 - 计算漏水速度和漏水的数量,将漏桶中的容量减去漏水的数量。 - 如果漏桶中的容量大于等于请求需要的容量,则返回 true 表示通过限流,将漏桶中的容量减去请求需要的容量。 - 如果漏桶中的容量不足,则返回 false 表示未通过限流。 下面是使用 Redis 的 Lua 脚本实现漏桶算法的示例代码: ```lua -- 限流的 key local key = KEYS[1] -- 漏桶的容量 local capacity = tonumber(ARGV[1]) -- 漏水速度 local rate = tonumber(ARGV[2]) -- 请求需要的容量 local size = tonumber(ARGV[3]) -- 当前时间戳 local now = redis.call('TIME')[1] -- 获取漏桶中的容量和最后一次请求的时间戳 local bucket = redis.call('HMGET', key, 'capacity', 'last') -- 如果漏桶为空,则初始化漏桶 if not bucket[1] then redis.call('HMSET', key, 'capacity', capacity, 'last', now) return 1 end -- 计算漏水的数量和漏桶中的容量 local last = tonumber(bucket[2]) local capacityInBucket = tonumber(bucket[1]) local leak = math.floor((now - last) * rate) -- 更新漏桶 capacityInBucket = math.min(capacity, capacityInBucket + leak) redis.call('HSET', key, 'capacity', capacityInBucket) redis.call('HSET', key, 'last', now) -- 检查容量是否足够 if capacityInBucket >= size then return 1 else return 0 end ``` 以上是使用 Redis 的 Lua 脚本实现分布式限流的两种方案,可以根据实际需求选择适合的方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值