深度解析Istio Proxy边车容器的功能与能力

嵌入式技术

1368人已加入

描述

一、背景

我们都知道集群中安装了istio后,只需要给 namespace 打上istio-injection=enabled 这个标签,之后这个namespace下的所有pod都会注入边车容器istio-proxy(存量pod需要重启才能生效,新下发的pod会直接注入sidecar容器)。那么1)sidecar 是如何自动注入的?2)我们都知道istio-proxy可以拦截流量,具体是如何拦截流量的?3)注入了sidecar之后,不想拦截特定流量如何处理?

二、sidecar容器如何注入

容器

在创建Pod的请求到达Kube-apiserver后,首先进行认证鉴权,然后在准入控制阶段 kube-apiserver以REST的方式同步调用sidecar-injector webhook服务进行init容器与istio-proxy容器的注入,最后将Pod对象持久化存储到Etcd中。对应MutatingWebhookConfiguration配置如下:

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: istio-revision-tag-default
webhooks:
......
- admissionReviewVersions:
  - v1beta1
  - v1
  clientConfig:
    caBundle:  xxx
    service:
      name: istiod
      namespace: istio-system
      path: /inject
      port: 443
  failurePolicy: Fail
  matchPolicy: Equivalent
  name: namespace.sidecar-injector.istio.io
  namespaceSelector:
    matchExpressions:
    - key: istio-injection
      operator: In
      values:
      - enabled
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - CREATE
    resources:
    - pods
    scope: '*'
  ... ...

由配置可知,webhook服务由istiod提供,在istio 1.5 版本之后sidecar-injector 被编译到istiod进程中。sidecar-injector 对标签匹配 istio-injection : enabled 的命名空间的Pod资源对象的创建生效。

业务pod被自动注入istio-init container和 istio-proxy container

# initContainer 配置 ,用于初始化pod网络,负责对Pod配置定制的iptables规则,所以也需要被赋予NET_ADMIN权限
initContainers:  
    - name: istio-init
      image: docker.io/istio/proxyv2:1.19.0
      args:
        - istio-iptables
        - '-p'
        - '15001'
        - '-z'
        - '15006'
        - '-u'
        - '1337'
        - '-m'
        - REDIRECT
        - '-i'
        - '*'
        - '-x'
        - ''
        - '-b'
        - '*'
        - '-d'
        - 15090,15021,15020
        - '--log_output_level=default:info'
      securityContext:
        capabilities:
          add:
            - NET_ADMIN
            - NET_RAW
          drop:
            - ALL
        ......

# istio-proxy container配置,
containers:
    - name: istio-proxy
      image: docker.io/istio/proxyv2:1.19.0
      args:
        - proxy
        - sidecar
        - '--domain'
        - $(POD_NAMESPACE).svc.cluster.local
        - '--proxyLogLevel=warning'
        - '--proxyComponentLogLevel=misc:error'
        - '--log_output_level=default:info'
      ports:
        - name: http-envoy-prom
          containerPort: 15090
          protocol: TCP
      env:
        ......
      volumeMounts:               # istio-proxy 容器挂载的证书及配置文件
        - name: workload-socket
          mountPath: /var/run/secrets/workload-spiffe-uds
        - name: credential-socket
          mountPath: /var/run/secrets/credential-uds
        - name: workload-certs
          mountPath: /var/run/secrets/workload-spiffe-credentials
        - name: istiod-ca-cert
          mountPath: /var/run/secrets/istio
        - name: istio-data
          mountPath: /var/lib/istio/data
        - name: istio-envoy#Envoy的启动配置文件 envoy-rev0.json
          mountPath: /etc/istio/proxy     
        - name: istio-token# Envoy 访问istiod用的token
          mountPath: /var/run/secrets/tokens 
        - name: istio-podinfo# 以文件形式保存 Pod自身服务的信息,包含annotations和labels文件,这两个文件将被pilot-agent读取
          mountPath: /etc/istio/pod      
        - name: kube-api-access-xsnpl
          readOnly: true
          mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      securityContext:
        ......
        runAsUser: 1337  # Sidecar运行用户 
        runAsGroup: 1337

三、istio拦截原理

容器

在完成Sidecar自动注入后,业务在Pod运行期间收发的网络流量将被透明的拦截进Sidecar。其流量拦截基于iptables规则,拦截应用容器的Inbound流量或Outbound流量。主要分为两大部分:

istio-init容器用于设置pod中的iptables转发规则,将流量先导入给istio-proxy(envoy)

Sidecar容器 istio-proxy拦截流量

3.1 istio-init容器分析

istio-init容器会在pod网络协议栈完成iptables规则配置操作后退出,该容器的启动命令(istio-iptables命令封装了一些iptables规则)

istio-iptables -p 15001 -z 15006 -u 1337 -m REDIRECT -i '*' -x "" -b '*' -d 15090,15021,15020

让sidecar代理可以拦截所有进出Pod的流量,除了15090,15021,15020端口的所有入站流量都被重定向到15006端口(sidecar),还可以拦截应用容器的出流量,这些流量经过sidcar(通过15001端口监听)处理后才能出站。

-z 15006 表示将进入应用容器的所有流量都转发到sidecar的15006端口

-u 1337 指定不应用重定向的uid,默认是1337,即使用istio-proxy用户身份运行

-m REDIRECT 表示使用REDIRECT模式重定向流量

-p 15001 将所有出战流量都重定向到sidecar的15001端口

-d 15090,15021,15020 表示排除该三个端口,所有的入流量都会被重定向处理。15020和15090都是遥测暴露指标的端口,15021监控检查的端口

-i "*" 表示重定向所有的出站流量

-x "" 表示排除指定的网端ip地址,对出站流量不进行重定向处理。为空 没有需要排除的ip

-b "*" 表示重定向所有入站流量到Envoy

3.2 sidecar拦截规则分析

sidecar与用户进程共享同一个网络命名空间,工作在相同的网络协议栈上。sidecar 对协议栈iptables规则的配置,将影响用户应用程序报文的流向,可以透明的拦截用户报文,并进行七层处理。进入被注入sidecar的容器网络命名空间(可以用nsenter命令从宿主机进入,或者直接进入有iptables模块的容器),查看对应iptables规则

#  iptables -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 -p tcp -m tcp ! --dport 15008 -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 -p tcp -m tcp ! --dport 15008 -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

-P PREROUTING ACCEPT : 接受进入PREROUTING链的报文,其他链同理

-N ISTIO_INBOUND : 声明一个自定义的链 ISTIO_INBOUND

-A PREROUTING -p tcp -j ISTIO_INBOUND : 将进入PREROUTING链的TCP流量跳转到ISTIO_INBOUND链 做进一步处理

-A ISTIO_INBOUND -p tcp -m tcp --dport 15008 -j RETURN : 对进入ISTIO_INBOUND链的目标端口为15008的TCP流量不做特殊处理,直接让其通过

-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006 : 对进入ISTIO_IN_REDIRECT链的TCP流量进行报文修改,REDIRECT 对应DNAT修改方式,修改目标端口为15006

-A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN: 对进入ISTIO_OUTPUT链的源地址为127.0.0.6的报文且目标网络设备为lo本地设备的流量,不进行特殊处理

-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -p tcp -m tcp ! --dport 15008 -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT:

对进入ISTIO_OUTPUT链且目标地址虽然不然127.0.0.1,但判断目标网络设备为本地,即pod自身地址的报文,若报文发送进程为uid=1337,则Envoy转到ISTIO_IN_REDIRECT链继续处理

3.2.1 Inbound场景流量规则详细分析

Inbound流量指从Pod外进入Pod内的流量。比如客户端访问服务端,应用数据报文在进入服务端时就会被拦截,从而进入envoy 15006监听端口来处理,同时例如15008 mtls隧道端口,15090,15020遥测监控端口,15021健康检查端口,均不会被拦截

容器

此时服务端POD接受流量,流量首先会经过prerouting链处理。然后命中iptables规则: -A PREROUTING -p tcp -j ISTIO_INBOUND ,将进入PREROUTING链的TCP流量跳转到ISTIO_INBOUND链 做进一步处理。接着命中iptables规则: -A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT,如果目标端口不是15008,15090,15020,15021,则将流量跳转到ISTIO_IN_REDIRECT链进行处理。最后命中iptables规则: -A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006,报文被拦截并通过DNAT方式修改目标地址,将访问端口置为15006。

此时流量被拦截至Envoy容器进行处理,Envoy容器将流量发送给业务容器进行处理。命中规则: -A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN ,表示Envoy发送给Pod后端backend容器的流量不再被拦截。

业务容器接受到报文后,会进行回包。

存在一些PassthroughCluster场景,例如直接访问podIP,此时Inbound请求根据目标地址没有找到后端服务时,下游请求将被转发到Envoy内置的PassthroughCluster服务中,最后按照原始目标地址转发下游请求。

Envoy发送报文,命中iptables规则: -A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN。处理Passthroughcluster流量时,源ip会被置为127.0.0.6。此时在output链处理阶段,被转发的请求会被放行。同时因为目标网络涉别为lo,可以访问到本pod内未注册的后端服务。

3.2.1 Outbound场景流量规则详细分析

容器

上图模拟了两个服务之间的通信,主要是描述东西向流量客户端访问服务端其中outbound流量的过程。(istio中很多流量治理的功能都是在outbound过程生效)

客户端frontend通过service访问服务端(svcName:port)。四元组信息: srcIP: ip1 srcPort: port1 ==> dst:backend ,dscPort:port2。服务端域名经过DNS解析后,得到clusterIP,然后封装发送SYN报文,随后报文被pod内的iptables规则拦截。首先命中该规则: -A OUTPUT -p tcp -j ISTIO_OUTPUT,将进入output链的tcp流量转发到istio_output链做进一步处理;随后命中该规则: -A ISTIO_OUTPUT -j ISTIO_REDIRECT , 将所有出站流量(非本地的)转发给istio_redirect链来处理;最后命中该规则: -A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001 ,对进入ISTIO_REDIRECT链的TCP流量进行报文修改,目标端口重定向到15001。

客户端Envoy接受到报文,开始针对流量进行治理。在Envoy中会根据配下发的配置还原目标服务的ClusterIp和port。随后根据Envoy内配置的负载均衡策略选择一个后端实例IP作为目的IP,并创建连接

客户端Envoy准备发送报文。istio-proxy的运行用户和用户组均为1337。四元组信息: srcIP:ip1, srcPort:随机端口(port3) ==> dstIP:ip2,dstPort:port2。命中规则: -A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN ,对进入istio_output链,报文发送用户为1337的流量 放行通过。

服务端backend的Envoy接受流量转发给backend,服务端backend已接受到报文,开始回包给客户端。四元组信息: scrIP:ip2, srcPort:port2 ==> dstIP:ip1,dstPort:port3

服务端Envoy发送流量。此时envoy会将报文转发给业务容器。四元组信息: scrIP: 127.0.0.1, srcPort:15001 ==> dstIP:ip1,dstPort:port1

3.3 如何放行部分流量不被envoy拦截

在实际的使用过程中,如果业务容器访问某些应用不希望流量被拦截,该怎么才能做到呢?istio中根据流量拦截的原理是iptables规则的配置。我们可以给应用加上对应的规则即可,例如出站流量对端口8080不做拦截,出站流量对172.16.2.0/24目标网段不做拦截。

容器

就需要配置以下规则: 因为所有的出站包都会转发到ISTIO_OUTPUT链上,所以基于该链配置放行规则

-A ISTIO_OUTPUT -p tcp -m tcp --dport 8080 -j RETURN -A ISTIO_OUTPUT -d 172.16.2.0/24 -j RETURN

istio提供了基于podAnnotation配置的方式控制拦截行为: https://istio.io/latest/docs/reference/config/annotations/  为对应工作负载添加

traffic.sidecar.istio.io/excludeOutboundPorts : 出站流量放行的端口
traffic.sidecar.istio.io/excludeOutboundIPRanges : 出站流量放行的ip地址

容器

登录pod中查看iptables规则: iptables -t nat -L -nv ,可以发现在ISTIO_OUTPUT 新增了两条放行规则

容器

审核编辑:黄飞

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分