美文网首页
Istio 流量劫持

Istio 流量劫持

作者: 程序员札记 | 来源:发表于2023-01-04 16:46 被阅读0次

    Istio 流量劫持需要理解下面的概念

    1. iptables概念 - 以通俗易懂的方式描述iptables的相关概念
    2. iptables指南 - iptables命令用法指南

    Sidecar代理方式简介

    Kubernetes平台上,Envoy Sidecar容器与application容器于同一个Pod中共存,它们共享NETWORK、UTS和IPC等名称空间,因此也共用同一个网络协议栈;
    Envoy Sidecar基于init容器(需要使用NET_ADMIN和NET_RAW Capability于Pod启动时设置iptables规则以实现流量拦截

    • 入站流量由iptables拦截后转发给Envoy
    • Envoy根据配置完成入站流量代理
    • 后端应用程序生成的出站流量依然由iptables拦截并转发给Envoy
    • Envoy根据配置完成出站流量代理

    流量拦截模式

    • REDIRECT:重定向模式
    • TPROXY:透明代理模式

    具体操作细节请参考:https://github.com/istio/istio/blob/master/tools/packaging/common/istio-start.sh

    Sidecar Envoy 流量劫持

    流量的透明劫持

    • 流量的透明劫持,用于确保让应用无需事先改造即可获得服务治理和观测能力
    • 开启透明劫持功能后,出入应用的业务流量将会被Sidecar Envoy自动拦截

    流量劫持过程

    1. Init流程(0):图中紫色虚线表示Init流程

      1. Pod启动时,会借助Init Container特权容器开启流量劫持,并设置流量劫持规则
      2. 劫持规则分为Inbound规则和Outbound规则
    2. Inbound流程(1、2、3):图中橙色实线代表着Inbound流程

      1. 请求发给APP前被traffic intercetion劫持

      2. traffic intercetion根据Inbound规则,将请求转发到Sidecar

      3. Sidecar将请求转发给APP

    3. Outbound流程(4、5、6):黑色实线表示Outbound请求

      1. App发出的Outbound请求被traffic interception劫持
      2. traffic interception根据Outbound规则将请求转发给Sidecar
      3. Sidecar处理之后将请求回复给请求者
    image

    结合下图理解本文中的内容,展示的是 reviews Pod 的内部结构,包括 Linux Kernel 空间中的 iptables 规则、Sidecar 容器、应用容器。

    Istio 流量劫持示意图

    productpage 访问 reviews Pod,入站流量处理过程对应于图示上的步骤:1、2、3、4、Envoy Inbound Handler、5、6、7、8、应用容器。

    reviews Pod 访问 rating 服务的出站流量处理过程对应于图示上的步骤是:9、10、11、12、Envoy Outbound Handler、13、14、15。

    注意:图中的路径 16 近用于路由规则说明,它不不出现在当前示例中。实际上仅当 Pod 内发出的对当前 Pod 内的服务访问的时候才会途径它。

    上图中关于流量路由部分,包含:

    • productpage 服务请求访问 http://reviews.default.svc.cluster.local:9080/,当流量进入 reviews Pod 内部时,流量是如何被 iptables 劫持到 Envoy 代理被 Inbound Handler 处理的;
    • reviews 请求访问 ratings 服务的 Pod,应用程序发出的出站流量被 iptables 劫持到 Envoy 代理的 Outbound Handler 的处理。

    在阅读下文时,请大家确立以下已知点:

    • 首先,productpage 发出的对 reivews 的访问流量,是在 Envoy 已经通过 EDS 选择出了要请求的 reviews 服务的某个 Pod,知晓了其 IP 地址,直接向该 IP 发送的 TCP 连接请求。
    • reviews 服务有三个版本,每个版本有一个实例,三个版本中的 sidecar 工作步骤类似,下文只以其中一个 Pod 中的 sidecar 流量转发步骤来说明。
    • 所有进入 reviews Pod 的 TCP 流量都根据 Pod 中的 iptables 规则转发到了 Envoy 代理的 15006 端口,然后经过 Envoy 的处理确定转发给 Pod 内的应用容器还是透传。

    Sidecar 模式

    将应用程序的功能划分为单独的进程运行在同一个最小调度单元中(例如 Kubernetes 中的 Pod)可以被视为 sidecar 模式。如下图所示,sidecar 模式允许您在应用程序旁边添加更多功能,而无需额外第三方组件配置或修改应用程序代码。

    Sidecar 模式示意图

    就像连接了 Sidecar 的三轮摩托车一样,在软件架构中, Sidecar 连接到父应用并且为其添加扩展或者增强功能。Sidecar 应用与主应用程序松散耦合。它可以屏蔽不同编程语言的差异,统一实现微服务的可观察性、监控、日志记录、配置、断路器等功能。

    使用 Sidecar 模式的优势

    使用 sidecar 模式部署服务网格时,无需在节点上运行代理,但是集群中将运行多个相同的 sidecar 副本。在 sidecar 部署方式中,每个应用的容器旁都会部署一个伴生容器(如 Envoy 或 MOSN),这个容器称之为 sidecar 容器。Sidecar 接管进出应用容器的所有流量。在 Kubernetes 的 Pod 中,在原有的应用容器旁边注入一个 Sidecar 容器,两个容器共享存储、网络等资源,可以广义的将这个包含了 sidecar 容器的 Pod 理解为一台主机,两个容器共享主机资源。

    因其独特的部署结构,使得 sidecar 模式具有以下优势:

    • 将与应用业务逻辑无关的功能抽象到共同基础设施,降低了微服务代码的复杂度。
    • 因为不再需要编写相同的第三方组件配置文件和代码,所以能够降低微服务架构中的代码重复度。
    • Sidecar 可独立升级,降低应用程序代码和底层平台的耦合度。

    Sidecar 注入示例分析

    以 Istio 官方提供的 bookinfoproductpage 的 YAML 为例,关于 bookinfo 应用的详细 YAML 配置请参考 bookinfo.yaml

    下文将从以下几个方面讲解:

    • Sidecar 容器的注入
    • iptables 规则的创建
    • 路由的详细过程
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: productpage-v1
      labels:
        app: productpage
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: productpage
          version: v1
      template:
        metadata:
          labels:
            app: productpage
            version: v1
        spec:
          serviceAccountName: bookinfo-productpage
          containers:
          - name: productpage
            image: docker.io/istio/examples-bookinfo-productpage-v1:1.15.0
            imagePullPolicy: IfNotPresent
            ports:
            - containerPort: 9080
            volumeMounts:
            - name: tmp
              mountPath: /tmp
          volumes:
          - name: tmp
            emptyDir: {}
    
    

    再查看下 productpage 容器的 Dockerfile

    FROM python:3.7.4-slim
    
    COPY requirements.txt ./
    RUN pip install --no-cache-dir -r requirements.txt
    
    COPY test-requirements.txt ./
    RUN pip install --no-cache-dir -r test-requirements.txt
    
    COPY productpage.py /opt/microservices/
    COPY tests/unit/* /opt/microservices/
    COPY templates /opt/microservices/templates
    COPY static /opt/microservices/static
    COPY requirements.txt /opt/microservices/
    
    ARG flood_factor
    ENV FLOOD_FACTOR ${flood_factor:-0}
    
    EXPOSE 9080
    WORKDIR /opt/microservices
    RUN python -m unittest discover
    
    USER 1
    
    CMD ["python", "productpage.py", "9080"]
    
    

    我们看到 Dockerfile 中没有配置 ENTRYPOINT,所以 CMD 的配置 python productpage.py 9080 将作为默认的 ENTRYPOINT,记住这一点,再看下注入 sidecar 之后的配置。

    $ istioctl kube-inject -f samples/bookinfo/platform/kube/bookinfo.yaml
    
    

    我们只截取其中与 productpage 相关的 Deployment 配置中的部分 YAML 配置。

          containers:
          - image: docker.io/istio/examples-bookinfo-productpage-v1:1.15.0 # 应用镜像
            name: productpage
            ports:
            - containerPort: 9080
          - args:
            - proxy
            - sidecar
            - --domain
            - $(POD_NAMESPACE).svc.cluster.local
            - --configPath
            - /etc/istio/proxy
            - --binaryPath
            - /usr/local/bin/envoy
            - --serviceCluster
            - productpage.$(POD_NAMESPACE)
            - --drainDuration
            - 45s
            - --parentShutdownDuration
            - 1m0s
            - --discoveryAddress
            - istiod.istio-system.svc:15012
            - --zipkinAddress
            - zipkin.istio-system:9411
            - --proxyLogLevel=warning
            - --proxyComponentLogLevel=misc:error
            - --connectTimeout
            - 10s
            - --proxyAdminPort
            - "15000"
            - --concurrency
            - "2"
            - --controlPlaneAuthPolicy
            - NONE
            - --dnsRefreshRate
            - 300s
            - --statusPort
            - "15020"
            - --trust-domain=cluster.local
            - --controlPlaneBootstrap=false
            image: docker.io/istio/proxyv2:1.5.1 # sidecar proxy
            name: istio-proxy
            ports:
            - containerPort: 15090
              name: http-envoy-prom
              protocol: TCP
          initContainers:
          - command:
            - istio-iptables
            - -p
            - "15001"
            - -z
            - "15006"
            - -u
            - "1337"
            - -m
            - REDIRECT
            - -i
            - '*'
            - -x
            - ""
            - -b
            - '*'
            - -d
            - 15090,15020
            image: docker.io/istio/proxyv2:1.5.1 # init 容器
            name: istio-init
    
    

    Istio 给应用 Pod 注入的配置主要包括:

    • Init 容器 istio-init:用于 pod 中设置 iptables 端口转发
    • Sidecar 容器 istio-proxy:运行 sidecar 代理,如 Envoy 或 MOSN。

    iptables 规则注入解析

    为了查看 iptables 配置,我们需要登陆到 sidecar 容器中使用 root 用户来查看,因为 kubectl 无法使用特权模式来远程操作 docker 容器,所以我们需要登陆到 productpage pod 所在的主机上使用 docker 命令登陆容器中查看。

    如果您使用 minikube 部署的 Kubernetes,可以直接登录到 minikube 的虚拟机中并切换为 root 用户。查看 iptables 配置,列出 NAT(网络地址转换)表的所有规则,因为在 Init 容器启动的时候选择给 istio-iptables 传递的参数中指定将入站流量重定向到 sidecar 的模式为 REDIRECT,因此在 iptables 中将只有 NAT 表的规格配置,如果选择 TPROXY 还会有 mangle 表配置。iptables 命令的详细用法请参考 iptables 命令。

    我们仅查看与 productpage 有关的 iptables 规则如下,因为这些规则是运行在该容器特定的网络空间下,因此需要使用 nsenter 命令进入其网络空间。进入的时候需要指定进程 ID(PID),因此首先我们需要找到 productpage 容器的 PID。对于在不同平台上安装的 Kubernetes,查找容器的方式会略有不同,例如在 GKE 上,执行 docker ps -a 命令是查看不到任何容器进程的。下面已 minikube 和 GKE 两个典型的平台为例,指导你如何进入容器的网络空间。

    在 minikube 中查看容器中的 iptabes 规则

    对于 minikube,因为所有的进程都运行在单个节点上,因此你只需要登录到 minikube 虚拟机,切换为 root 用户然后查找 productpage 进程即可,参考下面的步骤。

    # 进入 minikube 并切换为 root 用户,minikube 默认用户为 docker
    $ minikube ssh
    $ sudo -i
    
    # 查看 productpage pod 的 istio-proxy 容器中的进程
    $ docker top `docker ps|grep "istio-proxy_productpage"|cut -d " " -f1`
    UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
    1337                10576               10517               0                   08:09               ?                   00:00:07            /usr/local/bin/pilot-agent proxy sidecar --domain default.svc.cluster.local --configPath /etc/istio/proxy --binaryPath /usr/local/bin/envoy --serviceCluster productpage.default --drainDuration 45s --parentShutdownDuration 1m0s --discoveryAddress istiod.istio-system.svc:15012 --zipkinAddress zipkin.istio-system:9411 --proxyLogLevel=warning --proxyComponentLogLevel=misc:error --connectTimeout 10s --proxyAdminPort 15000 --concurrency 2 --controlPlaneAuthPolicy NONE --dnsRefreshRate 300s --statusPort 15020 --trust-domain=cluster.local --controlPlaneBootstrap=false
    1337                10660               10576               0                   08:09               ?                   00:00:33            /usr/local/bin/envoy -c /etc/istio/proxy/envoy-rev0.json --restart-epoch 0 --drain-time-s 45 --parent-shutdown-time-s 60 --service-cluster productpage.default --service-node sidecar~172.17.0.16~productpage-v1-7f44c4d57c-ksf9b.default~default.svc.cluster.local --max-obj-name-len 189 --local-address-ip-version v4 --log-format [Envoy (Epoch 0)] [%Y-%m-%d %T.%e][%t][%l][%n] %v -l warning --component-log-level misc:error --concurrency 2
    
    # 使用 nsenter 进入 sidecar 容器的命名空间(以上任何一个都可以)
    $ nsenter -n --target 10660
    
    # 查看 NAT 表中规则配置的详细信息。
    $ iptables -t nat -L
    
    

    在 GKE 中查看容器的 iptables 规则

    如果你在 GKE 中安装的多节点的 Kubernetes 集群,首先你需要确定这个 Pod 运行在哪个节点上,然后登陆到那台主机,使用下面的命令查找进程的 PID,你会得到类似下面的输出。

    $ ps aux|grep "productpage"
    chronos     4268  0.0  0.6  43796 24856 ?        Ss   Apr22   0:00 python productpage.py 9080
    chronos     4329  0.9  0.6 117524 24616 ?        Sl   Apr22  13:43 /usr/local/bin/python /opt/microservices/productpage.py 9080
    root      361903  0.0  0.0   4536   812 pts/0    S+   01:54   0:00 grep --colour=auto productpage
    
    

    在本示例中,productpage 进程的 PID 是 4329,使用 nsenter -n --target 4329 进入该进程的命名空间,然后在终端中输入 iptables -t nat -L 即可查看 iptables 规则。

    iptables 流量劫持过程详解

    经过上面的步骤,你已经可以查看到 init 容器向 Pod 中注入的 iptables 规则,如下所示。

    # PREROUTING 链:用于目标地址转换(DNAT),将所有入站 TCP 流量跳转到 ISTIO_INBOUND 链上。
    Chain PREROUTING (policy ACCEPT 2701 packets, 162K bytes)
     pkts bytes target     prot opt in     out     source               destination
     2701  162K ISTIO_INBOUND  tcp  --  any    any     anywhere             anywhere
    
    # INPUT 链:处理输入数据包,非 TCP 流量将继续 OUTPUT 链。
    Chain INPUT (policy ACCEPT 2701 packets, 162K bytes)
     pkts bytes target     prot opt in     out     source               destination
    
    # OUTPUT 链:将所有出站数据包跳转到 ISTIO_OUTPUT 链上。
    Chain OUTPUT (policy ACCEPT 79 packets, 6761 bytes)
     pkts bytes target     prot opt in     out     source               destination
       15   900 ISTIO_OUTPUT  tcp  --  any    any     anywhere             anywhere
    
    # POSTROUTING 链:所有数据包流出网卡时都要先进入 POSTROUTING 链,内核根据数据包目的地判断是否需要转发出去,我们看到此处未做任何处理。
    Chain POSTROUTING (policy ACCEPT 79 packets, 6761 bytes)
     pkts bytes target     prot opt in     out     source               destination
    
    # ISTIO_INBOUND 链:将所有入站流量重定向到 ISTIO_IN_REDIRECT 链上。目的地为 15090(Prometheus 使用)和 15020(Ingress gateway 使用,用于 Pilot 健康检查)端口的流量除外,发送到以上两个端口的流量将返回 iptables 规则链的调用点,即 PREROUTING 链的后继 POSTROUTING 后直接调用原始目的地。
    Chain ISTIO_INBOUND (1 references)
     pkts bytes target     prot opt in     out     source               destination
        0     0 RETURN     tcp  --  any    any     anywhere             anywhere             tcp dpt:ssh
        2   120 RETURN     tcp  --  any    any     anywhere             anywhere             tcp dpt:15090
     2699  162K RETURN     tcp  --  any    any     anywhere             anywhere             tcp dpt:15020
        0     0 ISTIO_IN_REDIRECT  tcp  --  any    any     anywhere             anywhere
    
    # ISTIO_IN_REDIRECT 链:将所有的入站流量跳转到本地的 15006 端口,至此成功的拦截了流量到 sidecar 代理的 Inbound Handler 中。
    Chain ISTIO_IN_REDIRECT (3 references)
     pkts bytes target     prot opt in     out     source               destination
        0     0 REDIRECT   tcp  --  any    any     anywhere             anywhere             redir ports 15006
    
    # ISTIO_OUTPUT 链:规则比较复杂,将在下文解释
    Chain ISTIO_OUTPUT (1 references)
     pkts bytes target     prot opt in     out     source               destination
        0     0 RETURN     all  --  any    lo      127.0.0.6            anywhere #规则1
        0     0 ISTIO_IN_REDIRECT  all  --  any    lo      anywhere            !localhost            owner UID match 1337 #规则2
        0     0 RETURN     all  --  any    lo      anywhere             anywhere             ! owner UID match 1337 #规则3
       15   900 RETURN     all  --  any    any     anywhere             anywhere             owner UID match 1337 #规则4
        0     0 ISTIO_IN_REDIRECT  all  --  any    lo      anywhere            !localhost            owner GID match 1337 #规则5
        0     0 RETURN     all  --  any    lo      anywhere             anywhere             ! owner GID match 1337 #规则6
        0     0 RETURN     all  --  any    any     anywhere             anywhere             owner GID match 1337 #规则7
        0     0 RETURN     all  --  any    any     anywhere             localhost #规则8
        0     0 ISTIO_REDIRECT  all  --  any    any     anywhere             anywhere #规则9
    
    # ISTIO_REDIRECT 链:将所有流量重定向到 Envoy 代理的 15001 端口。
    Chain ISTIO_REDIRECT (1 references)
     pkts bytes target     prot opt in     out     source               destination
        0     0 REDIRECT   tcp  --  any    any     anywhere             anywhere             redir ports 15001
    
    

    这里着重需要解释的是 ISTIO_OUTPUT 链中的 9 条规则,为了便于阅读,我将以上规则中的部分内容使用表格的形式来展示如下:

    规则 target in out source destination
    1 RETURN any lo 127.0.0.6 anywhere
    2 ISTIO_IN_REDIRECT any lo anywhere !localhost owner UID match 1337
    3 RETURN any lo anywhere anywhere !owner UID match 1337
    4 RETURN any any anywhere anywhere owner UID match 1337
    5 ISTIO_IN_REDIRECT any lo anywhere !localhost owner GID match 1337
    6 RETURN any lo anywhere anywhere !owner GID match 1337
    7 RETURN any any anywhere anywhere owner GID match 1337
    8 RETURN any any anywhere localhost
    9 ISTIO_REDIRECT any any anywhere anywhere

    <figcaption class="text-center" style="box-sizing: border-box; display: block; color: gray; font-size: 0.8em; font-weight: 500; text-align: center !important; margin-bottom: 20px; font-family: poppins, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">ISTIO_OUTPUT 链中的路由规则</figcaption>

    下图展示了 ISTIO_ROUTE 规则的详细流程。

    Istio_ROUTE iptalbes 规则判断流程图

    我将按照规则的出现顺序来解释每条规则的目的、对应文章开头图示中的步骤及详情。其中规则 5、6、7 是分别对规则 2、3、4 的应用范围扩大(从 UID 扩大为 GID),作用是类似的,将合并解释。注意,其中的规则是按顺序执行的,也就是说排序越靠后的规则将作为默认值。出站网卡(out)为 lo (本地回环地址,loopback 接口)时,表示流量的目的地是本地 Pod,对于 Pod 向外部发送的流量就不会经过这个接口。所有 review Pod 的出站流量只适用于规则 4、7、8、9。

    规则 1

    • 目的:透传 Envoy 代理发送到本地应用容器的流量,使其绕过 Envoy 代理,直达应用容器。
    • 对应图示中的步骤:6 到 7。
    • 详情:该规则使得所有来自 127.0.0.6(该 IP 地址将在下文解释) 的请求,跳出该链,返回 iptables 的调用点(即 OUTPUT)后继续执行其余路由规则,即 POSTROUTING 规则,把流量发送到任意目的地址,如本地 Pod 内的应用容器。如果没有这条规则,由 Pod 内 Envoy 代理发出的对 Pod 内容器访问的流量将会执行下一条规则,即规则 2,流量将再次进入到了 Inbound Handler 中,从而形成了死循环。将这条规则放在第一位可以避免流量在 Inbound Handler 中死循环的问题。

    规则 2、5

    • 目的:处理 Envoy 代理发出的站内流量(Pod 内部的流量),但不是对 localhost 的请求,通过后续规则将其转发给 Envoy 代理的 Inbound Handler。该规则适用于 Pod 对自身 IP 地址调用的场景,即 Pod 内服务之间的访问。
    • 详情:如果流量的目的地非 localhost,且数据包是由 1337 UID(即 istio-proxy 用户,Envoy 代理)发出的,流量将被经过 ISTIO_IN_REDIRECT 最终转发到 Envoy 的 Inbound Handler。

    规则 3、6

    • 目的:透传 Pod 内的应用容器的站内流量。该规则适用于容器内部的流量。例如在 Pod 内对 Pod IP 或 localhost 的访问。
    • 对应图示中的步骤:6 到 7。
    • 详情:如果流量不是由 Envoy 用户发出的,那么就跳出该链,返回 OUTPUT 调用 POSTROUTING,直达目的地。

    规则 4、7

    • 目的:透传 Envoy 代理发出的出站请求。
    • 对应图示中的步骤:14 到 15。
    • 详情:如果请求是由 Envoy 代理发出的,则返回 OUTPUT 继续调用 POSTROUTING 规则,最终直接访问目的地。

    规则 8

    • 目的:透传 Pod 内部对 localhost 的请求。
    • 详情:如果请求的目的地是 localhost,则返回 OUTPUT 调用 POSTROUTING,直接访问 localhost。

    规则 9

    • 目的:所有其他的流量将被转发到 ISTIO_REDIRECT 后,最终达到 Envoy 代理的 Outbound Handler。
    • 对应图示中的步骤:10 到 11。

    以上规则避免了 Envoy 代理到应用程序的路由在 iptables 规则中的死循环,保障了流量可以被正确的路由到 Envoy 代理上,也可以发出真正的出站请求。

    关于 RETURN target

    你可能留意到上述规则中有很多 RETURN target,它的意思是,指定到这条规则时,跳出该规则链,返回 iptables 的调用点(在我们的例子中即 OUTPUT)后继续执行其余路由规则,在我们的例子中即 POSTROUTING 规则,把流量发送到任意目的地址,你可以把它直观的理解为透传

    关于 127.0.0.6 IP 地址

    127.0.0.6 这个 IP 是 Istio 中默认的 InboundPassthroughClusterIpv4,在 Istio 的代码中指定。即流量在进入 Envoy 代理后被绑定的 IP 地址,作用是让 Outbound 流量重新发送到 Pod 中的应用容器,即 Passthought(透传),绕过 Outbound Handler。该流量是对 Pod 自身的访问,而不是真正的对外流量。至于为什么选择这个 IP 作为流量透传,请参考 Istio Issue-29603

    流量路由过程详解

    通过上文,你已经了解了 Istio 是如何在 Pod 中做透明流量劫持的,那么流量被劫持到 Envoy 代理中之后是如何被处理的呢?流量路由分为 Inbound 和 Outbound 两个过程,下面将根据上文中的示例及 sidecar 的配置为读者详细分析此过程。

    理解 Inbound Handler

    Inbound Handler 的作用是将 iptables 拦截到的 downstream 的流量转发给 Pod 内的应用程序容器。在我们的实例中,假设其中一个 Pod 的名字是 reviews-v1-545db77b95-jkgv2,运行 istioctl proxy-config listener reviews-v1-545db77b95-jkgv2 --port 15006 查看该 Pod 中 15006 端口上的监听器情况 ,你将看到下面的输出。

    ADDRESS PORT  MATCH                                                                                           DESTINATION
    0.0.0.0 15006 Addr: *:15006                                                                                   Non-HTTP/Non-TCP
    0.0.0.0 15006 Trans: tls; App: istio-http/1.0,istio-http/1.1,istio-h2; Addr: 0.0.0.0/0                        InboundPassthroughClusterIpv4
    0.0.0.0 15006 Trans: raw_buffer; App: http/1.1,h2c; Addr: 0.0.0.0/0                                           InboundPassthroughClusterIpv4
    0.0.0.0 15006 Trans: tls; App: TCP TLS; Addr: 0.0.0.0/0                                                       InboundPassthroughClusterIpv4
    0.0.0.0 15006 Trans: raw_buffer; Addr: 0.0.0.0/0                                                              InboundPassthroughClusterIpv4
    0.0.0.0 15006 Trans: tls; Addr: 0.0.0.0/0                                                                     InboundPassthroughClusterIpv4
    0.0.0.0 15006 Trans: tls; App: istio,istio-peer-exchange,istio-http/1.0,istio-http/1.1,istio-h2; Addr: *:9080 Cluster: inbound|9080||
    0.0.0.0 15006 Trans: raw_buffer; Addr: *:9080                                                                 Cluster: inbound|9080||
    
    

    下面列出了以上输出中各字段的含义:

    • ADDRESS:下游地址
    • PORT:Envoy 监听器监听的端口
    • MATCH:请求使用的传输协议或匹配的下游地址
    • DESTINATION:路由目的地

    reviews Pod 中的 Iptables 将入站流量劫持到 15006 端口上,从上面的输出我们可以看到 Envoy 的 Inbound Handler 在 15006 端口上监听,对目的地为任何 IP 的 9080 端口的请求将路由到 inbound|9080|| Cluster 上。

    从该 Pod 的 Listener 列表的最后两行中可以看到,0.0.0.0:15006/TCP 的 Listener(其实际名字是 virtualInbound)监听所有的 Inbound 流量,其中包含了匹配规则,来自任意 IP 的对 9080 端口的访问流量,将会路由到 inbound|9080|| Cluster,如果你想以 Json 格式查看该 Listener 的详细配置,可以执行 istioctl proxy-config listeners reviews-v1-545db77b95-jkgv2 --port 15006 -o json 命令,你将获得类似下面的输出。

    [
        /*省略部分内容*/
        {
            "name": "virtualInbound",
            "address": {
                "socketAddress": {
                    "address": "0.0.0.0",
                    "portValue": 15006
                }
            },
            "filterChains": [
                /*省略部分内容*/
                {
                    "filterChainMatch": {
                        "destinationPort": 9080,
                        "transportProtocol": "tls",
                        "applicationProtocols": [
                            "istio",
                            "istio-peer-exchange",
                            "istio-http/1.0",
                            "istio-http/1.1",
                            "istio-h2"
                        ]
                    },
                    "filters": [
                        /*省略部分内容*/
                        {
                            "name": "envoy.filters.network.http_connection_manager",
                            "typedConfig": {
                                "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
                                "statPrefix": "inbound_0.0.0.0_9080",
                                "routeConfig": {
                                    "name": "inbound|9080||",
                                    "virtualHosts": [
                                        {
                                            "name": "inbound|http|9080",
                                            "domains": [
                                                "*"
                                            ],
                                            "routes": [
                                                {
                                                    "name": "default",
                                                    "match": {
                                                        "prefix": "/"
                                                    },
                                                    "route": {
                                                        "cluster": "inbound|9080||",
                                                        "timeout": "0s",
                                                        "maxStreamDuration": {
                                                            "maxStreamDuration": "0s",
                                                            "grpcTimeoutHeaderMax": "0s"
                                                        }
                                                    },
                                                    "decorator": {
                                                        "operation": "reviews.default.svc.cluster.local:9080/*"
                                                    }
                                                }
                                            ]
                                        }
                                    ],
                                    "validateClusters": false
                                },
                                /*省略部分内容*/
                            }
                        }
                    ],
                /*省略部分内容*/
            ],
            "listenerFilters": [
            /*省略部分内容*/
            ],
            "listenerFiltersTimeout": "0s",
            "continueOnListenerFiltersTimeout": true,
            "trafficDirection": "INBOUND"
        }
    ]
    
    

    既然 Inbound Handler 的流量中将来自任意地址的对该 Pod 9080 端口的流量路由到 inbound|9080|| Cluster,那么我们运行 istioctl pc cluster reviews-v1-545db77b95-jkgv2 --port 9080 --direction inbound -o json 查看下该 Cluster 配置,你将获得类似下面的输出。

    [
        {
            "name": "inbound|9080||",
            "type": "ORIGINAL_DST",
            "connectTimeout": "10s",
            "lbPolicy": "CLUSTER_PROVIDED",
            "circuitBreakers": {
                "thresholds": [
                    {
                        "maxConnections": 4294967295,
                        "maxPendingRequests": 4294967295,
                        "maxRequests": 4294967295,
                        "maxRetries": 4294967295,
                        "trackRemaining": true
                    }
                ]
            },
            "cleanupInterval": "60s",
            "upstreamBindConfig": {
                "sourceAddress": {
                    "address": "127.0.0.6",
                    "portValue": 0
                }
            },
            "metadata": {
                "filterMetadata": {
                    "istio": {
                        "services": [
                            {
                                "host": "reviews.default.svc.cluster.local",
                                "name": "reviews",
                                "namespace": "default"
                            }
                        ]
                    }
                }
            }
        }
    ]
    
    

    我们看其中的 TYPEORIGINAL_DST,将流量发送到原始目标地址(Pod IP),因为原始目标地址即当前 Pod,你还应该注意到 upstreamBindConfig.sourceAddress.address 的值被改写为了 127.0.0.6,而且对于 Pod 内流量是通过 lo 网卡发送的,这刚好呼应了上文中的 iptables ISTIO_OUTPUT 链中的第一条规则,根据该规则,流量将被透传到 Pod 内的应用容器。

    理解 Outbound Handler

    在本示例中 reviews 会向 ratings 服务发送 HTTP 请求,请求的地址是:http://ratings.default.svc.cluster.local:9080/,Outbound Handler 的作用是将 iptables 拦截到的本地应用程序向外发出的流量,经由 Envoy 代理路由到上游。

    Envoy 监听在 15001 端口上监听所有 Outbound 流量,Outbound Handler 处理,然后经过 virtualOutbound Listener、0.0.0.0_9080 Listener,然后通过 Route 9080 找到上游的 cluster,进而通过 EDS 找到 Endpoint 执行路由动作。

    ratings.default.svc.cluster.local:9080 路由

    运行 istioctl proxy-config routes reviews-v1-545db77b95-jkgv2 --name 9080 -o json 查看 route 配置,因为 sidecar 会根据 HTTP header 中的 domains 来匹配 VirtualHost,所以下面只列举了 ratings.default.svc.cluster.local:9080 这一个 VirtualHost。

    [
      {
        "name": "9080",
        "virtualHosts": [
           {
               "name": "ratings.default.svc.cluster.local:9080",
               "domains": [
                   "ratings.default.svc.cluster.local",
                   "ratings.default.svc.cluster.local:9080",
                   "ratings",
                   "ratings:9080",
                   "ratings.default.svc",
                   "ratings.default.svc:9080",
                   "ratings.default",
                   "ratings.default:9080",
                   "10.8.8.106",
                   "10.8.8.106:9080"
               ],
               "routes": [
                   {
                       "name": "default",
                       "match": {
                           "prefix": "/"
                       },
                       "route": {
                           "cluster": "outbound|9080||ratings.default.svc.cluster.local",
                           "timeout": "0s",
                           "retryPolicy": {
                               "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                               "numRetries": 2,
                               "retryHostPredicate": [
                                   {
                                       "name": "envoy.retry_host_predicates.previous_hosts"
                                   }
                               ],
                               "hostSelectionRetryMaxAttempts": "5",
                               "retriableStatusCodes": [
                                   503
                               ]
                           },
                           "maxStreamDuration": {
                               "maxStreamDuration": "0s",
                               "grpcTimeoutHeaderMax": "0s"
                           }
                       },
                       "decorator": {
                           "operation": "ratings.default.svc.cluster.local:9080/*"
                       }
                   }
               ],
               "includeRequestAttemptCount": true
           },
           /*省略部分内容*/
         ],
         "validateClusters": false
        }
    ]
    
    

    从该 Virtual Host 配置中可以看到将流量路由到outbound|9080||ratings.default.svc.cluster.local 集群。

    outbound|9080||ratings.default.svc.cluster.local 集群的端点

    运行 istioctl proxy-config endpoint reviews-v1-545db77b95-jkgv2 --port 9080 -o json --cluster "outbound|9080||ratings.default.svc.cluster.local" 查看集群的 Endpoint 配置,结果如下。

    [
        {
            "name": "outbound|9080||ratings.default.svc.cluster.local",
            "addedViaApi": true,
            "hostStatuses": [
                {
                    "address": {
                        "socketAddress": {
                            "address": "10.4.1.12",
                            "portValue": 9080
                        }
                    },
                    "stats": [
                        {
                            "name": "cx_connect_fail"
                        },
                        {
                            "name": "cx_total"
                        },
                        {
                            "name": "rq_error"
                        },
                        {
                            "name": "rq_success"
                        },
                        {
                            "name": "rq_timeout"
                        },
                        {
                            "name": "rq_total"
                        },
                        {
                            "type": "GAUGE",
                            "name": "cx_active"
                        },
                        {
                            "type": "GAUGE",
                            "name": "rq_active"
                        }
                    ],
                    "healthStatus": {
                        "edsHealthStatus": "HEALTHY"
                    },
                    "weight": 1,
                    "locality": {
                        "region": "us-west2",
                        "zone": "us-west2-a"
                    }
                }
            ],
            "circuitBreakers": {
                "thresholds": [
                    {
                        "maxConnections": 4294967295,
                        "maxPendingRequests": 4294967295,
                        "maxRequests": 4294967295,
                        "maxRetries": 4294967295
                    },
                    {
                        "priority": "HIGH",
                        "maxConnections": 1024,
                        "maxPendingRequests": 1024,
                        "maxRequests": 1024,
                        "maxRetries": 3
                    }
                ]
            },
            "observabilityName": "outbound|9080||ratings.default.svc.cluster.local"
        }
    ]
    
    

    我们看到端点的地址是 10.4.1.12。实际上,Endpoint 可以是一个或多个,sidecar 将根据一定规则选择适当的 Endpoint 来路由。至此 review Pod找到了它上游服务 rating 的 Endpoint。

    小结

    本文使用了 Istio 官方提供的 bookinfo 示例,按图索骥得带领读者了解了 sidecar 注入、iptables 透明流量劫持及 sidecar 中流量路由背后的实现细节。Sidecar 模式和流量透明劫持是 Istio 服务网格的特色和基础功能,理解该功能的背后过程及实现细节,将有助于大家理解 Service Mesh 的原理和 Istio Handbook 后面章节中的内容,因此希望读者可以在自己的环境中从头来试验一遍以加深理解。

    使用 iptables 做流量劫持只是 service mesh 的数据平面中做流量劫持的方式之一,还有更多的流量劫持方案,下面引用自 云原生网络代理 MOSN 官网中给出的流量劫持 部分的描述。

    使用 iptables 做流量劫持时存在的问题

    目前 Istio 使用 iptables 实现透明劫持,主要存在以下三个问题:

    1. 需要借助于 conntrack 模块实现连接跟踪,在连接数较多的情况下,会造成较大的消耗,同时可能会造成 track 表满的情况,为了避免这个问题,业内有关闭 conntrack 的做法。
    2. iptables 属于常用模块,全局生效,不能显式的禁止相关联的修改,可管控性比较差。
    3. iptables 重定向流量本质上是通过 loopback 交换数据,outbond 流量将两次穿越协议栈,在大并发场景下会损失转发性能。

    上述几个问题并非在所有场景中都存在,比方说某些场景下,连接数并不多,且 NAT 表未被使用到的情况下,iptables 是一个满足要求的简单方案。为了适配更加广泛的场景,透明劫持需要解决上述三个问题。

    透明劫持方案优化

    为了优化 Istio 中的透明流量劫持的性能,业界提出了以下方案。

    使用 Merbridge 开源项目利用 eBPF 劫持流量

    Merbridge 是由 DaoCloud 在 2022 年初开源的的一款利用 eBPF 加速 Istio 服务网格的插件。使用 Merbridge 可以在一定程度上优化数据平面的网络性能。

    Merbridge 利用 eBPF 的 sockopsredir 能力,可以直接将数据包从 inbound socket 传输到 outbound socket。eBPF 提供了 bpf_msg_redirect_hash 函数可以直接转发应用程序的数据包。

    使用 tproxy 处理 inbound 流量

    tproxy 可以用于 inbound 流量的重定向,且无需改变报文中的目的 IP/端口,不需要执行连接跟踪,不会出现 conntrack 模块创建大量连接的问题。受限于内核版本,tproxy 应用于 outbound 存在一定缺陷。目前 Istio 支持通过 tproxy 处理 inbound 流量。

    使用 hook connect 处理 outbound 流量

    为了适配更多应用场景,outbound 方向通过 hook connect 来实现,实现原理如下:

    hook-connect 原理示意图

    无论采用哪种透明劫持方案,均需要解决获取真实目的 IP/端口的问题,使用 iptables 方案通过 getsockopt 方式获取,tproxy 可以直接读取目的地址,通过修改调用接口,hook connect 方案读取方式类似于 tproxy。

    实现透明劫持后,在内核版本满足要求(4.16以上)的前提下,通过 sockmap 可以缩短报文穿越路径,进而改善 outbound 方向的转发性能。

    Istio注入的Envoy Sidecar

    • Istio基于Kubernetes Admission Controller Webhook完成sidecar自动注入,它会为每个微服务分别添加两个相关的容器
      • istio-init:隶属于Init Containers,即初始化容器,负责在微服务相关的Pod中生成iptables规则以进行流量拦截并向Envoy Proxy进行转发;运行完成后退出;
      • istio-proxy:隶属于Contianers,即Pod中的正常容器,程序为Envoy Proxy;

    istio-init初始化容器

    • istio-init初始化容器基于istio/proxyv2镜像启动,它运行istio-iptables程序以生成流量拦截规则

      • 拦截的流量将转发至两个相关的端口

        • 15006:由-z选项定义,指定用于接收拦截所有发往当前Pod/VM的入站流量的目标端口,该配置仅用于REDIRECT转发模式;
        • 15001:由-p选项定义,指定用于接收拦截的所有TCP流量的目标端口;
      • 流量拦截模式由-m选项指定,目前支持REDIRECT和TPROXY两种模式

      • 流量拦截时要包含的目标端口列表使用-b选项指定,而要排除的目标端口列表则使用-d选项指定;

      • 流量拦截时要包含的目标CIDR地址列表可使用-i选项指定,而要排除的目标CIDR格式的地址列表则使用-x选项指定;

    • 此前版本中,该初始化容器会运行一个用于生成iptables规则的相关的脚本来生成iptables规则,脚本地址为:https://github.com/istio/cni/blob/master/tools/packaging/common/istio-iptables.sh

    ~# kubectl get pod
    NAME                          READY   STATUS    RESTARTS        AGE
    demoappv10-78b6586d58-gstt2   2/2     Running   2 (7h29m ago)   22h
    demoappv10-78b6586d58-qc2wg   2/2     Running   2 (7h29m ago)   22h
    demoappv11-78bf898c74-ggfgd   2/2     Running   2 (7h29m ago)   22h
    demoappv11-78bf898c74-qscbn   2/2     Running   2 (7h29m ago)   22h
    
    ~# kubectl describe pod demoappv10-78b6586d58-gstt2
    ...
    Init Containers:
      istio-init:
        Container ID:  containerd://630aa2dc07c6950d1b8a4a0076d15970ac28867a3af3c4b7912c210b84f2e151
        Image:         docker.io/istio/proxyv2:1.15.2
        Image ID:      docker.io/istio/proxyv2@sha256:74a901daca055491e62f167bfbbe24a0880005cd67ae849ed6c09a3a3ec9feb7
        Port:          <none>
        Host Port:     <none>
        Args:
          istio-iptables
          -p
          15001
          -z
          15006
          -u
          1337
          -m
          REDIRECT
          -i
          *
          -x
    
          -b
          *
          -d
          15090,15021,15020
          --log_output_level=default:info
        State:          Terminated
          Reason:       Completed
          Exit Code:    0
          Started:      Fri, 28 Oct 2022 09:49:03 +0800
          Finished:     Fri, 28 Oct 2022 09:49:06 +0800
        Ready:          True
        Restart Count:  1
        Limits:
          cpu:     2
          memory:  1Gi
    ...
    

    Istio中用于流量拦截的iptables规则

    root@k8s-node-03:~# crictl ps
    CONTAINER           IMAGE               CREATED             STATE               NAME                ATTEMPT             POD ID              POD
    917370a99e332       25585bdfb0f7a       6 hours ago         Running             istio-proxy         1                   24bb9c2fe2b8d       demoappv10-78b6586d58-qc2wg
    39f34d79de972       25585bdfb0f7a       6 hours ago         Running             istio-proxy         1                   0e43ddf1d02a7       demoappv11-78bf898c74-qscbn
    2a8fe8c88c1f0       258641e274412       6 hours ago         Running             demoapp             1                   0e43ddf1d02a7       demoappv11-78bf898c74-qscbn
    c11fc4ca9aa00       3342b7518915f       6 hours ago         Running             demoapp             1                   24bb9c2fe2b8d       demoappv10-78b6586d58-qc2wg
    276fccc66da4e       5f5175f39b19e       6 hours ago         Running             calico-node         16                  f091078ccf398       calico-node-mdxh6
    73de2c09b9142       b6f09c63d6ec6       6 hours ago         Running             grafana             6                   47b8f1c025d96       grafana-56bdf8bf85-qbnwl
    f6f02fc18cafe       9d3f84f2ca38b       6 hours ago         Running             jaeger              6                   28ca588056de3       jaeger-c4fdf6674-c7tq8
    ede6404fcc5d7       245047de26c06       6 hours ago         Running             discovery           11                  ae4d951f3ba26       istiod-5456fd558d-4djxr
    e81b8e9108818       5fafbe4dd936b       6 hours ago         Running             kiali               6                   a25db5dc29a0e       kiali-5ff49b9f69-q8gqn
    
    root@k8s-node-03:~# crictl inspect -o json 917370a99e332 |grep pid
        "pid": 2818,
                "pid": 1
                "type": "pid"
    
    # root@k8s-node-03:~# nsenter -t 2818 -n iptables -t nat -S
    root@k8s-node-03:~# nsenter -t 2818 -n iptables-legacy -t nat -S
    -P PREROUTING ACCEPT
    -P INPUT ACCEPT
    -P OUTPUT ACCEPT
    -P POSTROUTING ACCEPT
    -N ISTIO_INBOUND
    -N ISTIO_IN_REDIRECT
    -N ISTIO_OUTPUT
    -N ISTIO_REDIRECT
    -A PREROUTING -p tcp -j ISTIO_INBOUND
    -A OUTPUT -p tcp -j ISTIO_OUTPUT
    -A ISTIO_INBOUND -p tcp -m tcp --dport 15008 -j RETURN
    -A ISTIO_INBOUND -p tcp -m tcp --dport 15090 -j RETURN
    -A ISTIO_INBOUND -p tcp -m tcp --dport 15021 -j RETURN
    -A ISTIO_INBOUND -p tcp -m tcp --dport 15020 -j RETURN
    -A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
    -A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
    -A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN
    -A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
    -A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
    -A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
    -A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
    -A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
    -A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
    -A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
    -A ISTIO_OUTPUT -j ISTIO_REDIRECT
    -A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
    

    Istio流量拦截的处理机制

    入向流量拦截:PREROUTING -> ISTIO_INBOUND

    • RETURN
      • 目标端口为TCP协议的15020、15021、15008、15090时将报文返回至上级的PREROUTING链,即不予拦截;
    • ISTIO_IN_REDIRECT:其它目标端口的请求报文则由该自定链中的规则(-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006)将请求重定向至15006端口;
      • 15006端口是为Envoy的VirtualInboundListener绑定的端口,于是请求则转交由Envoy处理;

      • Envoy根据报文目标地址匹配入向(Inbound)方向的Listener,若存在,则根据该Listener的配置决定如何将请求转发至Envoy后端的应用程序;

    image

    出向流量拦截:OUTPUT -> ISTIO_OUTPUT

    • RETURN

      • 从lo接口流出,且源地址为127.0.0.6/32的出向流量

      • UID Owner为1337的出向流量

      • GID Owner为1337的出向流量

      • 从lo接口流出, 且UID Owner为1337的出向流量

      • 从lo接口流出,且GID Owner非为1337的出向流量

      • 目标地址是127.0.0.1/32的出向流量

    • ISTIO_IN_REDIRECT:Sidecar Envoy同其反向代理的本地后端应用程序间的通信

      • 从lo接口流出,目标地址非为127.0.0.1/32,且UID Owner为1337的出向流量

      • 从lo接口流出,目标地址非为127.0.0.1/32,且GID Owner为1337的出向流量

    • ISTIO_REDIRECT:其它报文则由该自定义链中的规则(-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001)重定向至15001端口;

      • Envoy根据报文目标地址匹配出向(Outbound)方向的Listener,若存在,则根据该Listener的配置决定如何将报文向外转发;

    istio-proxy容器

    istio-proxy 进程

    • istio-proxy即所谓的sidecar容器,它运行两个进程

      • pilot-agent

        • 基于k8s api server为envoy初始化出可用的boostrap配置文件并启动envoy;

        • 监控并管理envoy的运行状态,包括envoy出错时重启envoy,以及envoy配置变更后将其重载等;

      • envoy

        • envoy由pilot-agent进程基于生成bootstrap配置进行启动,而后根据配置中指定的pilot地址,通过xDS API获取动态配置信息;
        • Sidecar形式的Envoy通过流量拦截机制为应用程序实现入站和出站代理功能;
    ~# kubectl get pod
    NAME                          READY   STATUS    RESTARTS       AGE
    demoappv10-78b6586d58-gstt2   2/2     Running   2 (6h3m ago)   21h
    demoappv10-78b6586d58-qc2wg   2/2     Running   2 (6h3m ago)   21h
    demoappv11-78bf898c74-ggfgd   2/2     Running   2 (6h3m ago)   21h
    demoappv11-78bf898c74-qscbn   2/2     Running   2 (6h3m ago)   21h
    proxy-649b4d887d-kxmvs        2/2     Running   0              55m
    
    ~# kubectl exec -it demoappv10-78b6586d58-gstt2 -c istio-proxy -- ps aux
    USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    istio-p+       1  0.0  1.2 753480 48468 ?        Ssl  01:49   0:11 /usr/local/bin/pilot-agent proxy sidecar --domain default.svc.cluster.local --proxyLogLevel=warning --proxyComponentLogLevel=misc:error --log_output_level=default:info --concurrency 2
    istio-p+      17  0.2  1.3 179412 55488 ?        Sl   01:49   0:56 /usr/local/bin/envoy -c etc/istio/proxy/envoy-rev.json --drain-time-s 45 --drain-strategy immediate --parent-shutdown-time-s 60 --local-address-ip-version v4 --file-flush-interval-msec 1000 --disable-hot-restart --log-format %Y-%m-%dT%T.%fZ.%l.envoy %n.%v -l warning --component-log-le
    istio-p+      31  0.0  0.0   7060  1560 pts/0    Rs+  07:52   0:00 ps aux
    

    pilot-agent使用帮助

    ~# kubectl exec -it demoappv10-78b6586d58-gstt2 -c istio-proxy -- pilot-agent --help
    Istio Pilot agent runs in the sidecar or gateway container and bootstraps Envoy.
    
    Usage:
      pilot-agent [command]
    
    Available Commands:
      completion           Generate the autocompletion script for the specified shell
      help                 Help about any command
      istio-clean-iptables Clean up iptables rules for Istio Sidecar
      istio-iptables       Set up iptables rules for Istio Sidecar
      proxy                XDS proxy agent
      request              Makes an HTTP request to the Envoy admin API
      version              Prints out build version information
      wait                 Waits until the Envoy proxy is ready
    
    Flags:
      -h, --help                          help for pilot-agent
          --log_as_json                   Whether to format output as JSON or in plain console-friendly format
          --log_caller string             Comma-separated list of scopes for which to include caller information, scopes can be any of [ads, all, authn, authorization, ca, cache, citadelclient, controllers, default, delta, dns, gcecred, googleca, googlecas, grpcgen, healthcheck, iptables, klog, mockcred, model, proxyconfig, sds, security, serviceentry, spiffe, stsclient, stsserver, telemetry, token, trustBundle, validation, wasm, wle, xdsproxy]
          --log_output_level string       Comma-separated minimum per-scope logging level of messages to output, in the form of <scope>:<level>,<scope>:<level>,... where scope can be one of [ads, all, authn, authorization, ca, cache, citadelclient, controllers, default, delta, dns, gcecred, googleca, googlecas, grpcgen, healthcheck, iptables, klog, mockcred, model, proxyconfig, sds, security, serviceentry, spiffe, stsclient, stsserver, telemetry, token, trustBundle, validation, wasm, wle, xdsproxy] and level can be one of [debug, info, warn, error, fatal, none] (default "default:info")
          --log_rotate string             The path for the optional rotating log file
          --log_rotate_max_age int        The maximum age in days of a log file beyond which the file is rotated (0 indicates no limit) (default 30)
          --log_rotate_max_backups int    The maximum number of log file backups to keep before older files are deleted (0 indicates no limit) (default 1000)
          --log_rotate_max_size int       The maximum size in megabytes of a log file beyond which the file is rotated (default 104857600)
          --log_stacktrace_level string   Comma-separated minimum per-scope logging level at which stack traces are captured, in the form of <scope>:<level>,<scope:level>,... where scope can be one of [ads, all, authn, authorization, ca, cache, citadelclient, controllers, default, delta, dns, gcecred, googleca, googlecas, grpcgen, healthcheck, iptables, klog, mockcred, model, proxyconfig, sds, security, serviceentry, spiffe, stsclient, stsserver, telemetry, token, trustBundle, validation, wasm, wle, xdsproxy] and level can be one of [debug, info, warn, error, fatal, none] (default "default:none")
          --log_target stringArray        The set of paths where to output the log. This can be any path as well as the special values stdout and stderr (default [stdout])
          --vklog Level                   number for the log level verbosity. Like -v flag. ex: --vklog=9
    
    Use "pilot-agent [command] --help" for more information about a command.
    

    istio-proxy容器Listener

    • Envoy的bootstrap配置文件由pilot-agent负责生成,而生效的大多配置则定义成了dynamic resources,并通过xDS静态集群指向的Pilot获取;

      • 而这些配置则来自于VirtualService、DestinationRule、Gateway和ServiceEntry资源对象等提供的配置;
    • 由Pilot基于Kubernetes发现的网格内的各Service均会被自动转换为下发给各Sidecar实例的Listener、Route、Cluster和Endpoint的相关配置

      • 为了能够让Sidecar Envoy代理网格内进出各Service的流量,所以需要进行流量劫持,而后由Envoy根据请求特征,将流量派发至合适的Listener,进而完成后续的流量路由、负载均衡等
    ~# kubectl exec -it demoappv10-78b6586d58-gstt2 -c istio-proxy -- pilot-agent request GET /listeners
    d61bf198-79db-47dc-a11f-4c0227603d0e::0.0.0.0:15090
    bad902ee-6657-4325-81b8-21b8897cefc8::0.0.0.0:15021
    10.100.0.2_53::10.100.0.2:53
    10.100.121.95_443::10.100.121.95:443
    10.100.145.112_31400::10.100.145.112:31400
    10.100.0.1_443::10.100.0.1:443
    10.100.149.76_15012::10.100.149.76:15012
    10.100.145.112_443::10.100.145.112:443
    10.100.149.76_443::10.100.149.76:443
    10.100.145.112_15443::10.100.145.112:15443
    0.0.0.0_15014::0.0.0.0:15014
    0.0.0.0_20001::0.0.0.0:20001
    0.0.0.0_16685::0.0.0.0:16685
    10.100.126.122_14250::10.100.126.122:14250
    10.100.162.68_3000::10.100.162.68:3000
    10.100.0.2_9153::10.100.0.2:9153
    0.0.0.0_15010::0.0.0.0:15010
    10.100.145.112_15021::10.100.145.112:15021
    0.0.0.0_8080::0.0.0.0:8080
    10.100.107.86_443::10.100.107.86:443
    0.0.0.0_80::0.0.0.0:80
    0.0.0.0_9090::0.0.0.0:9090
    10.100.128.238_8000::10.100.128.238:8000
    10.100.126.122_14268::10.100.126.122:14268
    0.0.0.0_9411::0.0.0.0:9411
    virtualOutbound::0.0.0.0:15001
    virtualInbound::0.0.0.0:15006
    

    istio-proxy Listener

    • Envoy Listener支持绑定于IP Socket或Unix Domain Socket之上,也可以不予绑定,而是接收由其它的Listener转发来的数据

      • VirtualOutboundListener通过一个端口接收所有的出向流量,而后再按照请求的端口分别转发给相应的Listener进行处理;
      • VirtualInboundListener的功能相似,但它主要用于处理入向流量;
    • VirtualOutbound Listener

      • iptables将其所在的Pod中的外发流量拦截后转发至监听于15001的Listener,而该Listener通过在配置中将use_origin_dest参数设置为true,从而实现将接收到的请求转交给同请求原目标地址关联的Listener之上;
      • 若不存在可接收转发报文的Listener,则Envoy将根据Istio的全局配置选项outboundTrafficPolicy参数的值决定如何进行处理
        • ALLOW_ANY:允许外发至任何服务的请求,无论目标服务是否存在于Pilot的注册表中;此时,没有匹配的目标Listener的流量将由该侦听器上tcp_proxy过滤器指向的Passthrough Cluster进行透传;
        • REGISTRY_ONLY:仅允许外发请求至注册于Polit中的服务;此时,没有匹配的目标Listener的流量将由该侦听器上tcp_proxy过滤器指向的BlackHoleCluster将流量直接丢弃;

    istio-proxy Outbound Listener

    • Envoy将按需为其所处网格中的各外部服务按端口创建多个Listener以处理出站流量,

      • 0.0.0.0_9080 :处理发往details、reviews和rating等服务的流量

      • 0.0.0.0_9411:处理发往zipkin的流量

      • 0.0.0.0_3000:处理发往grafana的流量

      • 0.0.0.0_9090:处理发往prometheus的流量;

    VirtualInbound Listener

    入向流量劫持

    • 较早版本的Istio基于同一个VirtualListener在15001端口上同时处理入站和出站流量
    • 自1.4版本起,Istio引入了REDIRECT代理模式,它通过监听于15006端口的专用VirtualInboundListener处理入向流量代理以规避潜在的死循环问题;

    入向流量处理

    • 对于进入到侦听器“0.0.0.0:15006”的流量,VirtualInboundListener会在filterChains中,通过一系列的filter_chain_match对流量进行匹配检测,以确定应该由哪个或哪些过滤器进行流量处理
    image
    ~# kubectl exec -it demoappv10-78b6586d58-gstt2 -c istio-proxy -- pilot-agent request GET /config_dump
    
    • 事实上,入向流量的相关过滤器匹配条件及流量处理机制,也可以简单地通过istioctl proxy-config命令来获取
      • 仍然以监听于8080/tcp端口提供服务的demoapp应用的某实例为例

        • 命令:istioctl proxy-config listeners $DEMOAPP_POD --port 15006
      • 它的15006侦听器上相关配置如下

    image

    入向流量处理机制

    • VirtualInboundListener会将接收到请求将直接采用一系列filter_chain_match对入站流量进行匹配检测,匹配到的流量,将由相应的filters进行处理
      • 例如,对于监听于8080端口的应用,相关请求请求直接由特定的filter_chain_match匹配后,交由相应的filters进行处理,
    • 这有别于VirtualOutboundListener对入向流量的处理方式,并非将流量分发给独立的Egress Listener进行处理;
    image

    istio-proxy Clusters

    • Istio网格中的Cluster分别由static_resources提供的静态配置集群以及dynamic_resources提供的动态配置集群组成;
      • 静态集群由envoy-rev0.json的初始化配置中的prometheus_stats、xDS server和zipkin server等组成;
      • 动态集群则是由通过xDS API从Pilot获取的配置信息生成;
    • 对于网格内的一个特定Pod来说,其集群可主要分为两类
      • inbound:该Pod内的Sidecar Envoy直接代理的服务
      • outbound:网格内的所有服务
    ~# kubectl exec demoappv10-78b6586d58-gstt2 -c istio-proxy -- curl -s 127.0.0.1:15000/clusters | grep -o "^[^:]\+" | sort -u
    BlackHoleCluster
    InboundPassthroughClusterIpv4
    PassthroughCluster
    agent
    inbound|8080||
    outbound|14250||jaeger-collector.istio-system.svc.cluster.local
    outbound|14268||jaeger-collector.istio-system.svc.cluster.local
    outbound|15010||istiod.istio-system.svc.cluster.local
    outbound|15012||istiod.istio-system.svc.cluster.local
    outbound|15014||istiod.istio-system.svc.cluster.local
    outbound|15021||istio-ingressgateway.istio-system.svc.cluster.local
    outbound|15443||istio-ingressgateway.istio-system.svc.cluster.local
    outbound|16685||tracing.istio-system.svc.cluster.local
    outbound|20001||kiali.istio-system.svc.cluster.local
    outbound|3000||grafana.istio-system.svc.cluster.local
    outbound|31400||istio-ingressgateway.istio-system.svc.cluster.local
    outbound|443||istio-egressgateway.istio-system.svc.cluster.local
    outbound|443||istio-ingressgateway.istio-system.svc.cluster.local
    outbound|443||istiod.istio-system.svc.cluster.local
    outbound|443||kubernetes-dashboard.kubernetes-dashboard.svc.cluster.local
    outbound|443||kubernetes.default.svc.cluster.local
    outbound|53||kube-dns.kube-system.svc.cluster.local
    outbound|8000||dashboard-metrics-scraper.kubernetes-dashboard.svc.cluster.local
    outbound|8080||demoappv10.default.svc.cluster.local
    outbound|8080||demoappv11.default.svc.cluster.local
    outbound|80||istio-egressgateway.istio-system.svc.cluster.local
    outbound|80||istio-ingressgateway.istio-system.svc.cluster.local
    outbound|80||proxy.default.svc.cluster.local
    outbound|80||tracing.istio-system.svc.cluster.local
    outbound|9090||kiali.istio-system.svc.cluster.local
    outbound|9090||prometheus.istio-system.svc.cluster.local
    outbound|9153||kube-dns.kube-system.svc.cluster.local
    outbound|9411||jaeger-collector.istio-system.svc.cluster.local
    outbound|9411||zipkin.istio-system.svc.cluster.local
    prometheus_stats
    sds-grpc
    xds-grpc
    zipkin
    

    istio-proxy上的动态集群

    动态集群类型

    • Inbound Cluster:Sidecar Envoy直接代理的应用,同一Pod中,由Sidecar Envoy反向代理的应用容器;

    • Outbound Cluster:网格中的所有服务,包括当前Sidecar Envoy直接代理的服务,该类Cluster占了Envoy可用集群中的绝大多数;

    • PassthroughCluster和InboundPassthroughClusterIpv4:发往此类集群的请求报文会被直接透传至其请求中的原始目标地址,Envoy不会进行重新路由;

    • BlackHoleCluster:Envoy的一个特殊集群,它没有任何可用的endpoint,接收到的请求会被直接丢弃;未能正确匹配到目标服务的请求通常会被发往此Cluster;

    相关文章

      网友评论

          本文标题:Istio 流量劫持

          本文链接:https://www.haomeiwen.com/subject/paorcdtx.html