在ASM网关上基于Client IP进行路由

基于客户端IP进行路由是较为常见的需求,例如内网和公网的用户需要访问不同版本的应用、不同地区的用户需要访问不同类别的内容等。因此ASM提供了自定义插件能力,可以将请求的客户端IP转换为请求Header进行路由。本文将介绍如何在ASM中利用自定义插件实现基于客户端IP的路由。

背景信息

ASM使用Envoy作为网格代理来进行实际的流量转发,利用Envoy丰富的插件生态来对网格能力进行扩展。以HTTP协议为例,Envoy提供了高度灵活的扩展接口,您可以直接利用这些接口读取到详细的请求元数据,甚至可以修改请求Header、Body等属性。Envoy的插件可以分为以下三类:

  • 原生Envoy Filter:基于C++开发,难度大,但性能更好。

  • WASM插件:可以使用多种语言开发,更加安全、灵活,开发难度较低。

  • Lua插件:使用Lua进行开发,灵活度受限,但开发难度最低。

本文将使用一个简单的Lua插件,将请求的Client IP转换为请求的Header,通过虚拟服务匹配该Header,实现不同来源的请求返回不同的响应。

前提条件

步骤一:确认客户端源IP

首先,部署sleep应用到集群作为客户端。后面我们将分别从sleep和本地客户端来访问httpbin服务,从而测试不同源IP的请求。

说明

本地客户端特指当前场景下执行文档操作的用户终端,包括个人电脑,ECS等。例如使用个人电脑登录ECS,再通过kubectl访问ACK集群时,ECS就是本地客户端。

  1. 使用ACK集群kubecofig创建sleep.yaml。

    展开查看sleep.yaml内容

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: sleep
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: sleep
      labels:
        app: sleep
        service: sleep
    spec:
      ports:
      - port: 80
        name: http
      selector:
        app: sleep
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sleep
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: sleep
      template:
        metadata:
          labels:
            app: sleep
        spec:
          terminationGracePeriodSeconds: 0
          serviceAccountName: sleep
          containers:
          - name: sleep
            image: registry.cn-hangzhou.aliyuncs.com/acs/curl:8.1.2
            command: ["/bin/sleep", "infinity"]
            imagePullPolicy: IfNotPresent
            volumeMounts:
            - mountPath: /etc/sleep/tls
              name: secret-volume
          volumes:
          - name: secret-volume
            secret:
              secretName: sleep-secret
              optional: true
    ---
  2. 执行以下命令,部署sleep应用。

    kubectl apply -f sleep.yaml
  3. 执行以下命令,配置入口网关IP地址的环境变量。关于网关IP的获取方式,请参见获取入口网关地址

    export ASM_GATEWAY_IP=112.35.xx.xx
  4. 执行以下命令,获取本地客户端IP。

    curl ${ASM_GATEWAY_IP}/ip
  5. 执行以下命令,获取sleep PodIP。

    kubectl exec deploy/sleep -it -- curl ${ASM_GATEWAY_IP}/ip
    说明

    访问httpbin应用/ip路径的请求将会返回客户端IP。这种获取客户端IP的方式需要网关中ServiceExternalTrafficPolicy设为local

步骤二:创建Lua插件并应用到ASM网关上

Lua插件是通过EnvoyLua Filter实现的。您可以自行编写Lua代码,并将Lua代码直接作为Lua Filter配置的一部分发送给Envoy。Envoy会在请求处理时调用配置的Lua代码来执行相应操作。

本文中需要的Lua代码片段如下。

function envoy_on_request(request_handle)
    -- 定义envoy_on_request函数,处理接收到的请求
    -- 参数request_handle用来获取并修改请求信息
  local client_address = request_handle:streamInfo():downstreamRemoteAddress()
    --   获取请求源IP
  request_handle:headers():add("x-client-address", client_address)
    --   设置请求header,header的key为x-client-address
end
  1. 创建Envoy过滤器资源,具体操作,请参见使用Envoy过滤器模板创建Envoy过滤器

    创建Envoy过滤器模板过程中,将下方YAML粘贴至多版本适配Envoy过滤器模板的输入框中。

    apiVersion: networking.istio.io/v1alpha3
    kind: EnvoyFilter
    metadata:
      name: add-header-by-ip
      namespace: istio-system
    spec:
      workloadSelector:
        labels:
          istio: ingressgateway
      configPatches:
        - applyTo: HTTP_FILTER
          match:
            context: GATEWAY
            listener:
              filterChain:
                filter:
                  name: "envoy.filters.network.http_connection_manager"
          patch:
            operation: INSERT_BEFORE
            value:
              name: envoy.lua
              typed_config:
                "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
                inlineCode: |
                  function envoy_on_request(request_handle)
                    local client_address = request_handle:streamInfo():downstreamRemoteAddress()
                    request_handle:headers():add("x-client-address", client_address)
                  end
  2. 绑定至工作负载时,请选择选定工作负载绑定,选择istio-system命名空间,工作负载类型选择Service,选中ASM网关对应的Service(名称为istio-${ASM网关名称})。

  3. 绑定完成之后,单击Envoy过滤器。可以看到对应的Envoy过滤器已经创建成功。

步骤三:创建基于Client IP的路由规则

使用ASMkubeconfig,修改httpbin应用中部署的虚拟服务,最终YAML如下所示:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: httpbin-vs
  namespace: default
spec:
  gateways:
    - httpbin
  hosts:
    - '*'
  http:
    - match:
      - headers:
          x-client-address:
            prefix: ${获取到的Sleep客户端IP}
      directResponse:
        status: 200
        body:
          string: "from sleep"
    - match:
      - headers:
          x-client-address:
            prefix: ${获取到的本机IP}
      directResponse:
        status: 200
        body:
          string: "from my host"    
    - name: test
      route:
        - destination:
            host: httpbin.default.svc.cluster.local
            port:
              number: 8000

上述配置的含义是:

  • 所有来自sleep的请求,将直接返回一个from sleep字符串。

  • 所有来自本地客户端的请求,将直接返回一个from my host字符串。

步骤四:测试

  1. 在本地客户端执行以下命令。

    curl ${ASM_GATEWAY_IP}/ip

    预期输出:

    from my host
  2. 使用ACK集群的kubeconfig执行以下命令。

    kubectl exec deploy/sleep -it -- curl ${ASM_GATEWAY_IP}/ip

    预期输出:

    from sleep

相关文档

  • 在熟悉Envoy架构的前提下,Lua插件相较于其他Envoy插件开发难度最低。关于如何开发EnvoyLua插件,请参见Lua script for HTTP filters

  • WASM插件是基于WebAssembly For Proxies规范编写的Envoy插件,支持多种语言。您可以尝试使用较低入门难度的Golang来开发WASM插件。具体操作,请参见使用Go为网格代理编写WASM插件